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)