125 lines
4.3 KiB
Python
125 lines
4.3 KiB
Python
#!/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())
|