Metadata-Version: 2.4
Name: awesome-trading-strategy
Version: 0.4.0
Summary: 量化交易策略 CLI：信号生成、回测、走向前验证（DSR/PBO）、模拟盘与组合资金管理
Author: patricLee
License-Expression: MIT
Keywords: trading,backtesting,quantitative-finance,walk-forward,overfitting,paper-trading,crypto
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: ccxt>=4.5.44
Requires-Dist: jinja2>=3.1
Requires-Dist: pyyaml>=6.0
Requires-Dist: rich>=13.0
Provides-Extra: parquet
Requires-Dist: pyarrow>=15.0; extra == "parquet"
Dynamic: license-file

# Awesome Trading Strategy

一个强大的命令行交易策略管理工具，支持策略创建、信号生成、风险控制和交易复盘。

## 特性

- **策略管理** - 创建、查看、更新交易策略，支持状态流转和参数版本控制
- **多引擎信号生成** - 内置 MA 交叉、海龟、RSI、MACD、布林带、组合策略等引擎
- **持仓感知策略** - 结合实际仓位智能判断，支持强平警告、减仓建议
- **风控系统** - 全局开关、黑名单、仓位限制、日频限制多层防护
- **交易日志** - 记录交易并支持情绪标签和执行评分
- **复盘报告** - 自动生成周报/月报，支持参数优化建议和多种格式导出
- **样本外验证** - 走向前分析（walk-forward）+ 过拟合诊断（DSR / PBO）
- **模拟盘** - paper trading，与回测逐字段等价，补上信号到持仓的最后一段
- **组合资金模式** - 多标的可选独立子账户或共享资金池，含逐标的归因
- **灵活输出** - 支持 JSON、Markdown、Rich 表格多种输出格式
- **无信号重绘** - 默认只使用已收盘 K 线，同一根 bar 内信号不会来回翻转

## 安装

### 环境要求

- Python >= 3.12
- 推荐 [uv](https://docs.astral.sh/uv/) 管理环境

### 开发模式

```bash
uv sync
uv run python -m scripts.cli --help
```

### 安装模式

```bash
pip install awesome-trading-strategy
awesome-trading-strategy init
awesome-trading-strategy --help
```

## 快速开始

### 1. 生成交易信号

```bash
# 使用示例数据生成信号
uv run python -m scripts.cli generate-signals --symbols "BTC/USDT" --strategy ma_crossover --data-file examples/ma_crossover_long.yaml

# 持仓感知策略（结合实际仓位）
uv run python -m scripts.cli generate-signals --symbols "BTC/USDT" --strategy ma_position_aware --data-file examples/position_aware_example.yaml
```

### 2. 创建策略

```bash
uv run python -m scripts.cli create-strategy --name "BTC趋势跟踪" --type "趋势" --markets "加密货币" --timeframes "4H,1D" --entry "价格突破20日高点，成交量放大1.5倍" --stop "放在突破K线低点下方"
```

### 3. 记录交易

```bash
uv run python -m scripts.cli record-trade --symbol "BTCUSDT" --direction "long" --entry-price "60000" --exit-price "65000" --strategy "BTC趋势跟踪" --size "0.1"
```

### 4. 生成复盘报告

```bash
uv run python -m scripts.cli --format markdown generate-review --type weekly --week this --with-suggestions
```

## 命令概览

| 命令 | 说明 |
|------|------|
| `create-strategy` | 创建新策略 |
| `get-strategy` | 查看策略详情 |
| `list-strategies` | 列出所有策略 |
| `update-strategy` | 更新策略状态或参数 |
| `generate-signals` | 生成交易信号 |
| `list-signals` | 查询历史信号 |
| `get-signal` | 查看信号详情 |
| `get-macro-state` | 查看风控状态 |
| `set-macro-state` | 更新风控设置 |
| `record-trade` | 记录交易 |
| `query-journals` | 查询交易日志 |
| `generate-review` | 生成周报/月报 |
| `export-review` | 导出复盘报告 |
| `sync-data` | 增量同步市场数据到本地库 |
| `data-status` | 查看本地数据覆盖范围与缺口 |
| `import-data` | 导入既有 YAML 到本地库 |
| `export-data` | 从本地库导出 YAML / Parquet |
| `backtest` | 在本地数据上回测策略 |
| `compare-strategies` | 同一数据与设置下并排比较多个策略 |
| `walk-forward` | 样本外验证 + 过拟合诊断（DSR / PBO） |
| `paper-init` | 建一个模拟账户 |
| `paper-tick` | 推进模拟账户到最新已收盘 bar |
| `paper-status` | 查看模拟账户状态 |
| `paper-orders` | 查询订单历史 |
| `paper-positions` | 查看当前持仓 |
| `paper-reset` | 清空模拟账户（需 `--yes`） |

## 内置策略引擎

| 引擎 | 说明 |
|------|------|
| `ma_crossover` | 双均线交叉趋势策略 |
| `ma_position_aware` | 持仓感知均线策略（支持强平警告） |
| `turtle` | 唐奇安通道突破策略 |
| `rsi` | RSI 超买超卖策略 |
| `macd` | MACD 交叉策略 |
| `bollinger` | 布林带突破策略 |
| `composite` | 技术信号 + 新闻过滤组合策略 |

## 数据格式

市场数据使用 YAML 格式：

```yaml
symbols:
  - symbol: BTC/USDT
    timeframe: 4h   # 可选但推荐，用于判断最后一根 K 线是否已收盘
    candles:
      - timestamp: "2026-03-23T10:00:00Z"
        open: 65000
        high: 66000
        low: 64500
        close: 65800
        volume: 1000
    position:  # 可选，用于持仓感知策略
      has_position: true
      side: long
      size: 0.5
      entry_price: 72000
      current_price: 69000
      leverage: 10
      liquidation_price: 66500
    news:  # 可选，用于组合策略
      - timestamp: "2026-03-23T09:00:00Z"
        title: "BTC ETF 获批"
        sentiment: very_positive
        impact: high
```

时间戳一律按 **UTC** 解释。没写时区的 ISO 字符串也当作 UTC，不会按本机时区换算。

## 本地市场数据库

行情数据存在 `data/market.db`（SQLite）。同步是**增量**的——只拉本地水位线之后的数据，重复运行幂等，不会每次全量重拉：

```bash
# 同步单个标的
uv run python -m scripts.cli sync-data --symbol BTC/USDT --timeframe 4h

# 同步 configs/trading.yaml 里配置的全部标的
uv run python -m scripts.cli sync-data --all

# 回填历史（可中断续传）
uv run python -m scripts.cli sync-data --symbol BTC/USDT --timeframe 1d --backfill-until 2024-01-01

# 查看覆盖范围与缺口
uv run python -m scripts.cli --format markdown data-status
```

生成信号时直接读库，不必再准备 YAML 文件：

```bash
uv run python -m scripts.cli generate-signals \
  --symbols "BTC/USDT" --strategy ma_crossover --from-store --timeframe 4h
```

`--from-store` 与 `--data-file` **互斥**——同时给出会报错而不是猜一个。两条路径产出的信号逐字段一致（有测试守着）。

持仓感知策略需要仓位信息，用 `--position-file` 单独传（K 线来自库、持仓来自账户
导出，两者来源不同是正常的）：

```bash
uv run python -m scripts.cli generate-signals \
  --symbols "BTC/USDT" --strategy ma_position_aware \
  --from-store --timeframe 4h --position-file positions.yaml
```

**导入导出**：

```bash
# 把既有 YAML 灌进库
uv run python -m scripts.cli import-data --data-file examples/btc_latest.yaml

# 导回 YAML，或导出 Parquet 供回测批量扫描
uv run python -m scripts.cli export-data --symbol BTC/USDT --timeframe 4h --output data/btc.yaml
uv run python -m scripts.cli export-data --symbol BTC/USDT --timeframe 4h --format parquet --output data/btc.parquet
```

Parquet 需要可选依赖：`uv add pyarrow`。未安装时会提示安装命令，不影响其他功能。

数据同步的几个设计要点：

- **有意重叠一根**——上次的最后一根可能是未收盘状态写入的，重叠拉取才能用最终值覆盖
- **水位线只记已收盘 bar**——否则那根 bar 会永远停在未收盘状态
- 校验不通过的 K 线**拒绝入库并上报**，不静默丢弃
- 同步后自动做缺口检测，结果可在 `data-status` 里看到
- 多标的同步时**单个失败不中断整批**

## 回测

```bash
# 回测单个策略（参数缺省时取 signal-params.yaml，与实盘信号同源）
uv run python -m scripts.cli backtest --strategy turtle --symbols BTC/USDT --timeframe 1d

# 指定区间、保存产物到 data/backtests/
uv run python -m scripts.cli backtest --strategy macd --symbols BTC/USDT --timeframe 1d \
  --start 2025-01-01 --save

# 同一份数据、同一套成本与仓位设置下并排比较
uv run python -m scripts.cli --format markdown compare-strategies \
  --strategies "ma_crossover,turtle,macd,bollinger" --symbols BTC/USDT --timeframe 1d
```

### 结果怎么读

真实 BTC 日线上的一次实测：

| 策略 | 净收益% | 毛利 | 成本 | 笔数 | 回撤% |
|---|---:|---:|---:|---:|---:|
| turtle | +5.46 | 574.4 | 130.8 | 28 | 6.13 |
| macd | +4.57 | 536.0 | 357.1 | 76 | 7.46 |
| ma_crossover | +1.34 | 192.7 | 269.6 | 58 | 7.55 |
| bollinger | −5.60 | −471.4 | 434.8 | 94 | 10.14 |

**成本单列不是装饰**：`ma_crossover` 的毛利 192.7 被 269.6 的成本吃成净亏，
只看收益率完全看不出这一点。交易越频繁成本占比越高——这是选策略时的一等信息。

### 几条刻意的设计

- **默认成交模型是 `next_open_with_slippage`**。用当根收盘价成交是隐性前视：
  收盘那一刻才知道收盘价，不可能以该价成交
- **每次结果都附「未经样本外验证」**。参数没在独立样本上检验过，收益可能来自过拟合。
  要做样本外验证，用下面的 `walk-forward`
- **样本不足时 Sharpe/Sortino/Calmar 返回 `null` 并说明理由**，而不是给一个
  看着精确却是噪声的数字
- **参数搜索不作为独立命令提供**。给了回测又给裸的搜索，很容易搜出一组「最优参数」
  并当真，而那多半只是过拟合。搜索只作为 `walk-forward` 的样本内阶段存在，
  结果永远和样本外表现、过拟合诊断一起出现（见下节）
- `--no-costs` 仅用于对照实验，结果会额外标注不可当作真实预期

### 引擎如何保证无前视

策略在第 `i` 根 bar 拿到的是一个**长度钉死的只读视图**：下标越界抛异常、
开区间切片返回空、负下标折回窗口内、迭代到第 `i` 根即止。不是靠约定，是靠结构。

配套有一条**随机漫步检验**作为常驻测试：在几何布朗运动数据上跑全部 7 个策略
（开启成本），断言净收益不显著为正。随机数据上任何技术策略的期望收益应约等于
负的成本——**如果回测在随机数据上赚钱，引擎一定有隐性前视**。

## 多标的的资金模式

跑多个标的时有一个常被忽略的问题：**这些标的是共用一份钱，还是各花各的？**

默认是 `isolated` —— 每个标的一个独立子账户，各自持有 `--equity`，互不占用
保证金。跑 3 个标的时组合期初是 `3 × equity`，而 BTC 的浮亏不会减少 ETH 能开
的仓位。

真实交易所不是这样的。一个统一账户里所有仓位共用一份保证金，BTC 亏钱会实实在在
地吃掉 ETH 的可用保证金。

```bash
# 各花各的（默认）：期初 = 2 × 10000
uv run python -m scripts.cli backtest --strategy turtle \
  --symbols "BTC/USDT,ETH/USDT" --timeframe 1d --equity 10000

# 共用一份：期初 = 10000，两个标的抢同一份保证金
uv run python -m scripts.cli backtest --strategy turtle \
  --symbols "BTC/USDT,ETH/USDT" --timeframe 1d --equity 10000 --capital-mode shared

# 组合级并发持仓上限（只在 shared 下有意义）
uv run python -m scripts.cli backtest --strategy turtle \
  --symbols "BTC/USDT,ETH/USDT" --timeframe 1d --capital-mode shared \
  --max-portfolio-positions 1
```

同一份数据、同一个策略，两种模式的实测对比：

| | isolated | shared |
|---|---:|---:|
| 组合期初 | 20000 | 10000 |
| 净收益 | +0.222% | +0.377% |
| **最大回撤** | **2.357%** | **4.665%** |
| 成交笔数 | 63 | 63 |

同样的交易，共享账户的回撤翻了一倍 —— 因为分母只有一半的钱。**独立模型系统性
高估资金效率**，这是它最容易误导人的地方。

`--capital-mode` 在 `backtest` / `compare-strategies` / `walk-forward` /
`paper-init` 上都可用。paper 的模式在 `paper-init` 时定死，之后不可改 —— 改了
重放出的历史账户会整个不同，等于篡改已发生的交易，要改就 `paper-reset`。

### 组合归因

多标的的结果会附带逐标的归因，回答三个不同的问题：

- **谁在赚钱** —— 贡献拆成已实现与未实现两段。靠浮盈撑着和已经落袋，风险完全不同
- **谁在烧钱** —— 成本按标的归属，同时给出毛利。毛利可观但被手续费吃光的标的，
  和本来就亏的标的是两回事，只看净额分不出来
- **谁占着资金** —— 资金占用份额（按持仓**时长**加权）。贡献 5% 却占了 60%
  保证金的标的，和贡献 3% 只占 10% 的，不是一回事

三条对账关系恒成立：贡献合计 = 组合净盈亏、成本合计 = 总成本、占用份额合计 = 1。

**回撤刻意不做分摊。** 各标的的回撤在时间上错开，组合回撤不是它们的线性和，
强行分摊会给出一个看着精确却无意义的数。这里报的是「组合最深那一刻各标的的
累计贡献」—— 那是可验证的事实。

### 已知局限

- **同一根 bar 上的优先权按标的名字典序** —— `BTC/USDT` 永远排在 `ETH/USDT`
  前面并因此先拿到保证金。顺序是任意的（不是按风险或信号强度），但必须确定，
  否则同一份数据两次回测结果会不同。按风险预算分配是组合层的下一步
- **强平仍是逐仓** —— 单个标的爆仓不会拖垮整个账户，但亏损从共享现金里扣，
  其他标的的可用保证金随之下降。**不模拟**组合级的全账户强平
- **不做跨标的净额结算** —— 多空同时持有按毛敞口占保证金，与多数交易所的逐仓
  模式一致
- **不做相关性与风险平价** —— 那需要先有共享资金池这个地基

### isolated 逐字段不变

这一层动的是回测引擎的核心记账。`isolated`（默认）下的输出与本功能引入之前
**逐字段相同**，包括 `run_id` —— `tests/unit/test_capital_mode_parity.py` 对
7 个内置策略 × 单标的/多标的共 14 组基线做常驻断言。默认配置的 `to_dict()` 里
根本不含 `capital_mode` 键，所以配置哈希不变，历史归档仍然对得上。

## 走向前验证与过拟合诊断

回测告诉你「这组参数在这段历史上赚了多少」，`walk-forward` 回答那个更重要的问题：
**这个结果有多少是真信号，多少是拟合噪声**。

```bash
# 样本内 300 根选参、样本外 100 根验证，滚动推进
uv run python -m scripts.cli --format markdown walk-forward \
  --strategy turtle --symbols BTC/USDT --timeframe 1d \
  --space '{"entry_period":[10,20,30],"exit_period":[5,10]}' \
  --is-span 300 --oos-span 100

# 锚定窗口（样本内起点固定，随窗口不断变长）
uv run python -m scripts.cli walk-forward --strategy ma_crossover \
  --symbols BTC/USDT --timeframe 1d --scheme anchored \
  --space '{"fast_period":{"start":5,"stop":20,"step":5}}' \
  --is-span 300 --oos-span 100

# 参数空间太大时改随机采样，种子固定即可复现
uv run python -m scripts.cli walk-forward --strategy macd \
  --symbols BTC/USDT --timeframe 1d --search random --samples 20 --seed 42 \
  --space '{"fast_period":{"start":6,"stop":18,"step":2},"slow_period":{"start":20,"stop":40,"step":2}}'
```

### 结果怎么读

真实 BTC 日线上的一次实测（turtle，6 组参数 × 6 个窗口）：

| 指标 | 值 | 含义 |
|---|---:|---|
| 样本内平均目标 | 2.90 | 选参时看到的表现 |
| **样本外平均目标** | **0.45** | 换到没见过的数据上还剩多少 |
| **IS→OOS 衰减** | **2.46** | 差额就是选参偏差的规模 |
| 紧缩 Sharpe (DSR) | 0.523 | 扣掉试验次数与分布形状后，收益为正的置信度 |
| 过拟合概率 (PBO) | 20.9% | 样本内的赢家在样本外掉进后半区的频率 |

同一次运行里最有信息量的是逐窗口明细：某个窗口选中了样本内最高的
`entry_period=10`（目标 4.93），它在样本外拿到 **−3.07**。这就是过拟合的样子——
不看样本外，你只会看到那个 4.93。

**参数稳定性**同样值得看：上面这次 `exit_period` 六个窗口全选 5，而 `entry_period`
在 10/20/30 之间摇摆。前者像是真的有效，后者更像在拟合各段的噪声。

### 两个诊断分别在说什么

- **DSR（紧缩 Sharpe）**——试的组数越多，它压得越狠。试 500 组总能碰到一组
  看起来很好的，这是选择性偏差不是技能。它同时把收益的偏度与峰度算进去：
  负偏 + 厚尾意味着 Sharpe 本身就更不可靠。
- **PBO（过拟合概率）**——不看收益高低，只看**排名能否迁移**。把样本切成 16 份、
  枚举 12870 种训练/测试划分，统计样本内的赢家有多大比例在样本外落进后半区。
  超过 50% 就说明所选参数很可能是过拟合的。

### 几条刻意的设计

- **搜索没有独立入口**。没有 `optimize` 子命令，`walk-forward` 是唯一入口，
  每次输出**强制包含** DSR 与 PBO，没有关闭开关。试验次数自动记账送进 DSR——
  搜得越多紧缩越狠，这是数学上的自动惩罚，不是一句警告
- **窗口边界是评估区间，warm-up 额外向前取**。否则一个 60 根的样本外窗口会被
  21 根 warm-up 吃掉三分之一，而且不同策略吃掉的比例还不一样，窗口之间就不可比了
- **样本外用收益率拼接，不用权益值**。每段都从初始权益起跑，直接首尾相接会在
  窗口边界产生阶跃，最大回撤被人为截断。拼收益率再复利还原，回撤跨窗口连续
- **不合格的窗口被跳过并列出**。某个窗口所有试验的目标值都未定义时（最常见是
  成交笔数不够，Sharpe 被闸掉），它不参与拼接，但会带着原因出现在报告里——
  静默跳过等于伪造样本外区间
- **试验预算是前置门禁**。超过 `--max-trials`（默认 500）直接拒绝运行并给出
  三条缩减建议，而不是跑到一半喊停
- **不合格 ≠ 零分**。目标值未定义的试验被排除出排序，而不是按 0 分参与竞争——
  后者会让「没交易过所以没有 Sharpe」的参数排在真实亏损的参数之前

### 已知的局限

- **DSR 假设各期收益独立**。实际收益有自相关，这会让它**偏乐观**
- **跨运行的试验次数不会合并**。跑十次 walk-forward 再挑最好的一次，这个选择性
  偏差 DSR 看不见。工具会记录「这个策略+数据你已经验证过 N 次」作为提示，
  但堵不住这个漏洞——窗口参数应当在看结果之前定死
- **窗口参数本身也是自由度**。反复调 `--is-span` 直到结果好看，就是在对验证方案
  过拟合

### 数据够不够

样本外验证很吃数据量。`--is-span 300 --oos-span 100` 至少需要 400 根 bar
才能切出一个窗口，还要额外的 warm-up 前置历史。本地 BTC 日线 1000 根大约能切
6 个窗口；不够时会明确报出需要多少、实际多少，可以回填历史或改用 4h 周期：

```bash
uv run python -m scripts.cli sync-data --symbol BTC/USDT --timeframe 1d --backfill-until 2020-01-01
```

运行记录落在 `data/validation/`（不入版本控制），用于统计历史验证次数。

## Paper trading（模拟盘）

回测告诉你「这套参数在历史上会怎样」，walk-forward 告诉你「这个结果有多可信」，
paper trading 补上最后一段：**信号到持仓之间**。它不承担真实风险，但走的是完整
链路——出信号、过风控、下单、撮合、结算资金费、检查强平、记账。

```bash
# 1. 建一个模拟账户
uv run python -m scripts.cli paper-init \
  --strategy turtle --symbols BTC/USDT --timeframe 1d --equity 10000

# 2. 拉行情，然后推进
uv run python -m scripts.cli sync-data --symbol BTC/USDT --timeframe 1d
uv run python -m scripts.cli --format markdown paper-tick

# 3. 看账户
uv run python -m scripts.cli --format markdown paper-status
uv run python -m scripts.cli --format markdown paper-positions
uv run python -m scripts.cli --format markdown paper-orders --state filled --limit 20
```

真实 BTC 日线上跑完 999 根 bar 的一次实测：

```
权益 10548.11（初始 10000）  +5.48%
现金 10327.70  浮盈 220.41  持仓 1  成交 27  强平 0
持仓: long 0.0159 @ 65143.04  浮盈 220.41
```

### 与回测**逐字段等价**

这是这一层最重要的设计，也是它区别于「另写一个模拟器」的地方。

若 paper 与回测是两套实现，「回测 +5.46%、实盘 -2%」这种结果就永远无法归因——
差异到底来自策略、来自成本假设、还是来自执行实现，无从判断。

所以 paper 账户的状态**不是**增量维护的：每次 tick 拿全部历史 bar 重放一遍，
而重放调用的就是回测引擎本身。等价性不靠维护，靠构造。
`tests/unit/test_paper_backtest_parity.py` 把这一点钉死成可执行的断言——
7 个内置策略，成交列表、权益曲线、绩效指标逐字段比对。

代价是每次 tick 是 O(n)。首次推进 999 根 bar 约 5 秒，此后每次 tick 只处理新
收盘的 bar，实测 285ms。

### 风控是 paper 相对回测多出来的一层

回测不过 `RiskFilter`，paper 过。这是两者唯一**有意**的差异：

- 全局开关、黑名单、仓位上限、日频上限都会真正拦下订单，被拦的单以
  `rejected` 状态留痕，带着是哪条检查拦的
- `current_positions` 不再靠手工 `set-macro-state` 维护，由账户的真实持仓数
  驱动；与手工值不一致时会**报出校正**（前值 → 新值），不静默改写

风控裁决是有副作用的（日频计数会累加），因此只在处理新 bar 时做一次并落盘；
重放时读取已落盘的裁决，不重新裁决——否则第二次 tick 就会因为「日频信号数超限」
把历史上本已放行的信号拒掉。

### 几条刻意的设计

- **只推进已收盘的 bar**。未收盘那根的 close 还在变，拿它成交等于用了当时并不
  存在的信息，也会让「重复 tick 幂等」不成立
- **重复 tick 幂等**。没有新的已收盘 bar 时不产生订单、不改账户，如实报告
  「没有新数据」而不是报错
- **状态存在独立的 `data/paper.db`**。`market.db` 是纯缓存，删了 `sync-data`
  就能长回来；账户状态删了就没了，两者不该混在一个文件里
- **库里只存不可重算的东西**：配置、水位线、风控裁决。持仓、现金、权益曲线
  一律由重放算出——存两份等于给自己留一个不一致的机会
- **订单 ID 确定性**。重放会重新产生订单，ID 用自增或 UUID 的话每次重放都变，
  落盘的订单历史就永远对不上
- **`paper-reset` 必须带 `--yes`**。它销毁的东西重放不回来

### paper **不模拟**什么

这些在每次输出里都会跟着结果一起出现，不是只写在文档里：

- **部分成交** —— 真实市场里大单会被拆开、按不同价格逐步成交
- **订单排队与撮合优先级**
- **交易所拒单、限频与维护窗口**
- **网络延迟与下单失败重试**

多标的时还有一条：各标的是**独立子账户**，互不占用保证金；真实交易所是共享的，
一个标的的浮亏会吃掉另一个的可用保证金。真正的组合层是后续变更。

**不要把 paper 的结果当作实盘预期。** 它和回测一样，是在一组明确假设下的推演，
只不过假设更接近真实了一点。

## 未收盘 K 线与信号重绘

交易所返回的最后一根 K 线是**进行中**的——它的收盘价随行情实时变动。若直接拿它计算指标，同一根 bar 内反复生成信号会得到**来回翻转**的方向。

因此本工具**默认剔除未收盘 K 线**，策略只看已收盘的数据：

```bash
# 默认行为：只用已收盘 K 线，bar 内信号稳定
uv run python -m scripts.cli generate-signals --symbols "BTC/USDT" --strategy ma_crossover --data-file data/btc_4h.yaml

# 盘中观察：保留进行中的 K 线，信号标记为临时、收盘前可能变化
uv run python -m scripts.cli generate-signals --symbols "BTC/USDT" --strategy ma_crossover --data-file data/btc_4h.yaml --include-unclosed
```

**K 线周期的确定顺序**：`--timeframe` 参数 > 数据文件里的 `timeframe` 字段 > 从时间戳间隔自动推断。三者都拿不到时不做过滤，并在 stderr 打印警告——此时可用 `--timeframe 4h` 显式指定。

`fetch_data.py` 抓取的数据会自动写入 `timeframe` 字段，无需手动指定。

## 项目结构

```
trading-strategy/
├── scripts/
│   ├── cli.py              # CLI 入口
│   ├── signal_engine.py    # 兼容门面（实现已迁至 core/，仅再导出）
│   ├── core/               # 内核：纯逻辑，可独立测试
│   │   ├── types.py        #   Signal / PositionContext / MarketData
│   │   ├── indicators/     #   8 个 O(n) 共享指标纯函数
│   │   ├── strategies/     #   7 个内置策略，每个一个模块
│   │   ├── registry.py     #   SignalEngine 注册表
│   │   ├── confidence.py   #   共享置信度启发式
│   │   ├── templates.py    #   信号提示语模板
│   │   ├── news.py         #   新闻情绪分析
│   │   ├── backtest/       #   事件驱动回测引擎（含共享资金池 portfolio_book）
│   │   ├── analytics/      #   绩效分析（收益/风险/风险调整/交易级/组合归因）
│   │   ├── costs/ sizing/  #   成本模型与仓位管理
│   │   ├── validation/     #   走向前验证层
│   │   │   ├── windows.py  #     IS/OOS 窗口切分（rolling / anchored）
│   │   │   ├── search.py   #     参数空间、试验编排、预算门禁、缓存
│   │   │   ├── walkforward.py #  IS 选参 → OOS 评估 → 收益率拼接
│   │   │   └── diagnostics.py #  DSR 与 PBO
│   │   ├── execution/      #   执行层（paper trading）
│   │   │   ├── orders.py   #     订单状态机、确定性 ID
│   │   │   ├── replay.py   #     调回测引擎重放出账户状态
│   │   │   ├── session.py  #     tick 编排：裁决 → 落盘 → 重放
│   │   │   └── store.py    #     账户持久化（data/paper.db）
│   │   └── stats.py        #   正态 CDF/PPF、偏度、峰度（纯标准库）
│   ├── data/               # 本地市场数据库（SQLite）
│   │   ├── store.py        #   幂等 UPSERT、范围查询、同步水位线
│   │   ├── validate.py     #   OHLCV 校验、缺口检测
│   │   └── schema.sql
│   ├── strategy_manager.py │ journal_manager.py │ review_generator.py
│   ├── risk_filter.py      │ metrics.py         │ fetch_data.py
│   └── utils.py            # 时间/周期/模板/解析工具
├── configs/                # 人工编辑的配置
├── data/                   # 业务数据与运行时状态
│   ├── macro_state.yaml    #   风控运行时状态（程序写入，勿手工编辑）
│   ├── market.db           #   本地行情库（不入版本控制）
│   ├── validation/         #   walk-forward 运行记录（不入版本控制）
│   ├── paper.db            #   模拟盘账户状态（不入版本控制）
│   └── strategies/ signals/ journals/ reviews/
├── tests/
│   ├── unit/               # 单元测试
│   └── golden/             # 策略引擎黄金快照 + 公开符号基线
├── examples/               # 示例数据（5 个，覆盖不同代码路径）
├── references/commands.md  # 命令参考手册
└── docs/
```

**分层原则**：`core/` 是无 IO 的纯逻辑内核，指标是纯函数、策略只做决策；`cli.py`
与 `SKILL.md` 是薄壳。这样指标可以脱离策略单测，策略可以脱离 CLI 回测。

## 开发与测试

```bash
# 安装含测试依赖的环境
uv sync

# 跑全部测试
uv run pytest

# 带覆盖率
uv run pytest --cov=scripts
```

测试全程不访问网络，也不会写入仓库的 `data/`、`configs/` 目录。

**黄金快照**：`tests/golden/` 存放 7 个内置策略引擎的输入与期望输出，逐字段（含全部 `metadata`）比对，用于保证重构不改变数值行为。有意变更策略输出时重录：

```bash
GOLDEN_RECORD=1 uv run pytest tests/unit/test_golden_strategies.py
```

重录后务必人工复核数值合理性，不要盲目接受。

## 典型工作流

### 日常交易

```bash
# 1. 查看风控状态
uv run python -m scripts.cli --format markdown get-macro-state

# 2. 增量同步行情（只拉本地没有的部分），然后直接从库里生成信号
uv run python -m scripts.cli sync-data --symbol BTC/USDT --timeframe 4h
uv run python -m scripts.cli generate-signals \
  --symbols "BTC/USDT" --strategy ma_crossover --from-store --timeframe 4h

# 3. 记录交易
uv run python -m scripts.cli record-trade --symbol "BTCUSDT" --direction "long" --entry-price "60000" --exit-price "65000" --strategy "均线突破"
```

### 周末复盘

```bash
# 1. 查询本周交易
uv run python -m scripts.cli --format markdown query-journals --date-start "2026-03-18"

# 2. 生成周报
uv run python -m scripts.cli generate-review --type weekly --week this --with-suggestions

# 3. 导出报告
uv run python -m scripts.cli export-review --id "2026-W13" --format html --output reports/weekly.html
```

## 全局参数

```bash
# 输出格式
uv run python -m scripts.cli --format json|markdown|rich list-strategies

# 自定义配置
uv run python -m scripts.cli --config path/to/config.yaml generate-signals ...
```

## 文档

- [完整使用文档](docs/trading_strategy_统一使用文档.md)
- [命令参考手册](references/commands.md)

## 依赖

- [ccxt](https://github.com/ccxt/ccxt) - 交易所数据获取
- [Rich](https://github.com/Textualize/rich) - 终端美化输出
- [PyYAML](https://pyyaml.org/) - YAML 解析
- [Jinja2](https://jinja.palletsprojects.com/) - 模板渲染

## License

MIT
