Metadata-Version: 2.4
Name: tp-quant
Version: 1.0.1
Summary: Trading Pattern Scanner Identifies complex patterns like head and shoulder, wedge and many more.
Author: Preetam Sharma
License: CC BY-NC-SA 4.0
Requires-Python: ==3.12.*
Description-Content-Type: text/markdown
License-File: LICENSE.md
Requires-Dist: numpy>=2.0.0
Requires-Dist: pandas<4,>=3.0.0
Requires-Dist: kdata-quant==1.0.0
Requires-Dist: mootdx2>=1.0.8
Requires-Dist: TA-Lib>=0.4.32
Requires-Dist: pyyaml>=6.0
Provides-Extra: backtest
Requires-Dist: backtrader>=1.9.78.123; extra == "backtest"
Dynamic: license-file

# Python API 接口文档 (API Reference)

本文档面向需要在下游系统（如 LLM AI Agent、量化自动化交易流水线、复盘面板）中直接集成 `tp-quant` 的开发者。

安装包：`pip install tp-quant`

本仓库的最核心 Python API 是位于 `tradingpatterns` 命名空间下的 **`pre_screen_and_scan`** 函数。

---

## 1. 核心接口：`pre_screen_and_scan`

该接口是一体化的预筛选与形态扫描管道 (v2.4)，它不仅计算经典的技术形态，还会结合生命周期、趋势环境与量价配合度，对标的进行严格漏斗过滤，并生成详尽的 **Context Package**（上下文数据包）。

## 周线走势评分

`evaluate_weekly_trend()` 是独立的周线中期趋势质量接口，不参与 `pre_screen_and_scan()` 的 `total_score` 合成。

```python
from tradingpatterns import evaluate_weekly_trend, evaluate_weekly_trends

result = evaluate_weekly_trend(df, symbol="512880")
```

输入为日线 OHLCV（`open/high/low/close/volume`，DatetimeIndex 或 `date` 列）。接口内部聚合完整周线，并在周中自动排除当前未完成周；结果中的 `weekly_reference_date` 表示实际参与评分的最后一根周线日期。

核心输出包括：`weekly_trend_score`（0-100）、`weekly_trend_score_10`、`trend_state`、`evaluation_state`、`rating`、`score_breakdown`、`weekly_metrics`、`reasons` 和 `warnings`。传入 `weekly_only=True` 时，`daily_trigger` 固定为中性 6 分；未传市场基准时，相对强度固定为中性 4 分并给出 warning。

批量接口 `evaluate_weekly_trends(jobs, min_score=..., states=...)` 会按周线评分降序返回结果，单个任务异常会保留为带 `error` 字段的结果，不中断其他标的。

### 1.1 导入方式

```python
from tradingpatterns import pre_screen_and_scan
```

### 1.2 函数签名

```python
def pre_screen_and_scan(
    df: Union[pd.DataFrame, Sequence[PreScreenJob]],
    symbol: str = None,
    mode: str = "all",
    pattern: str = None,
    min_score: float = 6.0,
    config: PreScreenConfig = None,
    enforce_trend_alignment: bool = None,
    filter_acceleration: bool = None,
    ss_scanner: StrongStockScanner = None,
    pattern_scanner: PatternScanner = None,
    max_workers: Optional[int] = None,
    input_bar_limit: Optional[int] = None,
    market_context_df: Optional[pd.DataFrame] = None,
    local_state: Optional[dict] = None,
    session_asof: Optional[str] = None,
) -> Union[dict, List[dict]]
```

#### 参数详解

| 参数名 | 类型 | 说明 |
| --- | --- | --- |
| **`df`** | `DataFrame` | **单标的**：包含 `open`, `high`, `low`, `close`, `volume` 的 DataFrame。 |
| **`symbol`** | `str` | 标的代码，用于匹配市场预设（ETF/A股/港股）；A 股建议使用 `sh.600519` / `sz.000001`，ETF 可使用 `510300`。 |
| **`mode`** | `str` | `"all"`: 全扫描；`"bottom"`: 仅限底部反转；`"trend"`: 仅限趋势延续。 |
| **`pattern`** | `str` | 可选的形态名称过滤条件；传入时仅保留 `patterns[].pattern` 包含该文本的结果。 |
| **`min_score`** | `float` | **预筛选排序分阈值（0~10）**：在通过信号过期、止损可行性、`mode` / `strategy_hint` 等硬条件后，若 **`total_score`** 低于此值则 `pre_screen_passed=false`，`rejection_reason` 为 `综合分未达阈值`（兼容旧文案）。诊断可设 `0.0`。 |
| **`config`** | `PreScreenConfig` | 可选的预筛选配置；省略时按 `symbol` 自动匹配默认配置。 |
| **`enforce_trend_alignment`** | `bool` | 保留兼容参数，传入时覆盖配置中的趋势对齐选项。 |
| **`filter_acceleration`** | `bool` | 保留兼容参数，传入时覆盖配置中的加速冲顶过滤选项。 |
| **`ss_scanner`** | `StrongStockScanner` | 可选扫描器；单标的场景可复用它生成 `PatternScanner.scan(..., phase_history=...)` 所需的生命周期历史。 |
| **`pattern_scanner`** | `PatternScanner` | 可选扫描器；单标的场景可复用已实例化的形态扫描器。 |
| **`max_workers`** | `int` | 批量模式线程数；`None` 时默认 `min(可用逻辑 CPU 数, 32)`。 |
| **`input_bar_limit`** | `int` | 预筛选与指标计算最多使用的最近 K 线数；`None` 使用默认窗口，`<=0` 表示不截断。 |
| **`market_context_df`** | `DataFrame` | 宽基指数数据，用于计算相对强度（RS）与市场环境门控。 |
| **`local_state`** | `dict` | 可选的持久化状态，用于计算 `signal_age_days`（信号年龄）。 |
| **`session_asof`** | `str` | 扫描会话日 `YYYY-MM-DD`；用于队列龄与信号龄，省略时回退到 K 线最后日期。 |

---

## 2. 返回值结构 (Context Package)

系统返回一个结构化的 Context Package，旨在为 LLM 提供最直接、无需二次计算的决策依据。

### 2.1 返回值 JSON 示例

```json
{
  "symbol": "sh.600519",
  "name": "贵州茅台",
  "market": "A股",
  "date": "2026-05-01",
  "pre_screen": {
    "strategy_hint": "趋势跟随",
    "priority_score": 0.88,
    "priority_breakdown": {
      "freshness": 0.9,
      "proximity": 1.0,
      "confluence_factor": 0.96,
      "adjusted_proximity": 0.96,
      "rs_factor": 1.0,
      "vol_factor": 1.0,
      "risk_discount": 1.0,
      "wyckoff_factor": 1.03,
      "market_support": 0.8,
      "maturity": 1.0
    },
    "signal_age_days": 2,
    "stale": false,
    "stop_infeasible": false,
    "estimated_stop_dist_atr": 1.15,
    "urgency": "MEDIUM"
  },
  "trend_structure": {
    "ema_alignment": "bullish_aligned",
    "adx": 28.5,
    "ema_squeeze": false,
    "ema_compression_pct": 3.2
  },
  "volume": {
    "vr": 1.1,
    "vr_type": "正常",
    "mfi_zone": "健康",
    "obv_accumulation": true
  },
  "wyckoff": {
    "phase": "accumulation",
    "event": "spring",
    "bias": "demand",
    "effort_result": "absorption",
    "score": 1.5,
    "reasons": ["跌破近期箱体低点后收回", "成交量放大且收盘位置偏强"]
  },
  "weekly_context": {
    "weekly_trend_direction": "bullish",
    "weekly_adx": 22.0
  },
  "relative_strength": {
    "rs_20d": 3.5,
    "rs_trend": "improving"
  },
  "calculated_constraints": {
    "base_confidence_score": 3.2,
    "confidence_breakdown": {
      "weekly_context": 1.0,
      "ema_alignment": 0.6,
      "vwma_support": 0.4,
      "adx_trend": 0.4,
      "volume_match": 0.4,
      "momentum_match": 0.4,
      "pattern_divergence": 0.0,
      "wyckoff_supply_demand": 0.2
    },
    "max_position_pct": 30,
    "max_stop_dist_atr": 1.5,
    "min_rrr": 1.2
  },
  "total_score": 8.5,
  "unified_breakdown": {
    "priority_10": 8.5,
    "pattern_top_10": 7.0,
    "confidence_10": 8.9,
    "confluence_10": 8.6,
    "weights": { "priority": 0.35, "pattern": 0.3, "confidence": 0.2, "confluence": 0.15 }
  },
  "strategy_type": "趋势跟随",
  "pre_screen_passed": true,
  "rejection_reason": null,
  "pre_screen_summary": "v2.4 分析概览\n趋势:bullish_aligned | 量能:正常 | 动量:bullish_zero_above"
}
```

### 2.2 核心字段解读 (Core Fields)

**预筛选排序分（v3.1）**  
- **`total_score`**（0~10）为 `pre_screen_and_scan()` 的**日线预筛选排序分**，由 `unified_breakdown` 中四项子分（均已压到 0~10）按权重加权得到；**`min_score` 与此字段比较**。  
- 子分项含义：`priority_10` = `priority_score×10`（时机/衰减）；`pattern_top_10` = 形态列表最高分；`confidence_10` = `base_confidence_score` 按理论上限归一；`confluence_10` = 共振 `score/max×10`。默认权重 **0.35 / 0.30 / 0.20 / 0.15**（可用环境变量 `TP_UNIFIED_W_*` 覆盖，见 README）。
- `total_score` 是规则型排序启发式，不等同于胜率、收益率或独立概率；多个子项会共享周线、共振、相对强度、风险等信息，因此不要把它解释为统计独立模型。
- 若后续同时输出 `weekly_trend_score`，两者应并列展示：`total_score` 解释日线预筛选质量，`weekly_trend_score` 解释周线中期趋势质量；不得直接相加生成新总分。

**子分数仍保留**（用于解释与调试，勿与 `total_score` 或其他评分直接相加当作第二套总分）：

| 字段 / 区域 | 典型范围 | 含义 |
| :--- | :--- | :--- |
| **`pre_screen.priority_score`** | 0~1.25 | 新鲜度 × 调整后临近度（含共振调节）× RS 因子 × 波动率因子 × 风险折扣 × 成熟度（见 `priority_breakdown`），并参与合成 `priority_10`。 |
| **`patterns[].score`** | 0~10 | 单形态强度，取最高进入 `pattern_top_10`。 |
| **`signal_confluence`** | `score` / `max` | 共振命中条数，用于 `confluence_10`。 |
| **`calculated_constraints.base_confidence_score`** | 0~3.8 | 置信度原始分，归一后进入 `confidence_10`。 |

| 字段路径 | 类型 | 说明 |
| :--- | :--- | :--- |
| **`pre_screen.strategy_hint`** | `str` | **AI 决策锚点**。建议的策略框架：`趋势跟随`、`底部反转`、`区间震荡`、`等待突破`、`观望`。 |
| **`pre_screen.priority_score`** | `float` | **排序依据**。典型范围 0~1.25（RS/vol 加成可突破 1.0）。公式：`Freshness × AdjustedProximity × RS_factor × Vol_factor × RiskDiscount × Maturity × WyckoffFactor`。 |
| **`pre_screen.priority_breakdown`** | `dict` | **优先分明细**。含 `freshness`(时效)、`proximity`(原始临近度)、`confluence_factor`(共振调节)、`adjusted_proximity`、`rs_factor`(相对强度，仅趋势跟随)、`vol_factor`(波动率可靠性)、`risk_discount`(风险折扣)、`wyckoff_factor`(正向供需事件轻量加成)、`market_support`(仅供观测，不入公式)、`maturity`(队列老化)。 |
| **`pre_screen.stop_infeasible`** | `bool` | **硬约束**。若为 `true`，表示当前价格距离技术位超过当前市场档位允许的 ATR 上限。 |
| **`weekly_context.weekly_trend_direction`** | `str` | **周线大环境**。`bearish` 时通常强制 `strategy_hint` 为 `观望`。 |
| **`rejection_reason`** | `str` | **拦截原因**。当 `pre_screen_passed` 为 `false` 时，如 `"信号过期"`, `"综合分未达阈值"`, `"周线趋势走坏"` 等。 |
| **`unified_breakdown`** | `dict` | **统一分合成明细**（子项 0~10 与权重）。 |

### 2.3 全字段参考手册 (Full Field Reference)

#### 1. 基础信息 & 预筛选 (Pre-screen & Meta)
- **`total_score` / `unified_breakdown`**: 日线预筛选 0~10 排序分及加权明细。
- **`symbol` / `name` / `market` / `date`**: 标的代码、名称、资产类别与分析日期。
- **`pre_screen.urgency`**: 紧急程度 (`HIGH`/`MEDIUM`/`LOW`)。基于 `priority_score` 阈值：≥ 0.80 = HIGH，≥ 0.50 = MEDIUM，其余 = LOW。
- **`pre_screen.signal_age_days`**: 信号距离今日的天数。通常 > 3 天视为过期。
- **`pre_screen.queue_age_days`**: 标的在等待队列中的滞留天数（用于衡量成熟度）。
- **`pre_screen.priority_breakdown`**: 优先分明细字典。
- **`pre_screen.universe_rank` / `universe_size`**: 在全市场扫描通过标的中的排名与总数。
- **`pre_screen.stale`**: 布尔值。信号是否已过期。
- **`pre_screen.stop_infeasible`**: 布尔值。基于当前波动率，常规止损是否不可行。
- **`pre_screen.estimated_stop_dist_atr`**: 预估止损距离的 ATR 倍数。
- **`pre_screen_passed`**: 布尔值。是否通过所有漏斗门控（准入标识）。

#### 2. 技术特征 (Technical Features)
- **`trend_structure`**: 趋势结构。
    - `ema_alignment`: 均线排列状态（如 `bullish_aligned`）。
    - `ema_compression_pct`: 均线粘合度（数值越小越接近爆发点）。
- **`volume`**: 量能环境。
    - `vr_type`: 量能分布（如 `正常`, `放量确认`, `高潮量`）。
    - `obv_accumulation`: OBV 指标是否呈现资金堆积态势。
- **`wyckoff`**: 日线 OHLCV 的威科夫供需解释，不代表 Level2 或逐笔盘口。
    - `phase`: `accumulation`、`markup`、`distribution`、`markdown` 或 `unknown`。
    - `event`: `spring`、`test`、`sos`、`lps`、`upthrust`、`sow` 或 `none`。
    - `bias` / `effort_result`: 当前供需方向与量价结果解释。
    - `score`: -1.5 至 1.5 的解释分；正向事件仅轻量增加置信度/优先级，负向事件写入 `risk_flags`。
- **`momentum`**: 动量状态。包含 `macd_state`, `rsi_zone` 等动量振荡指标。
- **`price_context`**: 价格位置。
    - `dist_to_ema20_atr`: 价格偏离 EMA20 的 ATR 倍数（用于判断是否追高）。
    - `pullback_depth_pct`: 距离近期高点的回撤幅度。
- **`relative_strength`**: 相对强度。`rs_20d` > 0 表示强于大盘，包含 `rs_trend` (走势)。

#### 3. 形态与信号共振 (Patterns & Signals)
- **`patterns`**: 探测到的图表形态列表（如 `Ascending Triangle`）。每个形态含 `score` 和 `date`。
- **`candle_signals`**: 最近 3 日的 K 线组合信号（如 `Doji`, `Hammer`）。
- **`signal_confluence`**: 信号共振评分。
    - `score` / `max`: 共振得分与满分。
    - `details`: 触发的共振项目列表（如 `["C1", "C2", "C3"]`），对应均线、量能、趋势等维度的确认。

#### 4. 宏观与环境 (Context & Phase)
- **`weekly_context`**: 周线级别环境。包含 `weekly_trend_direction` (大趋势), `weekly_adx` 等。
- **`lifecycle_phase`**: 生命周期阶段。常见值包括 `均线收敛/蓄势`、`突破确认`、`主升浪（回调整理期）`、`趋势延续`、`底部构筑 / 启动预热`、`横盘震荡`、`未知`。
- **`market_context`**: 大盘环境。包含 `bias` (偏多/偏空) 和 `vol_regime` (波动率状态)。

#### 5. 自动约束 (Calculated Constraints)
- **`calculated_constraints.base_confidence_score`**: 系统预计算的基准置信度（Logic Shift-Left 产物，0~3.8）。
- **`calculated_constraints.confidence_breakdown`**: 置信度得分明细，对应周线与各项技术共振的得分。
- **`calculated_constraints.max_position_pct`**: 建议最大仓位百分比。
- **`calculated_constraints.max_stop_dist_atr`**: 当前环境允许的最大止损宽容度。
- **`calculated_constraints.min_rrr`**: 要求的最小盈亏比。
- **`risk_flags`**: 风险标签数组（如 `["回撤过深", "RS绝对弱势"]`）。

### 2.4 独立量价接口：`detect_wyckoff_context`

当下游只需要日线级供需解释、而不需要完整预筛选时，可直接调用该公开接口：

```python
from tradingpatterns import detect_wyckoff_context

wyckoff = detect_wyckoff_context(df)
```

```python
def detect_wyckoff_context(
    df: pd.DataFrame,
    volume_status: Optional[dict] = None,
    price_context: Optional[dict] = None,
) -> dict
```

`df` 至少包含 21 根已完成日线的 `open`、`high`、`low`、`close`、`volume`。传入由预筛选管线产出的 `volume_status` 与 `price_context` 时，会额外使用 OBV、MFI 和 60 日价格位置；纯 OHLCV 调用仍可识别基础事件。

返回字段为 `phase`、`event`、`bias`、`effort_result`、`score`、`reasons`。事件以当前 K 线前 20 根已完成日线的高低点和均量为基准，不使用 Level2、逐笔成交或未来数据。`spring`、`test`、`sos`、`lps` 为正向供需事件；`upthrust`、`sow` 为供给风险事件。该接口返回的是量价解释，不构成交易指令。

### 2.5 进阶：结合威科夫与生命周期阶段提升准确度

在系统底层设计中，**`StrongStockScanner`（生命周期阶段）** 与 **`detect_wyckoff_context`（威科夫量价）** 是解耦的。`StrongStockScanner` 基于稳健的均线、波动率和 ADX 划分宏观阶段；而威科夫量价专门解析微观的单 K 线供需与量价行为。之所以在底层解耦，是为了防止在未经过大样本跨市场回测前，将对周期极为敏感的威科夫事件变成死板的硬性过滤条件，导致过度拟合或误杀。

**然而，在策略调用层，将两者结合使用是提升交易胜率的极佳手段。** 威科夫理论的核心（吸筹 -> 拉升 -> 派发）与生命周期阶段高度同构，您可以通过组合两者的输出，过滤假突破和“假底部”：

- **验证底部构筑 / 启动预热**：单纯的均线走平可能是下跌中继。但在 `lifecycle_phase == "bottom_building"` 期间，如果 `wyckoff.event` 出现 `spring`（弹簧效应）或 `test`（缩量二次测试），说明此处有真实的需求吸收，这是高胜率的潜伏买点。
- **验证真突破**：在 `lifecycle_phase == "breakout"` 时，要求带有 `wyckoff.event == "sos"`（强势出现），可以过滤掉大量没有主力控盘的假突破。
- **提前规避见顶风险**：在 `lifecycle_phase == "main_wave"` (主升) 或 `"acceleration"` (加速) 阶段，传统均线死叉通常有严重滞后。如果高位频繁出现放量滞涨的 `upthrust` (上冲回落) 或 `sow` (弱势涌现)，系统会将这些供给风险写入 `risk_flags`，您可以通过识别这些标志，在趋势反转前提前止盈。

示例组合策略逻辑：
```python
# 获取预筛选的完整上下文
signals = pre_screen_and_scan(df)
phase = signals.get("lifecycle_phase")
wyckoff_event = signals.get("wyckoff", {}).get("event")

# 策略组合应用
if phase in ["底部构筑", "启动预热"] and wyckoff_event in ["spring", "test"]:
    print("强烈买入信号：均线底部蓄势，且威科夫量价确认主力吸筹")
elif phase == "突破确认" and wyckoff_event == "sos":
    print("确定性追高：带明显买盘强势(SOS)的真突破")
elif phase in ["主升浪", "加速冲顶"] and wyckoff_event in ["upthrust", "sow"]:
    print("风险预警：高位出现派发迹象，即使均线未破也应准备止盈")
```

#### 2.5.1 更多高胜率实战搭配用法

在 API 返回的 `signals` 包中，您还可以将威科夫量价与其他几个维度进行共振匹配，写出更立体的选股逻辑：

1. **顺大势逆小势 (Weekly Context + Wyckoff)**
   - **条件**：`weekly_context.weekly_trend_direction == "bullish"` (周线大趋势向上) + `lifecycle_phase == "底部构筑"` + `wyckoff.event == "spring" 或 "test"`。
   - **逻辑**：利用大级别（周线）的多头背景保护，精准狙击日线回调末端（底部构筑）的吸筹信号，盈亏比极佳。

2. **形态与内在共振 (Pattern + Wyckoff LPS)**
   - **条件**：`patterns` 列表中扫出 `VCP` 或 `Cup with Handle` (杯柄形态)，且近几日出现 `wyckoff.event == "lps"`。
   - **逻辑**：VCP 和杯柄形态的“柄”部理论上必须是缩量的。如果在柄部扫出威科夫的 `LPS`（最后支撑点：缩量回调不破前低），这证明了“外在形态收敛”与“内在主力洗盘”的完美共振，往往是大行情爆发的前夕。

3. **聪明的资金潜伏 (Relative Strength + Wyckoff)**
   - **条件**：`relative_strength.rs_20d > 0` (近期走势强于大盘) + `lifecycle_phase == "启动预热"` + `wyckoff.event == "sos"` 或 `"test"`。
   - **逻辑**：价格虽然还在横盘预热，但相对强度 (RS) 已经逆势走高，并且底层成交量显示主力在不断要货（SOS）。这种量价背离和 RS 背离是发现大牛股的标志性特征。

4. **动态风控收紧 (Dynamic Stop Loss)**
   - **条件**：持仓阶段 `lifecycle_phase == "main_wave"`，突然监控到 `wyckoff.event == "sow"` (弱势涌现) 或 `"upthrust"` (上冲回落)。
   - **逻辑**：突破传统的均线死叉止损。主力一旦在主升浪高位暴露出派发意图（放量滞涨或跌破关键位），系统会在 `risk_flags` 中预警，您可以据此立即收紧止损（例如上调至该威科夫风险 K 线的最低点），避免利润回撤。

---


## 3. 右侧状态接口：`detect_right_side_state`

该接口只判断做多右侧状态机，不输出买入建议。它适合用在单标的收盘后确认、批量右侧机会扫描，以及与 `StrongStockScanner` 并列参考。

### 3.1 导入方式

```python
from tradingpatterns import detect_right_side_state, RightSideState
```

### 3.2 函数签名

```python
def detect_right_side_state(
    df: pd.DataFrame,
    weekly_mode: str = "balanced",
) -> dict
```

| 参数 | 类型 | 说明 |
| --- | --- | --- |
| `df` | `DataFrame` | 按时间升序的 OHLCV 数据，至少需要 60 根 K 线，需包含 `open/high/low/close/volume`。 |
| `weekly_mode` | `str` | 周线筛选模式：`early` 不过滤周线；`balanced` 排除周线冲突；`strict` 只保留周线同步。 |

### 3.3 返回字段

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `state` | `str` | 当前右侧状态：`BASE`、`CANDIDATE`、`RIGHT_CONFIRMED`、`RIGHT_ACTIVE`、`RIGHT_EXTENDED`、`FAILED`。 |
| `event` | `str` | 本次扫描观察到的新事件，如 `RIGHT_ENTERED`、`RIGHT_CONTINUING`、`RIGHT_FAILED`。 |
| `entered_right_side` | `bool` | 当前最后一根 K 线是否刚完成日线右侧确认。 |
| `in_right_side` / `is_right_side` | `bool` | 当前是否处于已确认右侧趋势中。 |
| `is_tradeable` | `bool` | 按 `weekly_mode` 过滤后的可交易标记。 |
| `confidence_score` | `int` | 日线右侧置信度，范围 0-100。 |
| `signal_date` / `breakout_date` | `str` | 确认日与首次突破日；双收盘确认时二者可能不同。 |
| `breakout_level` / `invalidation_level` | `float` | 突破位与右侧失效位。 |
| `weekly_sync_state` | `str` | 已完成周线同步状态：`WEEKLY_ALIGNED`、`WEEKLY_NEUTRAL`、`WEEKLY_CONFLICT`。 |
| `weekly_reference_date` | `str` | 本次判定实际使用的已完成周线日期。周内未完成时使用上一完整周。 |
| `weekly_close` / `weekly_ema10` / `weekly_breakout_level` | `float` | 周线参考价格与指标。 |
| `reasons` / `warnings` | `list[str]` | 日线判定原因和风险提示。 |
| `weekly_reasons` / `weekly_warnings` | `list[str]` | 周线同步判定原因和数据提示。 |

`entered_right_side` 始终表示原始日线事件；`is_tradeable` 才会额外考虑周线过滤。例如 `weekly_mode="balanced"` 时，日线刚确认但周线为 `WEEKLY_CONFLICT`，会输出 `entered_right_side=True` 且 `is_tradeable=False`。

### 3.4 单只标的示例

```python
from tradingpatterns import RightSideState, detect_right_side_state
from kdata import get_ohlc

symbol = "sh.600519"
start_date = "2024-01-01"
end_date = "2026-07-17"

df = get_ohlc(symbol, start_date, end_date)
result = detect_right_side_state(df, weekly_mode="balanced")

if result["is_tradeable"]:
    print(f"右侧可交易: {result['confidence_score']}")
    print(f"突破位={result['breakout_level']} 失效位={result['invalidation_level']}")
elif result["state"] == RightSideState.CANDIDATE.value:
    print("候选右侧，等待突破确认")
```

### 3.5 与 `StrongStockScanner` 并列参考

```python
from tradingpatterns import (
    StrongStockPhase,
    StrongStockScanner,
    detect_right_side_state,
)
from kdata import get_ohlc

df = get_ohlc("sh.600519", "2024-01-01", "2026-07-17")

rs = detect_right_side_state(df, weekly_mode="balanced")
sr = StrongStockScanner().scan(df)

if rs["is_tradeable"] and sr.current_phase in (
    StrongStockPhase.BREAKOUT,
    StrongStockPhase.MAIN_WAVE,
):
    print("右侧信号成立，且生命周期处于突破/主升阶段")
elif sr.current_phase == StrongStockPhase.ACCELERATION:
    print("已进入加速阶段，注意追高和背离风险")
```

### 3.6 批量右侧报告

仓库提供独立演示脚本：

```bash
python demos/04_right_side_demo.py data/etf.yaml \
  --workers 8 \
  --start 2024-01-01 \
  --end 2026-07-18 \
  --limit 20 \
  --output output/right_side_report.md
```

脚本生成的 Markdown 报告写入全部扫描成功的标的；控制台默认显示 `in_right_side=True` 的右侧机会，以及 `BOTTOM_MATURE`、`STARTUP_PREHEAT` 且 `maturity_score >= 75` 的核心筑底标的；加 `--no-show-bottom` 只显示右侧机会，`--all` 可显示全部状态。`BOTTOM_BUILDING` 和低成熟度筑底保留在 Markdown 报告中。报告的“状态”列使用综合状态接口的唯一主状态，“可交易”列使用更严格的 `is_tradeable`。内置合成场景可用 `python demos/04_right_side_demo.py --synthetic` 运行。

### 3.7 快速开始

```python
from tradingpatterns import detect_right_side_state
from kdata import get_ohlc

df = get_ohlc("sh.600519", "2024-01-01", "2026-07-17")
result = detect_right_side_state(df, weekly_mode="balanced")

print(result["state"])
print(result["entered_right_side"])
print(result["is_tradeable"])
```

### 3.8 状态展示对应关系

API 的 `result["state"]` 保持英文枚举，方便程序判断；控制台和 Markdown 报告使用中文展示。

| API 状态 | 展示中文 | 含义 |
| --- | --- | --- |
| `BASE` | 基础观察 | 尚未形成有效右侧结构 |
| `CANDIDATE` | 右侧候选 | 已有止跌/反弹证据，等待结构突破 |
| `RIGHT_CONFIRMED` | 右侧确认 | 当前 K 线刚完成右侧确认 |
| `RIGHT_ACTIVE` | 右侧延续 | 右侧趋势仍有效 |
| `RIGHT_EXTENDED` | 右侧过度延伸 | 趋势有效但偏离较大，追高风险上升 |
| `FAILED` | 右侧失效 | 候选或已确认结构失效 |

### 3.9 使用说明

- `right_side_report.md` 写入全部扫描成功的标的，便于复盘和排查。
- 控制台默认输出右侧机会，以及 `BOTTOM_MATURE`、`STARTUP_PREHEAT` 且 `maturity_score >= 75` 的核心筑底标的；`--no-show-bottom` 只显示右侧机会，`--all` 输出全部状态。
- 报告中的“可交易”列使用更严格的 `is_tradeable`，表示本根右侧刚确认且通过 `weekly_mode` 周线过滤。
- 报告只显示一个主状态；完整筑底和右侧上下文可通过 `detect_side_state()` 读取。
- 右侧状态机判定规则（候选 → 确认 → 延续 → 延伸 → 失效）与周线同步过滤逻辑，详见上文第 3.1–3.8 节；筑底状态机详见第 4 节，综合主状态映射详见第 5 节。

---

## 4. 筑底跟踪接口：`detect_bottom_tracking_state`

该接口跟踪日线筑底结构的状态、成熟度、支撑压力和失效条件。它只描述左侧筑底背景，不会把筑底成熟直接转换为右侧确认或买入信号。

### 4.1 导入方式

```python
from tradingpatterns import (
    BottomTrackingState,
    detect_bottom_tracking_state,
)
```

### 4.2 函数签名

```python
def detect_bottom_tracking_state(
    df: pd.DataFrame,
    weekly_mode: str = "balanced",
) -> dict
```

| 参数 | 类型 | 说明 |
| --- | --- | --- |
| `df` | `DataFrame` | 按时间升序排列的日线 OHLCV 数据，必须包含 `open`、`high`、`low`、`close`、`volume`，至少 60 根 K 线。 |
| `weekly_mode` | `str` | 取值为 `early`、`balanced` 或 `strict`。目前用于保持与右侧接口一致的参数校验；周线只作为输出背景，不作为筑底硬过滤条件。 |

### 4.3 返回字段

接口返回最后一根已完成日线的 `dict`。`state` 和 `event` 使用英文枚举，展示层可自行转换为中文。

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `state` | `str` | `DECLINING`、`BOTTOM_WATCH`、`BOTTOM_BUILDING`、`BOTTOM_MATURE`、`STARTUP_PREHEAT` 或 `BOTTOM_FAILED`。 |
| `event` | `str` | 最近一根 K 线发生的新事件，如 `BOTTOM_STARTED`、`BOTTOM_CONFIRMED`、`BOTTOM_MATURED`、`STARTUP_STARTED`、`BOTTOM_FAILED`、`NONE`。 |
| `in_bottom_tracking` | `bool` | 是否处于底部观察、筑底中、成熟或启动预热状态。 |
| `bottom_mature` | `bool` | 是否达到 `BOTTOM_MATURE` 或 `STARTUP_PREHEAT`。 |
| `ready_for_right_side` | `bool` | 是否可作为右侧候选背景；不代表已经右侧确认。 |
| `bottom_score` / `maturity_score` | `int` | 当前筑底强度和成熟度评分，范围 0-100。 |
| `anchor_low` / `anchor_low_date` | `float` / `str` | 底部锚点价格及日期。 |
| `box_low` / `box_high` | `float` | 当前底部箱体下沿和上沿。 |
| `support_level` / `resistance_level` | `float` | 当前支撑位和右侧突破前参考压力位。 |
| `invalidation_level` | `float` | 筑底结构失效价格。 |
| `base_duration_bars` / `bars_since_low` | `int` | 当前底部结构持续 K 线数及距锚点低点的 K 线数。 |
| `range_width_pct` | `float` | 底部箱体宽度比例。 |
| `atr_contracting` / `volume_dry_up` | `bool` | ATR 是否收敛、成交量是否缩减。 |
| `higher_low_count` | `int` | 已观察到的更高低点数量。 |
| `startup_preheat` | `bool` | 是否处于启动预热状态。 |
| `weekly_bottom_context` / `weekly_reference_date` | `str` | 已完成周线的筑底背景及实际参考日期。 |
| `reasons` / `warnings` | `list[str]` | 判定依据和风险提示。 |

`BOTTOM_FAILED` 只在触发失效的当日保留；下一根 K 线重置为 `DECLINING`，以便后续重新观察新的底部结构。

### 4.4 调用示例

```python
from tradingpatterns import detect_bottom_tracking_state
from kdata import get_ohlc

df = get_ohlc("sh.600519", "2024-01-01", "2026-07-17")
result = detect_bottom_tracking_state(df, weekly_mode="balanced")

print(result["state"], result["bottom_score"])
print(result["support_level"], result["resistance_level"])

if result["ready_for_right_side"]:
    print("筑底结构可作为右侧候选背景，仍需右侧接口确认")
```

与右侧接口组合使用时：

```python
from tradingpatterns import detect_bottom_tracking_state, detect_right_side_state

bottom = detect_bottom_tracking_state(df)
right = detect_right_side_state(df)

if bottom["ready_for_right_side"] and right["entered_right_side"]:
    print("筑底背景和右侧确认同时成立")
```

筑底状态机（DECLINING → BOTTOM_WATCH → BOTTOM_BUILDING → BOTTOM_MATURE → STARTUP_PREHEAT → BOTTOM_FAILED）、成熟度评分与失效规则详见上文第 4.1–4.3 节；右侧状态机详见第 3 节，综合主状态映射详见第 5 节。

---

## 5. 综合状态接口：`detect_side_state`

该接口将筑底与右侧判定合并为唯一主状态，适用于展示、扫描和策略入口。右侧 `CANDIDATE`、确认、延续、延伸或失效优先；右侧为 `BASE` 时才返回筑底状态。

```python
from tradingpatterns import SideState, detect_side_state

result = detect_side_state(df, weekly_mode="balanced")

print(result["state"], result["state_source"])
print(result["right_side_plan"], result["bottom_observation"])
```

| 字段 | 说明 |
| --- | --- |
| `state` / `event` | 唯一主状态及最新事件。状态属于 `SideState` 枚举。 |
| `state_source` | `right_side` 或 `bottom_tracking`。 |
| `state_score` | 右侧主状态时为右侧置信度；筑底主状态时为筑底成熟度。 |
| `right_side_plan` | `WAIT_BREAKOUT`、`HOLD_AND_TRACK`、`AVOID_CHASING`、`STAND_ASIDE` 或 `NONE`。 |
| `bottom_observation` | `RIGHT_SIDE_ACTIVE`、`BOTTOM_READY`、`BOTTOM_TRACKING`、`BOTTOM_FAILED` 或 `NO_BOTTOM_SETUP`。 |
| `entered_right_side` / `in_right_side` / `is_tradeable` | 原始右侧交易语义。 |
| `right_side` / `bottom_tracking` | 完整子接口结果，用于解释、风控和回测。 |

`right_side_plan` 和 `bottom_observation` 是英文枚举；展示层可映射为中文。它们不构成第二个状态，主状态始终只看 `state`。

| 右侧状态 / 背景 | `right_side_plan` |
| --- | --- |
| `CANDIDATE`，或筑底已就绪但尚未右侧确认 | `WAIT_BREAKOUT` |
| `RIGHT_CONFIRMED` / `RIGHT_ACTIVE` | `HOLD_AND_TRACK` |
| `RIGHT_EXTENDED` | `AVOID_CHASING` |
| `FAILED` | `STAND_ASIDE` |
| 其他 | `NONE` |

| 当前上下文 | `bottom_observation` |
| --- | --- |
| 已处于右侧趋势 | `RIGHT_SIDE_ACTIVE` |
| `BOTTOM_BUILDING` / `BOTTOM_MATURE` / `STARTUP_PREHEAT` | `BOTTOM_READY` |
| `BOTTOM_WATCH` | `BOTTOM_TRACKING` |
| `BOTTOM_FAILED` | `BOTTOM_FAILED` |
| 其他 | `NO_BOTTOM_SETUP` |

---

## 6. 预筛选调用场景示例

### 6.1 场景 A：强制诊断单只股票（无论是否及格）

```python
# 硬条件通过后，还会用 total_score（日线预筛选 0~10 排序分）与 min_score 比较
result = pre_screen_and_scan(df, symbol="sh.600519", min_score=0.0)

if not result["pre_screen_passed"]:
    print(f"该标的被拦截。原因：{result['rejection_reason']}")
    print(f"预筛选排序分 {result['total_score']}，明细 {result.get('unified_breakdown')}")
    print(f"分析摘要：{result['pre_screen_summary']}")
```

### 6.2 场景 B：批量选股流水线

```python
results = pre_screen_and_scan(jobs, min_score=6.0)

# 仅保留通过预筛选且周线向好的机会
candidates = [
    r for r in results 
    if r["pre_screen_passed"] and r["weekly_context"]["weekly_trend_direction"] == "bullish"
]
```

---

## 7. 网格交易接口

网格模块提供两个公开 Python API：

- `build_grid_plan()`：根据截至当前的日线数据生成单标的网格计划。
- `simulate_grid_strategy()`：使用收盘确认、下一交易日开盘成交规则进行轻量级事件驱动回测。

两个接口均只计算和返回数据，不连接券商、不提交订单。返回值由 Python 基础类型组成，可直接使用 `json.dumps()` 序列化。

### 7.1 导入方式

```python
from tradingpatterns import (
    GRID_STATE_ACTIVE,
    build_grid_plan,
    evaluate_strict_grid_candidate,
    get_etf_optimal_grid_params,
    ETF_WEEKLY_OPTIMAL_PARAMS,
    simulate_grid_strategy,
)
```

### 7.2 输入数据

`df` 为按时间排列的日线 `pandas.DataFrame`：

| 字段 | 必需 | 说明 |
| --- | --- | --- |
| `open` | 是 | 开盘价，必须大于 0 |
| `high` | 是 | 最高价，必须大于 0 |
| `low` | 是 | 最低价，必须大于 0 |
| `close` | 是 | 收盘价，必须大于 0 |
| `volume` | 是 | 成交量，必须大于等于 0 |
| `amount` | 否 | 成交额，存在时优先用于流动性判断 |
| `turnover` | 否 | `amount` 不存在时使用 |

索引推荐使用 `DatetimeIndex`；也可以提供 `date` 或 `datetime` 列。字段名不区分大小写，接口会按索引升序处理数据。成交额缺失时使用 `close * volume`。

### 7.3 计划接口：`build_grid_plan`

```python
def build_grid_plan(
    df: pd.DataFrame,
    symbol: str | None = None,
    capital: float = 100000.0,
    lookback: int | None = None,
    grid_count: int = 8,
    min_step_pct: float = 0.008,
    max_step_pct: float = 0.035,
    atr_period: int = 14,
    atr_multiplier: float = 0.8,
    base_position_pct: float = 0.30,
    max_position_pct: float = 0.80,
    fee_rate: float = 0.0003,
    slippage_rate: float = 0.0005,
    weekly_mode: str = "balanced",
    lot_size: int = 100,
    min_bars: int | None = None,
    min_avg_turnover: float = 0.0,
    support: float | None = None,
    resistance: float | None = None,
    invalidation_level: float | None = None,
    breakdown_buffer_pct: float = 0.015,
    breakout_buffer_pct: float = 0.015,
    max_grid_age_days: int = 40,
    atr_reset_threshold_pct: float = 0.50,
    max_consecutive_buy_levels: int = 4,
    min_effective_grid_count: int = 4,
    expand_range: bool = False,
    allocation_style: str = "equal",
    weekly: bool = False,
) -> dict
```

`lookback` 与 `min_bars` 默认为 `None`：未显式传入时按周期自适应——
日线默认 `min_bars=120` / `lookback=60`，周线（`weekly=True`）默认 `min_bars=6` / `lookback=6`。
显式传参会覆盖自适应默认值（调用方已知周期时推荐直接传 `weekly=` 并省略具体阈值）。


主要参数：

| 参数 | 说明 |
| --- | --- |
| `capital` | 计划使用的账户资金，必须大于 0 |
| `lookback` | 估计区间使用的最近交易日数量 |
| `grid_count` | 上下边界之间的等比网格数量 |
| `min_step_pct` / `max_step_pct` | ATR 网格间距的最小值和最大值 |
| `base_position_pct` | 初始底仓资金比例；价格接近下边界时自动降低 |
| `max_position_pct` | 底仓与网格仓合计的最大资金比例 |
| `fee_rate` / `slippage_rate` | 单边费率和单边滑点率 |
| `lot_size` | 最小交易数量单位，A 股和 ETF 通常为 100 |
| `min_avg_turnover` | 最近 20 日最低平均成交额；`0` 表示不启用该过滤 |
| `support` / `resistance` | 可选的外部支撑位和阻力位 |
| `invalidation_level` | 可选的外部失效位；省略时按下边界和跌破缓冲计算 |
| `expand_range` | 是否允许根据大步长强制向外双向扩展震荡区间上下限 |
| `allocation_style` | 资金分配模式，默认 `equal`（等额），`pyramid`（金字塔式按 1:2:3...加权） |

> **费率默认值口径**：上表 `fee_rate=0.0003` / `slippage_rate=0.0005` 是 `build_grid_plan` **函数自身的保守裸默认**。生产链路（单标的严选 `evaluate_strict_grid_candidate` 与组合层 `CN_ETF` Profile）统一使用**万一/万二**（`0.0001`/`0.0002`，见 §7.4），§8.2 净收益下限即按此运营费率测算。

返回值顶层结构：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `symbol` / `analysis_date` | `str \| None` | 标的和实际分析日期 |
| `state` | `str` | 网格状态枚举 |
| `state_zh` | `str` | 网格状态的中文本地化描述 |
| `is_grid_tradeable` | `bool` | 当前是否可以建立新网格 |
| `reason` | `str` | 当前状态的主要原因 |
| `close` / `capital` | `float` | 当前收盘价和计划资金 |
| `bounds` | `dict` | 上下边界、失效位、跌破位及区间来源 |
| `grid` | `dict` | 等比网格线、买卖网格、单格毛收益和净收益 |
| `position` | `dict` | 底仓、网格仓、保留现金及交易单位 |
| `orders` | `list[dict]` | 可执行的初始网格买入计划 |
| `next_triggers` | `dict` | 距当前价格最近的下一买入和卖出触发位 |
| `risk_flags` | `list[str]` | 机器可读风险标记 |
| `context` | `dict` | ATR、成交额、区间位置等计算上下文 |

状态枚举：

| 状态 | 含义 |
| --- | --- |
| `NO_SETUP` | 数据、流动性或波动率不足，无法生成计划 |
| `WAIT_RANGE` | 区间或价格位置暂不适合开启网格 |
| `GRID_ACTIVE` | 网格有效，可以按计划跟踪 |
| `PAUSED_TREND_UP` | 向上突破，暂停新增网格买入 |
| `FAILED_BREAKDOWN` | 跌破区间或失效位，需要退出 |
| `RESET_REQUIRED` | 网格超期或波动结构显著变化，需要重算 |

缺失 OHLCV 字段、有效行情不足等数据问题返回 `state="NO_SETUP"`，原因写入 `risk_flags`。资金、费率、仓位比例、网格数等调用参数非法时抛出 `ValueError`；`df` 不是 `DataFrame` 时抛出 `TypeError`。

### 7.4 严选评估接口：`evaluate_strict_grid_candidate`

一站式评估标的否符合开网格的严苛风控标准。集成网格计划生成、趋势形态识别（剔除 `FAILED` 破位与 `RIGHT_EXTENDED` 高位博傻）及收益率、区间位置和成交额过滤。

```python
def evaluate_strict_grid_candidate(
    df: pd.DataFrame,
    symbol: str,
    capital: float,
    grid_count: int,
    name: str = "",
    base_position_pct: float = 0.30,
    max_position_pct: float = 0.80,
    fee_rate: float = 0.0001,
    slippage_rate: float = 0.0002,
    min_net_ret: float = 0.02,
    max_close_pos: float = 0.35,
    min_turnover: float = 50_000_000,
    is_weekly_grid: bool = False,
) -> dict
```

额外筛选参数：

| 参数 | 说明 | 默认值 |
| --- | --- | --- |
| `name` | 标的名称（用于内置最优参数匹配与波动率属性分类） | `""` |
| `min_net_ret` | 单格预期净收益率下限（**函数默认，会被下方 §8.2 门槛覆盖**） | `0.02` (2.0%) |
| `max_close_pos` | 当前价格处于历史区间的相对位置上限 | `0.35` (35%) |
| `min_turnover` | 近 20 日平均成交额下限（元） | `50_000_000` (5000万) |
| `is_weekly_grid` | 是否使用周线大网格模式（重采样周K线以大幅降低交易频率） | `False` |

> **`min_net_ret` 默认值说明**：上表 `min_net_ret=0.02`（2.0%）仅为函数形参默认值；在 `evaluate_strict_grid_candidate` 内部，实际强制门槛是设计手册 [§8.2 分类指导表](../grid/grid_trading_strategy_design.md#82-分类指导表工具默认口径) 的按类型/周期下限（宽基日线 3.8% / 周线 4.8%、行业主题日线 5.5% / 周线 9.5%，周线模式还会自动提升至 `0.03`）。**有效下限为 §8.2 值，而非 2.0%**——当文档门槛更高时以文档为准。

#### 模式参数动态覆盖与内置最优参数池（`ETF_WEEKLY_OPTIMAL_PARAMS`）：
当启用 `is_weekly_grid=True` 时，为适配长周期、大间距的周线网格特征，接口会执行自动分流与参数注入：
- **内置优选参数匹配（`ETF_WEEKLY_OPTIMAL_PARAMS`）**：系统内置了基于 2025 年度完整实证测算的 44 只经典 ETF 专属优选配置。当传入的 `symbol` 属于内置列表且为周线模式时，将**自动优先使用**其实证优化参数（包括专属的 `grid_count`、步长上下限 `min_step_pct / max_step_pct`、初始底仓与最高持仓上限 `base_position_pct / max_position_pct` 以及金字塔资金分配 `pyramid`）；当调用方传入常规默认层数 `grid_count=8` 时，也会自动被内置优选层数覆盖。
- **动态分类兜底机制**：未在内置库的标的则根据 `classify_etf_volatility(symbol, name)` 自动分流：
  - **宽基 (`BROAD`)**：默认 6 层，步长 `5.0%~8.0%`，底仓/持仓上限 `30%/80%`。
  - **行业主题 (`SECTOR`)**：默认 8 层，步长 `10.0%~15.0%`，底仓/持仓上限 `30%/80%`。
- **单格净收益率下限 (`min_net_ret`)**：以设计手册 [§8.2 分类指导表](../grid/grid_trading_strategy_design.md#82-分类指导表工具默认口径) 的「单格净收益下限」为硬性门槛——宽基日线 3.8% / 周线 4.8%、行业主题日线 5.5% / 周线 9.5%；用户传入的 `min_net_ret`（周线模式已自动提升至 `0.03`）作为兜底，文档门槛更高时以文档为准。
- **历史价格区间水位 (`max_close_pos`)**：自动提升至 `0.45` (45%)。处于底部 45% 以内的半山腰以下区域均判定为安全建仓期。

可以通过公开辅助接口 `get_etf_optimal_grid_params(symbol, is_weekly=True, default_name="")` 直接查询任一 ETF 标的优选参数配置字典。

返回值在 `build_grid_plan` 的基础结构上，扩展了以下字段：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `trend_state` | `str` | 趋势形态英文代码（如 `CANDIDATE`, `STARTUP_PREHEAT`, `FAILED` 等） |
| `trend_state_zh` | `str` | 趋势形态中文本地化描述（如 `右侧突破候选`, `启动预热`, `破位下跌` 等） |
| `is_strict_pass` | `bool` | 是否通过全套严苛风控过滤（适合开启网格） |
| `strict_fail_reasons` | `list[str]` | 未通过严选的原因列表 |

### 7.5 回测接口：`simulate_grid_strategy`

该接口的大部分参数与 `build_grid_plan()` 相同，但不接收固定的 `support`、`resistance` 和 `invalidation_level`；每次建立新网格时只使用当时可见的历史 K 线重新计算。

```python
result = simulate_grid_strategy(
    df,
    symbol="sh.510300",
    capital=100000,
    grid_count=8,
    fee_rate=0.0003,
    slippage_rate=0.0005,
    expand_range=False,
    allocation_style="equal",
    max_loss_pct=0.12,
)
```

`simulate_grid_strategy()` 额外支持：

| 参数 | 说明 |
| --- | --- |
| `expand_range` | 与 `build_grid_plan()` 同口径，允许回测按大步长向外扩展网格区间 |
| `allocation_style` | 与 `build_grid_plan()` 同口径，支持 `equal` / `pyramid`；回测会按买入价位复用对应委托金额 |
| `weekly` | 与 `build_grid_plan()` 同口径，未传 `min_bars` / `lookback` 时按周线自适应（默认 `min_bars=6` / `lookback=6`） |
| `max_loss_pct` | 单标的账户权益亏损止损阈值，默认 `0.12`；传 `None` 可关闭 |

撮合顺序：

1. 执行上一交易日收盘后生成的订单，按今日开盘价加减滑点成交。
2. 使用今日收盘价计算现金、持仓和账户权益。
3. 检查最大亏损、跌破、突破、超期和 ATR 变化。
4. 未触发风控时再生成网格买卖信号。
5. 没有活动网格时，使用截至今日的数据尝试建立新网格。

返回值：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `symbol` | `str \| None` | 标的代码 |
| `summary` | `dict` | 收益、年化收益、最大回撤、夏普、成交数、网格往返数、亏损止损次数及基准收益 |
| `trades` | `list[dict]` | 按实际成交日记录的买卖明细 |
| `lots` | `list[dict]` | 每个网格 lot 的买卖配对及净收益 |
| `daily_equity` | `list[dict]` | 每日现金、持仓和收盘权益 |
| `grid_resets` | `list[dict]` | 跌破、突破、超期、ATR 重置和 `STOP_LOSS` 事件 |
| `plans` | `list[dict]` | 回测期间实际建立过的网格计划 |

`summary.return_pct`、`annual_return_pct`、`max_drawdown_pct` 和 `buy_and_hold_return_pct` 的单位均为百分数。例如 `4.2` 表示 `4.2%`；`max_position_pct` 使用 0 到 1 的比例。

### 7.6 完整调用示例

```python
import json

from kdata import get_ohlc
from tradingpatterns import build_grid_plan, evaluate_strict_grid_candidate, simulate_grid_strategy

df = get_ohlc("sh.510300", "2024-01-01", "2026-07-24")

plan = build_grid_plan(
    df,
    symbol="sh.510300",
    capital=100000,
    min_avg_turnover=100_000_000,
)

if plan["is_grid_tradeable"]:
    print("下一买入位:", plan["next_triggers"]["buy"])
    print("下一卖出位:", plan["next_triggers"]["sell"])
else:
    print(plan["state"], plan["reason"], plan["risk_flags"])

backtest = simulate_grid_strategy(
    df,
    symbol="sh.510300",
    capital=100000,
)
print(json.dumps(backtest["summary"], ensure_ascii=False, indent=2))
```

离线可复现 demo：

```bash
python demos/08_grid_trading_advisor.py
python demos/08_grid_trading_advisor.py --json
```

网格策略的假设、状态机（NO_SETUP / WAIT_RANGE / GRID_ACTIVE / PAUSED_TREND_UP / FAILED_BREAKDOWN / RESET_REQUIRED / STOP_LOSS）与风控细节，详见上文第 7.1–7.5 节；多标的组合网格与调仓换仓顾问详见第 8 节。

---

## 8. 多标的组合网格与调仓换仓顾问 (`build_etf_grid_advice`)

针对大容量 ETF 池（如 A 股 44 只 ETF 候选池）在固定持仓数量上限（如 `max_active_symbols = 10`）下的组合网格交易、账户资金统筹与调仓换仓调度，提供组合层面的对外接口：

### 8.1 导入方式

```python
from tradingpatterns import build_etf_grid_advice, compute_candidate_score
```

### 8.2 核心职责与 8 步调仓换仓
- **0~100 综合候选评分 (`compute_candidate_score`)**：根据 ETF 的**区间位置分**（满分 35 分，`[0.15, 0.50]` 获满分）、**单格净收益分**（满分 35 分）和**趋势安全分**（满分 30 分）独立打分。
- **合格候补队列 (`waiting_queue`)**：在活跃持仓达到 `max_active_symbols` 时，将未入围但合规优质的 ETF 依总分降序排列，对外输出第一顺位梯队。
- **8 步组合调度与分差调仓**：每日遵循 **“硬风控 > 既有持仓风控 > 既有网格维护 > 新候选开仓 > 资金闲置”** 的执行顺序。若满仓且持仓满 20 个交易日 (`min_holding_days`)、候补池第一名分差领先超过 15.0 分 (`min_switch_score_gap`)，自动触发旧标的调出与新候选接力调入。
- **并行候选评估 (`max_workers`)**：支持通过可选参数 `max_workers=N` 对输入的多只 ETF 进行 K 线重采样、网格试算与右侧趋势识别的线程池并发，显著加速多标的批量测评与回测。

### 8.3 核心返回值解析
调用 `build_etf_grid_advice(...)` 返回的报告结构中，包含以下核心组合调仓数据：
- `rotations`: 本次计算周期中发生的调仓换仓列表，每条记录说明 `date`, `rotated_out`, `rotated_out_score`, `rotated_in`, `rotated_in_score`, `reason`。
- `waiting_queue`: 合格候选项等待队列清单，附带 `symbol`, `name`, `score`。
- `account_state.rotation_history`: 纸上模拟账户自成立以来所有历史调仓换仓流水。

### 8.4 详细文档与调用示例

组合网格接口、0–100 候选评分（区间位置 / 单格净收益 / 趋势安全三项）、8 步调仓换仓与回测逻辑已在第 8.1–8.3 节完整说明；下方为可运行 CLI Demo 与动态参数配置：

- **可运行 CLI Demo 与动态参数配置**:
  ```bash
  # 基础运行示例
  uv run demos/10_portfolio_grid_advisor_demo.py --top-n 0 --max-active 10

  # 自定义网格参数与持仓策略（不建议网格太密以控制交易换手频率）
  uv run demos/10_portfolio_grid_advisor_demo.py \
    -s 2024-01-01 -e 2025-01-01 \
    -g 6 \
    --min-step 0.04 \
    --max-step 0.08 \
    --atr-period 14 \
    --atr-multiplier 1.5 \
    -t weekly \
    --max-weight 0.35
  ```
  **新增 CLI 选项释义与策略建议**：
  - `-g / --grid-count <int>`：自定义网格层数（例如 8 代表 8 层）。推荐在 4~8 层之间。
  - `--min-step <float>`：自定义单格间距下限（例如 0.025 代表 2.5%）。**不建议网格过于密集**，否则会导致买卖触发过于频繁、增加滑点及手续费磨损。
  - `--max-step <float>`：自定义单格间距上限（例如 0.06 代表 6.0%）。
  - `--atr-period <int>`：自定义 ATR 计算周期（默认 14），动态自适应调整间距。
  - `--atr-multiplier <float>`：自定义 ATR 倍数系数。倍数越大间距越宽，操作频率相应降低。
  - `-t / --timeframe <str>`：选择网格计算周期：**默认是周（`weekly`），不是日（`daily`）**！策略默认将日 K 线按周五收盘聚合成周 K 线进行网格步长与高低点推导，能够极大平滑短期行情扰动，有效保持策略低换手与长周期稳健性。
  - `--max-weight <float>`：自定义单个 ETF 的最大允许持仓资金占比（如 0.35 代表 35%）。通过加大权重配置即可改善总体账户资金利用率，无需依赖超密集网格。
