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

14 KiB
Raw Blame History

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_factorsaction 字段(信号来源明细和动作建议),不动核心参数(买入区/止损/止盈)。

关键发现: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

业务逻辑:管理策略卡的"状态机"——标记"推荐操作"、"人工标记"等标签,决定策略的生命周期流转。

关联:改 tagaction 字段。被组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.py2805行)

职责:纯技术参数计算引擎(不调 LLM

函数 职责
regenerate_all() 全量技术参数重评(所有持仓+自选)
reassess_strategy() 单只股票技术分析(支撑阻力位→买入区/止损/止盈)
enforce_strategy_quality() 策略质量门禁(三轮自动修复)
enrich_timing_signal() 多因子信号合成(大盘+行业+基本面+技术+组合风险)
calc_atr() / calc_chip_sr() 技术指标计算(ATR/筹码分布)
load_*() 数据加载(行业映射/市场上下文/宏观上下文)

调用关系

  • premarket_full_reviewregenerate_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 只读不写。