feat: 全参与改Monte Carlo随机选装(50次均值±σ)——消灭评分挑选偏差,v1.0从+53.9%崩到+31.2%(σ12.5%),v7.1以σ0.3%证明真实力

This commit is contained in:
hmo
2026-07-29 23:25:33 +08:00
parent 08dd66c7e1
commit 7f8a1e63d3
+53 -13
View File
@@ -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'}