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