---
title: 位置分档口径统一记录（2026-09-12）
tags: [位置, 口径治理, 变更记录]
created: 2026-09-12
status: done-1st-pass
---

# 位置分档口径统一：变更记录与排除理由

## 规范口径

**低 < 20 / 高 ≥ 65**（中档细分用 40）

依据 `position_threshold_study.py`：全A随机 400 只 / **13,893 窗口** / 20日超额制
- base rate 超额胜率 42.7%
- 低<20/高≥65 → 分离度 **+1.52pp**（低档 n=5,197 胜率 47.8%，有效性 +5.1pp）
- 旧值 低<30/高≥70 → +1.07pp（白丢 0.45pp）
- 扫描最优 低<20/高≥60 → +1.53pp，与 65 差 0.01pp = 噪声 → 取 65 以对齐已有站点

## 第一批（手工，5 处）

| 文件 | 原 | 新 |
|---|---|---|
| `position_layer.py`（规范工具） | 30/70 | **20/65** |
| `full_analysis_pipeline.py` | 20/40/60/80 | 20/40/65（砍"中高位"档） |
| `sell_advisor.py` | 35/80 | 20/65 |
| `research_report.py:176` | 35/65 | 20/65 |
| `unified_report_engine.py` | 35/65 | 20/65 |

## 第二批（AST 自动对齐，21 文件 / 29 处）

`align_position_tiers.py` 规则：只改【真分档】行（所在语句含 低/中/高 档位词）
- 低线 `</<=` 值 ∈ {15,25,30,35} → **20**
- 高线 `>/>=` 值 ∈ {70,75,80} → **65**
- 每文件改后 `py_compile` + `import` 双验证，失败自动回滚（本次 **回滚 0**）

## ⚠️ 三类【故意不改】+ 理由（重要）

1. **85 档保护** —— 实战里 85 常作「极端高位/派发」独立档：
   `"极端高位→主力派发" if pos_pct > 85 else "高位..."`。
   塌成 65 会把两档并成一档 = 语义损坏。是独立档还是高线需人工判定。
2. **复合条件保护** —— 例 `elif pct60 < 35 and yoy > 1.2:`、`if pct60 < 30 and vol_ratio >= 3.0:`
   阈值与其他变量共同定义选股集，单改阈值会改变语义。
3. **自有语义阶梯保护** —— `kline_position_semantics.py`（8 档）、`crowd_psychology.py`（7 档），
   不是「低/中/高」三档，强改会破坏其分级语义。

另：AST 扫出 **上下文阈值 36 文件 / 90 节点**（如 `position_pct > 10` 的门槛）——
本来就不是分档口径，不迁移。

## 工具三件

| 工具 | 作用 |
|---|---|
| `position_threshold_study.py` | 测量：哪套阈值分离度最强（可复跑） |
| `position_tier_guard.py` | 清点：AST 扫全部位置比较节点 + 判定真分档/上下文（**清单不做告警**） |
| `align_position_tiers.py` | 执行：AST 精确定位对齐（含字节偏移换算/复合条件/85 保护/逐文件回滚） |

## 被自己机制拦下的 3 次事故（全部发生在写入前）

1. 手工替换把 `unified_report_engine.py` 改出 IndentationError（注释行没缩进）→ py_compile 抓到，已修
2. guard 第一版用正则 → 373 命中约半数假阳性（print 格式化数字/算术行/复合条件）→ 改 AST
   （**铁律7 当天刚写完就第 5 次踩：FP≥30% 不许上线**）
3. align 首版演练暴露两类**真损坏**：
   - `ast` 的 `col_offset` 是 **UTF-8 字节偏移**，按字符切 → 改进了字符串内部（`"⚠️ 中性"` → `"20 中性"`）
   - 复合条件塌档（`>85` 改成 `>65` 使两个分支同阈值）
   → 修掉后重跑演练才 apply。**演练机制的价值在此实证。**



## 第三批（AST 自动对齐，11 文件 / 11 处，收尾）

覆盖了前两批没抓到的三种形状：
1. **降序三元链** —— `"高位" if >0.65 else "中位" if >0.35 else "低位"`（下界用 `>` 表达，非 `<`）
2. **if/elif 链跨语句** —— `if pct60<20 ... elif pct60<40 ... elif pct60<70`（一档一句）
3. **分数标度** —— `pos_pct > 0.35 → 0.2`（保持 0-1 标度，不混成 20）

改动：`orchestrator`(80→65) / `hindsight_pipeline`(80→65) / `market_causal_chain`(75→65) /
`vol_price_trainer`(70→65) / `banruo_elastic_value`(70→65) / `rotation_phase`(0.35→0.2) /
`历史性复盘`(0.35→0.2) / `run_tree_scan_v2` `run_tree_scan_v3` `tree_scan_detailed`(35→20)

### 执行器踩的第 4 个坑（这次也是演练/验证拦下的）
- **`chain_head` 被内层 If 覆盖** —— `ast.walk` 后访问的 elif 会把自己重新登记为链头，
  导致链合并完全失效（第一版演练报「0 文件」暴露）→ 修：先标记「作为 elif 出现的 If」并跳过。
- **回滚是误判** —— 3 个文件因 `import` 时 `Mootdx init failed: timed out`（瞬时网络）
  被 60s 超时判失败而回滚，扫描器第二次手工 import 就 ✅ → 修：import 重试 2 次再判。

## 定义偏差登记（比阈值更根本）

「60日位置」在系统里有**两套不同定义**，却都叫 `pos60`：

| 定义 | 公式 | 使用文件 |
|---|---|---|
| **区间位置**（主流，12+ 文件） | `(cur − 60日最低) / (60日最高 − 最低)` | position_layer / position_stage / market_state / five_layer_pipeline … |
| **百分位排名**（1 个文件） | `(60日内收盘 < 今收的天数) / n` | `nightly_full_scan.py:122` |

两者数值不同但同名字段 → 跨文件比较/合并时会得到互相矛盾的位置结论。
`nightly_full_scan.py` 已加入执行器排除名单（阈值不可直接套用）。

## 剩余（登记保留，理由在案）

- **上下文阈值**（无档位词）：proactive_scanner / wei_laoshi_patterns / pullback_screener /
  engine_fix / buy_at_tipping_point_scanner / daily_review / wuwei_volume / wuwei_signals /
  stock_profiler / midday_scan / three_battles
- **语义阶梯**（多档非低中高）：kline_position_semantics(8档) / crowd_psychology(7档) /
  consensus_stage / daily_pangan_trainer
- **85 极端档**：trend_engine / tongbi_scanner / unified_pipeline（85 是独立档，需人工判定）
- **60 档**：taoge_funnel / banruo_data_priority 等 —— 与 65 差 0.01pp（噪声内），登记为等价

## 遗留（需人工逐个判定）

- 清单 42 个文件未同时采用 20/65（含上述三类保护对象）
- 未做：把各文件改为 `from position_layer import LOW_PCT, HIGH_PCT`（单一真源）
  —— 更彻底，但要逐文件判断该处是否**应该**用规范口径

## 第四批：单一真源绑定（结构性收口）

前四批把**值**对齐了，但 `20` / `65` 仍是 **30 个文件各自硬编码的字面量** ——
下次调口径又要全库改一遍。第四批把已对齐的站点改成 import 单一真源。

```
新增: position_layer.LOW_PCT_FRAC / HIGH_PCT_FRAC  (0.20 / 0.65, 供分数标度文件用)
绑定: from position_layer import LOW_PCT, HIGH_PCT, LOW_PCT_FRAC, HIGH_PCT_FRAC
      30 文件 / 53 处（只绑【等于规范值】的站点 ⇒ 零行为变化）
```

**为什么可以绑 position_layer（安全性已实测）**：
- 它模块级只 import stdlib（`data_hub` 在函数内 line 207）→ 冷启动 **0.01s**，不给轻量扫描器增负
- 无循环 import 风险
- 绑定器三验证：`py_compile` + `import` + **常量值断言**（`LOW_PCT==20 and HIGH_PCT==65`，值被改则拒写）

**零行为变化的机械证明**：diff 里只允许出现「数字字面量 → 常量名」，不允许出现任何其它数字变化。


---
**相关概念**: [[选股策略框架]] | [[风险控制]]
## 2026-10-10 增补（cron 全量审计裁决）

- **裁决**: 低/中/高**显示标签**统一 position_layer 20/65；策略判据阈值（low_launch / bingjia / dragon_fusion 等用 35 做**选样条件**的）不属标签，不动。
- **本轮已迁 7 个 cron 产出链路文件**（doctrine / dynamic / pan_gan / pullback / kline_position_semantics 3处 / mainforce_scan / market_debate）+ **同日追加 9 个 py 显示标签**（fugui_data_provider / fugui_web_api / five_layer_pipeline / early_indexer / tree_scanner / battlefield / tree / excalidraw 颜色分档）+ **4 个活跃 skill**（pan-gan-signals / bingjia-13-questions / external-analysis-absorption / market-discipline）。
- **kline_position_semantics「8档」保护声明更正**: 该文件已无任何档位阶梯（grep 档=0 命中），现存为 3 处三档标签，已迁 20/65；EXCLUDE 保留仅防误伤形态判据（锤子40/吞没75/射星60 等非分档阈值）。align_position_tiers.py 注释同步更正。
- **未动**: lambda_rank position_safety(35) = 模型特征阈值非显示标签；pool_build(35/65) = 0 消费端。
- 验证: bash -n / py_compile 全过 + mock 实测 36/36 + 活跃 skill 35/65 残留 = 0。
