From 853e0cc9425412b5d8eb5cb4ad1a9a8388607c9a Mon Sep 17 00:00:00 2001 From: hmo Date: Thu, 30 Jul 2026 15:38:59 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20MC=E5=B9=B4=E5=8C=96=E7=A1=AC=E7=BC=96?= =?UTF-8?q?=E7=A0=81^0.5=E8=99=9A=E9=AB=98=E2=80=94=E2=80=94=E6=94=B9?= =?UTF-8?q?=E7=94=A8=E5=AE=9E=E9=99=85=E5=8C=BA=E9=97=B4=E5=B9=B4=E6=95=B0?= =?UTF-8?q?(5=E5=B9=B4=E7=AD=96=E7=95=A5=E5=B9=B4=E5=8C=96=E4=BB=8E21.8%?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=88=B0=E7=9C=9F=E5=AE=9E~8.9%)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- strategy_lab.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/strategy_lab.py b/strategy_lab.py index 900d9678..8cd107b2 100644 --- a/strategy_lab.py +++ b/strategy_lab.py @@ -1123,10 +1123,18 @@ def portfolio_sim_full(trades, capital=1000000, runs=50): 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 + # 年化用实际区间年数(修复硬编码^0.5=2年的虚高问题) + _dts = sorted({t['entry_date'] for t in trades if t.get('entry_date')}) + if _dts: + from datetime import datetime as _dt2 + _span_days = (_dt2.strptime(_dts[-1], '%Y-%m-%d') - _dt2.strptime(_dts[0], '%Y-%m-%d')).days + _years = max(_span_days / 365.0, 0.5) + else: + _years = 2.0 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), + 'cagr_pct': round((((1 + mean_ret / 100) ** (1 / _years)) - 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)), @@ -1154,9 +1162,22 @@ def portfolio_sim(trades, capital=1000000, max_positions=10, cost=True, random_s if not trades: return {} # 交易日历(用大盘指数日期) - cal = sorted(_MKT_CTX.keys()) - if not cal: + # 日历从 stock_daily 实际交易日生成(覆盖策略完整区间,不被全局_MKT_CTX的2年限制截断) + _dates = sorted({t['entry_date'] for t in trades if t.get('entry_date')}) + if not _dates: return {} + _min_d, _max_d = _dates[0], _dates[-1] + # 日历缓存(避免每策略x50次MC重复扫stock_daily) + global _CAL_CACHE + if '_CAL_CACHE' not in globals() or _CAL_CACHE.get('range') != (_min_d, _max_d): + _c = sqlite3.connect(DB_PATH) + _CAL_CACHE = {'range': (_min_d, _max_d), 'cal': [r[0] for r in _c.execute( + "SELECT DISTINCT date FROM stock_daily WHERE date>=? AND date<=? ORDER BY date", + (_min_d, _max_d)).fetchall()]} + _c.close() + cal = _CAL_CACHE['cal'] + if not cal: + cal = _dates cal_idx = {d: i for i, d in enumerate(cal)} def add_days(d, n): i = cal_idx.get(d)