179 lines
6.5 KiB
Python
179 lines
6.5 KiB
Python
#!/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
|
||
|
||
_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()
|