tradingSystem/MACRO_TIMING_PLAN.md

383 lines
34 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.

# 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.153153 代理)**,标定脚本顺带完成列名探查与核实。
> 写作纪律沿袭项目既有文档:术语先白话、阈值给硬数字且全部可配置、失败路径显式、未确认事项进清单。
> 关联:`POSITION_MGMT_DESIGN.md`(总体设计)、`README.md`(模块地图与三条铁律)、`DEVLOG.md` 2026-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}`——语义完全一致(释放金额 = 规模 × pctplanner 按 停新买→清弱票→收利润→等比微减 四档凑额,这正是"pms 根据规则执行对个股的降仓位操作")。反向就是 `INCREASE_EXPOSURE`。存量靠命令,增量靠闸:偏热期间把自主增持的**扫描**停掉(不产生候选就不会有指令),两头合起来才是完整的"仓位受宏观控制"。
刻意**不做**的两种姿势:
1. **不做"连续仓位控制器"**(宏观层直接持有目标仓位、每分钟收敛)——绕开命令系统另立产生买卖的路径,与命令冲突时没有仲裁者,正是禁区。
2. **不把宏观观点塞进规则闸**——规则闸是合规终检,塞行情观点会让"没通过合规"和"宏观不看好"在评审账本里分不开;宏观闸放在**扫描入口**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 扫描入口:
→ 规则闸 → 择时 → 下发 → 对账 偏热确认 → 增持类扫描暂停(留痕)
```
六条设计原则(每条都能在代码评审时逐一核对):
1. **默认关**`PMS_MACRO_ENABLED=False`。部署完成后系统行为与今天完全一致,开关和档位都在页面上,随时收权。
2. **唯一出口**:宏观层产生仓位变化的途径只有两条——`command_service.issue()` 下命令、扫描入口的宏观闸(只拦不发)。不直接写 `pms_plan` / `pms_instruction` / `pms_position`,不调 executor不碰账本。
3. **故障即守成**:任一数据源取不到、样本不足、日龄超限 → 信号置 `UNAVAILABLE`**不动作、不落闸**(拿不到 ≠ 偏热,闸的安全方向是"不额外拦"——增持本身另有规则闸把关),面板亮黄条。挂在调度器 `guard` 下,异常吞掉只记 ERROR。
4. **命令至上**与用户在途命令冲突issue 返回 CONFLICT→ 本轮放弃并留痕,**绝不 force**`HALT_ALL`(休假)由调度守卫直接跳过;`PMS_GLOBAL_BUY_HALT` 生效时不发升仓;组合刹车期间默认不发升仓(可参数放开)。**用户命令永不受宏观闸限制**,且随时可在命令台撤销宏观命令。
5. **限频防抖**:进区确认天数 + 迟滞退出带 + 同方向冷却 + 每信号每交易日至多一次动作 + 对数映射的"目标追踪"§5.1,同一深度不重复加码)+ 上一条宏观命令在途不叠加。指数在极值区横跳不会造成反复买卖。
6. **全留痕**:信号值每日入 `pms_macro_signal`(含中间量,可复算);每次触发/被拦/落闸入 `pms_action_ledger`action=MACROhard_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判定带迟滞防横跳阈值初值 ±2515**以标定结果为准回填**
| 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 | index153 待探) | 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 只有一项:
```python
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 张表)
```sql
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(...)`,规则按优先级:
1. `PMS_MACRO_ENABLED=False` 或档位 off 或 zone ∈ {NEUTRAL, UNAVAILABLE} → NONE。
2. 确认:连续处于同一极值区 ≥ `PMS_MACRO_CONFIRM_DAYS`;触发口径 `PMS_MACRO_TRIGGER_MODE`zone_enter / zone_exit**默认值由标定结果定**,两种都实现、页面可切)。
3. **对数映射(核心)**
- 超额深度 `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 个点页面可调
4. 冷却同方向上一次**生效**动作距今 < `PMS_MACRO_COOLDOWN_TDAYS`默认 3 个交易日)→ 本轮不动下轮 target 追踪仍在冷却只是把步子隔开)。「生效」= 所发命令未被立刻 CANCELLED零方案的升仓命令不占冷却次日重试)。
5. 在途上一条宏观命令仍在 EXECUTING/PARTIAL 不叠加
6. 方向裁剪
- 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`默认用户未反对标注为**假设**不发降仓不受这两条限制"冻结与刹车只挡增持不挡减持"同口径)。
### 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 下达即普通命令
```python
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 表加一行
```python
"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在桥机跑一次回答四件事
1. **列名探查** 153proxy 199index两源探查三张表的位置列名样本行——结果回填 §4.2 后才写 macro_repo
2. **分布体检**hedge_index 历史分位数、±20/25/30 各阈值的触发天数与年频次极值区段的持续天数与最大深度——回答25 合不合身"
3. **触发口径对比**zone_enter vs zone_exit 各自触发事件后 5/10/20 交易日上证的均值/中位数/胜率HOT 后应偏负COLD 后应偏正才算有效)——**注意样本量会很小结论当参考不当真理**
4. **对数映射标定**极值周期超额深度 e 的分布 解出 S0/k 建议值e 中位数5 95 分位12 封顶 20 并打 e调整幅度对照表
**运行方式(不需要重建镜像**——脚本从宿主工作树经 stdin 喂给容器 python`python -` sys.path[0] 是工作目录 /app`config.settings` 照常可导入
```bash
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样本不足/停更UNAVAILABLEzone 迟滞进出与横跳对数映射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 更新)
1. ~~触发口径默认值~~ **改为由标定结果定**跑完 §10 脚本拿两种口径的事后对比数据来选样本小的话建议 zone_enter + CONFIRM_DAYS 1~2 里标定更稳的那档)。
2. ~~固定步长 vs 目标带~~ **已定:对数映射**用户拍板)。S0/k/SHIFT_MAX 初值等标定回填
3. **策略层(网格/做T的买入侧要不要受宏观闸**V2 建议先不动理由 §5.2 要管的话复用 `strategy_service.pause_buy` 一行接入
4. **升仓是否受组合刹车约束**上轮问题未获直接答复V2 **受约束True**落地并标注为假设要放开改参数即可
5. **数据源**已答——153zs_day_data 是否也统一走 153 由探查定
## 13. 风险与已知限制(说在前面)
- **信号本身是慢变量**20 日收益 + 40 Z天然滞后抓的是阶段冷热不是日内顶底对数映射 + 冷却是为此设的缓冲
- **z-score 的尾部行为**40 日窗里刚经历过极端行情时 std 变大新极端可能"钝化"反之平静期小波动被放大标定报告的分位数表就是用来看这个的
- **事后对比样本小**:±25 级别的极值一年可能只有几次标定里的口径对比是参考不是显著性检验别过度拟合
- **升仓依赖候选池**极冷区触发升仓但当日无强传导候选 命令零方案 CANCELLED留痕可见不占冷却次日重试)——"宁缺毋滥"纪律优先于宏观意愿这是设计取向不是缺陷
- **宏观闸的机会成本**偏热确认期间个股级的好机会也会被一并拦下闸不看个股质地)——这是"个股动作受宏观控制"的题中之义propose_only 观察期里可以从 skipped 留痕回看拦了什么评估闸的松紧
- **UNAVAILABLE 长期化**数据停更时宏观层安静失效不动作不落闸只有面板黄条与日报提示——不会误动作但也不会提醒你"该人工判断了"需要把面板纳入日常一眼