docs: 更新架构文档——记录A-D组改动/策略版本化/两份历史记录/策略到期评估

This commit is contained in:
xxm
2026-08-21 02:30:41 +08:00
parent c0cfb89757
commit 8dd12ca1e8
@@ -0,0 +1,268 @@
# holding_strategies 多写方分析(2026-08-20
> 目标:梳理所有写入 holding_strategies 表的模块,按业务关联性分组,明确每个模块的职责、触发时机、改什么字段、与其他模块的关联。为后续"REPLACE→UPDATE"改造提供依据。
---
## 一、背景
holding_strategies 是 MoFin 系统的核心表——每只股票的"策略卡",记录买入区/止损/止盈/信号/完整分析等。系统里所有模块都围着这张表转。
**历史问题**:8+ 个模块反复覆写同一行,参数互相覆盖,66% 的 strategy_history 是同参数重复快照。
**已修复**(8/19):白名单机制——只有 per_stock_12d/batch_12d/promote 能写核心参数(entry/stop/tp/rr/position_advice),其他模块保留 DB 当前值。
**待修复**:所有模块仍用 REPLACE 整行(INSERT OR REPLACE),改一个字段连带重写全行。应改为 UPDATE 特定字段。
---
## 二、写入保护机制(在 mofin_db.py 的 write_holding_strategy 内部)
| 保护 | 代码位置 | 保护什么 | 规则 |
|---|---|---|---|
| 参数白名单 | L2080 `_PARAM_WHITELIST` | entry_low/entry_high/stop_loss/take_profit/rr_ratio/position_advice | 只有 `per_stock_12d`/`batch_12d`/`promote` 能写,其他保留DB当前值 |
| 信号权威 | L2058-2075 | timing_signal | 技术路径不得降级新鲜(<20h)的12维信号 |
| action权威 | L2098-2107 | action | 非LLM路径不得覆盖新鲜(<20h)的12维action |
| tag保护 | L2108-2109 | tag | `active_manual`(人工标记)永远不被自动流程覆盖 |
| 快照瘦身 | L1999-2020 | strategy_history | 只有关键字段(entry/stop/tp/signal/rr)有变化才拍快照 |
---
## 三、12个写入方(按业务关联性分组)
### 组A:策略参数制定(核心分析引擎)
| 模块 | 文件 | 业务角色 | 触发频率 | 改什么字段 |
|---|---|---|---|---|
| 个股12维重评 | `per_stock_reassess.py` | **权威源**——AI分析给出买入区/止损/止盈/信号 | 交易日12:00/15:00 | entry_low/entry_high/stop_loss/take_profit/rr_ratio/position_advice/timing_signal/full_analysis |
**职责**:系统的"大脑",所有其他模块的判断都基于它给出的参数。唯一被白名单授权写核心参数的模块。
**关联**:它的输出 → 被组B/C/D/E所有模块读取和使用。
---
### 组B:候选发现与评估(分支自成长管道)
| 模块 | 文件 | 业务角色 | 触发频率 | 改什么字段 |
|---|---|---|---|---|
| 分支扫描 | `branch_scanner.py` | 盘中实时发现候选信号 | 每15分钟 | signal_factors |
| 分支评估 | `branch_evaluator.py` | 评估候选信号质量 | 被扫描器调用 | action/timing_signal |
| 策略树分析 | `strategy_tree.py` | 宏观→大盘→行业→个股层级分析 | 被其他模块调用 | signal_factors/action |
| 分支剪枝 | `prune_branches.py` | 砍掉低质量分支 | 每日21:00 | status |
**业务逻辑**:一条管道——
```
策略树分析(提供层级框架)
分支扫描(盘中发现"这只股票的某个条件触发了")
分支评估("这个触发信号可靠吗?")
写入策略卡(signal_factors/action
分支剪枝(收盘后砍掉成功率低的分支)
```
**关联**:只改策略卡的 `signal_factors``action` 字段(信号来源明细和动作建议),不动核心参数(买入区/止损/止盈)。
**关键发现**:4个模块形成闭环——扫描→评估→写入→剪枝→下一轮扫描。数据依赖是顺序的(扫描在前,评估在后,剪枝在收盘后),不存在并发冲突。
---
### 组C:自选池维护
| 模块 | 文件 | 业务角色 | 触发频率 | 改什么字段 |
|---|---|---|---|---|
| 清理自选池 | `clean_watchlist.py` | 移除不在持仓的股票 | 每日9:05 | tag/status |
| 持仓对账 | `holdings_reconciliation.py` | 核对系统股数与券商持仓 | 手动 | shares/position_pct |
| 导入持仓 | `import_holding_xls.py` | 从Excel导入最新持仓 | 手动 | shares/cost/market_value |
**业务逻辑**:维护"自选池"的准确性——
```
导入持仓(券商导出Excel → 导入系统)
持仓对账(系统记录 vs 券商实际持仓,以券商为准)
清理自选池(不在持仓的股票从自选移除)
```
**关联**:只改 `shares`/`position_pct`/`tag`/`status` 字段(股数/仓位/标签/状态),不动核心参数。数据源是券商持仓(外部输入),与组A的AI分析是两个独立的数据流。
**关键发现**:持仓对账和导入持仓是手动触发(低频),清理自选池是每日一次。顺序依赖(先导入→再对账→再清理),不存在并发冲突。
---
### 组D:准确率回顾
| 模块 | 文件 | 业务角色 | 触发频率 | 改什么字段 |
|---|---|---|---|---|
| 策略评估 | `strategy_evaluator.py` | 统计历史预测准确率 | 每周六21:00 | evaluation |
| 建议对账 | `advice_reconciliation.py` | 核对AI建议是否被执行 | 每周六20:00 | advice_timeline标记 |
**业务逻辑**:事后复盘——
```
建议对账("上周说卖出,实际卖了吗?")
策略评估("整体准确率多少?")
```
**关联**:只加评估/标记字段(evaluation/advice_timeline),完全不动核心参数和信号。只读分析+标记,不改变策略逻辑。
**关键发现**:每周一次的低频任务,与其他模块没有数据竞争。
---
### 组E:策略状态管理
| 模块 | 文件 | 业务角色 | 触发频率 | 改什么字段 |
|---|---|---|---|---|
| 策略生命周期 | `strategy_lifecycle.py` | 管理推荐标签/动作判断 | 被其他模块调用 | tag/action |
**业务逻辑**:管理策略卡的"状态机"——标记"推荐操作"、"人工标记"等标签,决定策略的生命周期流转。
**关联**:改 `tag``action` 字段。被组A(重评后标记推荐)和组B(分支评估后标记状态)调用。
**关键发现**:不是独立运行的,是被其他模块调用的"工具函数"。写入时机取决于调用方。
---
## 四、5组的关联关系
```
组A(12维重评)──输出──→ 组B(分支扫描/评估/剪枝)──读取──→ 组A的参数
│ │
│ ↓
│ 组E(策略状态管理)──标记──→ 推荐/人工标签
├──输出──→ 组C(自选池维护)──独立数据流──→ 券商持仓
└──输出──→ 组D(准确率回顾)──只读分析──→ 评估标记
```
**核心结论**
1. **组A是权威源**,其他组都基于它的输出做判断
2. **组B内部是顺序管道**(扫描→评估→剪枝),不存在并发
3. **组C是独立数据流**(券商持仓),与组A的AI分析互不干扰
4. **组D是只读分析**,不改变策略逻辑
5. **组E是工具函数**,被其他组调用
---
## 五、当前问题与改造方向
### 问题:REPLACE 整行
每个模块只改自己负责的字段,但 `write_holding_strategy` 用的是 `INSERT OR REPLACE`(整行替换)。如果两个模块并发运行(比如组B的分支扫描和组C的持仓对账同时跑),后写的会覆盖先写的。
### 改造方向:REPLACE → UPDATE 特定字段
每个模块只 UPDATE 自己负责的列,不动其他列。具体方案:
| 组 | 改哪些列 | 不动哪些列 |
|---|---|---|
| 组A | entry_low/entry_high/stop_loss/take_profit/rr_ratio/position_advice/timing_signal/full_analysis | shares/cost/market_value/tag/status/evaluation/advice_timeline/signal_factors |
| 组B | signal_factors/action/timing_signal | entry/stop/tp/rr/position_advice/shares/cost/market_value/tag/status/evaluation |
| 组C | shares/position_pct/tag/status | entry/stop/tp/rr/position_advice/timing_signal/full_analysis/signal_factors/evaluation |
| 组D | evaluation/advice_timeline | entry/stop/tp/rr/position_adice/timing_signal/full_analysis/signal_factors/shares/cost/market_value/tag/status |
| 组E | tag/action | entry/stop/tp/rr/position_advice/timing_signal/full_analysis/signal_factors/shares/cost/market_value/evaluation |
### 前置条件
1. 确认每个模块的 source_trigger 映射到哪个组
2.`write_holding_strategy` 内部根据 source_trigger 决定用 REPLACE 还是 UPDATE
3. UPDATE 时只 SET 该组负责的列,其他列保持不变
---
## 六、验证要点
改造后需要验证:
1. 组A(12维重评)仍能正常写入所有字段(它是权威源,应该用 REPLACE 或全列 UPDATE
2. 组B/C/D/E 改用 UPDATE 特定列后,不会覆盖其他组的字段
3. 快照逻辑仍正常(只有关键字段变化时才拍快照)
4. 并发场景下(组B扫描 + 组C对账同时跑)不会互相覆盖
---
## 七、架构演进记录(2026-08-20~21
### 7.1 策略版本化
**问题**:旧版 write_holding_strategy 直接覆盖 active 记录,无历史版本,D 组评估无数据。
**方案**
- 重评时对比关键字段(entry/stop/tp/signal
- 有变化 → 旧策略 superseded + 新策略 activeversion minor+1
- 无变化 → 不动(不浪费记录)
**版本号格式**`major.minor`(如 1.1、1.2、2.1
- 新入自选 → 1.1
- 重评有变化 → minor+11.1→1.2
- 清仓回归自选 → major+1, minor=1(→2.1
- 淘汰后重新选入 → major+1(→3.1
### 7.2 两份历史记录
**recommendation_log**(推荐记录):
- 写入时机:重评写入策略卡时同步写入
- 记录:推荐时间、动作、价位、策略版本
**execution_log**(执行记录):
- 写入时机:trade_capture / import_holding_xls 执行后
- 记录:执行时间、动作、数量、价格、来源
### 7.3 策略到期评估
**strategy_effectiveness**(替代旧 strategy_evaluator + advice_reconciliation):
- 触发:盘后自动(16:45 cron
- 数据源:stock_daily(完整K线)+ holding_strategies(区间定义)+ recommendation_log + execution_log
- 评估:每个区间的职责履行(买入区是否盈利/止损是否有效/止盈是否正确)
- 输出:买入区准确性/止损准确性/止盈准确性/时间准确性 + 综合评价 + 改进建议
### 7.4 已删除模块
| 模块 | 删除原因 |
|---|---|
| strategy_evaluator.py | 功能被 strategy_effectiveness 替代 |
| advice_reconciliation.py | 功能合并到 strategy_effectiveness |
| stale_push_wlin.py | 功能被 price_monitor 完全覆盖 |
| branch_scanner.py | 违反"只有重评才能改信号和操作"的方法论 |
| branch_evaluator.py | 违反方法论(越权给信号)|
| strategy_tree.py | 与重评12维分析重复 |
| prune_branches.py | 剪枝概念多余(重评自然覆盖)|
### 7.5 数据表结构
| 表 | 用途 | 关键字段 |
|---|---|---|
| holding_strategies | 策略卡(版本化)| code, version, strategy_source, status, entry/stop/tp |
| recommendation_log | 推荐历史 | strategy_id, code, recommend_time, action, entry/stop/tp |
| execution_log | 执行历史 | code, action, shares, price, execute_time, source |
| strategy_effectiveness | 到期评估 | strategy_id, buy_zone_accuracy, stop_loss_accuracy, overall_assessment |
| stock_daily | K线数据(全市场)| code, date, open/close/high/low/volume |
| stock_fundamentals | 基本面(全市场)| code, pe, pb, mcap_total |
### 7.6 数据流全景
```
采集层:
daily_kline_collector(16:10) → stock_daily(全市场K线)
fundamentals_full_refresh(16:35) → stock_fundamentals(全市场PE/PB/市值)
加工层:
factor_engine → stock_indicators(mcap_q/pe_q/bias60/rsi等)
technical_analysis → 支撑阻力/MA/多周期
业务层:
重评(per_stock_reassess) → holding_strategies(版本化) + recommendation_log
交易(trade_capture/import_holding_xls) → holdings + execution_log
监控(price_monitor) → 区间触发 → 重评
评估层:
strategy_effectiveness(16:45) → 读K线+推荐+执行 → 评估区间职责
→ MoFin研究页面(展示) → 策略自我进化
```