feat: 温区双市场改造——market_regime/temp_band/regime_tracker/regime_perf支持market维度(A股默认行为不变,港股hkHSI独立温区)+migrate_regime_market迁移脚本(4表加market列,幂等+备份)+backfill_hk_index恒指历史回填脚本

This commit is contained in:
hmo
2026-08-14 17:20:59 +08:00
parent 29db5efd95
commit 3a764d587d
6 changed files with 458 additions and 83 deletions
@@ -0,0 +1,90 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""backfill_hk_index.py — 恒生指数(hkHSI)历史日K回填(一次性脚本)
背景(阶段3 港股接入):stock_daily 的 hkHSI 仅 2024 起(643 条),温区回填
需要更长历史(对齐 A 股约 10 年)。本脚本从腾讯日K接口拉取 hkHSI 全量历史
(约 3000 根 ≈ 12 年),写入 stock_daily。
数据源(与 market_data.fetch_tx_klines 同一腾讯接口,hkHSI 已验证返回 day 键):
http://ifzq.gtimg.cn/appstock/app/fqkline/get?param=hkHSI,day,,,{count},qfq
用法:
python3 backfill_hk_index.py # 拉 3000 根(约12年)
python3 backfill_hk_index.py --count 4000
写入:INSERT OR IGNORE(code,date) 唯一键防重复,已有日期跳过)。
"""
import json
import sqlite3
import sys
import urllib.request
from pathlib import Path
_SCRIPT_DIR = Path(__file__).resolve().parent
_MOFIN_ROOT = _SCRIPT_DIR.parent.parent
DB_PATH = Path(_MOFIN_ROOT) / "data" / "mofin.db"
UA = "Mozilla/5.0"
INDEX_CODE = "hkHSI"
def fetch_hsi(count=3000):
"""从腾讯接口拉 hkHSI 日Kcount 根)"""
url = (f"http://ifzq.gtimg.cn/appstock/app/fqkline/get?"
f"param={INDEX_CODE},day,,,{count},qfq")
req = urllib.request.Request(url, headers={"User-Agent": UA})
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
with opener.open(req, timeout=30) as r:
text = r.read().decode("utf-8", errors="replace")
data = json.loads(text)
node = data.get("data", {}).get(INDEX_CODE, {})
bars = node.get("qfqday") or node.get("day") or []
return bars
def main():
count = 3000
for a in sys.argv[1:]:
if a.startswith("--count"):
count = int(a.split("=")[-1] if "=" in a else sys.argv[sys.argv.index(a) + 1])
print(f"拉取 {INDEX_CODE} 最近 {count} 根日K...")
bars = fetch_hsi(count)
if not bars:
print("拉取失败/为空,终止")
return 1
print(f"拉到 {len(bars)} 根,时间范围 {bars[0][0]} ~ {bars[-1][0]}")
conn = sqlite3.connect(str(DB_PATH), timeout=30)
conn.execute("PRAGMA busy_timeout=30000")
# stock_daily 列:code, date, open, close, high, low, volume(与 daily_kline_collector 一致)
inserted = 0
skipped = 0
for b in bars:
# 格式: [date, open, close, high, low, volume, ...]
try:
date, open_, close, high, low = b[0], b[1], b[2], b[3], b[4]
volume = float(b[5]) if len(b) > 5 and b[5] not in (None, "") else 0
except (IndexError, ValueError):
skipped += 1
continue
cur = conn.execute(
"INSERT OR IGNORE INTO stock_daily (code, date, open, close, high, low, volume) "
"VALUES (?,?,?,?,?,?,?)",
(INDEX_CODE, date, float(open_), float(close), float(high), float(low), volume)
)
inserted += cur.rowcount
conn.commit()
# 验证
total = conn.execute(
"SELECT COUNT(*), MIN(date), MAX(date) FROM stock_daily WHERE code=?", (INDEX_CODE,)
).fetchone()
conn.close()
print(f"写入 {inserted} 根(跳过 {skipped} 根格式异常 / 已有日期自动 IGNORE)")
print(f"stock_daily 的 {INDEX_CODE}: 共 {total[0]} 条,{total[1]} ~ {total[2]}")
return 0
if __name__ == "__main__":
sys.exit(main())
+44 -26
View File
@@ -24,8 +24,9 @@ regime 分类(与回测 v_next4/v_mr 分工对齐):
python3 market_regime.py --print # 只打印当前市场状态
作为库: from market_regime import compute_regime, load_market_regime
写表: market_regime(date PK, above_ma20, ma20_slope, roc, adx, regime, close, created_at)
数据源: mofin.db stock_daily 的 sh000001import_full_stocks 每日收盘后更新,
写表: market_regime((date, market) PK, above_ma20, ma20_slope, roc, adx, regime, close, created_at)
market='a'(A股 sh000001) / 'hk'(港股 hkHSI),同一交易日两个市场各一条(2026-08-14 阶段3 双市场)
数据源: mofin.db stock_daily 的 sh000001/hkHSI(每日收盘后更新,
盘中用最新可得日线,未收盘日不计入最终判断,adx 用真实历史)。
"""
import sys
@@ -167,8 +168,8 @@ def compute_regime(index_code=INDEX_CODE, db_path=None, lookback_days=120):
}
def save_regime(regime, db_path=None):
"""写入 market_regime 表(按 date 去重,同日覆盖)"""
def save_regime(regime, market='a', db_path=None):
"""写入 market_regime 表(按 (date, market) 去重,同日同市场覆盖)"""
if not regime:
return False
db = db_path or DB_PATH
@@ -176,22 +177,25 @@ def save_regime(regime, db_path=None):
try:
conn.execute("""
CREATE TABLE IF NOT EXISTS market_regime (
date TEXT PRIMARY KEY,
date TEXT NOT NULL,
market TEXT NOT NULL DEFAULT 'a',
above_ma20 INTEGER,
ma20_slope REAL,
roc REAL,
adx REAL,
regime TEXT,
close REAL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (date, market)
)
""")
conn.execute("""
INSERT OR REPLACE INTO market_regime
(date, above_ma20, ma20_slope, roc, adx, regime, close, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
(date, market, above_ma20, ma20_slope, roc, adx, regime, close, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
""", (
regime["date"],
market,
1 if regime["above_ma20"] else 0,
regime.get("ma20_slope"),
regime.get("roc"),
@@ -205,14 +209,16 @@ def save_regime(regime, db_path=None):
return True
def load_market_regime(db_path=None):
"""读取最新 market_regime(供 strategy_lifecycle 等消费方调用)"""
def load_market_regime(db_path=None, market='a'):
"""读取指定市场最新 market_regime(供 strategy_lifecycle 等消费方调用)
market='a' 默认 → 与改造前行为完全一致(A 股温区)"""
db = db_path or DB_PATH
conn = sqlite3.connect(str(db), timeout=5)
try:
row = conn.execute(
"SELECT date, above_ma20, ma20_slope, roc, adx, regime, close "
"FROM market_regime ORDER BY date DESC LIMIT 1").fetchone()
"FROM market_regime WHERE market=? ORDER BY date DESC LIMIT 1",
(market,)).fetchone()
finally:
conn.close()
if not row:
@@ -236,21 +242,33 @@ REGIME_DESC = {
def main():
regime = compute_regime()
if "--print" in sys.argv or not regime:
if not regime:
print("sh000001 数据不足,无法计算市场状态")
return 1
print(json.dumps(regime, ensure_ascii=False, indent=2))
print(f"判断: {REGIME_DESC.get(regime['regime'], '')}")
return 0
ok = save_regime(regime)
if ok:
print(f"[market_regime] {regime['date']}{regime['regime']} "
f"(above_ma20={regime['above_ma20']} adx={regime['adx']} "
f"slope={regime['ma20_slope']} roc={regime['roc']})")
else:
print("[market_regime] 写入失败")
# 双市场:A股(sh000001) + 港股(hkHSI),各自独立温区
results = {}
for market, index_code in (("a", INDEX_CODE), ("hk", INDEX_CODE_HK)):
r = compute_regime(index_code=index_code)
if not r:
print(f"[market_regime] {index_code} 数据不足,跳过 {market} 市场", flush=True)
continue
results[market] = (index_code, r)
if "--print" in sys.argv:
for market, (idx, regime) in results.items():
print(json.dumps(regime, ensure_ascii=False, indent=2))
print(f"[{market}] {idx} 判断: {REGIME_DESC.get(regime['regime'], '')}")
return 0 if results else 1
ok_any = False
for market, (idx, regime) in results.items():
ok = save_regime(regime, market=market)
if ok:
ok_any = True
print(f"[market_regime] {market} {regime['date']}{regime['regime']} "
f"(above_ma20={regime['above_ma20']} adx={regime['adx']} "
f"slope={regime['ma20_slope']} roc={regime['roc']})")
else:
print(f"[market_regime] {market} 写入失败")
if not ok_any:
print("[market_regime] 两个市场均写入失败/数据不足")
return 1
return 0
@@ -0,0 +1,222 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""migrate_regime_market.py — 温区系统双市场表迁移(一次性脚本)
背景(阶段3 港股接入):温区相关 4 张表加 market 维度('a' A股 / 'hk' 港股),
A 股历史数据统一回填 market='a',与改造前语义完全一致。
迁移表与方式:
- market_regime : 重建。主键 date → (date, market)。旧数据 market='a'
- strategy_regime_perf : 重建。主键 (strategy,regime) → (strategy,regime,market)
- market_indicators : 重建。主键 date → (date, market)。旧数据 market='a'
- regime_cycles : ALTER 加列(主键 id 不变,无需重建)
幂等:每张表先查 market 列是否已存在,已存在则跳过(可重复执行)。
备份:迁移前每张表备份为 xxx_bak_20260814CREATE TABLE AS SELECT,含数据,
不含约束——回滚时数据完整可导回)。数据库另有每日备份。
用法:
python3 migrate_regime_market.py # 迁移全部 4 张表
python3 migrate_regime_market.py --check # 只检查现状,不迁移
"""
import sqlite3
import sys
from pathlib import Path
_SCRIPT_DIR = Path(__file__).resolve().parent
_MOFIN_ROOT = _SCRIPT_DIR.parent.parent
DB_PATH = Path(_MOFIN_ROOT) / "data" / "mofin.db"
BAK_SUFFIX = "_bak_20260814"
TABLES = ["market_regime", "regime_cycles", "strategy_regime_perf", "market_indicators"]
def _connect():
conn = sqlite3.connect(str(DB_PATH), timeout=30)
conn.execute("PRAGMA busy_timeout=30000")
return conn
def _table_exists(conn, name):
row = conn.execute(
"SELECT 1 FROM sqlite_master WHERE type='table' AND name=?", (name,)
).fetchone()
return row is not None
def _has_column(conn, table, col):
cols = [r[1] for r in conn.execute(f"PRAGMA table_info({table})")]
return col in cols
def _backup_table(conn, table):
"""迁移前备份:CREATE TABLE xxx_bak_20260814 AS SELECT * FROM xxx"""
bak = table + BAK_SUFFIX
conn.execute(f"DROP TABLE IF EXISTS {bak}")
conn.execute(f"CREATE TABLE {bak} AS SELECT * FROM {table}")
n = conn.execute(f"SELECT COUNT(*) FROM {bak}").fetchone()[0]
print(f" [备份] {table}{bak} ({n} 行)")
return bak
def _migrate_market_regime(conn):
"""重建:主键 (date, market),旧数据 market='a'"""
if _has_column(conn, "market_regime", "market"):
print("[跳过] market_regime 已有 market 列")
return
_backup_table(conn, "market_regime")
conn.execute("DROP TABLE IF EXISTS market_regime_new")
conn.execute("""
CREATE TABLE market_regime_new (
date TEXT NOT NULL,
market TEXT NOT NULL DEFAULT 'a',
above_ma20 INTEGER,
ma20_slope REAL,
roc REAL,
adx REAL,
regime TEXT,
close REAL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (date, market)
)
""")
conn.execute("""
INSERT INTO market_regime_new
(date, market, above_ma20, ma20_slope, roc, adx, regime, close, created_at)
SELECT date, 'a', above_ma20, ma20_slope, roc, adx, regime, close, created_at
FROM market_regime
""")
conn.execute("DROP TABLE market_regime")
conn.execute("ALTER TABLE market_regime_new RENAME TO market_regime")
n = conn.execute("SELECT COUNT(*) FROM market_regime").fetchone()[0]
print(f" [完成] market_regime: {n} 行, 主键 (date,market), 历史数据 market='a'")
def _migrate_strategy_regime_perf(conn):
"""重建:主键 (strategy, regime, market),旧数据 market='a'"""
if _has_column(conn, "strategy_regime_perf", "market"):
print("[跳过] strategy_regime_perf 已有 market 列")
return
_backup_table(conn, "strategy_regime_perf")
conn.execute("DROP TABLE IF EXISTS strategy_regime_perf_new")
conn.execute("""
CREATE TABLE strategy_regime_perf_new (
strategy TEXT,
market TEXT NOT NULL DEFAULT 'a',
regime TEXT,
trades INTEGER,
win_rate REAL,
avg_pnl REAL,
avg_hold_days REAL,
total_return_pct REAL,
cagr_pct REAL,
portfolio_max_dd_pct REAL,
capital_final REAL,
positions_taken INTEGER,
sharpe_ratio REAL,
profit_factor REAL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (strategy, regime, market)
)
""")
conn.execute("""
INSERT INTO strategy_regime_perf_new
(strategy, market, regime, trades, win_rate, avg_pnl, avg_hold_days,
total_return_pct, cagr_pct, portfolio_max_dd_pct, capital_final,
positions_taken, sharpe_ratio, profit_factor, updated_at)
SELECT strategy, 'a', regime, trades, win_rate, avg_pnl, avg_hold_days,
total_return_pct, cagr_pct, portfolio_max_dd_pct, capital_final,
positions_taken, sharpe_ratio, profit_factor, updated_at
FROM strategy_regime_perf
""")
conn.execute("DROP TABLE strategy_regime_perf")
conn.execute("ALTER TABLE strategy_regime_perf_new RENAME TO strategy_regime_perf")
n = conn.execute("SELECT COUNT(*) FROM strategy_regime_perf").fetchone()[0]
print(f" [完成] strategy_regime_perf: {n} 行, 主键 (strategy,regime,market)")
def _migrate_market_indicators(conn):
"""重建:主键 date → (date, market),旧数据 market='a'"""
if _has_column(conn, "market_indicators", "market"):
print("[跳过] market_indicators 已有 market 列")
return
_backup_table(conn, "market_indicators")
conn.execute("DROP TABLE IF EXISTS market_indicators_new")
conn.execute("""
CREATE TABLE market_indicators_new (
date TEXT NOT NULL,
market TEXT NOT NULL DEFAULT 'a',
mkt_rsi REAL, mkt_dd60 REAL, mkt_adx REAL, mkt_above_ma20 INTEGER,
mkt_down_days INTEGER, mkt_slope REAL, mkt_roc REAL,
updated_at TEXT,
PRIMARY KEY (date, market)
)
""")
conn.execute("""
INSERT INTO market_indicators_new
(date, market, mkt_rsi, mkt_dd60, mkt_adx, mkt_above_ma20,
mkt_down_days, mkt_slope, mkt_roc, updated_at)
SELECT date, 'a', mkt_rsi, mkt_dd60, mkt_adx, mkt_above_ma20,
mkt_down_days, mkt_slope, mkt_roc, updated_at
FROM market_indicators
""")
conn.execute("DROP TABLE market_indicators")
conn.execute("ALTER TABLE market_indicators_new RENAME TO market_indicators")
n = conn.execute("SELECT COUNT(*) FROM market_indicators").fetchone()[0]
print(f" [完成] market_indicators: {n} 行, 主键 (date,market)")
def _migrate_regime_cycles(conn):
"""ALTER 加列:主键 id 不变,无需重建。market TEXT NOT NULL DEFAULT 'a'"""
if _has_column(conn, "regime_cycles", "market"):
print("[跳过] regime_cycles 已有 market 列")
return
_backup_table(conn, "regime_cycles")
conn.execute("ALTER TABLE regime_cycles ADD COLUMN market TEXT NOT NULL DEFAULT 'a'")
n = conn.execute("SELECT COUNT(*) FROM regime_cycles").fetchone()[0]
print(f" [完成] regime_cycles: {n} 行, 已加 market 列 (id 主键保留)")
def check_status(conn):
"""检查现状(--check):每张表是否存在、是否已有 market 列"""
print(f"DB: {DB_PATH}")
for t in TABLES:
if not _table_exists(conn, t):
print(f" {t:<24} 表不存在")
continue
n = conn.execute(f"SELECT COUNT(*) FROM {t}").fetchone()[0]
m = _has_column(conn, t, "market")
cols = ", ".join(r[1] for r in conn.execute(f"PRAGMA table_info({t})"))
print(f" {t:<24} {n:>7} 行 | market列: {'' if m else ''} | {cols}")
def main():
if not DB_PATH.exists():
print(f"DB 不存在: {DB_PATH}")
return 1
conn = _connect()
if "--check" in sys.argv:
check_status(conn)
conn.close()
return 0
for t in TABLES:
if not _table_exists(conn, t):
print(f"[跳过] {t} 表不存在")
continue
if t == "market_regime":
_migrate_market_regime(conn)
elif t == "strategy_regime_perf":
_migrate_strategy_regime_perf(conn)
elif t == "market_indicators":
_migrate_market_indicators(conn)
elif t == "regime_cycles":
_migrate_regime_cycles(conn)
conn.commit()
print("全部完成。验证:")
check_status(conn)
conn.close()
return 0
if __name__ == "__main__":
sys.exit(main())
+22 -11
View File
@@ -34,10 +34,13 @@ def load_all_strategies():
return [r[0] for r in rows if r[0]]
def load_regime_map():
def load_regime_map(market='a'):
"""读取指定市场的 date→regime 映射(温区归因用)"""
conn = sqlite3.connect(DB, timeout=30)
conn.execute("PRAGMA busy_timeout=30000")
rows = conn.execute("SELECT date, regime FROM market_regime").fetchall()
rows = conn.execute(
"SELECT date, regime FROM market_regime WHERE market=?", (market,)
).fetchall()
conn.close()
return dict(rows)
@@ -96,15 +99,22 @@ def calc_extra(trades):
def main():
regime_map = load_regime_map()
# --market=a|hk:策略温区归因市场。当前所有策略为 A 股(market='a' 默认),
# HK 维度留给阶段4(港股策略落地后按 strategy→market 映射归因)
market = "a"
for a in sys.argv[1:]:
if a.startswith("--market="):
market = a.split("=", 1)[1]
regime_map = load_regime_map(market)
strategies = load_all_strategies()
print(f"策略数: {len(strategies)}")
print(f"市场: {market} | 策略数: {len(strategies)}")
conn = sqlite3.connect(DB, timeout=60)
conn.execute("PRAGMA busy_timeout=60000")
conn.execute("""
CREATE TABLE IF NOT EXISTS strategy_regime_perf (
strategy TEXT,
market TEXT NOT NULL DEFAULT 'a',
regime TEXT,
trades INTEGER,
win_rate REAL,
@@ -118,10 +128,10 @@ def main():
sharpe_ratio REAL,
profit_factor REAL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (strategy, regime)
PRIMARY KEY (strategy, regime, market)
)
""")
conn.execute("DELETE FROM strategy_regime_perf")
conn.execute("DELETE FROM strategy_regime_perf WHERE market=?", (market,))
written = 0
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@@ -144,11 +154,11 @@ def main():
continue
conn.execute(
"""INSERT OR REPLACE INTO strategy_regime_perf
(strategy, regime, trades, win_rate, avg_pnl, avg_hold_days,
(strategy, market, regime, trades, win_rate, avg_pnl, avg_hold_days,
total_return_pct, cagr_pct, portfolio_max_dd_pct, capital_final,
positions_taken, sharpe_ratio, profit_factor, updated_at)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(v, reg, len(reg_trades),
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(v, market, reg, len(reg_trades),
extra.get("win_rate"), extra.get("avg_pnl"), extra.get("avg_hold_days"),
sim.get("total_return_pct"), sim.get("cagr_pct"), sim.get("portfolio_max_dd_pct"),
sim.get("capital_final"), sim.get("positions_taken"),
@@ -157,13 +167,14 @@ def main():
written += 1
conn.commit()
conn.close()
print(f"写入 strategy_regime_perf {written} 条(含温区级组合模拟)")
print(f"写入 strategy_regime_perf {written} 条(market={market}含温区级组合模拟)")
# 打印样例
conn = sqlite3.connect(DB, timeout=30)
rows = conn.execute(
"SELECT strategy, regime, trades, win_rate, cagr_pct, capital_final FROM strategy_regime_perf "
"WHERE strategy IN ('v_oversold','v_mr_sel','s2_panic') ORDER BY strategy, regime"
"WHERE strategy IN ('v_oversold','v_mr_sel','s2_panic') AND market=? ORDER BY strategy, regime",
(market,)
).fetchall()
conn.close()
for r in rows:
+65 -39
View File
@@ -8,8 +8,8 @@
- 温区周期记录:regime_cycles 表(start/end/regime/days),供策略评估归因
- 温度(rsi):不滞后,实时反映恐慌/亢奋(与温区互补:温区滞后、温度实时)
写表: regime_cycles(date_start, date_end, regime, days)
输出: market_regime_smoothed.json(当前平滑温区 + 温度)
写表: regime_cycles(market, start_date, end_date, regime, days) # 2026-08-14 阶段3 加 market 维度
输出: market_regime_smoothed.json(当前平滑温区 + 温度;A股顶层键向后兼容 + markets 双市场详情
"""
import sys
import json
@@ -27,11 +27,12 @@ OUT = "/home/hmo/MoFin/data/market_regime_smoothed.json"
# 滞回确认天数(数据选参:K=5 甜区)
CONFIRM_DAYS = 5
def load_daily_regime():
"""读取 market_regime 逐日数据(时间正序)"""
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 ORDER BY date ASC"
"SELECT date, above_ma20, adx, regime FROM market_regime "
"WHERE market=? ORDER BY date ASC", (market,)
).fetchall()
conn.close()
return rows
@@ -81,13 +82,14 @@ def smooth_states(rows, k=CONFIRM_DAYS):
cycles.append(cur)
return states, cycles, dates
def save_cycles(cycles):
"""写入 regime_cycles 表"""
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,
@@ -95,58 +97,82 @@ def save_cycles(cycles):
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
# 清空重建(保持与 market_regime 同步)
conn.execute("DELETE FROM regime_cycles")
# 只清本市场周期(保持与 market_regime 同步)
conn.execute("DELETE FROM regime_cycles WHERE market=?", (market,))
for cy in cycles:
conn.execute(
"INSERT INTO regime_cycles (regime, start_date, end_date, days) VALUES (?,?,?,?)",
(cy["regime"], cy["start"], cy["end"], cy["days"])
"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():
"""实时温度(rsi 档位,不滞后)"""
def get_temp(market='a'):
"""实时温度(rsi 档位,不滞后),按市场"""
try:
from temp_band import get_market_temp
return get_market_temp()
return get_market_temp(market=market)
except Exception:
return {"band": "unknown", "rsi": None}
def main():
rows = load_daily_regime()
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:
print(f"数据不足: {len(rows)}")
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
states, cycles, dates = smooth_states(rows)
n_cycles = save_cycles(cycles)
# 当前平滑温区(最新确认态)
current_regime = states[-1]
current_date = dates[-1]
temp = get_temp()
# 最近周期列表
recent = cycles[-8:]
# 顶层键保持 A 股(向后兼容 regime_gate/strategy_router 读 current_regime/temp/recent_cycles
a = details.get("a") or {}
out = {
"current_regime": current_regime,
"current_date": current_date,
"current_regime": a.get("current_regime"),
"current_date": a.get("current_date"),
"confirm_days": CONFIRM_DAYS,
"temp": temp,
"total_cycles": n_cycles,
"recent_cycles": recent,
"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")
print(f"平滑温区: {current_regime} (确认期{CONFIRM_DAYS}天, 至{current_date})")
print(f"温度: {temp.get('band')} (rsi={temp.get('rsi')})")
print(f"周期总数: {n_cycles}")
print("最近周期:")
for cy in recent:
print(f" {cy['regime']:<12} {cy['start']} ~ {cy['end']} ({cy['days']}天)")
if __name__ == "__main__":
main()
+15 -7
View File
@@ -20,8 +20,10 @@
import sqlite3
from pathlib import Path
from market_config import MARKETS
DB = Path("/home/hmo/MoFin/data/mofin.db")
INDEX = "sh000001"
INDEX = "sh000001" # A股默认指数(兜底);实际以 MARKETS[market]['index_code'] 为准
def calc_rsi(series, n=14):
@@ -69,19 +71,25 @@ def temp_multiplier(band, strategy_family="mr"):
}.get(band, 0.5)
def get_market_temp(db_path=None):
"""读取最新市场温度(rsi + 档位 + regime)。"""
def get_market_temp(market='a', db_path=None):
"""读取指定市场最新温度(rsi + 档位 + regime)。
market: 'a'(默认, A股 sh000001) / 'hk'(港股 hkHSI)。
指数代码从 market_config.MARKETS 读取,不写死。
"""
db = db_path or DB
index_code = MARKETS.get(market, MARKETS['a'])['index_code']
conn = sqlite3.connect(str(db), timeout=5)
try:
# 上证最近 30 日收盘算 rsi
# 指数最近 30 日收盘算 rsi
rows = conn.execute(
"SELECT date, close FROM stock_daily WHERE code=? ORDER BY date DESC LIMIT 30",
(INDEX,)
(index_code,)
).fetchall()
# 最新 regime
# 最新 regime(按市场过滤——否则 A 股温度会误读港股 regime)
reg = conn.execute(
"SELECT date, regime FROM market_regime ORDER BY date DESC LIMIT 1"
"SELECT date, regime FROM market_regime WHERE market=? ORDER BY date DESC LIMIT 1",
(market,)
).fetchone()
finally:
conn.close()