diff --git a/deploy/profile-scripts/backfill_hk_index.py b/deploy/profile-scripts/backfill_hk_index.py new file mode 100644 index 00000000..22ff6611 --- /dev/null +++ b/deploy/profile-scripts/backfill_hk_index.py @@ -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 日K(count 根)""" + 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()) diff --git a/deploy/profile-scripts/market_regime.py b/deploy/profile-scripts/market_regime.py index 05e5d02c..929d90c9 100644 --- a/deploy/profile-scripts/market_regime.py +++ b/deploy/profile-scripts/market_regime.py @@ -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 的 sh000001(import_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 diff --git a/deploy/profile-scripts/migrate_regime_market.py b/deploy/profile-scripts/migrate_regime_market.py new file mode 100644 index 00000000..fc87807c --- /dev/null +++ b/deploy/profile-scripts/migrate_regime_market.py @@ -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_20260814(CREATE 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()) diff --git a/deploy/profile-scripts/regime_perf.py b/deploy/profile-scripts/regime_perf.py index d5c33be8..bbd19eee 100644 --- a/deploy/profile-scripts/regime_perf.py +++ b/deploy/profile-scripts/regime_perf.py @@ -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: diff --git a/deploy/profile-scripts/regime_tracker.py b/deploy/profile-scripts/regime_tracker.py index 95e7a057..73e577cc 100644 --- a/deploy/profile-scripts/regime_tracker.py +++ b/deploy/profile-scripts/regime_tracker.py @@ -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}; + 数据不足(