# MoFin 架构改革 — 变更日志

> 日期：2026-06-29 ~ 2026-07-03
> 执行：Sisyphus (小小莫) + Zhiwei (知微)

---

## 2026-07-09 — 信号堆积修复

### 根因
xiaoguo_signal_consumer 仅处理今日信号（`date(created_at)=today`），之前日期的信号永远不被消费 → 33条堆积。SIGNAL_MAX_AGE_HOURS=4 已定义但未被使用。

### 修复清单
1. **xiaoguo_signal_consumer**: `date(today)` → `created_at > -4h` 年龄过滤
2. **xiaoguo_signal_consumer**: 无时效内信号时自动标记过期信号为已处理
3. **xiaoguo_signal_consumer**: 移除 `mo_data.get_price` 导入（import path 冲突，root mo_data.py 无 get_price），改用腾讯 API 直连 fallback
4. **intraday_health_check**: 信号堆积检查限定 4h 内，避免过期信号误报
5. **system_audit**: 同上

### 效果
- 已清理 20 条过期信号（>4h），剩余 0 条未处理
- 盘中健康检查不会再因过期信号误报堆积
- 信号管道只监控时效内的处理速度

### [2026-07-09 跟进] 监控盲区修复 + 信号积压根治
- **发现**: intraday_health_check 和 system_audit 仅监控 source LIKE 'xiaoguo%'，完全忽略 divergence_watch(80条)、trend(46条) 等来源信号积压
- **修复① (当日监控层)**: 两端都增加全量未处理查询(total_unproc)，区分 xiaoguo 和其他来源分别报告
- **修复② (同天根治)**: divergence_detector.py 写入 signal_news 时标记 processed=1（已有macro_divergence_state.json自用，signal_news只做归档）；trend 46条空summary死数据标记已处理
- **效果**: signal_news 1077条全量已处理，健康检查 PASS，后续不会再次积压

## 2026-07-03 — 代码重构

### 删除重复文件
- `scripts/strategy_lifecycle.py` — 旧版本，缺少 quality gate，root 版本是规范
- `scripts/price_monitor.py` — 旧版本，root 版本是规范

### 清理 JSON 路径常量
- 移除 scripts/ 中 12 个文件的 DECISIONS_PATH / PORTFOLIO_PATH / WATCHLIST_PATH 声明
- 移除 root level 中无用的 JSON 路径常量

### 文档更新
- `docs/SYSTEM_ARCHITECTURE.md` — 完整重写（v5.0）
- `CHANGELOG.md` — 补充重构记录

---

## 2026-07-03 — JSON 彻底移除 + 币种架构修正

### JSON→DB 全面迁移 (32+ 文件)

**数据层**
- `mo_data.py`：删除所有 JSON fallback，纯 SQLite 读取
- `mofin_db.py`：
  - `write_holding_strategy` 改为 DELETE+INSERT（旧 ON CONFLICT 静默失败）
  - `holding_strategies` 新增 10 列：avg_price, decision_timestamp, note, quality_check, quality_checked_at, quality_issues_json, position_advice, signal_factors_json, time_horizon, decision_type
  - 新增 `live_prices` / `mtf_cache` / `capital_flow_cache` 三张表 + 读写函数
  - 已有 `market_snapshots` 表接替 market.json

**server.py**
- 新增 `_save_portfolio` / `_save_decision` / `_save_watchlist` 写函数
- 所有 13 个 API 写端点切到 DB，读端点删除 JSON fallback

**脚本层**
- 15 个文件 `json.load → mo_data.read_*()`
- 12 个文件 `json.dump → mofin_db.write_*()` 或直接删除
- strategy_lifecycle JSON 冷备行全部删除
- branch_scanner / prune_branches 切到 DB
- system_audit / stale_push_wlin / refresh_mtf_cache JSON→DB

**LLM Prompt**
- evaluation-daily v2：`evaluation.json` → DB 表
- knowledge-extraction v1：`decisions.json` → DB 表
- analytics.py：删除 JSON 路径常量

**已移除的 JSON 文件**（不再被任何代码读写）
- portfolio.json / decisions.json / watchlist.json
- live_prices.json / multi_tf_cache.json / market.json / capital_flow_cache.json

### 币种架构

**港股** — 个股 price/cost/stop_loss 存 **HKD**，`currency='HKD'`
**A股** — 个股 price/cost/stop_loss 存 **CNY**，`currency='CNY'`
**总资产** — `calc_total_assets()` 汇总时港股 HKD×汇率→CNY

| 场景 | 港股 | A股 |
|------|------|-----|
| 个股显示（股软对照） | HKD | CNY |
| 技术位（止损/止盈/买入区） | HKD | CNY |
| 总资产 / 总市值 / P&L | CNY（汇总时转换） | CNY |

### 关键修复

- 根 `price_monitor.py` 与 `scripts/price_monitor.py` 不一致（前者存 HKD，后者转 CNY）→ 统一：存 HKD，不转换
- `s.get('price', 0)` 在 price 为 None 时返回 None 导致 TypeError → 改为 `s.get('price') or 0`
- 东财 API 限流（之前 0.1s 间隔疯狂刷被封）→ 第一只失败立刻切腾讯兜底

### 验证

```
holdings: 19 (8 HKD + 11 CNY)
decisions: 19 (8 HKD + 11 CNY)
total_assets: stored = calculated ✅
```

---

## 2026-07-01 — 统一读取层 + 现金日志

### 新增
- `mo_data.py` — `read_portfolio()` / `read_decisions()` / `read_watchlist()`
- `mofin_db.py` — `cash_log` 表 + `write_cash_log` / `query_cash_log`

### 修改
- 16 个文件 `json.load → mo_data.read_*()`
- HK 股成本 HKD→CNY 修复（import_holding_xls 导入时转换）

---

## 2026-06-30 — 初始架构重构

### 新增
- `mo_models.py` — `calc_total_assets()`, `is_hk_stock()`, `get_hk_rate()`, `to_cny()`, `validate_portfolio()`
- `mo_config.py` — 配置单例
- `mo_bridge.py` — DSA 集成桥
- `mo_provider.py` — DSA 数据源适配器
- `scripts/data_validate.py`, `holdings_reconciliation.py`, `process_trade.py`（知微）
- DSA Web UI: `http://192.168.1.246:8001`

### 修改
- `price_monitor.py`：港股腾讯→东方财富 + 统一 total_assets
- 8 个文件散落 `is_hk_stock` / `total_assets` 统一到 mo_models

---

## 2026-07-06 — 全面系统修复（知微）

### 1. XMPP Bot 稳定性修复
- **去自ping心跳**：之前bot每15秒给自己(zhiwei@yoin.fun)发XEP-0199 ping检测连接。gateway阻塞时自ping也超时，导致bot误判连接断开→断线重连→死循环。改为纯TCP 30秒检查。
- **永续重试**：gateway超时消息最多重试3次就丢弃 → 永不丢弃+指数退避（1s→2s→4s→...→120s），5次失败日志报警。
- **跨重连引用**：消息队列中 `bot_ref` 在重连后指向旧bot实例（已断开）→ 加 `_current_bot` 模块级变量，处理消息时优先用当前活跃bot实例。

### 2. Cron Delivery 修复（10个job）
- 宏观风险扫描 ×3（早/午/周末）：`deliver=all` → `local`
- 小果信号消费-盘中、策略复盘-每日、数据治理-每周、记忆守卫-每日、盘中自检-高频、系统常规体检-开盘前、自愈执行器：`deliver=origin` → `local`
- 根因：cron job没有上游渠道可推送，`origin/all`解析失败

### 3. 脚本Bug修复
- **refresh_mtf_cache.py**：导入 `strategy_lifecycle.PORTFOLIO_PATH` 报错（DB-only重构后常量已删除）。改为从 `mofin.db` 的holdings/watchlist_stocks表直接读股票代码列表，走腾讯API拉K线。
- **clean_watchlist.py**：`mo_data.read_portfolio()` 调用名错误（`mo_data`前缀有多余）+ 硬编码JSON路径。修复为纯DB读取。

### 4. 脚本目录同步
- MoFin/scripts/（git仓库）与~/.hermes/profiles/position-analyst/scripts/（cron运行版本）双向补全
- 57个文件从profile→MoFin同步，全部md5校验一致

### 5. Git提交
- `66962ae` 全面系统修复：包含以上所有变更
- `e055485` 加 `.gitignore`（取自code_only分支，排除`__pycache__/ *.pyc data/reports/`）
- 已推送到origin master

### 6. 数据
- watchlist新增 000850 华茂股份（金融股权低估逻辑，PB 0.83破净，2026-07-03公告分红3675万）
### 已知未解决
- ejabberd容器重启46次（exit code 0+无日志，系统负载高峰时被外部信号杀死）

## 2026-07-09

**stale_push_wlin 推送流程重构**

- 加4小时重评冷却：查DB `holding_strategies.updated_at`，冷却期内跳过 per_stock_reassess
- Zone notes冷却30分→4小时，匹配重评冷却
- 修早退bug：`if not actionable: return 0` → `if not actionable and not zone_notes`
- 标题自适应：有推荐→"自选买入提醒" | 仅区间→"操作区间提醒"
- 操作建议标题只在有可操作项时显示
- 区间说明格式改为 Dad 确认的"进入操作区间，但重评结果: xxx"

---

### 修正的活代码路径（唯一真正在读写 JSON 的路径）
- `price_monitor.py` — 每2分钟读 decisions.json 改为 mo_data.read_decisions()（DB）
- 其他18处代码注释写"写入 portfolio.json/decisions.json"但代码已实际写 DB，只修了注释

### 健康检查脚本
- `system_health_check.py`（主+scripts/两个版本）— 数据条目标签从"portfolio.json DB记录"改为"DB持仓记录"
- 消除 LLM cron 误读标签后 stat JSON 文件大小的问题

### 配置层标记废弃
- `mo_config.py`（主+scripts/两个版本）— 所有 JSON 路径 property 加 ⚠️ 已废弃 docstring

### 磁盘残留 JSON 文件
- portfolio.json / decisions.json / watchlist.json → 全部改为废弃 stub
- 保留空文件防导入报错，内容仅含废弃声明

### 变更总结
- 核心数据（持仓/自选/策略）的读和写已 100% 走 DB
- 唯一仍用 JSON 的非核心文件：reports/*.json（报告缓存）、stocks/*.json（个股详情缓存）、market.json（大盘数据缓存）、accuracy_stats.json（统计缓存）

## 2026-07-08 — BUGFIX: per_stock_reassess.py holding_strategies DB 写回 + JSON 最终移除

### 发现
Dad 反馈我的 v4 手动分析（买入区3.62~3.72）与 13:01 cron 报告（买入区3.78~3.87）不一致。经审计发现两个问题：
1. **per_stock_reassess.py** 调用 reassess_with_context() 后不写 holding_strategies 表（只写 watchlist_stocks 表）。而 read_decisions() 从 holding_strategies 读 → 新策略数据不可见
2. **decisions.json** 仍有残余依赖，2条旧数据的文件被当作权威源

### 修改
- `per_stock_reassess.py`：策略更新后追加 `write_holding_strategy()` 写入 DB，持仓/自选全部写回 holding_strategies 表
- `per_stock_reassess.py`：移除 DECISIONS_PATH 常量 + 清理 JSON 死代码注释
- 同步修复 profile 副本（`~/.hermes/profiles/position-analyst/scripts/per_stock_reassess.py`）
- `sync_db_to_json.py`：删除（DB→JSON 反向同步，无调用者）
- `decisions.json`（两处）：删除，备份为 .bak

### 审计确认
- MoFin/scripts/ + web-dashboard/ + profile/scripts/ 三个目录下的 active python 代码 **均不再直接读写 decisions.json**
- 所有策略数据通过 `mo_data.read_decisions()` → DB holding_strategies 表 读取
- 所有策略写入通过 `mofin_db.write_holding_strategy()` → DB holding_strategies 表

### 验证
- `per_stock_reassess 688981` → 输出 `[DB] holding_strategies 已更新: 688981`
- DB holding_strategies 从 1 条 → 17 条
- web-dashboard PID 4003859 正常运行，API 通过 DB 读取

## 2026-07-10 — 知微XMPP Bot离线修复

### 根因
Bot被bash包装器手动启动（非systemd），形成孤儿进程。systemd因PID文件冲突僵在`activating (auto-restart)`循环无法接管。孤儿bot在11:40:48断线后卡在重连循环中。

### 修复
1. 杀死孤儿进程 PID 422464
2. 清理 `/home/hmo/.hermes/zhiwei_bot.pid` 和 `/tmp/xmpp_zhiwei_bot.pid`
3. systemd RestartSec=10s 自动重启接管
4. 验证：加入coregroup、XMPP就绪、presence available、bridge:5805正常、gateway正常

### 预防
自愈系统新增检查项：systemd状态优先于进程检查——发现进程存在但非systemd管理时自动清理孤儿。

## 2026-07-13 — XMPP Bot非阻塞 + 深套规则重构 + 换股规划

### XMPP Bot修复
- gateway调用改为 45s短超时 → "处理中"预回复 → 后台300s轮询，不再阻塞事件循环
- 远程XMPP服务器不通，改为连本地 127.0.0.1:5222 代理
- 注册 xep_0045 修复MUC崩溃
- **涉及文件**: /home/hmo/xmpp_agent_core.py

### price_monitor推送冷却持久化
- _push_cooldown 从内存dict改为文件持久化，解决每2分钟重复推操作区间通知
- **涉及文件**: scripts/price_monitor.py

### 深套股规则重构
- 删除全部 `is_deep_loss` 特殊处理（止损/止盈/买入区/质量检查6处guard + action_note覆盖）
- 回归客观判断，该加就加该割就割
- **涉及文件**: scripts/strategy_lifecycle.py

### 换股规划功能
- `python3 strategy_lifecycle.py swap-plan [--need 金额]`
- 五维评分排序，自动计算释放资金方案
- **涉及文件**: scripts/strategy_lifecycle.py

### 技术分析增强
- 周线/月线趋势/MA/支撑阻力注入mtf_context传递到LLM
- 健康Tab写无读警告 3→0
- 每日汇总只发一次(16:35)
- 健康监控新增数据实体检查
- **涉及文件**: technical_analysis.py, mofin_health.py, cron_to_xmpp.py, cron_health_monitor.py

## 2026-07-14 — price_monitor DB写锁死锁根治

### 根因
多 cron 脚本（price_monitor、mofin_health 等）同时 BEGIN IMMEDIATE 写 mofin.db，WAL 文件膨胀到 2.7MB 未 checkpoint，进程卡在 D 状态，后续全部超时。原代码的 3 次重试 + 2s 退避不足，且 write_portfolio_summary / write_live_prices 返回值未检查。

### 修复
1. **统一 BEGIN IMMEDIATE + 内联 SQL**（替代调用 write_holdings_batch/write_portfolio_summary/write_live_prices）：一个事务包裹所有写操作，任一失败立即 rollback + 重试
2. **5 次重试 + 指数退避**（1s → 2s → 4s → 8s → 16s，共 ~31s），原 3 次 + 固定 2s
3. **Emergency WAL checkpoint**：所有重试耗尽后自动执行 PRAGMA wal_checkpoint(TRUNCATE) 释放死锁
4. **try/except 确保连接始终释放**（rollback + close），修复原代码异常时 conn 泄漏
5. **三副本同步**：profile/scripts + MoFin/scripts + MoFin/root
- **涉及文件**: scripts/price_monitor.py, /home/hmo/MoFin/scripts/price_monitor.py, /home/hmo/MoFin/price_monitor.py
