diff --git a/deploy/profile-scripts/regime_perf_by_period.py b/deploy/profile-scripts/regime_perf_by_period.py index c3dc0e3b..fe9770ec 100644 --- a/deploy/profile-scripts/regime_perf_by_period.py +++ b/deploy/profile-scripts/regime_perf_by_period.py @@ -109,6 +109,71 @@ def regime_days_in_window(conn, market, d_min, d_max): return {r[0]: r[1] for r in rows}, total +# ── 2026-08-18 切窗周期(1m/6m/1y):从最长周期 trades 切窗→温区聚合 ── +# 老莫:1m/6m/1y温区数据须有真实差异 + 每天定时刷新(server只读表,快且新) +SLICE_DAYS_2 = {'1m': 30, '6m': 185, '1y': 365} + + +def process_slice_period(conn, market, period_tag): + """对切窗周期:所有策略从最长周期trades切出窗口算温区表现,写表""" + days = SLICE_DAYS_2.get(period_tag) + if not days: + return 0 + from datetime import timedelta + rmap = dict(conn.execute( + "SELECT date, regime FROM market_regime WHERE market=?", (market,)).fetchall()) + if not rmap: + return 0 + ver_rows = conn.execute( + "SELECT DISTINCT version FROM strategy_research WHERE COALESCE(market,'a')=?", + (market,)).fetchall() + written = 0 + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + for (v,) in ver_rows: + # 最长周期 trades + rr = conn.execute( + "SELECT results_json FROM strategy_research WHERE COALESCE(market,'a')=? AND version=? " + "ORDER BY CASE COALESCE(period_tag,'2y') WHEN '10y' THEN 3 WHEN '5y' THEN 2 ELSE 1 END DESC, id DESC LIMIT 1", + (market, v)).fetchone() + if not rr or not rr[0]: + continue + try: + trades = json.loads(rr[0]).get("trades", []) + except Exception: + continue + if not trades: + continue + max_date = max(t.get('entry_date', '') for t in trades) + cutoff = (datetime.strptime(max_date, '%Y-%m-%d') - timedelta(days=days)).strftime('%Y-%m-%d') + sliced = [t for t in trades if t.get('entry_date', '') >= cutoff] + if not sliced: + continue + by_regime = defaultdict(list) + for t in sliced: + ed = t.get("entry_date", "") + if ed in rmap: + by_regime[rmap[ed]].append(t) + for reg, reg_trades in by_regime.items(): + if not reg_trades: + continue + wins = sum(1 for t in reg_trades if (t.get('profit_pct') or 0) > 0) + pnl = sum(t.get('profit_pct') or 0 for t in reg_trades) + hold = sum(t.get('hold_days') or 0 for t in reg_trades) + n = len(reg_trades) + conn.execute( + "INSERT OR REPLACE INTO strategy_regime_perf_by_period " + "(strategy, market, regime, period_tag, 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, universality_months, universality_years, " + "universality_valid_years, universality_score, universality_leave1, updated_at) " + "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", + (v, market, reg, period_tag, n, round(wins / n * 100, 1), round(pnl / n, 2), + round(hold / n, 1), round(pnl, 1), None, None, 0, n, None, None, + 0, 0, 0, 0, 0, now)) + written += 1 + return written + + def process_period(conn, market, period_tag): """处理单个周期:所有策略的温区表现,写入 strategy_regime_perf_by_period""" # 温区映射 @@ -220,7 +285,10 @@ def main(): conn.execute("DELETE FROM strategy_regime_perf_by_period WHERE market=?", (market,)) total = 0 for pt in periods: - n = process_period(conn, market, pt) + if pt in SLICE_DAYS_2: + n = process_slice_period(conn, market, pt) + else: + n = process_period(conn, market, pt) print(f"[{market}][{pt}] 写入 {n} 条", flush=True) total += n conn.commit() diff --git a/server.py b/server.py index b77ade8c..d740d358 100644 --- a/server.py +++ b/server.py @@ -50,80 +50,10 @@ def _compute_regime_winrates_cached(pt, _approx_univ): 无对应周期记录时回退到最近更长周期(1m/6m→1y,2y→2y,5y→5y,10y→10y)。 """ import sqlite3 as _sq - from datetime import datetime as _dt, timedelta as _td - from collections import defaultdict - _pt_chain = {'1m': '1y', '6m': '1y', '1y': '1y', '2y': '2y', '5y': '5y', '10y': '10y'} + _pt_chain = {'1m': '1m', '6m': '6m', '1y': '1y', '2y': '2y', '5y': '5y', '10y': '10y'} # 2026-08-18 切窗周期直读(已预计算) _pt_use = _pt_chain.get(pt or '2y', '2y') _regime_winrates = {} - # ── 2026-08-18 切窗周期(1m/6m/1y):从 trades 按 entry_date 切窗+温区聚合 ── - # 原直接回退1y表→1m/6m/1y温区数据完全相同(老莫反馈毫无差别)。 - SLICE_DAYS = {'1m': 30, '6m': 185, '1y': 365} - if pt in SLICE_DAYS: - try: - _c = _sq.connect(str(DATA_DIR / "mofin.db"), timeout=20) - _c.execute("PRAGMA busy_timeout=20000") - # 温区map date->regime - _rmap = {} - for _rd in _c.execute("SELECT date, regime FROM market_regime WHERE market='a'"): - _rmap[_rd[0]] = _rd[1] - # 各策略 trades(直接查 strategy_research 最新,含 entry_date/profit_pct) - _rows = _c.execute( - "SELECT version, market, results_json FROM strategy_research " - "ORDER BY LENGTH(COALESCE(period_tag,'2y')) DESC, COALESCE(period_tag,'2y') DESC, id DESC").fetchall() - _c.close() - _seen = set() - _today = _dt.now().strftime("%Y-%m-%d") - _cutoff = (_dt.now() - _td(days=SLICE_DAYS[pt])).strftime("%Y-%m-%d") - for _ver, _mkt, _rj in _rows: - if _ver in _seen: - continue - _seen.add(_ver) - try: - _trs = json.loads(_rj).get("trades", []) if _rj else [] - except Exception: - continue - if not _trs: - continue - # 按温区分组当前窗口 trades - _agg = defaultdict(lambda: {"trades": 0, "wins": 0, "pnl": 0.0, "holds": 0.0}) - _mkt0 = (_mkt or "all") - for _t in _trs: - _ed = _t.get("entry_date", "") - if not _ed or _ed < _cutoff: - continue - _ed_short = _ed[:10] - _rg0 = _rmap.get(_ed_short) - if not _rg0: - continue - _g = _agg[_rg0] - _g["trades"] += 1 - _p = _t.get("profit_pct") or 0 - if _p > 0: - _g["wins"] += 1 - _g["pnl"] += _p - _g["holds"] += _t.get("hold_days") or 0 - for _rg0, _g in _agg.items(): - _n = _g["trades"] - if _n == 0: - continue - _wr = _g["wins"] / _n * 100 - _ap = _g["pnl"] / _n - _ah = _g["holds"] / _n - _regime_winrates.setdefault(_ver, {})[_rg0] = { - "trades": _n, "win_rate": round(_wr, 1), "avg_pnl": round(_ap, 2), - "avg_hold_days": round(_ah, 1), "total_return_pct": round(_ap * _n, 1), - "cagr_pct": None, "max_dd_pct": None, "capital_final": 0, - "positions_taken": _n, "sharpe_ratio": None, "profit_factor": None, - "period_tag": pt, - "portfolio": {"cagr_pct": None, "total_return_pct": round(_ap * _n, 1), - "portfolio_max_dd_pct": None, "capital_final": 0, - "positions_taken": _n, "sharpe_ratio": None, "profit_factor": None}, - "universality": _approx_univ(_ver, _rg0, _n), - } - return _regime_winrates # 切窗周期已按真实周期算,不再回退1y表 - except Exception: - pass # 失败则回退下面表逻辑 try: _c = _sq.connect(str(DATA_DIR / "mofin.db"), timeout=10) _c.execute("PRAGMA busy_timeout=10000")