diff --git a/deploy/profile-scripts/backfill_hk_regime.py b/deploy/profile-scripts/backfill_hk_regime.py new file mode 100644 index 00000000..003101da --- /dev/null +++ b/deploy/profile-scripts/backfill_hk_regime.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""backfill_hk_regime.py — 港股温区历史回填(一次性脚本) + +背景(阶段3 港股接入):market_regime 表刚加 market 维度,港股温区只有今天 +1 条,regime_tracker 的 K=5 滞回平滑需要足够历史。本脚本对 stock_daily 的 +hkHSI 全历史(2014 起,约 3000 条)逐日计算温区,写入 market_regime(market='hk')。 + +算法:完全复用 market_regime.compute_regime 的判定逻辑(ma20/adx/roc → 三态), +只是按日期逐日循环(compute_regime 只算最新一天)。 + +用法: + python3 backfill_hk_regime.py # 回填港股温区全历史 + python3 backfill_hk_regime.py --days 500 # 只回填最近 500 个交易日 + +幂等:INSERT OR REPLACE(同日同市场覆盖),可重复执行。 +""" +import sqlite3 +import sys +from pathlib import Path + +_SCRIPT_DIR = Path(__file__).resolve().parent +import sys as _sys +_sys.path.insert(0, str(_SCRIPT_DIR)) +from market_regime import calc_ma, calc_trend_strength, calc_roc, ADX_TREND_MIN + +DB_PATH = Path("/home/hmo/MoFin/data/mofin.db") +INDEX_HK = "hkHSI" +LOOKBACK = 120 # 与 compute_regime 的 lookback_days 一致 + + +def classify(above_ma20, adx): + """与 market_regime.compute_regime 的 regime 分类完全一致""" + if above_ma20 is True and adx is not None and adx >= ADX_TREND_MIN: + return "trend_up" + elif adx is not None and adx < ADX_TREND_MIN: + return "choppy" + else: + return "trend_down" + + +def main(): + days = None + for a in sys.argv[1:]: + if a.startswith("--days"): + days = int(a.split("=")[-1] if "=" in a else sys.argv[sys.argv.index(a) + 1]) + + conn = sqlite3.connect(str(DB_PATH), timeout=30) + rows = conn.execute( + "SELECT date, close, high, low FROM stock_daily WHERE code=? ORDER BY date ASC", + (INDEX_HK,) + ).fetchall() + if not rows or len(rows) < 30: + print(f"{INDEX_HK} 数据不足({len(rows)} 条),无法回填") + return 1 + + dates = [r[0] for r in rows] + closes = [r[1] for r in rows] + highs = [r[2] for r in rows] + lows = [r[3] for r in rows] + n = len(rows) + if days: + n = min(n, days) + print(f"{INDEX_HK} 共 {len(rows)} 条({dates[0]} ~ {dates[-1]}),回填最近 {n} 个交易日温区") + + # 逐日计算(i 从第 30 天起,窗口用前 LOOKBACK 天) + written = 0 + start_i = max(30, n - days) if days else 30 + for i in range(start_i, n): + # 取截至 i 的最近 LOOKBACK 天 + j0 = max(0, i - LOOKBACK + 1) + c = closes[j0:i + 1] + h = highs[j0:i + 1] + l = lows[j0:i + 1] + if len(c) < 30: + continue + ma20 = calc_ma(c, 20) + trend = calc_trend_strength(h, l, c) + roc = calc_roc(c) + k = len(c) - 1 + close = c[k] + m20 = ma20[k] + above = (close > m20) if m20 else None + slope = None + if k >= 5 and ma20[k - 5] and ma20[k - 5] > 0 and m20: + slope = round((m20 - ma20[k - 5]) / ma20[k - 5] * 100, 3) + adx = trend[k] if k < len(trend) else None + roc_v = roc[k] if k < len(roc) else None + regime = classify(above, adx) + conn.execute(""" + INSERT OR REPLACE INTO market_regime + (date, market, above_ma20, ma20_slope, roc, adx, regime, close, created_at) + VALUES (?, 'hk', ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) + """, ( + dates[i], + 1 if above else 0, + slope, + round(roc_v, 3) if roc_v is not None else None, + round(adx, 2) if adx is not None else None, + regime, + close, + )) + written += 1 + if written % 500 == 0: + conn.commit() + print(f" 已回填 {written} 条(至 {dates[i]})...") + conn.commit() + + # 验证 + total = conn.execute( + "SELECT COUNT(*), MIN(date), MAX(date) FROM market_regime WHERE market='hk'" + ).fetchone() + dist = conn.execute( + "SELECT regime, COUNT(*) FROM market_regime WHERE market='hk' GROUP BY regime" + ).fetchall() + conn.close() + print(f"\n回填完成:写入 {written} 条") + print(f"market_regime 港股温区:共 {total[0]} 条,{total[1]} ~ {total[2]}") + print(f"温区分布: {dict(dist)}") + return 0 + + +if __name__ == "__main__": + sys.exit(main())