From 7f8a1e63d38f47e425eacc1c8a548375d9704b50 Mon Sep 17 00:00:00 2001 From: hmo Date: Wed, 29 Jul 2026 23:25:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=85=A8=E5=8F=82=E4=B8=8E=E6=94=B9Mon?= =?UTF-8?q?te=20Carlo=E9=9A=8F=E6=9C=BA=E9=80=89=E8=A3=85(50=E6=AC=A1?= =?UTF-8?q?=E5=9D=87=E5=80=BC=C2=B1=CF=83)=E2=80=94=E2=80=94=E6=B6=88?= =?UTF-8?q?=E7=81=AD=E8=AF=84=E5=88=86=E6=8C=91=E9=80=89=E5=81=8F=E5=B7=AE?= =?UTF-8?q?,v1.0=E4=BB=8E+53.9%=E5=B4=A9=E5=88=B0+31.2%(=CF=8312.5%),v7.1?= =?UTF-8?q?=E4=BB=A5=CF=830.3%=E8=AF=81=E6=98=8E=E7=9C=9F=E5=AE=9E?= =?UTF-8?q?=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- strategy_lab.py | 66 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/strategy_lab.py b/strategy_lab.py index 7a1bdabb..8bb6e1e9 100644 --- a/strategy_lab.py +++ b/strategy_lab.py @@ -1057,20 +1057,52 @@ def max_concurrency(trades): MIN_POSITION = 50000 # 单仓可行性下限(A股一手+手续费,约5万) -def portfolio_sim_full(trades, capital=1000000): - """全参与组合模拟(可行版):仓位槽 = min(信号流最大并发, 总资产/单仓下限)。 - 消除"选哪几笔上车"的运气成分,同时保证单仓金额实际可操作。""" +def portfolio_sim_full(trades, capital=1000000, runs=50): + """全参与组合模拟(Monte Carlo 随机选装版): + 仓位槽 = min(信号流最大并发, 总资产/单仓下限); + 同日竞争时随机洗牌选装,重复 runs 次取均值±方差。 + 彻底消灭评分挑选偏差(2026-07-29 老爸:v1.0高分信号被偏爱=虚高收益)""" if not trades: return {} natural = max_concurrency(trades) affordable = max(1, int(capital / MIN_POSITION)) slots = min(natural, affordable) - r = portfolio_sim(trades, capital, slots) - r['slots'] = slots - r['natural_concurrency'] = natural - r['min_position'] = MIN_POSITION - r['mode'] = 'full_feasible' - return r + # 交易少时直接单跑(无需MC) + if len(trades) <= slots * 2: + r = portfolio_sim(trades, capital, slots) + r.update({'slots': slots, 'natural_concurrency': natural, 'mode': 'full_single', + 'std': 0.0, 'p10': r['total_return_pct'], 'p90': r['total_return_pct']}) + return r + rets = [] + finals = [] + dds = [] + taken = [] + for k in range(runs): + r = portfolio_sim(trades, capital, slots, random_seed=42 + k) + rets.append(r['total_return_pct']) + finals.append(r['capital_final']) + dds.append(r['portfolio_max_dd_pct']) + taken.append(r['positions_taken']) + rets_s = sorted(rets) + mean_ret = sum(rets) / len(rets) + mean_dd = sum(dds) / len(dds) + std = (sum((x - mean_ret) ** 2 for x in rets) / len(rets)) ** 0.5 + return { + 'capital_final': round(sum(finals) / len(finals), 0), + 'total_return_pct': round(mean_ret, 1), + 'cagr_pct': round((((1 + mean_ret / 100) ** 0.5) - 1) * 100, 1), + 'portfolio_max_dd_pct': round(mean_dd, 1), + 'positions_taken': round(sum(taken) / len(taken)), + 'positions_skipped': len(trades) - round(sum(taken) / len(taken)), + 'slots': slots, + 'natural_concurrency': natural, + 'min_position': MIN_POSITION, + 'mode': 'full_mc', + 'mc_runs': runs, + 'std': round(std, 1), + 'p10': round(rets_s[int(len(rets_s) * 0.1)], 1), + 'p90': round(rets_s[int(len(rets_s) * 0.9)], 1), + } COST_RATE = 0.002 # 往返交易费用率(佣金+印花税+滑点≈0.2%) @@ -1079,7 +1111,7 @@ def _trade_legs(t): """波段类出场按2次往返计费""" return 2 if t.get('exit_reason') in ('swing_re', 'swing_ptp') else 1 -def portfolio_sim(trades, capital=1000000, max_positions=10, cost=True): +def portfolio_sim(trades, capital=1000000, max_positions=10, cost=True, random_seed=None): """组合级模拟:固定等分仓位,按交易日历执行,返回最终资产/总收益/资产曲线回撤 规则:每日先结算到期仓位 → 再执行当日入场(仓位满跳过)→ 持仓按成本估值 含交易费用:每笔往返扣 COST_RATE(2026-07-29 老爸:高频策略必须上费用天平)""" @@ -1096,12 +1128,20 @@ def portfolio_sim(trades, capital=1000000, max_positions=10, cost=True): return d return cal[min(i + n, len(cal) - 1)] - # 按入场日组织(同日高分优先) + # 按入场日组织 + # random_seed 设置时:同日信号随机洗牌(消灭评分挑选偏差, Monte Carlo用) + # 否则:同日高分优先(原行为, 集中仓位模拟用) entries = {} for t in trades: entries.setdefault(t['entry_date'], []).append(t) - for d in entries: - entries[d].sort(key=lambda x: -x.get('score', 0)) + if random_seed is not None: + import random as _rnd + rng = _rnd.Random(random_seed) + for d in entries: + rng.shuffle(entries[d]) + else: + for d in entries: + entries[d].sort(key=lambda x: -x.get('score', 0)) cash = capital open_pos = [] # {'exit_date','alloc','pnl'}