Files
MoFin/docs/holding-strategies-multewriter-analysis.md
T

317 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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研究页面(展示) → 策略自我进化
```
### 7.7 E组:策略状态管理(strategy_lifecycle
**模块**`strategy_lifecycle.py`2805行)
**职责**:纯技术参数计算引擎(不调 LLM
| 函数 | 职责 |
|---|---|
| `regenerate_all()` | 全量技术参数重评(所有持仓+自选)|
| `reassess_strategy()` | 单只股票技术分析(支撑阻力位→买入区/止损/止盈)|
| `enforce_strategy_quality()` | 策略质量门禁(三轮自动修复)|
| `enrich_timing_signal()` | 多因子信号合成(大盘+行业+基本面+技术+组合风险)|
| `calc_atr()` / `calc_chip_sr()` | 技术指标计算(ATR/筹码分布)|
| `load_*()` | 数据加载(行业映射/市场上下文/宏观上下文)|
**调用关系**
- `premarket_full_review``regenerate_all()``reassess_strategy()` × N只
- `per_stock_reassess` → 使用 `batch_reassess` 的 collect_data+build_prompt
- `price_monitor` → 触发 `per_stock_reassess`
**与LLM的关系**strategy_lifecycle 是**规则计算路径**(不调LLM),LLM 重评是**独立路径**。两者都写 holding_strategies,但用不同 source_trigger(白名单保护不冲突)。
**E组结论**:核心引擎,职责清晰,无需改动。
---
### 7.8 加工层:实时技术指标
**新增** `realtime_indicators.py`:盘中实时计算技术指标
**数据流**
```
price_monitor(每2分钟更新价格)
→ 联动调用 realtime_indicators
→ 计算 MA/支撑阻力/RSI/bias60/dist_ma20/ATR/candle_pattern
→ 写入 stock_indicators(盘中实时)
daily_kline_collector(收盘后 16:10)→ stock_daily
fundamentals_full_refresh(收盘后 16:35)→ stock_fundamentals
factor_engine(收盘后 17:05)→ stock_indicatorsmcap_q/pe_q 等)
batch_reassess(盘后重评)
→ 从 stock_indicators 读取所有技术指标(不再重新计算)
→ 构建 prompt → LLM → 写入 holding_strategies
```
**关键原则**:数据加工层产出的数据写入加工表(stock_indicators),LLM 只读不写。