# MoFin 统一数据库架构设计 ## 目标 将 MoFin 系统目前散落在各 JSON 文件中的数据统一纳入 SQLite 数据库,实现: - 数据关系化(板块 ↔ 个股 ↔ 持仓 ↔ 策略,SQL join 直连) - 历史可追溯(板块快照、价格序列、评分变更全部时序化) - 操作 no_agent 化(纯 SQL 即可完成大部分查询和分析) - 增量迁移(现有 JSON 不动,新数据双写,逐步切换) --- ## 一、数据库概览 ``` 数据库文件: /home/hmo/web-dashboard/data/mofin.db 引擎: SQLite 3 ``` ### 逻辑分组 | 组 | 表 | 说明 | |----|-----|------| | 市场 | market_snapshots, sector_snapshots | 全市场板块快照(每30分) | | 个股 | stocks, stock_daily, stock_weekly, stock_monthly, stock_fundamentals | 个股K线+基本面 | | 板块成分 | stock_sectors | 个股所属板块映射 | | 持仓 | holdings, holding_strategies | 持仓股+策略参数 | | 自选 | watchlist_stocks | 自选股+买入区 | | 候选池 | candidates, candidate_score_history | 小果+知微潜力股推荐 | | 事件 | price_events, zone_breaches | 价格预警、区间触发 | | 策略评估 | strategy_evaluations | 策略重评记录 | --- ## 二、表结构详述 ### 2.1 市场/板块快照 核心时间序列表。每次 market_watch 执行(每30分)写一条 snapshot,再把每个板块的数据写入 sector_snapshots。 ```sql -- 每次采集的元信息 CREATE TABLE market_snapshots ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT NOT NULL, -- '2026-06-19 15:30' source TEXT NOT NULL DEFAULT 'ths', -- ths / eastmoney up_ratio REAL, -- 上涨板块占比(%) mood TEXT, -- bullish / neutral / bearish created_at TEXT DEFAULT (datetime('now','localtime')) ); CREATE INDEX idx_snapshots_time ON market_snapshots(timestamp); -- 每个板块在每次采集中的快照数据 CREATE TABLE 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 idx_sector_name ON sector_snapshots(name); CREATE INDEX idx_sector_snapshot ON sector_snapshots(snapshot_id); CREATE INDEX idx_sector_name_time ON sector_snapshots(name, snapshot_id); ``` **典型查询:** ```sql -- 半导体板块最近5天的涨跌幅趋势 SELECT s.timestamp, ss.change_pct, ss.net_inflow FROM sector_snapshots ss JOIN market_snapshots s ON ss.snapshot_id = s.id WHERE ss.name = '半导体' ORDER BY s.timestamp DESC LIMIT 10; -- 资金连续3天净流入的板块(最近3次采集) SELECT name, COUNT(*) as times, AVG(net_inflow) as avg_inflow FROM sector_snapshots ss JOIN market_snapshots s ON ss.snapshot_id = s.id WHERE s.timestamp >= date('now', '-3 days') AND net_inflow > 0 GROUP BY name HAVING COUNT(*) >= 3 ORDER BY avg_inflow DESC; ``` --- ### 2.2 个股K线 + 基本面 替代现有的 multi_tf_cache.json 和 data/stocks/*.json。 ```sql CREATE TABLE stocks ( code TEXT PRIMARY KEY, -- '688981' name TEXT NOT NULL, -- '中芯国际' exchange TEXT DEFAULT 'SH', -- SH / SZ / HK type TEXT DEFAULT 'A', -- A / H updated_at TEXT ); -- 日线数据(时间序列) CREATE TABLE stock_daily ( code TEXT NOT NULL REFERENCES stocks(code), date TEXT NOT NULL, -- '2026-06-19' open REAL, close REAL, high REAL, low REAL, volume REAL, -- 成交量 amount REAL, -- 成交额 PRIMARY KEY (code, date) ); -- 周线数据 CREATE TABLE stock_weekly ( code TEXT NOT NULL REFERENCES stocks(code), date TEXT NOT NULL, -- 周结束日期 open REAL, close REAL, high REAL, low REAL, volume REAL, PRIMARY KEY (code, date) ); -- 月线数据 CREATE TABLE stock_monthly ( code TEXT NOT NULL REFERENCES stocks(code), date TEXT NOT NULL, -- 月结束日期 open REAL, close REAL, high REAL, low REAL, volume REAL, PRIMARY KEY (code, date) ); -- 基本面(最新,非时序) CREATE TABLE stock_fundamentals ( code TEXT PRIMARY KEY REFERENCES stocks(code), pe REAL, -- 市盈率 pb REAL, -- 市净率 eps REAL, -- 每股收益 mcap_total REAL, -- 总市值 mcap_flow REAL, -- 流通市值 updated_at TEXT ); ``` --- ### 2.3 板块成分股映射 个股和板块的关系表。目前股票不知道自己在哪个板块,板块不知道有哪些成分股。这使 join 分析无法进行。 ```sql CREATE TABLE stock_sectors ( code TEXT NOT NULL REFERENCES stocks(code), sector_name TEXT NOT NULL, -- 板块名称(与 sector_snapshots.name 一致) source TEXT DEFAULT 'ths', -- 数据来源 updated_at TEXT DEFAULT (datetime('now','localtime')), PRIMARY KEY (code, sector_name) ); CREATE INDEX idx_stock_sector ON stock_sectors(sector_name); ``` **典型查询:** ```sql -- 我的持仓股所在的板块今天表现如何 SELECT h.code, h.name, ss.change_pct, ss.net_inflow FROM holdings h JOIN stock_sectors ss_map ON h.code = ss_map.code JOIN sector_snapshots ss ON ss_map.sector_name = ss.name JOIN market_snapshots ms ON ss.snapshot_id = ms.id WHERE ms.timestamp = (SELECT MAX(timestamp) FROM market_snapshots) ORDER BY ss.change_pct DESC; ``` --- ### 2.4 持仓 ```sql CREATE TABLE holdings ( code TEXT PRIMARY KEY REFERENCES stocks(code), name TEXT NOT NULL, shares INTEGER NOT NULL, -- 持股数 cost REAL, -- 成本价 position_pct REAL, -- 仓位占比(%) added_at TEXT, -- 买入时间 is_active INTEGER DEFAULT 1, -- 1=持仓中, 0=已卖出 closed_at TEXT, -- 卖出时间 close_pnl REAL -- 最终盈亏(%) ); -- 持仓策略参数(可重评,保留历史) CREATE TABLE holding_strategies ( id INTEGER PRIMARY KEY AUTOINCREMENT, code TEXT NOT NULL REFERENCES holdings(code), version INTEGER DEFAULT 1, -- 策略版本号 stop_loss REAL, -- 止损价 take_profit REAL, -- 止盈价 entry_low REAL, -- 买入区下沿 entry_high REAL, -- 买入区上沿 strategy_type TEXT DEFAULT 'holding', -- holding / watch source TEXT, -- reassess / manual / initial reason TEXT, -- 策略理由 created_at TEXT DEFAULT (datetime('now','localtime')), superseded_at TEXT -- 被新版本替代的时间 ); CREATE INDEX idx_strategy_code ON holding_strategies(code); ``` --- ### 2.5 自选股 ```sql CREATE TABLE watchlist_stocks ( code TEXT PRIMARY KEY REFERENCES stocks(code), name TEXT NOT NULL, added_at TEXT DEFAULT (datetime('now','localtime')), is_active INTEGER DEFAULT 1 ); -- 自选股买入区策略(关联到 holding_strategies,共用策略表) ``` 说明:自选股的买入区策略直接使用 `holding_strategies` 表(`strategy_type='watch'`),不需要单独建表。 --- ### 2.6 候选池 ```sql CREATE TABLE candidates ( code TEXT PRIMARY KEY REFERENCES stocks(code), name TEXT NOT NULL, sector TEXT, -- 所属板块 reason TEXT, -- 推荐理由 entry_range TEXT, -- '49.00-52.00' stop_loss REAL, target REAL, zhiwei_star REAL, -- 知微星级 5.0/4.5/4.0/3.5/3.0 zhiwei_reviewed INTEGER DEFAULT 0, zhiwei_reviewed_at TEXT, promoted INTEGER DEFAULT 0, -- 是否已向老爸推荐过 promoted_at TEXT, dropped INTEGER DEFAULT 0, drop_reason TEXT, created_at TEXT DEFAULT (datetime('now','localtime')) ); -- 评分历史(每次小果/知微评分的记录) CREATE TABLE candidate_score_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, code TEXT NOT NULL REFERENCES candidates(code), score REAL NOT NULL, -- 评分 1-10 source TEXT NOT NULL, -- xiaoguo / zhiwei reason TEXT, created_at TEXT DEFAULT (datetime('now','localtime')) ); CREATE INDEX idx_candidate_history ON candidate_score_history(code, created_at); ``` **典型查询:** ```sql -- 候选池状态(活跃候选及其最新评分) SELECT c.code, c.name, c.sector, c.zhiwei_star, c.promoted, (SELECT score FROM candidate_score_history WHERE code = c.code ORDER BY created_at DESC LIMIT 1) as latest_score FROM candidates c WHERE c.dropped = 0 ORDER BY c.zhiwei_star DESC NULLS LAST; -- 某候选的评分趋势 SELECT created_at, score, source FROM candidate_score_history WHERE code = '688981' ORDER BY created_at; ``` --- ### 2.7 价格事件 ```sql CREATE TABLE price_events ( id INTEGER PRIMARY KEY AUTOINCREMENT, code TEXT NOT NULL REFERENCES stocks(code), name TEXT, event_type TEXT NOT NULL, -- entry_zone / stop_breach / take_profit / zone_warning price REAL, -- 触发时价格 trigger_value TEXT, -- '15.36-16.49' 区间描述 event_label TEXT, -- '买入区' / '止损预警' created_at TEXT DEFAULT (datetime('now','localtime')), date TEXT -- 触发日期 ); CREATE INDEX idx_events_code ON price_events(code); CREATE INDEX idx_events_date ON price_events(date); ``` --- ### 2.8 策略评估记录 ```sql CREATE TABLE strategy_evaluations ( id INTEGER PRIMARY KEY AUTOINCREMENT, code TEXT NOT NULL REFERENCES stocks(code), eval_type TEXT NOT NULL, -- daily / weekly / adhoc status TEXT DEFAULT 'pending', -- pending / completed / error old_stop_loss REAL, new_stop_loss REAL, old_tp REAL, new_tp REAL, reason TEXT, created_at TEXT DEFAULT (datetime('now','localtime')) ); ``` --- ## 三、与现有系统的关系 ### 3.1 分步迁移计划 ``` 阶段1:新库就位 → 创建 mofin.db + 所有表 → market_watch.py 改:写 market.json(保留兼容)+ 同时写 market_snapshots / sector_snapshots → 验证:数据正常双写,旧管道不受影响 阶段2:个股数据迁移 → refresh_mtf_cache.py 改:写 multi_tf_cache.json + 同时写 stock_daily/weekly/monthly → 新增 stock_sectors 填充(从 market.json 的板块数据提取成分股) → 验证:K线数据在SQLite可查 阶段3:业务表迁移 → portfolio / watchlist / candidate_pool 逐步迁移 → 每个模块:先写JSON+SQLite双写,确认无问题后切到SQLite → 淘汰对应JSON文件 阶段4:no_agent 化 → 通用查询脚本:mofin_query.py "SELECT ..." → 输出JSON/TEXT → 定时报表:纯SQL生成,无需LLM → 预警规则:SQL条件 → 推送通知 ``` ### 3.2 JSON 文件保留策略 | 文件 | 阶段1 | 阶段2 | 阶段3 | 阶段4 | |------|-------|-------|-------|-------| | market.json | ✅ 双写 | ✅ 双写 | ❌ 淘汰 | — | | multi_tf_cache.json | — | ✅ 双写 | ❌ 淘汰 | — | | portfolio.json | — | — | ✅ 双写 | ❌ 淘汰 | | watchlist.json | — | — | ✅ 双写 | ❌ 淘汰 | | candidate_pool.json | — | — | ✅ 双写 | ❌ 淘汰 | | price_events.json | — | — | ✅ 双写 | ❌ 淘汰 | | decisions.json | — | — | ⚠️ 按需迁移 | — | --- ## 四、关键关联路径 以下是最有价值的跨表查询链: ``` 持仓股 → stock_sectors(查所属板块)→ sector_snapshots(板块最近表现) ↓ sector_snapshots 历史(板块趋势) ↓ stock_daily(个股K线与板块对比) ↓ candidate_score_history(候选评分变化) 事件触发 → stock_daily(看当日量价)→ holding_strategies(检查策略偏离) ↓ strategy_evaluations(策略重评记录) ``` --- ## 五、迁移风险控制 - 每条写操作先写 JSON(现有流程不受影响),再写 SQLite - SQLite 写失败时降级报警,不阻塞 JSON 写 - 全部验证通过后才切掉 JSON - 所有变更通过 market_watch.py / refresh_mtf_cache.py 的 patch 实现,不重写