From 2ce88ae436c9af19c34ebf609b2e2a69c0c8b75c Mon Sep 17 00:00:00 2001 From: xxm Date: Sat, 15 Aug 2026 20:37:48 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=B8=A9=E5=8C=BA=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E8=B7=9F=E9=9A=8F=E6=89=80=E9=80=89=E5=91=A8?= =?UTF-8?q?=E6=9C=9F(period=5Ftag)=E2=80=94=E2=80=94=E4=BB=8Estrategy=5Fre?= =?UTF-8?q?search=E5=AF=B9=E5=BA=94=E5=91=A8=E6=9C=9Ftrades=E7=8E=B0?= =?UTF-8?q?=E5=9C=BA=E6=B8=A9=E5=8C=BA=E5=BD=92=E5=9B=A0,=E6=9B=BF?= =?UTF-8?q?=E4=BB=A3=E8=AF=BB=E5=85=A8=E9=87=8Fstrategy=5Fregime=5Fperf(?= =?UTF-8?q?=E9=80=892y=E4=BB=8D=E6=98=BE=E7=A4=BA=E5=85=A8=E9=87=8F,?= =?UTF-8?q?=E4=B8=A5=E9=87=8D=E8=AF=AF=E5=AF=BC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server.py | 123 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 106 insertions(+), 17 deletions(-) diff --git a/server.py b/server.py index 6d38c543..72b8f9db 100644 --- a/server.py +++ b/server.py @@ -565,31 +565,120 @@ def api_research_strategies(): _active_set = set(_weights.get("active") or []) _hk_market = (_weights.get("markets") or {}).get("hk") or {} _active_set |= set(_hk_market.get("active") or []) + # ── 2026-08-15 温区数据跟随所选周期(period_tag)── + # 病状:旧版读 strategy_regime_perf 全量表(10y/5y 最长窗口),与前端周期下拉无关, + # 导致选"近2年"表内仍是全量数据(老莫抓包发现,严重误导)。 + # 修复:按 period_tag 从 strategy_research 对应周期 trades 现场温区归因(calc_extra+portfolio_sim), + # 周期记录缺失时回退到最近更长周期(1m/6m→1y,2y→2y,5y→5y,10y→10y)。 _regime_winrates = {} try: import sqlite3 as _sq + import math as _mth + from collections import defaultdict as _dd _c = _sq.connect(str(DATA_DIR / "mofin.db"), timeout=10) _c.execute("PRAGMA busy_timeout=10000") + # 周期回退链:前端可选 1m/6m/1y/2y/5y/10y;strategy_research 有 1y/2y/5y/10y 记录 + _pt_chain = {'1m': '1y', '6m': '1y', '1y': '1y', '2y': '2y', '5y': '5y', '10y': '10y'} + _pt_use = _pt_chain.get(pt or '2y', '2y') + # 各策略按周期取 trades(含市场维度;港股策略 market='hk') + _row_map = {} for _r in _c.execute( - "SELECT strategy, regime, trades, win_rate, avg_pnl, avg_hold_days, " - "total_return_pct, cagr_pct, portfolio_max_dd_pct, capital_final, " - "positions_taken, sharpe_ratio, profit_factor FROM strategy_regime_perf" + "SELECT version, market, period_tag, results_json FROM strategy_research " + "ORDER BY version, market, " + "CASE period_tag WHEN '1y' THEN 1 WHEN '2y' THEN 2 WHEN '5y' THEN 3 WHEN '10y' THEN 4 ELSE 5 END" ).fetchall(): - _regime_winrates.setdefault(_r[0], {})[_r[1]] = { - "trades": _r[2], "win_rate": _r[3], "avg_pnl": _r[4], - "avg_hold_days": _r[5], "total_return_pct": _r[6], - "cagr_pct": _r[7], "max_dd_pct": _r[8], "capital_final": _r[9], - "positions_taken": _r[10], "sharpe_ratio": _r[11], - "profit_factor": _r[12], - # 温区级组合级指标(温区行也要显示组合级列) - "portfolio": {"cagr_pct": _r[7], "total_return_pct": _r[6], - "portfolio_max_dd_pct": _r[8], "capital_final": _r[9], - "positions_taken": _r[10], "sharpe_ratio": _r[11], - "profit_factor": _r[12]}, - # 温区级 universality(近似:温区内信号月份≈trades/温区月均,温区总月份;避免逐笔遍历性能问题) - "universality": _approx_regime_universality(_r[0], _r[1], _r[2]), - } + _k = (_r[0], _r[1]) + if _k not in _row_map: + _row_map[_k] = {} + _row_map[_k][_r[2]] = _r[3] + # 温区映射(A股+港股分开) + _rmap_a = dict(_c.execute("SELECT date, regime FROM market_regime WHERE market='a'").fetchall()) + _rmap_hk = dict(_c.execute("SELECT date, regime FROM market_regime WHERE market='hk'").fetchall()) _c.close() + + def _calc_extra(_trades): + if not _trades: + return {} + _profits = [t.get("profit_pct", 0) for t in _trades] + _wins = [p for p in _profits if p > 0] + _losses = [p for p in _profits if p <= 0] + _wr = len(_wins) / len(_profits) * 100 if _profits else 0 + _avg = sum(_profits) / len(_profits) if _profits else 0 + _avg_w = sum(_wins) / len(_wins) if _wins else 0 + _avg_l = abs(sum(_losses) / len(_losses)) if _losses else 1 + _pf = _avg_w / _avg_l if _avg_l > 0 else 0 + _mr = _avg / 100 + _std = _mth.sqrt(sum((p / 100 - _mr) ** 2 for p in _profits) / (len(_profits) - 1)) if len(_profits) > 1 else 0 + _sh = _mr / _std * _mth.sqrt(252) if _std > 0 else 0 + _holds = [t.get("hold_days", 0) for t in _trades if t.get("hold_days")] + _ah = sum(_holds) / len(_holds) if _holds else 0 + return {"win_rate": round(_wr, 1), "avg_pnl": round(_avg, 2), + "avg_hold_days": round(_ah, 1), "sharpe_ratio": round(_sh, 2), + "profit_factor": round(_pf, 2)} + + def _portfolio_sim(_trades, _cap=1000000, _slots=10): + if not _trades: + return {} + try: + from strategy_lab import portfolio_sim + return portfolio_sim(_trades, capital=_cap, max_positions=_slots, cost=True) + except Exception: + return {} + + for (_ver, _mkt), _periods in _row_map.items(): + # 取目标周期 trades(缺失则用最近更长周期) + _js = None + _cand = None + for _p in [_pt_use, '5y', '10y']: + if _p in _periods and _periods[_p]: + _cand = _periods[_p] + break + if not _cand: + continue + try: + _trades = json.loads(_cand).get("trades", []) + except Exception: + continue + if not _trades: + continue + _rmap = _rmap_a if _mkt != 'hk' else _rmap_hk + _by_regime = _dd(list) + for _t in _trades: + _ed = _t.get("entry_date", "") + if _ed in _rmap: + _by_regime[_rmap[_ed]].append(_t) + for _reg, _reg_trades in _by_regime.items(): + if len(_reg_trades) < 2: + continue + _extra = _calc_extra(_reg_trades) + _sim = _portfolio_sim(_reg_trades) + if not _sim: + continue + _wr_v = _extra.get("win_rate") + _cagr_v = _sim.get("cagr_pct") + _ret_v = _sim.get("total_return_pct") + _dd_v = _sim.get("portfolio_max_dd_pct") + _cf_v = _sim.get("capital_final") + _pt_v = _sim.get("positions_taken") + _sh_v = _extra.get("sharpe_ratio") + _pf_v = _extra.get("profit_factor") + _regime_winrates.setdefault(_ver, {})[_reg] = { + "trades": len(_reg_trades), + "win_rate": _wr_v, "avg_pnl": _extra.get("avg_pnl"), + "avg_hold_days": _extra.get("avg_hold_days"), + "total_return_pct": _ret_v, "cagr_pct": _cagr_v, + "max_dd_pct": _dd_v, "capital_final": _cf_v, + "positions_taken": _pt_v, "sharpe_ratio": _sh_v, + "profit_factor": _pf_v, + "period_tag": _pt_use, + # 温区级组合级指标(温区行也要显示组合级列) + "portfolio": {"cagr_pct": _cagr_v, "total_return_pct": _ret_v, + "portfolio_max_dd_pct": _dd_v, "capital_final": _cf_v, + "positions_taken": _pt_v, "sharpe_ratio": _sh_v, + "profit_factor": _pf_v}, + # 温区级 universality(近似) + "universality": _approx_regime_universality(_ver, _reg, len(_reg_trades)), + } except Exception: pass # 温区级普适前先建立日期→温区映射(A股 market_regime)