317 lines
14 KiB
Markdown
317 lines
14 KiB
Markdown
# 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 + 新策略 active(version minor+1)
|
||
- 无变化 → 不动(不浪费记录)
|
||
|
||
**版本号格式**:`major.minor`(如 1.1、1.2、2.1)
|
||
- 新入自选 → 1.1
|
||
- 重评有变化 → minor+1(1.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_indicators(mcap_q/pe_q 等)
|
||
|
||
batch_reassess(盘后重评)
|
||
→ 从 stock_indicators 读取所有技术指标(不再重新计算)
|
||
→ 构建 prompt → LLM → 写入 holding_strategies
|
||
```
|
||
|
||
**关键原则**:数据加工层产出的数据写入加工表(stock_indicators),LLM 只读不写。
|