MoFin 测试文档
版本:1.0 | 日期:2026-07-03 | 维护:Sisyphus
一、数据管道测试
1.1 价格源测试
| ID |
用例 |
步骤 |
预期 |
| P1 |
A股价格拉取正确 |
调用 fetch_all_prices(['000657','600519']) |
返回 dict,price > 0,change_pct 有效 |
| P2 |
港股价格拉取正确 |
调用 fetch_hk_eastmoney(['00700','01888']) |
返回 dict,price > 0(HKD),change_pct 有效 |
| P3 |
东财失败→腾讯兜底 |
东财不可用时 |
自动切 _fetch_hk_tencent_fallback,有数据返回 |
| P4 |
价格写入 DB |
运行 price_monitor.refresh_data_prices() |
holdings.price 更新,portfolio_summary.updated_at 刷新 |
| P5 |
价格不变时不写 DB |
连续两次 refresh_data_prices() |
第二次 changed=False,不写入 |
1.2 数据读取测试
| ID |
用例 |
步骤 |
预期 |
| R1 |
read_portfolio 返回正确 |
pf = read_portfolio() |
holdings 数组 + total_assets/cash/frozen_cash |
| R2 |
read_decisions 返回正确 |
dec = read_decisions() |
decisions 数组,每条含 code/price/stop_loss/currency |
| R3 |
read_watchlist 返回正确 |
wl = read_watchlist() |
stocks 数组,每条含 code/price |
| R4 |
无数据时不崩溃 |
DB 为空时调用 read_*() |
返回空数组,不抛异常 |
二、币种测试
2.1 存储测试
| ID |
用例 |
步骤 |
预期 |
| C1 |
港股 holdings.currency=HKD |
查询 01888 |
currency = 'HKD' |
| C2 |
A股 holdings.currency=CNY |
查询 000657 |
currency = 'CNY' |
| C3 |
港股决策 currency=HKD |
查询 01888 的 holding_strategies |
currency = 'HKD' |
| C4 |
A股决策 currency=CNY |
查询 000657 的 holding_strategies |
currency = 'CNY' |
| C5 |
非法币种被拒绝 |
INSERT holdings WITH currency='USD' |
CHECK 约束拒绝,写入失败 |
2.2 币种转换测试
| ID |
用例 |
步骤 |
预期 |
| C6 |
is_hk_stock 正确 |
传入 '00700', '01888', '000657', 'AAPL' |
HK=True×2, A=False, US=False |
| C7 |
to_cny 港股转换 |
to_cny(100, '00700') |
= 100 × get_hk_rate() |
| C8 |
to_cny A股不转换 |
to_cny(100, '000657') |
= 100.0(原样返回) |
| C9 |
get_hk_rate 返回有效值 |
调用 get_hk_rate() |
0.85 < rate < 0.95 |
| C10 |
get_hk_rate 兜底 |
hk_rate 模块不可达 |
返回 0.87 |
2.3 跨币种隔离测试(关键!)
| ID |
用例 |
步骤 |
预期 |
| C11 |
港股price(HKD)不直接参与CNY加法 |
sum(hk_price + a_price) 不经过转换 |
应被 calc_total_mv 拦截转换 |
| C12 |
calc_total_mv 港股自动转CNY |
holdings 含港股,调用 calc_total_mv() |
港股市值 × 汇率,总值 CNY |
| C13 |
calc_total_assets 含港股正确 |
holdings 含港股 |
= calc_total_mv + cash + frozen(全部CNY) |
| C14 |
P&L 同币种比较 |
(price_hkd - cost_hkd) / cost_hkd |
同币种,不混算 |
| C15 |
P&L 跨币种比较被阻止 |
(price_hkd - cost_cny) 未经转换 |
不应出现此路径 |
三、总资产计算测试
| ID |
用例 |
步骤 |
预期 |
| A1 |
total_mv = Σ(shares × price) |
计算并对比 |
估值一致 |
| A2 |
total_assets = total_mv + cash + frozen_cash |
计算并对比 |
stored = calculated |
| A3 |
frozen_cash=0 不影响计算 |
冻结已清零 |
total_assets = total_mv + cash |
| A4 |
position_pct = total_mv / total_assets × 100 |
验证 |
值 0-100,合理 |
| A5 |
total_pnl = total_mv - total_cost |
对比 stored vs calculated |
差值 < 1 元 |
| A6 |
零持仓时 total_mv=0 |
holdings 为空 |
total_mv = 0,total_assets = cash |
四、DB 完整性测试
4.1 约束测试
| ID |
用例 |
步骤 |
预期 |
| D1 |
holdings.code UNIQUE |
重复 code INSERT |
ON CONFLICT DO UPDATE |
| D2 |
holdings.currency CHECK |
INSERT currency='EUR' |
拒绝 |
| D3 |
portfolio_summary.id=1 唯一 |
INSERT id=1 两次 |
ON CONFLICT DO UPDATE |
| D4 |
holding_strategies.code UNIQUE INDEX |
同 code 重复 INSERT |
唯一索引约束 |
| D5 |
FK: holdings.code → stocks.code |
插入不存在股票的持仓 |
外键约束 |
4.2 数据一致性测试
| ID |
用例 |
步骤 |
预期 |
| D6 |
holdings 与 portfolio_summary 一致 |
对比 total_mv |
一致 |
| D7 |
decisions 与 holdings 代码一一对应 |
有持仓必有决策,反之亦然 |
对上的数 ≥ 90% |
| D8 |
shares > 0 必须有 cost > 0 |
检查所有持仓 |
无 cost=0 的持仓(00700 除外,已知问题) |
| D9 |
无 holding_strategies 孤岛(无对应 holdings 的决策) |
检查 |
全部有对应持仓 |
| D10 |
watchlist 中的股票不在 holdings 中 |
检查 |
自选股和持仓股无重叠 |
五、JSON 移除验证
| ID |
用例 |
步骤 |
预期 |
| J1 |
无 json.load(open(portfolio.json)) |
grep 代码库 |
0 匹配 |
| J2 |
无 json.load(open(decisions.json)) |
grep 代码库 |
0 匹配 |
| J3 |
无 json.load(open(watchlist.json)) |
grep 代码库 |
0 匹配 |
| J4 |
无 json.dump(open(portfolio.json)) |
grep 代码库 |
0 匹配(注释除外) |
| J5 |
mo_data.py 无 JSON fallback |
读文件内容 |
无 json.load(open( 出现 |
| J6 |
server.py 不写 JSON 数据文件 |
_save_json 调用 |
仅用于 market/reports/stocks(非核心数据) |
六、LLM Prompt 测试
6.1 Prompt 内容标准
每个 LLM Prompt 必须满足:
| 标准 |
要求 |
| S1 |
不引用 JSON 文件 — 所有数据来源标注为 "DB 表名" |
| S2 |
币种明确 — 提及港股时标注 "(HKD)",A股标注 "(CNY)" |
| S3 |
禁止跨币种比较 — 如涉及计算,明确"同币种比较"或"已转 CNY 后比较" |
| S4 |
输出格式标注币种 — 港股输出价格带 "(HKD)" 后缀 |
| S5 |
数据源正确 — 不引用已废弃的 API 或 JSON 路径 |
6.2 Prompt 测试用例
| ID |
用例 |
Prompt ID |
检查项 |
预期 |
| L1 |
evaluation-daily 不引用 JSON |
evaluation-daily v2 |
全文搜索 .json |
0 匹配 |
| L2 |
evaluation-daily 引用 DB 表 |
evaluation-daily v2 |
搜索 strategy_evaluations / accuracy_stats |
存在 |
| L3 |
knowledge-extraction 不引用 JSON |
knowledge-extraction v1 |
全文搜索 .json |
0 匹配 |
| L4 |
knowledge-extraction 引用 DB |
knowledge-extraction v1 |
搜索 holding_strategies |
存在 |
| L5 |
system-health-check 不引用 JSON |
system-health-check v2 |
全文搜索 .json |
0 匹配 |
| L6 |
所有 prompt 无 "读 JSON" 措辞 |
全部 prompt |
grep 读.*\.json |
0 匹配 |
| L7 |
币种标注检查 |
全部 prompt |
如涉港股,检查有 "(HKD)" 标注 |
存在 |
| L8 |
无 "portfolio.json" 引用 |
全部 prompt |
grep portfolio\.json |
0 匹配 |
| L9 |
无 "decisions.json" 引用 |
全部 prompt |
grep decisions\.json |
0 匹配 |
| L10 |
无 "直接存 HKD 原值" 等过时措辞 |
全部 prompt |
grep 直接存.*HKD|存CNY |
0 匹配(或不导致误解) |
6.3 Prompt 内容自检脚本
七、API 测试
| ID |
用例 |
端点 |
预期 |
| API1 |
获取持仓 |
GET /api/portfolio |
200, holdings[] 数组 |
| API2 |
获取概览 |
GET /api/overview |
200, total_assets > 0 |
| API3 |
获取决策 |
GET /api/decisions |
200, decisions[] 数组 |
| API4 |
写入持仓 |
POST /api/update/portfolio |
200, DB 写入成功 |
| API5 |
写入决策 |
POST /api/decisions/add |
200, holding_strategies 更新 |
| API6 |
API 响应含 currency 字段 |
GET /api/portfolio |
每个 holding 有 currency='HKD' 或 'CNY' |
| API7 |
重启后 API 正常 |
kill + restart Flask |
200 响应 |
八、Cron 测试
| ID |
用例 |
命令 |
预期 |
| CR1 |
price_monitor 能跑完 |
python3 price_monitor.py |
30 秒内完成,无 TypeError |
| CR2 |
market_watch 能跑完 |
python3 market_watch.py |
正常退出 |
| CR3 |
market_screener 能跑完 |
python3 market_screener.py |
正常退出 |
| CR4 |
stale_push_wlin 能跑完 |
python3 scripts/stale_push_wlin.py |
正常退出 |
| CR5 |
东财不可用时走腾讯 |
东财 down |
price_monitor 仍完整拉取 HK 价格 |
| CR6 |
cron 日志无错误 |
查看 /tmp/price_monitor.log |
无 Exception/Error |
九、边界用例
| ID |
用例 |
步骤 |
预期 |
| E1 |
price=None 不崩溃 |
holdings 中某行 price=NULL |
s.get('price') or 0 → 0 |
| E2 |
cost=None 不崩溃 |
holdings 中某行 cost=NULL |
计算时用 0 |
| E3 |
shares=0 不崩溃 |
已清仓的残留持仓 |
市值计算正确(0) |
| E4 |
港股 0 开头代码不被误判 |
'000657' vs '00700' |
is_hk_stock 正确区分 |
| E5 |
汇率 API 不可达时用缓存 |
断网 |
hk_rate 返回上次缓存值 |
| E6 |
数据库被锁 |
多进程写入 |
SQLite WAL 模式,不阻塞 |
| E7 |
空 holdings 列表 |
全部清仓 |
total_mv=0, total_assets=cash 正常 |
十、快速验证命令