feat: 阶段1 — market_watch 双写 SQLite + 查询工具
- market_watch.py: 新增 init_db() 建表 + write_snapshot() 双写 SQLite - market_snapshots: 每次采集的元信息(时间、来源、涨跌比、情绪) - sector_snapshots: 每个板块的涨跌幅、资金流向、领涨股等 - JSON 写入保留不变,SQLite 写入失败不影响 JSON 管道 - mofin_query.py: 通用查询工具 - 板块趋势查询:「半导体最近5次采集的涨跌幅」 - 资金流向排行:「净流入最多的5个板块」 - 连续净流入检测:「最近3天连续净流入的板块」 - 市场情绪趋势 + 数据库概览 - 支持直接 SQL 查询
This commit is contained in:
@@ -13,10 +13,102 @@
|
||||
"""
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
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(首選,有板塊代碼+實時指數) ──
|
||||
@@ -159,6 +251,13 @@ def main():
|
||||
with open(DATA_DIR / "market.json", "w", encoding="utf-8") as f:
|
||||
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.close()
|
||||
|
||||
# 靜默:只寫文件,不輸出到stdout,避免cron推送
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user