Files
MoFin/deploy/profile-scripts/regime_tracker.py
T

186 lines
6.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""regime_tracker.py — 温区平滑跟踪 + 周期记录(2026-08-13
设计(老莫确认方向 + 数据选参):
- 实时温区判定:滞回确认 K=5(连续5天同温区才确认,滞后约5天,宁慢勿错)
数据依据:K=5 → 112周期/平均20.7天/无1天噪音(vs 原始457周期/151个1天)
- 温区周期记录:regime_cycles 表(start/end/regime/days),供策略评估归因
- 温度(rsi):不滞后,实时反映恐慌/亢奋(与温区互补:温区滞后、温度实时)
写表: regime_cycles(market, start_date, end_date, regime, days) # 2026-08-14 阶段3 加 market 维度
输出: market_regime_smoothed.json(当前平滑温区 + 温度;A股顶层键向后兼容 + markets 双市场详情)
"""
import sys
import json
import sqlite3
from pathlib import Path
from datetime import datetime
# ── 消息通道统一路由(broadcast/xmpp by delivery) ──
try:
from messenger import install_stdio_hook as _msh
_msh()
except Exception:
pass
_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"
OUT = "/home/hmo/MoFin/data/market_regime_smoothed.json"
# 滞回确认天数(数据选参:K=5 甜区)
CONFIRM_DAYS = 5
def load_daily_regime(market='a'):
"""读取指定市场 market_regime 逐日数据(时间正序)"""
conn = sqlite3.connect(DB, timeout=5)
rows = conn.execute(
"SELECT date, above_ma20, adx, regime FROM market_regime "
"WHERE market=? ORDER BY date ASC", (market,)
).fetchall()
conn.close()
return rows
def classify_day(above, adx):
"""单日温区(与 market_regime 同逻辑)"""
if above == 1 and adx is not None and adx >= 20:
return "trend_up"
if adx is not None and adx < 20:
return "choppy"
return "trend_down"
def smooth_states(rows, k=CONFIRM_DAYS):
"""滞回确认:连续 K 天同温区才确认切换。返回 (states, cycles)"""
dates = [r[0] for r in rows]
raw = [classify_day(r[1], r[2]) for r in rows]
n = len(dates)
# 状态机:current 确认态;每 K 天窗口看是否一致
states = [None] * n
current = None
for i in range(n):
if i < k - 1:
continue
window = raw[i - k + 1:i + 1]
if len(set(window)) == 1:
# 连续 K 天同温区 → 确认(切换)
current = window[0]
states[i] = current if current is not None else raw[i]
# 开头填补(前 K-1 天用原始值)
for i in range(min(k - 1, n)):
states[i] = raw[i]
# 聚合周期
cycles = []
cur = None
for i in range(n):
s = states[i]
if cur is None or s != cur["regime"]:
if cur:
cycles.append(cur)
cur = {"regime": s, "start": dates[i], "end": dates[i], "days": 1}
else:
cur["end"] = dates[i]
cur["days"] += 1
if cur:
cycles.append(cur)
return states, cycles, dates
def save_cycles(cycles, market='a'):
"""写入 regime_cycles 表(按市场重建:A/港股各自独立周期,互不清除)"""
conn = sqlite3.connect(DB, timeout=30)
conn.execute("PRAGMA busy_timeout=30000")
conn.execute("""
CREATE TABLE IF NOT EXISTS regime_cycles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
market TEXT NOT NULL DEFAULT 'a',
regime TEXT,
start_date TEXT,
end_date TEXT,
days INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
# 只清本市场周期(保持与 market_regime 同步)
conn.execute("DELETE FROM regime_cycles WHERE market=?", (market,))
for cy in cycles:
conn.execute(
"INSERT INTO regime_cycles (market, regime, start_date, end_date, days) VALUES (?,?,?,?,?)",
(market, cy["regime"], cy["start"], cy["end"], cy["days"])
)
conn.commit()
conn.close()
return len(cycles)
def get_temp(market='a'):
"""实时温度(rsi 档位,不滞后),按市场"""
try:
from temp_band import get_market_temp
return get_market_temp(market=market)
except Exception:
return {"band": "unknown", "rsi": None}
def get_smoothed_regime(market='a'):
"""计算指定市场当前平滑温区(K=5 滞回确认)。
返回 {current_regime, current_date, states, cycles, dates, total_cycles, recent_cycles}
数据不足(<K+1)时返回 None。"""
rows = load_daily_regime(market)
if len(rows) < CONFIRM_DAYS + 1:
return None
states, cycles, dates = smooth_states(rows)
return {
"current_regime": states[-1],
"current_date": dates[-1],
"states": states,
"cycles": cycles,
"dates": dates,
"total_cycles": len(cycles),
"recent_cycles": cycles[-8:],
}
def main():
details = {}
for market in ("a", "hk"):
r = get_smoothed_regime(market)
if not r:
print(f"[regime_tracker] {market} 数据不足: 不足{CONFIRM_DAYS + 1}条,跳过", flush=True)
continue
n_cycles = save_cycles(r["cycles"], market=market)
details[market] = {
"current_regime": r["current_regime"],
"current_date": r["current_date"],
"confirm_days": CONFIRM_DAYS,
"temp": get_temp(market),
"total_cycles": n_cycles,
"recent_cycles": r["recent_cycles"],
}
print(f"[{market}] 平滑温区: {r['current_regime']} (确认期{CONFIRM_DAYS}天, 至{r['current_date']})")
print(f"[{market}] 温度: {details[market]['temp'].get('band')} (rsi={details[market]['temp'].get('rsi')})")
print(f"[{market}] 周期总数: {n_cycles}")
for cy in r["recent_cycles"]:
print(f" {cy['regime']:<12} {cy['start']} ~ {cy['end']} ({cy['days']}天)")
if not details:
print("两个市场数据都不足,未生成输出", flush=True)
return
# 顶层键保持 A 股(向后兼容 regime_gate/strategy_router 读 current_regime/temp/recent_cycles
a = details.get("a") or {}
out = {
"current_regime": a.get("current_regime"),
"current_date": a.get("current_date"),
"confirm_days": CONFIRM_DAYS,
"temp": a.get("temp"),
"total_cycles": a.get("total_cycles"),
"recent_cycles": a.get("recent_cycles"),
"markets": details, # 双市场详情(A 股 + 港股)
"updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
}
Path(OUT).write_text(json.dumps(out, ensure_ascii=False, indent=1), encoding="utf-8")
if __name__ == "__main__":
main()