34 KiB
tradingSystem · 宏观择时接入方案 V2(股汇对冲指数 → 组合仓位 + 个股宏观闸)
状态: V2,按用户 2026-08-18 三条拍板意见修订,待标定后定稿 | 日期: 2026-08-18 V2 修订(依用户三点反馈):①触发口径不预设默认值,先跑标定脚本(
scripts/calibrate_macro_signal.py,已交付)用历史数据对比"进区即动 vs 极值回落再动"后再定;②仓位调整由固定步长改为对数映射——极值越深加码越多、边际递减,参数由标定给建议值;③新增个股宏观闸:偏热区确认后,自主增持类个股动作(新建仓/回踩补足/盈利加仓/补仓)一并暂停——这同时堵上 V1 的一个洞(宏观降仓释放的现金会被PMS_OPEN_AUTONOMY=full的自主新建仓在下一分钟买回去);④数据源按用户口径:gp_fx_daily/gp_shibor在 192.168.16.153(153 代理),标定脚本顺带完成列名探查与核实。 写作纪律沿袭项目既有文档:术语先白话、阈值给硬数字且全部可配置、失败路径显式、未确认事项进清单。 关联:POSITION_MGMT_DESIGN.md(总体设计)、README.md(模块地图与三条铁律)、DEVLOG.md2026-08-06「动 tradingSystem 以外的系统之前先答三题」。
0. 一句话
给 PMS 加一个「宏观择时层」:每个交易日算一次股汇对冲指数,指数进入极热区就像用户一样在命令台下降仓命令(REDUCE_EXPOSURE,幅度按对数映射随极值深度加码),进入极冷区下升仓命令,中性不动;偏热确认期间同时暂停自主增持类个股动作(宏观闸,防止降仓的钱被自动建仓买回去)。它产生仓位变化的唯一出口是命令与闸;方案生成、规则闸、择时出手、下发、对账、进度回报,全部走现有链路,一行不改。框架按「信号注册表」做,未来再接别的宏观信号(bionic 宏观水温、择时层日 QRS 等)只加注册表项。
1. 现状盘点:目标需要什么 vs 系统已有什么
| 环节 | 这个目标需要 | 系统现状 | 差距 |
|---|---|---|---|
| 信号数据 | zs_day_data(上证)、gp_fx_daily(USD/CNH)、gp_shibor(SHIBOR 1M) |
zs_day_data 在 199 库有实证(bionic market_regime 在读,列 symbol/timestamp/close/amount,代码点式 000001.SH);用户口径:fx 与 shibor 两表在 153 代理。两表在五个项目里零代码引用,列名未经实测 |
小:连接现成(153=proxy 源、199=index 源都已配),列名由标定脚本探查后回填 §4.2 |
| 信号计算 | 20 日对数收益 → spread → SHIBOR 修正 → 40 日 Z-Score ×10 | 全生态无任何股汇对冲指数实现(hedge 关键词全项目零命中)。bionic 有 market_regime.py(宏观水温),但那是决策系统内部件,PMS 不读 |
缺:需新写,约百行纯函数 |
| 组合级自动决策 | 极值 → 升降整体仓位 | 无。设计 §5 原话:「总仓上限由用户命令控制(升降仓判断权在用户,不自动联动)」。本需求是对这条设计决策的一次有意扩展,必须带着同样的档位/确认哲学落地 | 缺:决策引擎(区制/确认/冷却/对数映射/上下限) |
| 个股动作联动 | 偏热时管住增量(用户拍板新增) | 自主提议五类动作只看个股与组合约束,不看宏观;组合刹车有"回撤后停自主增持"的同型机制可借鉴 |
缺:宏观闸(§5.2,实现手势照抄刹车) |
| 执行面 | 把「降 X%」翻成逐股卖出并执行 | ✅ 已完备。REDUCE_EXPOSURE / INCREASE_EXPOSURE 任务命令全生命周期:command_service.issue() → planner 四档凑额(停新买→清弱票→收利润→等比微减)→ 方案落表 → executor 转指令 → 规则闸终检 → 择时出手 → 回报进度。pms_command.issued_by 字段与 /api/commands 的 by 参数都在 |
无差距,全量复用 |
| 冲突仲裁 | 自动命令不得顶撞用户在途命令 | ✅ command_spec.detect_conflicts 已覆盖 REDUCE↔INCREASE、LIQUIDATE、HALT 等对 |
复用,宏观侧遇冲突跳过不 force |
| 档位/确认哲学 | 先建议后自动,人可随时收权 | ✅ 有成熟模板(PMS_AUTONOMY / PMS_OPEN_AUTONOMY 三档)。但 pms_proposal 是个股级,组合级建议放不进去 |
小:宏观建议自己承载(§7) |
| 参数 | 阈值全部页面可调 | ✅ param_store 登记即出现在设置页、5 秒生效 |
登记即可 |
| 调度 | 每交易日定时算一次 | ✅ celery beat 骨架 + 三条守卫(交易日/休假/吞异常守成)成熟 | 加一个调度位 |
| 页面 | 看得见指数、区制、建议、闸状态 | ✅ 单页已有多面板先例 | 加一块面板 |
| 留痕 | 为什么动/为什么没动,可回查 | ✅ pms_action_ledger + 命令表 |
复用 + 新增信号历史表 |
| 历史存储 | 指数序列可回看、可判分、可标定 | 无 | 缺:新表 pms_macro_signal |
结论:执行面九成现成,缺的是"信号计算 + 组合级决策 + 宏观闸 + 一小块页面"这一层薄壳。风险不在工作量,在接入姿势——所以本方案把大部分篇幅花在护栏上。
2. 目标翻译成系统语言:为什么走命令通道 + 扫描层闸
「极热 → 降低整体仓位」在本系统里最忠实的表达就是一条 REDUCE_EXPOSURE {pct, window_tdays}——语义完全一致(释放金额 = 规模 × pct,planner 按 停新买→清弱票→收利润→等比微减 四档凑额,这正是"pms 根据规则执行对个股的降仓位操作")。反向就是 INCREASE_EXPOSURE。存量靠命令,增量靠闸:偏热期间把自主增持的扫描停掉(不产生候选就不会有指令),两头合起来才是完整的"仓位受宏观控制"。
刻意不做的两种姿势:
- 不做"连续仓位控制器"(宏观层直接持有目标仓位、每分钟收敛)——绕开命令系统另立产生买卖的路径,与命令冲突时没有仲裁者,正是禁区。
- 不把宏观观点塞进规则闸——规则闸是合规终检,塞行情观点会让"没通过合规"和"宏观不看好"在评审账本里分不开;宏观闸放在扫描入口(proposal_service),与"组合刹车""休假模式"同一落点、同一手势。
走命令通道的三个直接好处:宏观动作在命令台可见、可撤销、可改窗口(命令至上铁律天然成立);与用户命令的冲突由现有 detect_conflicts 仲裁;执行成本统计、进度日结、判分全部免费获得。
3. 总体架构与六条设计原则
每交易日 09:35 (beat: macro_scan) 页面「宏观择时」面板
│ │ 手动扫描 / 一键采纳
▼ ▼
┌──────────────────────────────────────────────────────────┐
│ macro_service.scan() │
│ 取数(macro_repo, 三张源表单表查) → 计算(macro_rules 纯函数) │
│ → 落 pms_macro_signal (当日一行, 重扫就地更新) │
│ → 决策(macro_rules.decide: 区制/确认/冷却/对数映射/上下限) │
│ → 分流: │
│ autonomy=full → command_service.issue( │
│ REDUCE/INCREASE_EXPOSURE, │
│ issued_by="macro") │
│ autonomy=propose_only → 当日建议落表, 面板等用户点头 │
│ autonomy=off / ENABLED=False / 信号UNAVAILABLE → 不动 │
│ → 每一次动作与每一次"想动被拦"落 pms_action_ledger │
└──────────────────────────────────────────────────────────┘
│ (命令一旦下达, 与用户手下的命令零区别) │ 宏观闸状态 (偏热确认中?)
▼ ▼
command_poll → planner → executor proposal_service 扫描入口:
→ 规则闸 → 择时 → 下发 → 对账 偏热确认 → 增持类扫描暂停(留痕)
六条设计原则(每条都能在代码评审时逐一核对):
- 默认关:
PMS_MACRO_ENABLED=False。部署完成后系统行为与今天完全一致,开关和档位都在页面上,随时收权。 - 唯一出口:宏观层产生仓位变化的途径只有两条——
command_service.issue()下命令、扫描入口的宏观闸(只拦不发)。不直接写pms_plan/pms_instruction/pms_position,不调 executor,不碰账本。 - 故障即守成:任一数据源取不到、样本不足、日龄超限 → 信号置
UNAVAILABLE,不动作、不落闸(拿不到 ≠ 偏热,闸的安全方向是"不额外拦"——增持本身另有规则闸把关),面板亮黄条。挂在调度器guard下,异常吞掉只记 ERROR。 - 命令至上:与用户在途命令冲突(issue 返回 CONFLICT)→ 本轮放弃并留痕,绝不 force;
HALT_ALL(休假)由调度守卫直接跳过;PMS_GLOBAL_BUY_HALT生效时不发升仓;组合刹车期间默认不发升仓(可参数放开)。用户命令永不受宏观闸限制,且随时可在命令台撤销宏观命令。 - 限频防抖:进区确认天数 + 迟滞退出带 + 同方向冷却 + 每信号每交易日至多一次动作 + 对数映射的"目标追踪"(§5.1,同一深度不重复加码)+ 上一条宏观命令在途不叠加。指数在极值区横跳不会造成反复买卖。
- 全留痕:信号值每日入
pms_macro_signal(含中间量,可复算);每次触发/被拦/落闸入pms_action_ledger(action=MACRO,hard_numbers 带当时指数值、区制、超额深度、当前仓位、本次步长);命令表issued_by='macro'一眼可辨。
4. 信号层设计
4.1 计算口径(按定义逐条落地)
数据统一为按国内交易日对齐的日频序列,全部用收盘值:
① stock_ret = ln(close_t / close_{t-20}) # zs_day_data, symbol='000001.SH'
fx_ret = ln(fx_t / fx_{t-20}) # gp_fx_daily USD/CNH, 正值=人民币贬值
(fx 的日期先 +1 自然日, 再对齐到国内交易日: 当日无值取最近前值, 即 as-of 对齐)
② spread = stock_ret + fx_ret
③ spread_adj = spread − β × (SHIBOR1M_t − SHIBOR1M_{t-20}) # β 默认 0.02
④ hedge_index = (spread_adj − mean40(spread_adj)) / std40(spread_adj) × 10
硬性口径,写进单测:窗口内任一序列缺口用 as-of 前值补齐但记录补齐天数;有效样本 < 20+40+5 个交易日 → UNAVAILABLE;std40 = 0 → UNAVAILABLE;三源中最新日期落后当前交易日超过 PMS_MACRO_STALE_TDAYS(默认 3)个交易日 → UNAVAILABLE。
区制(zone)判定带迟滞,防横跳(阈值初值 ±25/±15,以标定结果为准回填):
| zone | 进入条件 | 退出条件 |
|---|---|---|
| HOT(偏热,即 positive 区) | hedge_index > PMS_MACRO_HOT_TH |
回落到 < PMS_MACRO_EXIT_BAND |
| COLD(偏冷,negative 区) | hedge_index < PMS_MACRO_COLD_TH |
回升到 > −PMS_MACRO_EXIT_BAND |
| NEUTRAL | 其余 | — |
| UNAVAILABLE | 数据守卫未过 | 数据恢复 |
4.2 数据访问(严格单表,三条独立查询)
新增 app/repo/macro_repo.py。用户口径:gp_fx_daily / gp_shibor 在 153 代理 → 走 db.session.fetch_all(..., source="proxy");zs_day_data 已证实在 199(source="index"),若标定脚本探出 153 也可达则统一走 proxy(少一个依赖库)。每张表一条单表 SQL(WHERE ... ORDER BY 日期 DESC LIMIT 120 量级),完全符合单表守卫。列名以标定脚本探查结果为准回填此处,回填前不写 macro_repo:
| 表 | 源 | 日期列 | 取值列 | 过滤条件 |
|---|---|---|---|---|
| zs_day_data | index(153 待探) | timestamp | close | symbol='000001.SH' |
| gp_fx_daily | proxy(153) | 待探 | 待探 | 待探(USD/CNH 筛选) |
| gp_shibor | proxy(153) | 待探 | 待探(1M) | 待探 |
pms_macro_signal 的读写走 source="proxy"(与其余 pms_* 表同侧)。
4.3 信号注册表(多信号扩展点)
app/services/macro_service.py 内一张注册表,V1 只有一项:
SIGNALS = {
"hedge_fx": { # 股汇对冲指数
"label": "股汇对冲指数",
"fetch": macro_repo.fetch_hedge_inputs, # -> 原始序列
"compute": macro_rules.compute_hedge_index, # -> {value, zone, detail}
},
# 未来: "bionic_regime" (决策系统宏观水温) / "index_qrs" (择时层日QRS) ——
# 只加表项与各自参数(命名空间 PMS_MACRO_<KEY大写>_*), 决策层/执行层/闸/页面零改动。
}
启用哪些由 PMS_MACRO_SIGNALS(逗号分隔,默认 hedge_fx)控制。多信号合成 V1 定死最保守者优先:任一启用信号 HOT → 整体偏热;无 HOT 而有 COLD → 偏冷;否则中性。加权合成属二期,不做预设计。
4.4 新表 pms_macro_signal(第 18 张表)
CREATE TABLE IF NOT EXISTS pms_macro_signal (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
signal_key VARCHAR(32) NOT NULL COMMENT '信号名, 如 hedge_fx',
trade_date INT NOT NULL COMMENT '北京交易日 YYYYMMDD',
value DECIMAL(12,4) COMMENT 'hedge_index 值',
zone VARCHAR(16) NOT NULL DEFAULT 'NEUTRAL' COMMENT 'HOT/COLD/NEUTRAL/UNAVAILABLE',
detail_json TEXT COMMENT '中间量: stock_ret/fx_ret/spread/spread_adj/超额深度/各源末日/补齐天数/周期已执行调整',
action VARCHAR(32) NOT NULL DEFAULT 'NONE' COMMENT 'NONE/ADVICE_REDUCE/ADVICE_INCREASE/CMD_ISSUED/BLOCKED/GATED',
ref_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '关联 command_id (下达或采纳后回填)',
note VARCHAR(500) NOT NULL DEFAULT '' COMMENT '被拦原因/建议文案/闸状态',
updated_at DATETIME COMMENT '写入或重扫更新时间',
UNIQUE KEY uk_sig_date (signal_key, trade_date)
) COMMENT='宏观信号日快照与动作记录 (每信号每交易日一行, 重扫就地更新)';
一行四用:历史序列(面板画近 20 日)、当日建议的承载(propose 档)、冷却与"周期已执行调整"的事实源、宏观闸的状态源(扫描层 5 秒缓存读当日行)。
5. 决策层设计
5.1 组合仓位:对数映射(V2 改,替代固定步长)
极值越深,调整越多,但边际递减——既回应"越极端越该动",又天然防止 Z-Score 尾部把仓位一次打爆。纯函数 macro_rules.decide(...),规则按优先级:
PMS_MACRO_ENABLED=False或档位 off 或 zone ∈ {NEUTRAL, UNAVAILABLE} → NONE。- 确认:连续处于同一极值区 ≥
PMS_MACRO_CONFIRM_DAYS;触发口径PMS_MACRO_TRIGGER_MODE(zone_enter / zone_exit,默认值由标定结果定,两种都实现、页面可切)。 - 对数映射(核心):
- 超额深度
e = |hedge_index| − 进入阈值(e ≥ 0,单位=指数点); - 本轮极值周期的累计目标调整
target(e) = min( S0 × ln(1 + e / k), PMS_MACRO_SHIFT_MAX )(占规模的百分点); - 本次步长
step = target(e_now) − done_shift(done_shift = 周期内已执行的宏观调整,记在当日信号行 detail 里);step < PMS_MACRO_STEP_MIN(默认 0.02)→ 不动; - 周期定义:进区 → 迟滞退出;退出后 done_shift 清零,下个周期重新累计。同一深度不重复加码,指数横盘在区内不会连环下命令;指数更深才有新步;
S0/k/SHIFT_MAX初值由标定脚本给建议(目标形状:e 中位数 → 约 5 个点,e 95 分位 → 约 12 个点,封顶 20 个点),页面可调。
- 超额深度
- 冷却:同方向上一次生效动作距今 <
PMS_MACRO_COOLDOWN_TDAYS(默认 3 个交易日)→ 本轮不动(下轮 target 追踪仍在,冷却只是把步子隔开)。「生效」= 所发命令未被立刻 CANCELLED(零方案的升仓命令不占冷却,次日重试)。 - 在途:上一条宏观命令仍在 EXECUTING/PARTIAL → 不叠加。
- 方向裁剪:
- HOT → 降仓,
step再受地板裁剪:不把总仓位降到PMS_MACRO_MIN_PCT(默认 0.20)以下(用户命令不受此限); - COLD → 升仓,
step受PMS_MACRO_MAX_PCT(默认 0=由PMS_PORTFOLIO_CAP兜底)裁剪; - 升仓独有护栏:
PMS_GLOBAL_BUY_HALT生效不发;组合刹车生效且PMS_MACRO_RESPECT_BRAKE=True(默认,用户未反对,标注为假设)不发。降仓不受这两条限制(与"冻结与刹车只挡增持不挡减持"同口径)。
- HOT → 降仓,
5.2 个股宏观闸(V2 新增,用户拍板"个股交易动作受宏观信号控制")
语义:偏热(HOT)确认期间,PMS 自己发起的增持类个股动作暂停——自主新建仓(OPEN)、回踩补足(FILL)、盈利加仓(ADD)、补仓(DCA)的扫描整体跳过并留痕;减持类动作(TRIM/风控卖出/清仓)与用户命令永不受限。COLD 期间不加额外限制(自主动作照常,加量由升仓命令负责)。这与"组合刹车"的既有语义完全同型("自主增持停,命令类不受限"),实现手势照抄。
为什么必须有:不加闸的话存在一条自我打架的路径——宏观降仓卖出 → 现金变多 → PMS_OPEN_AUTONOMY=full 的自主新建仓下一分钟把钱又买回去。闸把增量管住,命令管存量,才闭环。
实现落点(两处,均为纯增量,与既有 exec_halt / 刹车检查并排):
proposal_service.scan_and_route入口:宏观闸生效 → 四类已有持仓动作中买入侧候选不扫(TRIM 照常),skipped 留痕"宏观偏热闸";proposal_service._scan_open入口:宏观闸生效 → 本轮不建仓,skipped 留痕。
闸状态由 macro_service.gate_state() 提供(读当日 pms_macro_signal,进程内 5 秒缓存,读不到 = 不落闸——闸的安全方向是"不额外拦",增持自有规则闸把关,不能让宏观层故障把整个自主引擎摁死)。开关 PMS_MACRO_STOCK_GATE(默认 True,随 PMS_MACRO_ENABLED 总开关生效;即便档位是 propose_only,闸也生效——"停止买入"是保守方向动作,与"减持方向不设确认门槛"同哲学)。
边界(待拍板 §12.3):个股交易方案(网格/做T/跟踪)的买入侧要不要一并受闸。V2 建议先不动策略层——策略是用户特意设的(signal_service 对挂策略票的处理哲学是"只提示不替你做"),宏观闸自动暂停它们容易造成困惑;若你要管,复用现成的 strategy_service.pause_buy(页面可恢复)一行接入即可。
6. 执行接入(兼容性核心节)
6.1 下达即普通命令
res = command_service.issue(
"REDUCE_EXPOSURE", # 或 INCREASE_EXPOSURE
{"pct": step, "window_tdays": param_store.get_int("PMS_MACRO_WINDOW_TDAYS", 3)},
issued_by="macro",
note="宏观择时: hedge_fx=+31.2 偏热确认(阈值+25, 超额6.2), 周期目标降8.6点已降0点, 本次降8.6点")
issue()内部自动做冲突检测:返回ok=False且 errors 含 CONFLICT → 宏观层留痕跳过(ledger verdict=REJECT),当日不再重试。- 命令落表后与用户命令完全同路:
command_poll每分钟排方案、intraday_exec择时出手、refresh_progress日结进度。shadow通道下只记账不真发(与现状一致,宏观层无需感知通道模式)。
6.2 调度时点:09:35,不是盘前
一个已核实的坑决定了时点:positions_view 在取不到实时价时用摊薄成本顶住价格并标 price_ok=False,而 planner._usable 排除 price_ok=False 的票(2026-07-31 静默失败专项第九条)。行情 db13 只有当日分钟线,盘前全部无价——盘前下达的 REDUCE 命令会在一分钟内被排成零方案并置 CANCELLED。所以 macro_scan 定在 09:35(开盘后行情已就位,也在 08:40 拉候选池之后,升仓有票可选)。信号用的是 T-1 日终数据,几点算值都一样。beat 表加一行:
"macro_scan": {"task": "pms.macro_scan", "schedule": crontab(hour=9, minute=35)},
任务本体 @guard(trade_day=True, respect_exec_halt=True)——休假模式自动跳过,异常吞掉守成。宏观闸的状态在 09:35 扫描落表后当日恒定(信号是日频慢变量),盘中每一跳扫描层读的是同一行。另设手动端点(§7.3)供盘中任意时刻重扫/试算。
6.3 护栏对照表(每条对应代码里一处显式检查)
| 场景 | 行为 | 靠什么保证 |
|---|---|---|
| 休假模式 HALT_ALL | 整跳跳过 | 调度 guard respect_exec_halt;手动端点内 macro_service 自查一遍 |
| 用户在途 REDUCE/INCREASE/LIQUIDATE 冲突 | 跳过 + 留痕,不 force | issue() 现有 detect_conflicts,宏观层永不传 force_conflict=True |
| 全局暂停买入 | 升仓不发 | 引擎显式读 PMS_GLOBAL_BUY_HALT(此开关在参数表而非命令表,冲突检测看不见它,必须自查) |
| 组合刹车 | 升仓默认不发 | 引擎读 PMS_BRAKE_UNTIL 比对当日 |
| 数据缺失/停更 | 不动作、不落闸 + 面板黄条 | 信号 UNAVAILABLE 分支 |
| 极值区横跳/横盘 | 不反复动 | 迟滞退出带 + 对数目标追踪(同深度不重复加码)+ 冷却 + 每日一次 |
| 上一条宏观命令未走完 | 不叠加 | 查 issued_by='macro' 的在途命令 |
| 参数表读不到 | 宏观层整体失效(安全方向) | PMS_MACRO_ENABLED 文件初值 False,表挂了退初值即关;闸读不到状态 = 不拦 |
| 仓位已到地板/天花板 | 不动 + 留痕 | decide() 映射裁剪 |
| 宏观闸误伤减持 | 不可能 | 闸只挡买入侧扫描,TRIM/EXIT/风控卖出路径不经过闸 |
7. 档位、建议承载与页面
7.1 三档(沿用系统词汇)
PMS_MACRO_AUTONOMY: off / propose_only(默认)/ full。
- off:只算信号、只落历史、只展示,不出建议不下命令(闸也不生效——off 是"只看不动"档)。
- propose_only:触发时把建议写进当日
pms_macro_signal行(action=ADVICE_*,note 带完整理由与建议 pct),面板出卡片等一键采纳;当日有效,隔日自动作废。宏观闸照常生效(保守方向自动,见 §5.2)。 - full:直接
issue(issued_by="macro")。三条铁律下用户仍可在命令台撤。
不复用 pms_proposal:它的采纳路径是"生成该股指令",是个股级语义;硬塞组合级建议要改共用 decide 端点。宏观建议放自己的表行 + 自己的采纳端点,互不沾染。
7.2 采纳路径
POST /api/macro/adopt {signal_key} → 校验建议仍是当日且未采纳 → 重跑一遍 decide 的护栏(冲突/开关/地板可能在建议挂出后变化)→ command_service.issue(..., issued_by="user")(用户点的头就是用户意志)→ 回填 ref_id=command_id, action=CMD_ISSUED。幂等:同一行只能采纳一次。
7.3 API 与页面
GET /api/macro/status:各信号 {value, zone, 超额深度, 近 20 日序列, 数据日龄, 当日建议, 闸状态, 最近宏观命令及进度, 冷却剩余, 周期已执行调整}。POST /api/ops/macro-scan?dry_run=true|false:手动扫描;dry_run 只算不落表不下达。POST /api/macro/adopt:见上。- 页面
index.html加「宏观择时」面板:指数当前值 + 区制色带(热红/冷蓝/中性灰/不可用黄)+ 近 20 日迷你走势 + 当日建议卡(一键采纳/忽略)+ 宏观闸状态条("偏热闸生效中:自主增持已暂停")+ 最近宏观命令进度。参数不用做界面——PMS_MACRO_*登记后自动出现在「参数设置」。 - Makefile 顺手加
t-macro(status + 试算),非必需。
8. 参数清单(settings.py 初值;全部经 param_store 页面可调;标 ⚙ 的初值由标定回填)
| 参数 | 默认 | 说明 |
|---|---|---|
| PMS_MACRO_ENABLED | False | 宏观择时总开关(关=调度位空转,不取数不计算,面板显示"未启用") |
| PMS_MACRO_AUTONOMY | propose_only | off / propose_only / full |
| PMS_MACRO_SIGNALS | hedge_fx | 启用的信号清单(逗号分隔) |
| PMS_MACRO_STOCK_GATE | True | 个股宏观闸:偏热确认期间暂停自主增持类扫描(off 档整体不生效) |
| PMS_MACRO_HOT_TH ⚙ | 25.0 | 偏热进入阈值 |
| PMS_MACRO_COLD_TH ⚙ | -25.0 | 偏冷进入阈值 |
| PMS_MACRO_EXIT_BAND ⚙ | 15.0 | 迟滞退出带(|值| 回落到此内算离区) |
| PMS_MACRO_CONFIRM_DAYS ⚙ | 1 | 进区连续 N 个交易日才触发 |
| PMS_MACRO_TRIGGER_MODE ⚙ | zone_enter | zone_enter=进区即动 / zone_exit=极值回落再动(默认值待标定对比后定) |
| PMS_MACRO_LOG_S0 ⚙ | 0.12 | 对数映射系数:target = S0·ln(1+e/k) |
| PMS_MACRO_LOG_K ⚙ | 10.0 | 对数映射尺度 k |
| PMS_MACRO_SHIFT_MAX ⚙ | 0.20 | 单个极值周期的累计调整封顶(占规模的百分点) |
| PMS_MACRO_STEP_MIN | 0.02 | 裁剪后步长小于此不动 |
| PMS_MACRO_COOLDOWN_TDAYS | 3 | 同方向两次动作最小间隔(交易日) |
| PMS_MACRO_MIN_PCT | 0.20 | 宏观降仓地板(自动降仓不把总仓位降到此下;用户命令不受限) |
| PMS_MACRO_MAX_PCT | 0.0 | 宏观升仓天花板;0=不单设,由 PMS_PORTFOLIO_CAP 兜底 |
| PMS_MACRO_WINDOW_TDAYS | 3 | 宏观命令的执行窗口 |
| PMS_MACRO_RESPECT_BRAKE | True | 组合刹车期间不自动升仓(假设项,见 §12.4) |
| PMS_MACRO_STALE_TDAYS | 3 | 任一数据源日龄超此(交易日)→ UNAVAILABLE |
| PMS_MACRO_RET_WIN / Z_WIN | 20 / 40 | 收益窗 / Z-Score 窗 |
| PMS_MACRO_SHIBOR_BETA | 0.02 | SHIBOR 修正系数 β |
_RANGES 同步登记,DESC 写中文说明。不进 FAIL_CLOSED:ENABLED 的文件初值就是 False,表读不到退初值即安全方向。
9. 触碰面清单与三条铁律自证
新增文件(5 个,互相独立,删净即回到今天——"三题"规矩第 3 题):
| 文件 | 内容 |
|---|---|
| app/core/macro_rules.py | 纯逻辑:对齐与四步计算、zone 迟滞判定、对数映射 decide(无 IO,可单测) |
| app/repo/macro_repo.py | 三张源表单表取数 + pms_macro_signal 读写 |
| app/services/macro_service.py | 编排:守卫→取数→算→落表→决策→分流→留痕;status / adopt / gate_state |
| scripts/calibrate_macro_signal.py | 标定脚本(已交付,探查+分布+口径对比+对数参数,只读) |
| scripts/test_batch14_units.py | 单测约 25 例(§11) |
触碰的既有文件(9 处,全部纯增量——只加行/加检查,不改既有行为,逐处可 diff 核对):
| 文件 | 加什么 | 不动什么 |
|---|---|---|
| config/settings.py | PMS_MACRO_* 字段一段 | 既有字段零改动 |
| app/services/param_store.py | DESC / _RANGES / _range_check 各加宏观项 | 缓存、FAIL_CLOSED、既有键 |
| app/scheduler.py | macro_scan 任务 + beat 一行 |
既有九个调度位 |
| app/services/proposal_service.py | 两处入口各加一个宏观闸检查(与 exec_halt 检查并排,闸关/读不到=原行为) | 扫描、闸门、分流逻辑零改动 |
| app/web/main.py | 三个新端点 | 既有端点 |
| app/web/static/index.html | 一块面板 | 既有面板 |
| ddl_pms_v1.sql | 第 18 张表 CREATE IF NOT EXISTS | 既有表 |
| scripts/check_db.py | PMS_TABLES 加 pms_macro_signal | (顺带发现:pms_strategy / pms_op_log 也不在这份清单里,属既有欠账,按"不夹带"纪律单独提,不在本次一起改) |
| scripts/run_tests.py | SUITES 登记 batch14 | 既有批次 |
三条铁律逐条自证:命令至上——宏观动作本身就是命令,冲突让位用户,可撤可改;宏观闸只拦 PMS 自主动作,用户命令与减持全不受限。分工不越权——宏观层只回答"整体该多重、增量该不该停",逐股怎么卖买仍由 planner/择时/规则闸决定,不碰决策系统(bionic/桥/择时层零改动,"三题"规矩天然满足)。先记账后动作 + 故障即守成——信号先落表再决策,命令走 issue 的先落表路径,任何取数/计算失败 = 不产生新指令、不落闸。
10. 前置标定(先于一切编码,脚本已交付)
scripts/calibrate_macro_signal.py(严格只读,只发 SELECT/SHOW)在桥机跑,一次回答四件事:
- 列名探查:对 153(proxy)与 199(index)两源探查三张表的位置、列名、样本行——结果回填 §4.2 后才写 macro_repo;
- 分布体检:hedge_index 历史分位数、±20/25/30 各阈值的触发天数与年频次、极值区段的持续天数与最大深度——回答"±25 合不合身";
- 触发口径对比:zone_enter vs zone_exit 各自触发事件后 5/10/20 交易日上证的均值/中位数/胜率(HOT 后应偏负、COLD 后应偏正才算有效)——注意样本量会很小,结论当参考不当真理;
- 对数映射标定:极值周期超额深度 e 的分布 → 解出 S0/k 建议值(e 中位数→5 点、95 分位→12 点、封顶 20 点),并打 e→调整幅度对照表。
运行方式(不需要重建镜像——脚本从宿主工作树经 stdin 喂给容器 python,python - 的 sys.path[0] 是工作目录 /app,config.settings 照常可导入):
cd ~/tradingSystem # git pull 之后
docker compose run --rm -T pms-web python - < scripts/calibrate_macro_signal.py > /tmp/macro_calib_report.md
cat /tmp/macro_calib_report.md
# 需要完整序列时:
docker compose run --rm -T pms-web python - --dump-csv < scripts/calibrate_macro_signal.py > /tmp/hedge_series.csv
探查失败的退路:脚本会打出该表全部列名与样本行,按提示带 --fx-table/--fx-date-col/--fx-price-col/--fx-where/--shibor-* 覆盖重跑;表真不存在 → 数据先落地(数据管道补),或立项由桥/择时层供数(跨系统改动,按"三题"规矩单独议,本方案不带)。
11. 实施步骤与判收
| 步 | 内容 | 判收标准 |
|---|---|---|
| 1 | 桥机跑标定脚本(§10,可立即做) | 三表列名落实;报告出分布/口径对比/S0-k 建议;把报告拿回来定 §8 带 ⚙ 的初值 |
| 2 | 写码 + batch14 单测 | make test ALL SUITES PASS。用例:合成序列对照手算值验四步计算;fx +1 对齐与缺口 as-of;样本不足/停更→UNAVAILABLE;zone 迟滞进出与横跳;对数映射(target 单调、同深度不重复加码、周期清零、封顶);confirm/cooldown/每日一次;零方案命令不占冷却;冲突/HALT_BUY/刹车跳过(打桩);宏观闸(HOT 拦买入侧不拦 TRIM、读不到不拦、off 档不拦);建议采纳幂等与隔日作废 |
| 3 | ddl 建表 + make deploy(收盘后) |
make check 见 18 表;页面参数区出现 PMS_MACRO_*,ENABLED=False,系统行为与部署前一致 |
| 4 | 手动 POST /api/ops/macro-scan?dry_run=true |
面板出指数值与区制;与标定脚本同日值一致(同一套纯函数,天然一致) |
| 5 | 开 ENABLED=True,档位 propose_only,观察 ≥1 周 | 每日一行信号历史;极值日出建议卡不出命令;宏观闸在 HOT 日真的把自主增持 skipped(留痕可查);采纳一次走通 建议→命令→方案→指令 全链 |
| 6 | 冲突演练:挂一条在途 INCREASE,手动扫描触发降仓 | 账本见 MACRO REJECT 留痕,无命令产生 |
| 7 | (拍板后)切 full | 首条自动命令端到端回执,日报关注区可见 |
回滚:页面把 PMS_MACRO_ENABLED 置 False 即回到现状(秒级,命令、闸、建议全停);代码级回滚 = 还原上表 9 处触碰 + 删 5 个新文件;新表留着无任何读方,无副作用。
12. 待拍板清单(V2 更新)
触发口径默认值→ 改为由标定结果定:跑完 §10 脚本,拿两种口径的事后对比数据来选(样本小的话建议 zone_enter + CONFIRM_DAYS 取 1~2 里标定更稳的那档)。固定步长 vs 目标带→ 已定:对数映射(用户拍板)。S0/k/SHIFT_MAX 初值等标定回填。- 策略层(网格/做T)的买入侧要不要受宏观闸:V2 建议先不动(理由 §5.2 末),要管的话复用
strategy_service.pause_buy一行接入。 - 升仓是否受组合刹车约束:上轮问题未获直接答复,V2 按**受约束(True)**落地并标注为假设;要放开改参数即可。
- 数据源:已答——153。zs_day_data 是否也统一走 153 由探查定。
13. 风险与已知限制(说在前面)
- 信号本身是慢变量:20 日收益 + 40 日 Z,天然滞后,抓的是阶段冷热不是日内顶底;对数映射 + 冷却是为此设的缓冲。
- z-score 的尾部行为:40 日窗里刚经历过极端行情时 std 变大,新极端可能"钝化";反之平静期小波动被放大。标定报告的分位数表就是用来看这个的。
- 事后对比样本小:±25 级别的极值一年可能只有几次,标定里的口径对比是参考不是显著性检验,别过度拟合。
- 升仓依赖候选池:极冷区触发升仓但当日无强传导候选 → 命令零方案 CANCELLED(留痕可见,不占冷却,次日重试)——"宁缺毋滥"纪律优先于宏观意愿,这是设计取向不是缺陷。
- 宏观闸的机会成本:偏热确认期间个股级的好机会也会被一并拦下(闸不看个股质地)——这是"个股动作受宏观控制"的题中之义;propose_only 观察期里可以从 skipped 留痕回看拦了什么,评估闸的松紧。
- UNAVAILABLE 长期化:数据停更时宏观层安静失效(不动作、不落闸),只有面板黄条与日报提示——不会误动作,但也不会提醒你"该人工判断了",需要把面板纳入日常一眼。