Files
MoFin/docs/DEVELOPMENT_STANDARDS.md
T

9.1 KiB
Raw Blame History

MoFin 开发规范

版本:1.1 | 日期:2026-07-031.1 更新 2026-08-10| 维护:Sisyphus + Zhiwei 1.1 新增:Cron 单例守卫/600s超时/LLM容错/调度单一(5.3-5.6)+ 部署规范(五·补 D1-D4)


一、代码结构

1.1 无重复代码

  • 一个功能一个文件。禁止出现两份同名/同功能的 .py 文件。
  • 公共逻辑抽取到 mo_models.py / mo_data.py / mofin_db.py,各脚本只调不写。
  • 每次新增功能前,先搜索是否已有类似实现。

1.2 文件职责

文件 职责 允许做的事
mo_models.py 统一数据模型 calc_total_assets, is_hk_stock, to_cny, get_hk_rate
mo_data.py 统一读取层 read_portfolio, read_decisions, read_watchlist — 只读 DB
mofin_db.py DB 层 建表、写函数、查询函数 — 所有 SQL 集中在这里
price_monitor.py 价格更新 从 API 拉价格,写入 holdings + portfolio_summary
strategy_lifecycle.py 策略生命周期 regenerate_all + quality gates
scripts/ 工具/一次性脚本 不通过 cron 调用的辅助工具

1.3 禁止事项

  • 在业务脚本里直接写 SQL(必须通过 mofin_db.py
  • 在业务脚本里直接 json.load(open(...)) 读数据(必须通过 mo_data.py
  • 自己实现 calc_total_assets / is_hk_stock(必须用 mo_models.py
  • 自己实现 read_portfolio / read_decisions / read_watchlist(必须用 mo_data.py
  • 新增 JSON 数据文件(所有持久化数据必须走 DB)

二、数据规范

2.1 币种

场景 港股 A股
个股 price/cost/stop_loss HKD CNY
currency 字段 'HKD' 'CNY'
总资产/总市值 CNY(汇总时 × 汇率) CNY
个股 P&L 计算 同币种 (price-cost)/cost 同币种
API/报告输出 标注 (HKD) 无标注

铁律:禁止跨币种比较或加减。calc_total_assets / calc_total_mv 是唯一可以做币种转换的地方。

2.2 汇率

  • 汇率必须通过 get_hk_rate() 获取(实时 API + 缓存)
  • 禁止硬编码 0.87 / 0.866 等值(仅 hk_rate.py 内部兜底例外)
  • 汇率 API 不可达时自动使用上次缓存值

2.3 数据源

  • 唯一写入源price_monitor.py 是唯一的价格写入者
  • DB 优先读取:所有数据读取走 mo_data.py → DB
  • API 兜底DB 无数据时,price_monitor 从外部 API 拉取
  • 禁止各脚本各自拉 API 写数据

三、DB 规范

3.1 表设计

  • 新增表必须在 mofin_db.pyinit_all_tables() 中定义
  • 新表必须有对应的 write_*() 函数和 query_*() 函数
  • 涉及币种的列必须有 CHECK(currency IN ('CNY','HKD')) 约束
  • code 列建议加 UNIQUE INDEX

3.2 迁移

  • 加列用 ALTER TABLE ADD COLUMN,幂等(IF NOT EXISTS 或 try/except
  • 不直接改线上 DB 结构,通过 mofin_db.py 的 init 函数执行
  • 数据修正脚本放到 scripts/ 目录

四、LLM Prompt 规范

4.1 强制要求

# 规则 检查方法
S1 不引用 JSON 文件名 grep \.json
S2 港股价格标注 (HKD) 检查 prompt 模板
S3 不指示跨币种直接比较 人工审查
S4 数据源标注为 DB 表名 strategy_evaluations 不是 evaluation.json
S5 不在 prompt 里硬编码路径 grep /home/hmo/

4.2 新增/修改 Prompt

  • prompt_manager/init_registry.py 注册新版本
  • 旧版本标记 status="deprecated",保留历史记录
  • 更新后运行测试脚本验证无 JSON 引用

五、Cron 规范

5.1 调度

  • Cron 脚本必须能独立运行(python3 xxx.py 不报错)
  • 处理异常,不因单个 API 失败而崩溃
  • 关键步骤加 try/except + print(..., file=sys.stderr) 日志

5.2 幂等性

  • price_monitor 价格不变时不写入 DB
  • regenerate_all 可重复运行不产生副作用
  • 所有写操作支持重复执行

5.3 单例守卫(2026-08-10 新增,铁律)

所有定时/常驻脚本必须加单例守卫,防止 cron 重复触发导致多个实例并发。

  • 实测教训:refresh_mtf_cache.py 无守卫,cron 重复触发两个实例 → SQLite database is locked 长锁,拖垮候选管道
  • 必须用 fcntl.flock(LOCK_EX | LOCK_NB) 实现(进程级,进程死自动释放)
  • 示例:
import fcntl
lock_f = open("/tmp/xxx.lock", "w")
try:
    fcntl.flock(lock_f, fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError:
    print("[XXX] 已有实例在运行,退出")
    return
  • 守卫在脚本 main() 开头执行,持有到脚本结束

5.4 600s 超时限制(2026-08-10 新增,铁律)

hermes cron child_timeout_seconds=600,脚本运行超 600s 会被 SIGTERM 杀掉(exit code -15)。

  • 实测教训:promote_candidates.py 处理 240 候选 × 18s/个 >> 600s 被杀
  • 重型脚本必须控制单次运行时长
    • 分批处理(每批限 N 个,剩余留待下批)
    • 内部子进程超时缩短(如重评 480s→120s)
    • 总时长护栏(如 500s,留 100s 余量)
  • 循环中定期检查 time.time() - START_TIME > BUDGET,超时 break

5.5 LLM 依赖容错(2026-08-10 新增)

依赖 LLM API 的 cron(简报/洞察/推荐)必须处理"LLM 失败"情况:

  • 实测教训:开盘简报 LLM 返回空流 → 静默失败无输出,状态未记录
  • 要求:
    • LLM 调用失败时输出明确错误标记(非静默)
    • jobs.json 状态正确记录失败(不显示 ok)
    • 有重试机制(3次内重试)
    • 失败时仍输出已生成的模板部分(不整体丢弃)

5.6 调度通道单一(2026-08-10 新增)

同一脚本只保留一个调度入口,避免重复触发:

  • 实测教训:cron_to_xmpp 有主 profile + position-analyst + 系统 crontab 三处调度,可能重复推送
  • 规范:一个脚本一个调度(选择最优的 profile/crontab),废弃的调度显式移除或禁用

五·补、部署规范(2026-08-10 新增,实战踩坑教训)

D1. deploy_guard 机制(必须遵守)

deploy_guard 监控 CODE_PATHSdeploy/profile-scripts 等),发现未入库改动即回滚到 git HEAD。

  • 禁止:直接改 246 上 deploy/profile-scripts 的脚本而不 commit → 会被守卫回滚
  • 正确流程:
1. 修改脚本(246 或本地)
2. 立即 commitGIT_ALLOW_COMMIT=1 git commit -m 'fix: 说明'
3. pushgit push https://hmo@git.yoin.fun/hmo/MoFin.git master
4. 守卫下次运行看到 CODE_PATHS 干净 → 不回滚

D2. 提交白名单

  • pre-commit 钩子要求 GIT_ALLOW_COMMIT=1 才放行(知微无此令牌 = 无法提交)
  • 小小莫本人操作显式携带该令牌(有 git 写权限)
  • 知微的修改路径:kanban 提单给小小莫评审执行

D3. push 身份

  • 必须用 hmo 身份 pushgit push https://hmo@git.yoin.fun/...),store 凭据
  • 禁止改 git config user.name 为 xxm/mohe(凭据匹配错身份 → push 被拒)

D4. 修改后验证

  • 每次修改必须验证:python3 -u xxx.py 实际运行(看是否报错/超时/锁冲突)
  • 验证结果记录到 CHANGELOG 或部署文档

六、开发流程

6.1 日常开发步骤

1. 理解需求 → 检查现有代码 → 确定改动范围
2. 本地修改代码
3. 运行 scripts/run_all_tests.py(必须全部通过)
4. 如有新增功能,补充 TEST_PLAN.md 测试用例
5. scp 部署到服务器
6. 在服务器上再次运行 run_all_tests.py
7. git commit + push
8. 如涉及 server.py 修改,重启 Flask
9. 更新 CHANGELOG.md

6.2 测试要求

每次代码变更后必须:

# 在服务器上运行全面测试
cd /home/hmo/MoFin
python3 scripts/run_all_tests.py

输出必须满足:

  • 0 个
  • 最多 1-2 个 ⚠️(已知问题,已记录在案)

如测试不通过:

  1. 修复问题
  2. 重新测试
  3. 不通过不提交

6.3 文档同步

哪些文档需要同步更新:

改动内容 需更新的文档
新增/修改表 docs/DATABASE_ARCHITECTURE.md
新增/修改 API docs/SYSTEM_ARCHITECTURE.md
架构变更 docs/SYSTEM_ARCHITECTURE.md
数据模型变更 docs/portfolio-data-model.md
任何功能变更 CHANGELOG.md(追加顶部)
新增测试用例 docs/TEST_PLAN.md

6.4 提交规范

  • Commit message 格式:type: 简短描述
  • 类型:fix: / feat: / refactor: / docs: / test: / migrate:
  • 一次 commit 一个逻辑变更,不要混搭

七、快速检查清单

每次提交前自检:

□ run_all_tests.py 全部通过
□ 无新增 json.load / json.dump 数据文件
□ 港股 currency='HKD'、A股 currency='CNY'
□ 无硬编码汇率
□ 无重复代码
□ CHANGELOG.md 已更新
□ 无残留 print 调试语句
□ git status 确认只改了应改的文件

八、紧急联系人

角色 负责
小小莫 架构、DB、重构、price_monitor
知微 策略、LLM prompt、cron、日常运营