refactor: 数据层重构 — 统一 SQLite 访问层 + 多脚本双写

新建 mofin_db.py 共享数据库模块:
- get_conn() 统一连接管理 (WAL + Row factory + 外键)
- init_all_tables() 幂等建表 (12张表: market/sector/stock/kline/fundamentals/sectors/holdings/strategies/watchlist/candidates/score_history/events/evaluations)
- write_market_snapshot() 市场快照双写
- write_klines() K线数据双写 (stocks + daily/weekly/monthly + fundamentals)
- write_price_event() 价格事件双写
- migrate_stock_sectors() 一次性迁移 stock_sector_map.json
- query_*() 通用查询函数 (sector_trend/top_inflow/consecutive_inflow/market_mood/db_stats)

重构现有脚本:
- market_watch.py: 删除内联 DB 代码,改用 mofin_db
- multi_timeframe.py: _save_local_history() 加 SQLite 双写
- price_monitor.py: record_event() 加 SQLite 双写
- mofin_query.py: 改用 mofin_db 查询函数

新增:
- migrate_sectors.py: 一次性迁移脚本

清理:
- get_realtime_prices.py: 死代码 (只读 portfolio.json,不调API)
This commit is contained in:
hmo
2026-06-20 16:25:36 +08:00
parent 8926b11090
commit 0924cf3124
7 changed files with 568 additions and 308 deletions
+9 -96
View File
@@ -13,102 +13,12 @@
"""
import json
import sqlite3
from datetime import datetime
from pathlib import Path
from mofin_db import get_conn, init_all_tables, write_market_snapshot
DATA_DIR = Path(__file__).parent / "data"
DB_PATH = DATA_DIR / "mofin.db"
# ── 数据库初始化 ──────────────────────────────────────
def init_db():
"""创建 mofin.db 及所有表(幂等,已存在则跳过)"""
DATA_DIR.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(str(DB_PATH))
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA foreign_keys=ON")
conn.executescript("""
CREATE TABLE IF NOT EXISTS market_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL,
source TEXT NOT NULL DEFAULT 'ths',
up_ratio REAL,
mood TEXT,
created_at TEXT DEFAULT (datetime('now','localtime'))
);
CREATE INDEX IF NOT EXISTS idx_snapshots_time ON market_snapshots(timestamp);
CREATE TABLE IF NOT EXISTS sector_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
snapshot_id INTEGER NOT NULL REFERENCES market_snapshots(id),
name TEXT NOT NULL,
change_pct REAL,
up_count INTEGER,
down_count INTEGER,
net_inflow REAL,
lead_stock TEXT,
lead_stock_change REAL,
volume REAL,
turnover REAL
);
CREATE INDEX IF NOT EXISTS idx_sector_name ON sector_snapshots(name);
CREATE INDEX IF NOT EXISTS idx_sector_snapshot ON sector_snapshots(snapshot_id);
CREATE INDEX IF NOT EXISTS idx_sector_name_time ON sector_snapshots(name, snapshot_id);
""")
conn.commit()
return conn
def write_snapshot(conn, market_data: dict):
"""将一次采集结果双写 SQLite(JSON 写入由 main 负责)"""
try:
# 1. INSERT market_snapshots
cur = conn.execute(
"""INSERT INTO market_snapshots (timestamp, source, up_ratio, mood)
VALUES (?, ?, ?, ?)""",
(
market_data["timestamp"],
market_data.get("source", "unknown"),
market_data.get("up_ratio", 0),
market_data.get("mood", "unknown"),
),
)
snapshot_id = cur.lastrowid
# 2. INSERT sector_snapshots(逐板块)
sectors = market_data.get("sectors", [])
rows = []
for s in sectors:
rows.append((
snapshot_id,
s.get("name", ""),
s.get("change", 0),
s.get("up_count"),
s.get("down_count"),
s.get("net_inflow"),
s.get("lead_stock"),
s.get("lead_stock_change"),
s.get("volume"),
s.get("turnover"),
))
if rows:
conn.executemany(
"""INSERT INTO sector_snapshots
(snapshot_id, name, change_pct, up_count, down_count,
net_inflow, lead_stock, lead_stock_change, volume, turnover)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
rows,
)
conn.commit()
return snapshot_id, len(rows)
except Exception as e:
print(f"[DB] SQLite 写入失败(JSON 不受影响): {e}", flush=True)
try:
conn.rollback()
except Exception:
pass
return None, 0
# ── 後端A:東方財富 push2 API(首選,有板塊代碼+實時指數) ──
@@ -252,10 +162,13 @@ def main():
json.dump(market_data, f, ensure_ascii=False, indent=2)
# ── SQLite 双写 ──
conn = init_db()
sid, count = write_snapshot(conn, market_data)
if sid:
print(f"[DB] snapshot_id={sid}, sectors={count}", flush=True)
conn = get_conn()
init_all_tables(conn)
ok, msg, sid = write_market_snapshot(conn, market_data)
if ok:
print(f"[DB] {msg}", flush=True)
else:
print(f"[DB] 写入失败(JSON 不受影响): {msg}", flush=True)
conn.close()
# 靜默:只寫文件,不輸出到stdout,避免cron推送