feat: 温区数据定时预计算——1m/6m/1y切窗由regime_perf_by_period每天生成+server直读(不再回退1y)
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user