#!/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())