175 lines
6.5 KiB
Python
175 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""regime_perf.py — 策略-温区表现常态化记录(2026-08-13)
|
||
|
||
记录策略在不同温区(trend_up/choppy/trend_down)的表现,供"适用温度"动态评估。
|
||
- 数据来源:strategy_research 回测 trades(按入场日归入温区)+ 实盘 strategy_tracking
|
||
- 表:strategy_regime_perf(strategy, regime, trades, win_rate, avg_pnl, updated_at)
|
||
- 原则:策略全温区发信号(去门控后),记录各温区真实表现;适用温区是动态的,随数据更新
|
||
|
||
用法:
|
||
python3 regime_perf.py # 全量更新(从回测+实盘重算)
|
||
from regime_perf import get_regime_perf
|
||
"""
|
||
import sys
|
||
import json
|
||
import sqlite3
|
||
from pathlib import Path
|
||
from datetime import datetime
|
||
from collections import defaultdict
|
||
|
||
_SCRIPT_DIR = Path(__file__).resolve().parent
|
||
sys.path.insert(0, str(_SCRIPT_DIR))
|
||
sys.path.insert(0, "/home/hmo/MoFin")
|
||
|
||
DB = "/home/hmo/MoFin/data/mofin.db"
|
||
|
||
def load_all_strategies():
|
||
"""从 strategy_research 读取所有策略版本(含历史/表现不佳的——可能在特定温区能打)"""
|
||
conn = sqlite3.connect(DB, timeout=30)
|
||
conn.execute("PRAGMA busy_timeout=30000")
|
||
rows = conn.execute("SELECT DISTINCT version FROM strategy_research ORDER BY version").fetchall()
|
||
conn.close()
|
||
return [r[0] for r in rows if r[0]]
|
||
|
||
# 关注的策略(动态:全部版本)
|
||
STRATEGIES = load_all_strategies()
|
||
|
||
def load_regime_map():
|
||
"""date -> regime(用平滑 regime_tracker 的周期反查更合理,这里用 market_regime 原始 + 手动按 K=5 平滑)
|
||
简化:直接用 market_regime 的 regime(与平滑 K=5 差异主要在边界几天,评估可接受)"""
|
||
conn = sqlite3.connect(DB, timeout=30)
|
||
conn.execute("PRAGMA busy_timeout=30000")
|
||
rows = conn.execute("SELECT date, regime FROM market_regime").fetchall()
|
||
conn.close()
|
||
return dict(rows)
|
||
|
||
def get_trades_from_research(version):
|
||
"""从 strategy_research 取最新回测 trades"""
|
||
conn = sqlite3.connect(DB, timeout=30)
|
||
conn.execute("PRAGMA busy_timeout=30000")
|
||
rows = conn.execute(
|
||
"SELECT results_json FROM strategy_research WHERE version=? ORDER BY period_tag DESC, created_at DESC LIMIT 1",
|
||
(version,)
|
||
).fetchall()
|
||
conn.close()
|
||
if not rows:
|
||
return []
|
||
try:
|
||
return json.loads(rows[0][0]).get("trades", [])
|
||
except Exception:
|
||
return []
|
||
|
||
def get_trades_from_tracking():
|
||
"""从实盘 strategy_tracking 取已平仓交易"""
|
||
conn = sqlite3.connect(DB, timeout=30)
|
||
conn.execute("PRAGMA busy_timeout=30000")
|
||
rows = conn.execute(
|
||
"SELECT version_seq, tracked_at, theoretical_pnl FROM strategy_tracking WHERE status='closed'"
|
||
).fetchall()
|
||
conn.close()
|
||
result = []
|
||
for version_seq, tracked_at, pnl in rows:
|
||
if version_seq and tracked_at:
|
||
result.append({"version": version_seq, "entry_date": tracked_at[:10], "profit_pct": pnl})
|
||
return result
|
||
|
||
def compute(use_tracking=True):
|
||
"""计算所有策略各温区表现"""
|
||
regime_map = load_regime_map()
|
||
stats = defaultdict(lambda: defaultdict(lambda: {"n": 0, "win": 0, "pnl": 0}))
|
||
|
||
for v in STRATEGIES:
|
||
trades = get_trades_from_research(v)
|
||
for t in trades:
|
||
ed = t.get("entry_date", "")
|
||
if ed not in regime_map:
|
||
continue
|
||
reg = regime_map[ed]
|
||
pnl = t.get("profit_pct", 0) or 0
|
||
stats[v][reg]["n"] += 1
|
||
stats[v][reg]["pnl"] += pnl
|
||
if pnl > 0:
|
||
stats[v][reg]["win"] += 1
|
||
|
||
if use_tracking:
|
||
for t in get_trades_from_tracking():
|
||
v = t["version"]
|
||
if v not in stats:
|
||
stats[v] = defaultdict(lambda: {"n": 0, "win": 0, "pnl": 0})
|
||
ed = t["entry_date"]
|
||
if ed in regime_map:
|
||
reg = regime_map[ed]
|
||
pnl = t["profit_pct"] or 0
|
||
stats[v][reg]["n"] += 1
|
||
stats[v][reg]["pnl"] += pnl
|
||
if pnl > 0:
|
||
stats[v][reg]["win"] += 1
|
||
|
||
return stats
|
||
|
||
def save(stats):
|
||
"""写入 strategy_regime_perf 表(清空重建,保持与最新数据同步)"""
|
||
conn = sqlite3.connect(DB, timeout=30)
|
||
conn.execute("PRAGMA busy_timeout=30000")
|
||
conn.execute("""
|
||
CREATE TABLE IF NOT EXISTS strategy_regime_perf (
|
||
strategy TEXT,
|
||
regime TEXT,
|
||
trades INTEGER,
|
||
win_rate REAL,
|
||
avg_pnl REAL,
|
||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (strategy, regime)
|
||
)
|
||
""")
|
||
conn.execute("DELETE FROM strategy_regime_perf")
|
||
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
for v, regs in stats.items():
|
||
for reg, s in regs.items():
|
||
if s["n"] < 2:
|
||
continue # 样本太少不记录(>=2 给观察机会)
|
||
wr = s["win"] / s["n"] * 100
|
||
avg = s["pnl"] / s["n"]
|
||
conn.execute(
|
||
"INSERT OR REPLACE INTO strategy_regime_perf (strategy, regime, trades, win_rate, avg_pnl, updated_at) VALUES (?,?,?,?,?,?)",
|
||
(v, reg, s["n"], round(wr, 1), round(avg, 2), now)
|
||
)
|
||
conn.commit()
|
||
conn.close()
|
||
|
||
def main():
|
||
stats = compute(use_tracking=True)
|
||
save(stats)
|
||
# 打印
|
||
print("=== 策略-温区表现(strategy_regime_perf)===")
|
||
conn = sqlite3.connect(DB, timeout=30)
|
||
rows = conn.execute("SELECT strategy, regime, trades, win_rate, avg_pnl FROM strategy_regime_perf ORDER BY strategy, regime").fetchall()
|
||
conn.close()
|
||
for r in rows:
|
||
print(f" {r[0]:<12} {r[1]:<12} {r[2]:>4}笔 胜率{r[3]:.0f}% 均盈{r[4]:+.2f}%")
|
||
|
||
def get_regime_perf(strategy=None):
|
||
"""读取策略-温区表现(供 router 动态适用温区)"""
|
||
conn = sqlite3.connect(DB, timeout=30)
|
||
conn.execute("PRAGMA busy_timeout=30000")
|
||
if strategy:
|
||
rows = conn.execute(
|
||
"SELECT regime, trades, win_rate, avg_pnl FROM strategy_regime_perf WHERE strategy=?",
|
||
(strategy,)
|
||
).fetchall()
|
||
else:
|
||
rows = conn.execute(
|
||
"SELECT strategy, regime, trades, win_rate, avg_pnl FROM strategy_regime_perf"
|
||
).fetchall()
|
||
conn.close()
|
||
if strategy:
|
||
return {r[0]: {"trades": r[1], "win_rate": r[2], "avg_pnl": r[3]} for r in rows}
|
||
result = defaultdict(dict)
|
||
for r in rows:
|
||
result[r[0]][r[1]] = {"trades": r[2], "win_rate": r[3], "avg_pnl": r[4]}
|
||
return dict(result)
|
||
|
||
if __name__ == "__main__":
|
||
main()
|