mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 14:34:18 +08:00
docs: 拆分超大文档——教程/参考分离,去过期版本横幅,模型枚举归并
- backtest_usage(742→485):CLI 章移交 cli-backtest.md,示例/注意事项 抽至 backtest-examples.md,重建目录 - quantitative-guide(628→325):第 5-7 章(滑点/执行仿真/归因/工作流) 抽至 quantitative-advanced.md 并重编号 - api_reference(704→478):删除过期版本横幅(1.16.2)与快速开始教程段; 数据模型/枚举与 field_mapping.md 逐表核对后去重(field_mapping 为唯一权威); WebSocket 节随 web-api.md 合并移除 - field_mapping:吸收 MAC 协议枚举(Period/Adjust/Category/BoardType/ SortType/ExMarket),全部文档回到 ≤500 行 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+41
-267
@@ -1,10 +1,11 @@
|
||||
# easy_tdx API 参考文档
|
||||
|
||||
> 版本: 1.16.2 | 运行时依赖: pandas / tzdata / click | 需要网络连接通达信行情服务器
|
||||
> 本文档为**方法速查参考**;上手教程见 [python-api.md](./python-api.md),数据模型与枚举字段见 [field_mapping.md](./field_mapping.md),Web 服务端点见 [web-api.md](./web-api.md)。
|
||||
>
|
||||
> 文档不写死版本号,对应版本以 [CHANGELOG.md](../CHANGELOG.md) 与 pyproject.toml 为准。
|
||||
|
||||
## 目录
|
||||
|
||||
- [快速开始](#快速开始)
|
||||
- [客户端](#客户端)
|
||||
- [TdxClient(同步)](#tdxclient同步)
|
||||
- [AsyncTdxClient(异步)](#asynctdxclient异步)
|
||||
@@ -18,29 +19,10 @@
|
||||
- [资金流向](#资金流向)
|
||||
- [文件下载](#文件下载)
|
||||
- [市场统计](#市场统计)
|
||||
- [数据模型](#数据模型)
|
||||
- [枚举](#枚举)
|
||||
- [异常](#异常)
|
||||
- [涨跌停价计算](#涨跌停价计算)
|
||||
|
||||
---
|
||||
|
||||
## 快速开始
|
||||
|
||||
```python
|
||||
from easy_tdx import TdxClient, Market, KlineCategory
|
||||
|
||||
# 自动选择最优服务器
|
||||
with TdxClient.from_best_host() as c:
|
||||
# 沪市证券总数
|
||||
count = c.get_security_count(Market.SH)
|
||||
|
||||
# 浦发银行日K线
|
||||
bars = c.get_security_bars(Market.SH, "600000", KlineCategory.DAY, 0, 10)
|
||||
|
||||
# 实时行情
|
||||
quotes = c.get_security_quotes([(Market.SH, "600000"), (Market.SZ, "000001")])
|
||||
```
|
||||
- [全局常量](#全局常量)
|
||||
- [完整 API 列表(MAC 协议客户端)](#完整-api-列表mac-协议客户端)
|
||||
|
||||
---
|
||||
|
||||
@@ -396,193 +378,6 @@ c.get_market_stat() -> MarketStat
|
||||
|
||||
---
|
||||
|
||||
## 数据模型
|
||||
|
||||
### SecurityInfo
|
||||
|
||||
证券列表条目。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| market | `Market` | 市场代码 |
|
||||
| code | `str` | 证券代码 |
|
||||
| name | `str` | 证券名称 |
|
||||
| volunit | `int` | 成交量单位(手 = volunit 股) |
|
||||
| decimal_point | `int` | 价格小数位数 |
|
||||
| pre_close | `float` | 昨收价 |
|
||||
| industry_tdx | `str` | 通达信行业代码(扩展字段) |
|
||||
| industry_sw | `str` | 申万行业代码(扩展字段) |
|
||||
|
||||
### SecurityQuote
|
||||
|
||||
实时五档行情。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| market | `Market` | 市场代码 |
|
||||
| code | `str` | 证券代码 |
|
||||
| price | `float` | 现价 |
|
||||
| pre_close | `float` | 昨收 |
|
||||
| open | `float` | 今开 |
|
||||
| high | `float` | 最高 |
|
||||
| low | `float` | 最低 |
|
||||
| vol | `float` | 总成交量(手) |
|
||||
| amount | `float` | 成交额(元) |
|
||||
| bid1~bid5 | `float` | 买一到买五价 |
|
||||
| bid_vol1~bid_vol5 | `float` | 买一到买五量 |
|
||||
| ask1~ask5 | `float` | 卖一到卖五价 |
|
||||
| ask_vol1~ask_vol5 | `float` | 卖一到卖五量 |
|
||||
| s_vol | `float` | 内盘(主动卖) |
|
||||
| b_vol | `float` | 外盘(主动买) |
|
||||
| rise_speed | `float` | 涨速 |
|
||||
| server_time | `str` | 服务器时间 |
|
||||
|
||||
### SecurityBar
|
||||
|
||||
K 线数据。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| open | `float` | 开盘价 |
|
||||
| close | `float` | 收盘价 |
|
||||
| high | `float` | 最高价 |
|
||||
| low | `float` | 最低价 |
|
||||
| vol | `float` | 成交量(股) |
|
||||
| amount | `float` | 成交额(元) |
|
||||
| year | `int` | 年 |
|
||||
| month | `int` | 月 |
|
||||
| day | `int` | 日 |
|
||||
| hour | `int` | 时 |
|
||||
| minute | `int` | 分 |
|
||||
| datetime_str | `str` | 属性,格式化时间字符串 |
|
||||
|
||||
### MinuteBar
|
||||
|
||||
分时数据。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| price | `float` | 价格 |
|
||||
| vol | `int` | 成交量 |
|
||||
|
||||
### TransactionRecord
|
||||
|
||||
逐笔成交。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| hour | `int` | 时 |
|
||||
| minute | `int` | 分 |
|
||||
| price | `float` | 成交价 |
|
||||
| vol | `int` | 成交量 |
|
||||
| buyorsell | `int` | 方向(0=买, 1=卖, 2=中性, 8=集合竞价) |
|
||||
|
||||
### XdxrRecord
|
||||
|
||||
除权除息记录。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| market | `Market` | 市场 |
|
||||
| code | `str` | 代码 |
|
||||
| year/month/day | `int` | 日期 |
|
||||
| category | `int` | 事件类型(见 XDXR_CATEGORY_NAMES) |
|
||||
| fenhong | `float \| None` | 每股分红(元) |
|
||||
| peigujia | `float \| None` | 配股价 |
|
||||
| songzhuangu | `float \| None` | 每股送转股比例 |
|
||||
| peigu | `float \| None` | 每股配股比例 |
|
||||
|
||||
### FinanceInfo
|
||||
|
||||
最新财务数据。包含股本结构(流通股本、总股本、国家股等)、资产负债(总资产、净资产等)、利润指标(主营收入、净利润等)和每股指标。字段名使用拼音,完整列表见源码 `models/finance.py`。
|
||||
|
||||
### CompanyInfoCategory
|
||||
|
||||
公司信息文件目录。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| name | `str` | 目录名 |
|
||||
| filename | `str` | 文件名 |
|
||||
| start | `int` | 起始偏移 |
|
||||
| length | `int` | 内容长度 |
|
||||
|
||||
### TdxBlock
|
||||
|
||||
板块信息。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| name | `str` | 板块名称 |
|
||||
| category | `int` | 分类(0=行业, 1=地域, 2=概念, 3=风格) |
|
||||
| count | `int` | 成分股数量 |
|
||||
| codes | `list[str]` | 成分股代码列表 |
|
||||
|
||||
### MarketStat
|
||||
|
||||
市场统计。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| up_count | `int` | 上涨家数 |
|
||||
| down_count | `int` | 下跌家数 |
|
||||
| neutral_count | `int` | 平盘家数 |
|
||||
| suspended_count | `int` | 停牌估算 |
|
||||
| total_count | `int` | 总计 |
|
||||
| total_amount | `float` | 总成交额 |
|
||||
| total_volume | `float` | 总成交量 |
|
||||
| total_market_cap | `float` | 总市值(元),来自 880001 收盘价 |
|
||||
| limit_up_count | `int` | 涨停家数,来自 880006 close |
|
||||
| limit_down_count | `int` | 跌停家数,来自 880006 open |
|
||||
|
||||
### FundFlow
|
||||
|
||||
资金流向。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| super_in / super_out | `float` | 超大单流入/流出 |
|
||||
| large_in / large_out | `float` | 大单流入/流出 |
|
||||
| medium_in / medium_out | `float` | 中单流入/流出 |
|
||||
| small_in / small_out | `float` | 小单流入/流出 |
|
||||
| main_net_inflow | `float` | 属性:主力净流入(超大+大) |
|
||||
| total_net_inflow | `float` | 属性:全单净流入 |
|
||||
|
||||
### HistoricalFundFlow
|
||||
|
||||
历史日线资金流向。字段同 FundFlow,额外包含 `year`/`month`/`day` 日期字段。
|
||||
|
||||
---
|
||||
|
||||
## 枚举
|
||||
|
||||
### Market
|
||||
|
||||
| 值 | 名称 | 说明 |
|
||||
|----|------|------|
|
||||
| 0 | SZ | 深圳 |
|
||||
| 1 | SH | 上海 |
|
||||
| 2 | BJ | 北京 |
|
||||
|
||||
### KlineCategory
|
||||
|
||||
| 值 | 名称 | 说明 |
|
||||
|----|------|------|
|
||||
| 0 | MIN_5 | 5 分钟 |
|
||||
| 1 | MIN_15 | 15 分钟 |
|
||||
| 2 | MIN_30 | 30 分钟 |
|
||||
| 3 | MIN_60 | 60 分钟 |
|
||||
| 4 | DAY | 日线 |
|
||||
| 5 | WEEK | 周线 |
|
||||
| 6 | MONTH | 月线 |
|
||||
| 7 | MIN_1 | 1 分钟 |
|
||||
| 8 | MIN_3 | 3 分钟(内部用) |
|
||||
| 9 | YEAR | 年线 |
|
||||
| 10 | SEASON | 季线 |
|
||||
| 11 | YEAR_ALT | 年线(备用) |
|
||||
|
||||
---
|
||||
|
||||
## 异常
|
||||
|
||||
所有异常继承自 `TdxError`。
|
||||
@@ -640,65 +435,44 @@ compute_price_limits(market, code, name, pre_close, listed_days=None)
|
||||
| `KNOWN_EX_HOSTS` | `list[str]` | 扩展行情服务器列表 |
|
||||
| `XDXR_CATEGORY_NAMES` | `dict[int, str]` | 除权除息事件类型映射 |
|
||||
|
||||
---
|
||||
## 完整 API 列表(MAC 协议客户端)
|
||||
|
||||
## WebSocket 实时行情(serve /ws/realtime/*)
|
||||
### MacClient / AsyncMacClient
|
||||
|
||||
`easy-tdx serve` 后可建立 WebSocket 连接(v1.28 起联动 `RealtimeDataFeed`,此前
|
||||
该端点不推送数据):
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| `get_stock_quotes(stocks, fields)` | 批量实时报价 |
|
||||
| `get_stock_quotes_list(category, ...)` | 市场分类排序报价 |
|
||||
| `get_stock_kline(market, code, period, ...)` | K 线(支持复权) |
|
||||
| `get_stock_kline_with_indicators(market, code, indicators, ...)` | K 线 + 技术指标 |
|
||||
| `get_tick_chart(market, code, date)` | 单日分时图 |
|
||||
| `get_tick_charts(market, code, days)` | 多日分时图 |
|
||||
| `get_chart_sampling(market, code)` | 分时缩略采样 |
|
||||
| `get_transactions(market, code, ...)` | 逐笔成交 |
|
||||
| `get_symbol_info(market, code)` | 个股特征快照 |
|
||||
| `get_board_list(board_type, ..., sort_column)` | 板块列表(sort_value 列=排序键指标值) |
|
||||
| `get_board_members(board_symbol, ...)` | 板块成分股报价 |
|
||||
| `get_board_summary(board_symbol, ...)` | 板块汇总(成交额、主力净流入、涨跌家数) |
|
||||
| `get_board_ranking(board_type, top_n, sort_by, ...)` | 板块涨跌幅排行榜(行业/概念排行) |
|
||||
| `get_board_change_ranking(board_type, target_date, days, ...)` | 板块 N 日涨跌幅排行 |
|
||||
| `get_belong_board(market, code)` | 个股所属板块 |
|
||||
| `get_capital_flow(market, code)` | 资金流向 |
|
||||
| `get_auction(market, code)` | 集合竞价 |
|
||||
| `get_unusual(market, ...)` | 市场异动 |
|
||||
| `get_server_info()` | 服务器交易时段 |
|
||||
| `get_kline_offset(offset, count)` | K 线偏移信息 |
|
||||
| `get_goods_list(market, ...)` | 扩展市场商品列表 |
|
||||
|
||||
```
|
||||
ws://127.0.0.1:8000/api/v1/ws/realtime/{symbol} # symbol 如 SZ000001 / SH600519
|
||||
```
|
||||
### MacExClient / AsyncMacExClient
|
||||
|
||||
### 服务端推送帧(JSON)
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| `goods_count(market)` | 商品总数 |
|
||||
| `goods_list(market, start, count)` | 商品列表 |
|
||||
| `goods_quotes(stocks, fields)` | 批量报价 |
|
||||
| `goods_quotes_list(market, ...)` | 市场分类报价列表 |
|
||||
| `goods_kline(market, code, period, ...)` | K 线(支持复权) |
|
||||
| `goods_tick_chart(market, code, ...)` | 分时图 |
|
||||
| `goods_chart_sampling(market, code)` | 分时缩略采样 |
|
||||
| `goods_transaction(market, code, ...)` | 逐笔成交 |
|
||||
|
||||
| type | 触发 | 字段 |
|
||||
|------|------|------|
|
||||
| `tick` | 轮询到标的的最新快照(价格/量变化才推,约 `interval` 秒一拍) | `symbol`、`market`、`code`、`price`、`volume`、`ts`(epoch 秒)、`open`、`high`、`low`、`pre_close`、`amount`、`name` |
|
||||
| `ping` | 连续 30s 未收到客户端消息的心跳 | —(客户端忽略即可,无须回包) |
|
||||
| `status` | 客户端 subscribe/unsubscribe 的确认 | `msg`(如 `subscribed SH600000`) |
|
||||
| `error` | 非法 JSON / 未知 action / 超出订阅上限 | `msg` |
|
||||
|
||||
### 客户端控制消息(JSON 文本帧)
|
||||
|
||||
```json
|
||||
{"action": "subscribe", "symbol": "SH600000"}
|
||||
{"action": "unsubscribe", "symbol": "SH600000"}
|
||||
```
|
||||
|
||||
### 行为约定
|
||||
|
||||
- **连接即订阅** path 上的 symbol;断开自动退订全部标的。
|
||||
- **按需轮询**:订阅集合为空时服务端不产生任何行情请求;去重后标的总数上限
|
||||
80(`get_stock_quotes` 协议约束)。
|
||||
- **交易时段**:默认 A 股时段外只睡不拉(无 tick 帧,心跳照发);mock 模式
|
||||
(`EASY_TDX_E2E_MOCK=1`)不受限制。
|
||||
- **背压**:消费过慢时丢最旧快照保最新,不积压。
|
||||
- 环境变量:`EASY_TDX_WS_INTERVAL`(轮询间隔秒数,默认 3.0)。
|
||||
|
||||
### 前端接入方式(自动重连 + 心跳容忍)
|
||||
|
||||
```typescript
|
||||
function connectRealtime(symbol: string, onTick: (f: TickFrame) => void) {
|
||||
let retry = 0
|
||||
let ws: WebSocket | null = null
|
||||
const open = () => {
|
||||
ws = new WebSocket(`ws://${location.host}/api/v1/ws/realtime/${symbol}`)
|
||||
ws.onmessage = (e) => {
|
||||
const frame = JSON.parse(e.data)
|
||||
if (frame.type === 'tick') { retry = 0; onTick(frame) } // ping/status 忽略
|
||||
}
|
||||
ws.onclose = () => {
|
||||
retry += 1
|
||||
setTimeout(open, Math.min(1000 * 2 ** (retry - 1), 30_000)) // 指数退避
|
||||
}
|
||||
}
|
||||
open()
|
||||
return () => ws?.close()
|
||||
}
|
||||
```
|
||||
|
||||
> 说明:看板/自选页的实时刷新已由 SSE `/stream/quotes`(全量快照、单连接共享)
|
||||
> 承担;WS 通道定位是**按需订阅单标的 tick 事件**(后续实时策略信号的接入点),
|
||||
> 两条链路按场景选用,不要求同时连接。手动冒烟见 `scripts/ws_smoke.py`。
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
# 回测完整示例集
|
||||
|
||||
本文件汇集回测引擎的完整可运行示例与注意事项,配合 [backtest_usage.md](./backtest_usage.md)(手册)与 [cli-backtest.md](./cli-backtest.md)(CLI 参考)使用。
|
||||
|
||||
## 完整示例
|
||||
|
||||
### 示例 1:双均线交叉策略
|
||||
|
||||
```python
|
||||
"""双均线交叉策略:MA5 上穿 MA20 买入,下穿卖出。"""
|
||||
import pandas as pd
|
||||
from easy_tdx.backtest import BacktestEngine, Strategy, crossover
|
||||
from easy_tdx import MyTT
|
||||
|
||||
|
||||
class DualMACross(Strategy):
|
||||
def init(self):
|
||||
self.ma5 = self.I(MyTT.MA, self.data.close, 5)
|
||||
self.ma20 = self.I(MyTT.MA, self.data.close, 20)
|
||||
self.golden = crossover(self.ma5, self.ma20)
|
||||
self.death = crossover(self.ma20, self.ma5)
|
||||
|
||||
def next(self):
|
||||
if self.golden[self._bar_index] and self.position["size"] == 0:
|
||||
self.buy(size=0)
|
||||
elif self.death[self._bar_index] and self.position["size"] > 0:
|
||||
self.sell(size=0)
|
||||
|
||||
|
||||
# 构造模拟数据(实际使用 TdxClient 获取)
|
||||
dates = pd.date_range("2024-01-01", periods=200, freq="D")
|
||||
import numpy as np
|
||||
rng = np.random.default_rng(42)
|
||||
close = 10.0 + np.cumsum(rng.normal(0, 0.2, 200))
|
||||
|
||||
df = pd.DataFrame({
|
||||
"datetime": dates,
|
||||
"open": close + rng.uniform(-0.1, 0.1, 200),
|
||||
"close": close,
|
||||
"high": close + rng.uniform(0, 0.3, 200),
|
||||
"low": close - rng.uniform(0, 0.3, 200),
|
||||
"vol": rng.integers(10000, 100000, 200),
|
||||
})
|
||||
|
||||
engine = BacktestEngine(DualMACross, cash=100000, commission=0.0003)
|
||||
result = engine.run(df)
|
||||
|
||||
result.summary()
|
||||
print(f"\n年化收益: {result.performance['annual_return']:.2%}")
|
||||
print(f"夏普比率: {result.performance['sharpe']:.2f}")
|
||||
```
|
||||
|
||||
### 示例 2:MACD 策略 + 预计算指标
|
||||
|
||||
```python
|
||||
"""MACD 策略:DIF 上穿 DEA 买入,下穿卖出。"""
|
||||
from easy_tdx.backtest import BacktestEngine, Strategy, crossover
|
||||
from easy_tdx import MyTT
|
||||
|
||||
|
||||
class MACDStrategy(Strategy):
|
||||
def init(self):
|
||||
dif, dea, macd_hist = self.I(MyTT.MACD, self.data.close)
|
||||
self.dif = dif
|
||||
self.dea = dea
|
||||
self.golden = crossover(dif, dea)
|
||||
self.death = crossover(dea, dif)
|
||||
|
||||
def next(self):
|
||||
if self.golden[self._bar_index] and self.position["size"] == 0:
|
||||
self.buy(size=0)
|
||||
elif self.death[self._bar_index] and self.position["size"] > 0:
|
||||
self.sell(size=0)
|
||||
|
||||
|
||||
engine = BacktestEngine(MACDStrategy, cash=100000)
|
||||
result = engine.run(df) # df 包含 OHLCV 数据
|
||||
```
|
||||
|
||||
### 示例 3:布林带突破 + 滑点模拟
|
||||
|
||||
```python
|
||||
"""布林带策略:跌破下轨买入,突破上轨卖出,模拟滑点。"""
|
||||
from easy_tdx.backtest import BacktestEngine, Strategy
|
||||
from easy_tdx import MyTT
|
||||
|
||||
|
||||
class BollingerBreakout(Strategy):
|
||||
def init(self):
|
||||
upper, mid, lower = self.I(MyTT.BOLL, self.data.close, 20)
|
||||
self.upper = upper
|
||||
self.lower = lower
|
||||
|
||||
def next(self):
|
||||
cur = self.data.close[0]
|
||||
if cur <= self.lower[self._bar_index] and self.position["size"] == 0:
|
||||
self.buy(size=0)
|
||||
elif cur >= self.upper[self._bar_index] and self.position["size"] > 0:
|
||||
self.sell(size=0)
|
||||
|
||||
|
||||
# 模拟滑点和保守成交价
|
||||
engine = BacktestEngine(
|
||||
BollingerBreakout,
|
||||
cash=100000,
|
||||
slippage=0.02, # 每股 2 分钱滑点
|
||||
execution="worst", # 保守成交价
|
||||
reject_policy="skip", # 资金不足直接跳过
|
||||
)
|
||||
result = engine.run(df)
|
||||
```
|
||||
|
||||
### 示例 4:从文件运行 CLI
|
||||
|
||||
```python
|
||||
# save as rsi_strategy.py
|
||||
from easy_tdx.backtest import Strategy
|
||||
from easy_tdx import MyTT
|
||||
|
||||
|
||||
class RSIStrategy(Strategy):
|
||||
"""RSI 超卖超买策略。"""
|
||||
def init(self):
|
||||
self.rsi = self.I(MyTT.RSI, self.data.close, 14)
|
||||
|
||||
def next(self):
|
||||
cur_rsi = self.rsi[self._bar_index]
|
||||
if cur_rsi < 30 and self.position["size"] == 0:
|
||||
self.buy(size=0)
|
||||
elif cur_rsi > 70 and self.position["size"] > 0:
|
||||
self.sell(size=0)
|
||||
```
|
||||
|
||||
```bash
|
||||
easy-tdx backtest SZ 000001 \
|
||||
--strategy-file rsi_strategy.py \
|
||||
--cash 200000 \
|
||||
--execution next_open \
|
||||
--count 1000 \
|
||||
--adjust QFQ \
|
||||
--table
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **DataFrame 格式要求**:必须包含 `datetime`, `open`, `close`, `high`, `low` 列。`vol`/`amount` 为可选但推荐。
|
||||
2. **成交时机**:默认 `next_open` 模式下,信号产生后需等待下一根 K 线才能成交。如果信号在最后一根 K 线产生,则无法成交。
|
||||
3. **整手交易**:A 股按 100 股整手交易。全仓模式会自动向下取整到 100 的倍数。
|
||||
4. **做空限制**:v1 不支持做空,卖出数量不能超过当前持仓。
|
||||
5. **未来函数警告**:使用 `this_close` 模式时,结果中的 `config.future_leak_warning` 会标记为 `True`。
|
||||
6. **多笔同 bar 交易**:引擎支持同一根 K 线上产生多笔交易(如分批建仓),按顺序依次撮合。
|
||||
+4
-261
@@ -21,15 +21,12 @@
|
||||
- [资金曲线](#资金曲线)
|
||||
- [交易记录](#交易记录)
|
||||
- [序列化输出](#序列化输出)
|
||||
- [CLI 命令行](#cli-命令行)
|
||||
- [内置策略列表(strategies)](#内置策略列表strategies)
|
||||
- [参数网格寻优(optimize)](#参数网格寻优optimize)
|
||||
- [组合级分析(portfolio)](#组合级分析portfolio)
|
||||
- [CLI 命令行](#cli-命令行) → 见 [cli-backtest.md](./cli-backtest.md)
|
||||
- [进阶用法](#进阶用法)
|
||||
- [预计算指标列](#预计算指标列)
|
||||
- [缠论结果注入](#缠论结果注入)
|
||||
- [自定义策略文件](#自定义策略文件)
|
||||
- [完整示例](#完整示例)
|
||||
- [完整示例](#完整示例) → 见 [backtest-examples.md](./backtest-examples.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -392,115 +389,10 @@ config = result.config
|
||||
|
||||
---
|
||||
|
||||
|
||||
## CLI 命令行
|
||||
|
||||
```bash
|
||||
# 基本用法
|
||||
easy-tdx backtest SZ 000001 --strategy-file my_strategy.py
|
||||
|
||||
# 查看帮助
|
||||
easy-tdx backtest --help
|
||||
|
||||
# 指定参数
|
||||
easy-tdx backtest SH 600519 \
|
||||
--strategy-file ma_cross.py \
|
||||
--cash 50000 \
|
||||
--commission 0.0003 \
|
||||
--execution next_open \
|
||||
--period DAILY \
|
||||
--count 500 \
|
||||
--table
|
||||
|
||||
# 预计算指标(MACD, KDJ 会作为额外列注入 DataFrame)
|
||||
easy-tdx backtest SZ 000001 \
|
||||
--strategy-file macd_strategy.py \
|
||||
--indicators MACD,KDJ
|
||||
|
||||
# 输出 JSON(默认)
|
||||
easy-tdx backtest SZ 000001 --strategy-file my_strategy.py
|
||||
|
||||
# 输出表格
|
||||
easy-tdx backtest SZ 000001 --strategy-file my_strategy.py --table
|
||||
```
|
||||
|
||||
**CLI 参数**:
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `MARKET` | — | 市场代码:SZ / SH |
|
||||
| `CODE` | — | 股票代码:如 000001 |
|
||||
| `--strategy-file` | — | Python 策略文件路径 |
|
||||
| `--strategy` | — | DSL 表达式(P1,尚未实现) |
|
||||
| `--cash` | 100000 | 初始资金 |
|
||||
| `--commission` | 0.0003 | 佣金率 |
|
||||
| `--execution` | next_open | 成交价规则 |
|
||||
| `--period` | DAILY | K 线周期 |
|
||||
| `--adjust` | NONE | 复权方式:NONE / QFQ / HFQ |
|
||||
| `--count` | 500 | K 线数量 |
|
||||
| `--indicators` | — | 预计算指标(逗号分隔) |
|
||||
| `--table` | False | 表格输出 |
|
||||
| `--output` | json | 输出格式:json / table / csv |
|
||||
| `--wf` | False | 附加 Walk-Forward 样本外验证 |
|
||||
| `--wf-windows` | 7 | Walk-Forward 窗口数 |
|
||||
| `--evaluate` | False | 一条龙评估(回测+WF+适配性+评分+评级+基准对比) |
|
||||
| `--auto-fees` | False | 按标的品种自动解析费率 |
|
||||
|
||||
### 内置策略列表(strategies)
|
||||
|
||||
```bash
|
||||
# 表格列出全部内置策略(名称/参数/预设寻优网格)
|
||||
easy-tdx strategies
|
||||
|
||||
# JSON 输出(含完整参数 schema,与 Web API GET /backtest/strategies 同构)
|
||||
easy-tdx strategies --output json
|
||||
```
|
||||
|
||||
### 参数网格寻优(optimize)
|
||||
|
||||
对注册表内置策略的 1-2 个参数做网格搜索,按总收益率排名:
|
||||
|
||||
```bash
|
||||
# 单策略:用该策略的预设寻优网格(见 strategies 命令)
|
||||
easy-tdx optimize SZ 000001 --strategy ma_cross
|
||||
|
||||
# 单策略:自定义网格(--param 参数名=值1,值2,可多次指定)
|
||||
easy-tdx optimize SZ 000001 --strategy ma_cross --param fast=5,10,15 --param slow=20,60
|
||||
|
||||
# 一键寻优所有内置策略:逐策略按预设网格寻优后全局排名
|
||||
easy-tdx optimize SZ 000001 --all
|
||||
|
||||
# 并行加速(2+ 进程级并行;1 = 串行 + 指标缓存复用)
|
||||
easy-tdx optimize SZ 000001 --all --workers 4
|
||||
```
|
||||
|
||||
**optimize 参数**:
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `--strategy` | — | 注册表策略名(与 `--all` 二选一) |
|
||||
| `--all` | False | 一键寻优所有内置策略(STRATEGY_PRESETS 预设网格) |
|
||||
| `--param` | 预设网格 | 自定义参数网格,如 `fast=5,10,15`(最多 2 个参数,笛卡尔积 ≤ 200) |
|
||||
| `--cash` | 1000000 | 初始资金 |
|
||||
| `--commission` | 0.0003 | 佣金率 |
|
||||
| `--slippage` | 0.0 | 滑点 |
|
||||
| `--workers` | 1 | 并行进程数 |
|
||||
| `--top` | 15 | 表格输出显示前 N 行 |
|
||||
|
||||
Python API 同名能力:`easy_tdx.backtest.optimizer.ParamGridOptimizer`(单策略)与
|
||||
`easy_tdx.backtest.optimizer.optimize_all_strategies`(一键全策略)。
|
||||
|
||||
### 组合级分析(portfolio)
|
||||
|
||||
```bash
|
||||
# 组合级 Walk-Forward 样本外验证(全部标的日期并集切窗,每窗独立开仓)
|
||||
easy-tdx portfolio --stocks SZ:000001,SH:600519 --strategy-file strategies/ma_cross.py --wf --wf-windows 7
|
||||
|
||||
# 组合级一条龙评估:组合回测 + 组合WF + 跨标的适配性 + 综合评分
|
||||
# + 组合评级 + 等权买入持有基准对比(与 Web UI /portfolio 页同构)
|
||||
easy-tdx portfolio --stocks SZ:000001,SH:600519 --strategy-file strategies/ma_cross.py --evaluate
|
||||
```
|
||||
|
||||
---
|
||||
CLI 用法见 [cli-backtest.md](./cli-backtest.md)(回测/寻优/组合/run-all 命令与参数)。
|
||||
|
||||
## 进阶用法
|
||||
|
||||
@@ -591,152 +483,3 @@ easy-tdx backtest SZ 000001 --strategy-file my_strategy.py --table
|
||||
|
||||
---
|
||||
|
||||
## 完整示例
|
||||
|
||||
### 示例 1:双均线交叉策略
|
||||
|
||||
```python
|
||||
"""双均线交叉策略:MA5 上穿 MA20 买入,下穿卖出。"""
|
||||
import pandas as pd
|
||||
from easy_tdx.backtest import BacktestEngine, Strategy, crossover
|
||||
from easy_tdx import MyTT
|
||||
|
||||
|
||||
class DualMACross(Strategy):
|
||||
def init(self):
|
||||
self.ma5 = self.I(MyTT.MA, self.data.close, 5)
|
||||
self.ma20 = self.I(MyTT.MA, self.data.close, 20)
|
||||
self.golden = crossover(self.ma5, self.ma20)
|
||||
self.death = crossover(self.ma20, self.ma5)
|
||||
|
||||
def next(self):
|
||||
if self.golden[self._bar_index] and self.position["size"] == 0:
|
||||
self.buy(size=0)
|
||||
elif self.death[self._bar_index] and self.position["size"] > 0:
|
||||
self.sell(size=0)
|
||||
|
||||
|
||||
# 构造模拟数据(实际使用 TdxClient 获取)
|
||||
dates = pd.date_range("2024-01-01", periods=200, freq="D")
|
||||
import numpy as np
|
||||
rng = np.random.default_rng(42)
|
||||
close = 10.0 + np.cumsum(rng.normal(0, 0.2, 200))
|
||||
|
||||
df = pd.DataFrame({
|
||||
"datetime": dates,
|
||||
"open": close + rng.uniform(-0.1, 0.1, 200),
|
||||
"close": close,
|
||||
"high": close + rng.uniform(0, 0.3, 200),
|
||||
"low": close - rng.uniform(0, 0.3, 200),
|
||||
"vol": rng.integers(10000, 100000, 200),
|
||||
})
|
||||
|
||||
engine = BacktestEngine(DualMACross, cash=100000, commission=0.0003)
|
||||
result = engine.run(df)
|
||||
|
||||
result.summary()
|
||||
print(f"\n年化收益: {result.performance['annual_return']:.2%}")
|
||||
print(f"夏普比率: {result.performance['sharpe']:.2f}")
|
||||
```
|
||||
|
||||
### 示例 2:MACD 策略 + 预计算指标
|
||||
|
||||
```python
|
||||
"""MACD 策略:DIF 上穿 DEA 买入,下穿卖出。"""
|
||||
from easy_tdx.backtest import BacktestEngine, Strategy, crossover
|
||||
from easy_tdx import MyTT
|
||||
|
||||
|
||||
class MACDStrategy(Strategy):
|
||||
def init(self):
|
||||
dif, dea, macd_hist = self.I(MyTT.MACD, self.data.close)
|
||||
self.dif = dif
|
||||
self.dea = dea
|
||||
self.golden = crossover(dif, dea)
|
||||
self.death = crossover(dea, dif)
|
||||
|
||||
def next(self):
|
||||
if self.golden[self._bar_index] and self.position["size"] == 0:
|
||||
self.buy(size=0)
|
||||
elif self.death[self._bar_index] and self.position["size"] > 0:
|
||||
self.sell(size=0)
|
||||
|
||||
|
||||
engine = BacktestEngine(MACDStrategy, cash=100000)
|
||||
result = engine.run(df) # df 包含 OHLCV 数据
|
||||
```
|
||||
|
||||
### 示例 3:布林带突破 + 滑点模拟
|
||||
|
||||
```python
|
||||
"""布林带策略:跌破下轨买入,突破上轨卖出,模拟滑点。"""
|
||||
from easy_tdx.backtest import BacktestEngine, Strategy
|
||||
from easy_tdx import MyTT
|
||||
|
||||
|
||||
class BollingerBreakout(Strategy):
|
||||
def init(self):
|
||||
upper, mid, lower = self.I(MyTT.BOLL, self.data.close, 20)
|
||||
self.upper = upper
|
||||
self.lower = lower
|
||||
|
||||
def next(self):
|
||||
cur = self.data.close[0]
|
||||
if cur <= self.lower[self._bar_index] and self.position["size"] == 0:
|
||||
self.buy(size=0)
|
||||
elif cur >= self.upper[self._bar_index] and self.position["size"] > 0:
|
||||
self.sell(size=0)
|
||||
|
||||
|
||||
# 模拟滑点和保守成交价
|
||||
engine = BacktestEngine(
|
||||
BollingerBreakout,
|
||||
cash=100000,
|
||||
slippage=0.02, # 每股 2 分钱滑点
|
||||
execution="worst", # 保守成交价
|
||||
reject_policy="skip", # 资金不足直接跳过
|
||||
)
|
||||
result = engine.run(df)
|
||||
```
|
||||
|
||||
### 示例 4:从文件运行 CLI
|
||||
|
||||
```python
|
||||
# save as rsi_strategy.py
|
||||
from easy_tdx.backtest import Strategy
|
||||
from easy_tdx import MyTT
|
||||
|
||||
|
||||
class RSIStrategy(Strategy):
|
||||
"""RSI 超卖超买策略。"""
|
||||
def init(self):
|
||||
self.rsi = self.I(MyTT.RSI, self.data.close, 14)
|
||||
|
||||
def next(self):
|
||||
cur_rsi = self.rsi[self._bar_index]
|
||||
if cur_rsi < 30 and self.position["size"] == 0:
|
||||
self.buy(size=0)
|
||||
elif cur_rsi > 70 and self.position["size"] > 0:
|
||||
self.sell(size=0)
|
||||
```
|
||||
|
||||
```bash
|
||||
easy-tdx backtest SZ 000001 \
|
||||
--strategy-file rsi_strategy.py \
|
||||
--cash 200000 \
|
||||
--execution next_open \
|
||||
--count 1000 \
|
||||
--adjust QFQ \
|
||||
--table
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **DataFrame 格式要求**:必须包含 `datetime`, `open`, `close`, `high`, `low` 列。`vol`/`amount` 为可选但推荐。
|
||||
2. **成交时机**:默认 `next_open` 模式下,信号产生后需等待下一根 K 线才能成交。如果信号在最后一根 K 线产生,则无法成交。
|
||||
3. **整手交易**:A 股按 100 股整手交易。全仓模式会自动向下取整到 100 的倍数。
|
||||
4. **做空限制**:v1 不支持做空,卖出数量不能超过当前持仓。
|
||||
5. **未来函数警告**:使用 `this_close` 模式时,结果中的 `config.future_leak_warning` 会标记为 `True`。
|
||||
6. **多笔同 bar 交易**:引擎支持同一根 K 线上产生多笔交易(如分批建仓),按顺序依次撮合。
|
||||
|
||||
+74
-1
@@ -1,6 +1,6 @@
|
||||
# easy_tdx 字段映射表
|
||||
|
||||
> 模型字段名 ↔ 中文含义 ↔ 数据类型对照
|
||||
> 模型字段名 ↔ 中文含义 ↔ 数据类型对照。方法速查见 [api_reference.md](./api_reference.md),上手教程见 [python-api.md](./python-api.md)。
|
||||
|
||||
---
|
||||
|
||||
@@ -313,3 +313,76 @@
|
||||
| 9 | YEAR | 年线 |
|
||||
| 10 | SEASON | 季线 |
|
||||
| 11 | YEAR_ALT | 年线(备用) |
|
||||
|
||||
## 枚举参考
|
||||
|
||||
### Period(K 线周期)
|
||||
|
||||
| 值 | 名称 | 说明 |
|
||||
|----|------|------|
|
||||
| 7 | `MIN_1` | 1 分钟 |
|
||||
| 0 | `MIN_5` | 5 分钟 |
|
||||
| 1 | `MIN_15` | 15 分钟 |
|
||||
| 2 | `MIN_30` | 30 分钟 |
|
||||
| 3 | `MIN_60` | 60 分钟 |
|
||||
| 4 | `DAILY` | 日线 |
|
||||
| 5 | `WEEKLY` | 周线 |
|
||||
| 6 | `MONTHLY` | 月线 |
|
||||
| 10 | `QUARTERLY` | 季线 |
|
||||
| 11 | `YEARLY` | 年线 |
|
||||
|
||||
### Adjust(复权类型)
|
||||
|
||||
| 值 | 名称 | 说明 |
|
||||
|----|------|------|
|
||||
| 0 | `NONE` | 不复权 |
|
||||
| 1 | `QFQ` | 前复权 |
|
||||
| 2 | `HFQ` | 后复权 |
|
||||
|
||||
### Category(市场分类)
|
||||
|
||||
| 值 | 名称 | 说明 |
|
||||
|----|------|------|
|
||||
| 0 | `SH` | 上证 A 股 |
|
||||
| 2 | `SZ` | 深证 A 股 |
|
||||
| 6 | `A` | 全部 A 股 |
|
||||
| 7 | `B` | B 股 |
|
||||
| 8 | `KCB` | 科创板 |
|
||||
| 12 | `BJ` | 北证 A 股 |
|
||||
| 14 | `CYB` | 创业板 |
|
||||
|
||||
### BoardType(板块类型)
|
||||
|
||||
| 值 | 名称 | 说明 |
|
||||
|----|------|------|
|
||||
| 0 | `HY` | 行业一级 |
|
||||
| 1 | `HY2` | 行业二级 |
|
||||
| 3 | `GN` | 概念 |
|
||||
| 4 | `FG` | 风格 |
|
||||
| 5 | `DQ` | 地区 |
|
||||
| 255 | `ALL` | 全部 |
|
||||
|
||||
### SortType(排序字段)
|
||||
|
||||
| 名称 | 说明 |
|
||||
|------|------|
|
||||
| `CODE` | 代码 |
|
||||
| `PRICE` | 现价 |
|
||||
| `CHANGE_PCT` | 涨幅% |
|
||||
| `VOLUME` | 成交量 |
|
||||
| `TOTAL_AMOUNT` | 成交额 |
|
||||
| `TURNOVER_RATE` | 换手% |
|
||||
| `MAIN_NET_AMOUNT` | 主力净额 |
|
||||
|
||||
### ExMarket(扩展市场)
|
||||
|
||||
| 值 | 名称 | 说明 |
|
||||
|----|------|------|
|
||||
| 28 | `ZZ_FUTURES` | 郑州商品 |
|
||||
| 29 | `DL_FUTURES` | 大连商品 |
|
||||
| 30 | `SH_FUTURES` | 上海期货 |
|
||||
| 31 | `HK_MAIN_BOARD` | 香港主板 |
|
||||
| 47 | `CFFEX_FUTURES` | 中金所期货 |
|
||||
| 48 | `HK_GEM` | 香港创业板 |
|
||||
| 74 | `US_STOCK` | 美国股票 |
|
||||
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
# 量化进阶:执行仿真与归因
|
||||
|
||||
滑点建模、执行仿真(TWAP/VWAP/限价单)、归因分析与完整工作流。因子/组合基础见 [quantitative-guide.md](./quantitative-guide.md)。
|
||||
|
||||
## 1. 高级回测
|
||||
|
||||
### 1.1 滑点模型
|
||||
|
||||
4 种可插拔滑点模型,替代原有固定滑点:
|
||||
|
||||
```python
|
||||
from easy_tdx.backtest import BacktestEngine
|
||||
from easy_tdx.backtest.slippage import (
|
||||
FixedSlippage,
|
||||
PercentSlippage,
|
||||
SquareRootSlippage,
|
||||
VolumeSlippage,
|
||||
)
|
||||
|
||||
# 1. 固定每股滑点(与旧行为一致)
|
||||
model1 = FixedSlippage(per_share=0.01)
|
||||
|
||||
# 2. 按金额百分比
|
||||
model2 = PercentSlippage(rate=0.001)
|
||||
|
||||
# 3. 方根市场冲击模型(Almgren-Chriss 简化版)
|
||||
# impact = sigma * sqrt(participation_rate) * price * size * coeff
|
||||
# A 股量化主流:参与率 >5% 时冲击显著
|
||||
model3 = SquareRootSlippage(impact_coeff=0.1)
|
||||
|
||||
# 4. 成交量比例滑点
|
||||
model4 = VolumeSlippage(base_bps=10.0)
|
||||
|
||||
# 在 BacktestEngine 中使用
|
||||
engine = BacktestEngine(
|
||||
MyStrategy,
|
||||
cash=1_000_000,
|
||||
slippage_model=SquareRootSlippage(impact_coeff=0.1),
|
||||
)
|
||||
result = engine.run(df)
|
||||
```
|
||||
|
||||
**模型选择建议**:
|
||||
|
||||
| 场景 | 推荐模型 | 参数 |
|
||||
|------|---------|------|
|
||||
| 快速原型 | `FixedSlippage` | `per_share=0.01` |
|
||||
| 中频策略 | `PercentSlippage` | `rate=0.001` |
|
||||
| 大额订单 | `SquareRootSlippage` | `impact_coeff=0.1` |
|
||||
| 低流动性股票 | `VolumeSlippage` | `base_bps=10.0` |
|
||||
|
||||
### 1.2 执行仿真
|
||||
|
||||
4 种执行模型,将单笔信号拆分为多笔子交易:
|
||||
|
||||
```python
|
||||
from easy_tdx.backtest.execution import (
|
||||
ImmediateExecution,
|
||||
TWAPExecution,
|
||||
VWAPExecution,
|
||||
LimitExecution,
|
||||
)
|
||||
|
||||
# 1. 即时成交(默认,与旧行为一致)
|
||||
exec1 = ImmediateExecution()
|
||||
|
||||
# 2. TWAP:时间加权平均价格,N 根 K 线均匀拆单
|
||||
exec2 = TWAPExecution(n_bars=5)
|
||||
|
||||
# 3. VWAP:成交量加权平均价格,按历史量分布拆单
|
||||
exec3 = VWAPExecution(n_bars=5, volume_lookback=20)
|
||||
|
||||
# 4. 限价单:目标价挂单,TTL 内未触发则放弃
|
||||
exec4 = LimitExecution(ttl_bars=5)
|
||||
|
||||
# 在 BacktestEngine 中使用
|
||||
engine = BacktestEngine(
|
||||
MyStrategy,
|
||||
cash=1_000_000,
|
||||
execution_model=TWAPExecution(n_bars=3),
|
||||
slippage_model=SquareRootSlippage(),
|
||||
)
|
||||
result = engine.run(df)
|
||||
```
|
||||
|
||||
**执行模型选择**:
|
||||
|
||||
| 场景 | 推荐模型 | 参数 |
|
||||
|------|---------|------|
|
||||
| 小额/快速验证 | `ImmediateExecution` | 默认 |
|
||||
| 大额建仓/平仓 | `TWAPExecution` | `n_bars=3~5` |
|
||||
| 追踪 VWAP 基准 | `VWAPExecution` | `n_bars=5` |
|
||||
| 精确入场价位 | `LimitExecution` | `ttl_bars=5` |
|
||||
|
||||
**TWAP vs VWAP 示例**:
|
||||
|
||||
```python
|
||||
# TWAP: 300 股拆成 3 笔 100 股,在 bar 1/2/3 以 close 执行
|
||||
engine = BacktestEngine(
|
||||
MyStrategy, cash=100_000,
|
||||
execution_model=TWAPExecution(n_bars=3),
|
||||
)
|
||||
|
||||
# VWAP: 按成交量分布拆 300 股 — 成交量大的 bar 分配更多
|
||||
engine = BacktestEngine(
|
||||
MyStrategy, cash=100_000,
|
||||
execution_model=VWAPExecution(n_bars=3, volume_lookback=20),
|
||||
)
|
||||
|
||||
# 限价单:在 50 元挂买入,5 根 K 线内 low <= 50 才成交
|
||||
class LimitBuyStrategy(Strategy):
|
||||
def init(self): pass
|
||||
def next(self):
|
||||
if self._bar_index == 0:
|
||||
self.buy(size=100, price=50.0) # 指定限价
|
||||
|
||||
engine = BacktestEngine(
|
||||
LimitBuyStrategy, cash=100_000,
|
||||
execution_model=LimitExecution(ttl_bars=5),
|
||||
)
|
||||
```
|
||||
|
||||
### 1.3 归因分析
|
||||
|
||||
从回测结果生成归因报告:
|
||||
|
||||
```python
|
||||
from easy_tdx.backtest import BacktestEngine
|
||||
from easy_tdx.backtest.attribution import AttributionAnalyzer
|
||||
|
||||
# 运行回测
|
||||
engine = BacktestEngine(MyStrategy, cash=1_000_000)
|
||||
result = engine.run(df)
|
||||
|
||||
# --- 成本归因 ---
|
||||
analyzer = AttributionAnalyzer(result.trades, result.equity_curve)
|
||||
cost_report = analyzer.cost_attribution()
|
||||
print(f"总收益: {cost_report.total_return:.2%}")
|
||||
print(f"总交易成本: {cost_report.total_trade_cost:.0f} 元")
|
||||
print(f" 佣金: {cost_report.commission_cost:.0f}")
|
||||
print(f" 滑点: {cost_report.slippage_cost:.0f}")
|
||||
print(f" 印花税: {cost_report.stamp_tax_cost:.0f}")
|
||||
|
||||
# --- Brinson 归因(需要基准)---
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
# 构造基准曲线(如沪深300)
|
||||
benchmark = pd.DataFrame({
|
||||
"datetime": result.equity_curve["datetime"],
|
||||
"total": np.linspace(100000, 108000, len(result.equity_curve)),
|
||||
})
|
||||
analyzer = AttributionAnalyzer(result.trades, result.equity_curve, benchmark=benchmark)
|
||||
brinson_report = analyzer.brinson_attribution()
|
||||
print(f"配置贡献: {brinson_report.allocation_return:.2%}")
|
||||
print(f"选股贡献: {brinson_report.selection_return:.2%}")
|
||||
print(f"交叉效应: {brinson_report.interaction_return:.2%}")
|
||||
|
||||
# --- 因子归因(需要因子数据)---
|
||||
exposures = pd.DataFrame({"momentum": [0.5, 0.3, 0.2], "quality": [0.1, -0.1, 0.0]})
|
||||
returns = pd.DataFrame({"momentum": [0.05, 0.03, 0.02], "quality": [0.01, -0.02, 0.0]})
|
||||
analyzer = AttributionAnalyzer(
|
||||
result.trades, result.equity_curve,
|
||||
factor_exposures=exposures, factor_returns=returns,
|
||||
)
|
||||
factor_report = analyzer.factor_attribution()
|
||||
for name, ret in factor_report.factor_returns.items():
|
||||
print(f" {name}: {ret:.4f}")
|
||||
print(f"特质收益: {factor_report.specific_return:.4f}")
|
||||
|
||||
# --- 完整报告(自动选择最佳归因模式)---
|
||||
full_report = analyzer.full_report()
|
||||
```
|
||||
|
||||
**归因模式优先级**:因子归因 > Brinson 归因 > 成本归因。`full_report()` 自动选择数据最完整的模式。
|
||||
|
||||
---
|
||||
|
||||
## 2. CLI 命令
|
||||
|
||||
```bash
|
||||
# 列出所有内置因子
|
||||
easy-tdx factor list --table
|
||||
|
||||
# 因子分析(需要数据,输出示例代码)
|
||||
easy-tdx factor analyze momentum_20d
|
||||
|
||||
# 组合因子回测(需要数据,输出示例代码)
|
||||
easy-tdx pfactor backtest momentum_20d --n-stocks 10 --optimizer factor_weighted
|
||||
```
|
||||
|
||||
CLI 命令输出 Python API 示例代码,方便复制使用。完整的因子计算和组合回测建议通过 Python API 完成。
|
||||
|
||||
---
|
||||
|
||||
## 3. 完整工作流示例
|
||||
|
||||
从数据获取到组合回测再到归因分析的完整管道:
|
||||
|
||||
```python
|
||||
"""
|
||||
easy-tdx 量化研究完整工作流示例。
|
||||
|
||||
依赖: pip install easy-tdx
|
||||
"""
|
||||
|
||||
from easy_tdx import TdxClient, Market, KlineCategory
|
||||
from easy_tdx.factor import FactorEngine, FactorAnalyzer, preprocess
|
||||
from easy_tdx.portfolio import RebalanceEngine, FactorWeightedOptimizer
|
||||
from easy_tdx.backtest import BacktestEngine
|
||||
from easy_tdx.backtest.slippage import SquareRootSlippage
|
||||
from easy_tdx.backtest.execution import TWAPExecution
|
||||
from easy_tdx.backtest.attribution import AttributionAnalyzer
|
||||
|
||||
# ── 1. 数据获取 ──────────────────────────────────────
|
||||
client = TdxClient()
|
||||
stock_pool = ["000001", "000858", "600519", "600036", "601318",
|
||||
"000333", "002415", "601012", "600276", "000568"]
|
||||
|
||||
data = {}
|
||||
for code in stock_pool:
|
||||
market = Market.SH if code.startswith("6") else Market.SZ
|
||||
data[code] = client.get_security_bars(
|
||||
market, code, KlineCategory.DAY, 0, 500
|
||||
)
|
||||
print(f"获取 {len(data)} 只股票数据")
|
||||
|
||||
# ── 2. 因子计算 ──────────────────────────────────────
|
||||
engine = FactorEngine()
|
||||
factor_data = engine.compute_cross_section(
|
||||
data, ["momentum_20d", "volatility_20d", "rsi_14"]
|
||||
)
|
||||
print(f"截面因子数据: {len(factor_data)} 行")
|
||||
|
||||
# ── 3. 因子预处理 ─────────────────────────────────────
|
||||
clean = preprocess(
|
||||
factor_data,
|
||||
factor_names=["momentum_20d", "volatility_20d", "rsi_14"],
|
||||
steps=["winsorize", "zscore", "fill_missing"],
|
||||
)
|
||||
|
||||
# ── 4. 因子分析 ──────────────────────────────────────
|
||||
forward_returns = engine.compute_forward_returns(data, period=5)
|
||||
|
||||
for factor_name in ["momentum_20d", "volatility_20d", "rsi_14"]:
|
||||
analyzer = FactorAnalyzer(clean, forward_returns)
|
||||
report = analyzer.full_report(factor_name)
|
||||
print(f"\n── {factor_name} ──")
|
||||
print(f" IC均值: {report.mean_ic:.4f} ICIR: {report.icir:.4f}")
|
||||
print(f" 多头年化: {report.long_only_annual:.2%}")
|
||||
print(f" 多空夏普: {report.long_short_sharpe:.4f}")
|
||||
|
||||
# ── 5. 组合回测 ──────────────────────────────────────
|
||||
rebalancer = RebalanceEngine(
|
||||
optimizer=FactorWeightedOptimizer(),
|
||||
factor_name="momentum_20d",
|
||||
n_stocks=5,
|
||||
rebalance_freq="M",
|
||||
cash=1_000_000,
|
||||
)
|
||||
result = rebalancer.run(data, start_date=20230101, end_date=20240101)
|
||||
print(f"\n── 组合回测 ──")
|
||||
print(f" 总收益: {result.performance['total_return']:.2%}")
|
||||
print(f" 年化: {result.performance['annual_return']:.2%}")
|
||||
print(f" 最大回撤: {result.performance['max_drawdown']:.2%}")
|
||||
print(f" 夏普: {result.performance['sharpe']:.4f}")
|
||||
|
||||
# ── 6. 高级单策略回测(滑点 + 执行仿真)──────
|
||||
from easy_tdx.backtest import Strategy
|
||||
|
||||
class MomentumStrategy(Strategy):
|
||||
def init(self):
|
||||
pass
|
||||
def next(self):
|
||||
if self._bar_index < 20:
|
||||
return
|
||||
ret = (self.data.close[0] - self.data.close[-20]) / self.data.close[-20]
|
||||
if ret > 0.05 and self.position["size"] == 0:
|
||||
self.buy(size=0)
|
||||
elif ret < -0.03 and self.position["size"] > 0:
|
||||
self.sell(size=0)
|
||||
|
||||
bt_engine = BacktestEngine(
|
||||
MomentumStrategy,
|
||||
cash=500_000,
|
||||
slippage_model=SquareRootSlippage(impact_coeff=0.1),
|
||||
execution_model=TWAPExecution(n_bars=3),
|
||||
)
|
||||
# 选一只股票做回测
|
||||
bt_result = bt_engine.run(data["600519"])
|
||||
print(f"\n── 高级回测(600519)──")
|
||||
print(f" 总收益: {bt_result.performance['total_return']:.2%}")
|
||||
print(f" 夏普: {bt_result.performance['sharpe']:.4f}")
|
||||
|
||||
# ── 7. 归因分析 ──────────────────────────────────────
|
||||
att_analyzer = AttributionAnalyzer(bt_result.trades, bt_result.equity_curve)
|
||||
cost_report = att_analyzer.cost_attribution()
|
||||
print(f"\n── 成本归因 ──")
|
||||
print(f" 总交易成本: {cost_report.total_trade_cost:.0f} 元")
|
||||
print(f" 佣金: {cost_report.commission_cost:.0f}")
|
||||
print(f" 滑点: {cost_report.slippage_cost:.0f}")
|
||||
print(f" 印花税: {cost_report.stamp_tax_cost:.0f}")
|
||||
|
||||
print("\n完成。")
|
||||
client.close()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
+1
-309
@@ -18,12 +18,7 @@
|
||||
- [4.1 权重优化器](#41-权重优化器)
|
||||
- [4.2 风险模型](#42-风险模型)
|
||||
- [4.3 再平衡引擎](#43-再平衡引擎)
|
||||
- [5. 高级回测](#5-高级回测)
|
||||
- [5.1 滑点模型](#51-滑点模型)
|
||||
- [5.2 执行仿真](#52-执行仿真)
|
||||
- [5.3 归因分析](#53-归因分析)
|
||||
- [6. CLI 命令](#6-cli-命令)
|
||||
- [7. 完整工作流示例](#7-完整工作流示例)
|
||||
- [高级回测(滑点/执行仿真/归因)、CLI 与完整工作流](#高级回测滑点执行仿真归因cli-与完整工作流) → 见 [quantitative-advanced.md](./quantitative-advanced.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -309,309 +304,6 @@ for state in result.states[-5:]:
|
||||
|
||||
---
|
||||
|
||||
## 5. 高级回测
|
||||
|
||||
### 5.1 滑点模型
|
||||
|
||||
4 种可插拔滑点模型,替代原有固定滑点:
|
||||
|
||||
```python
|
||||
from easy_tdx.backtest import BacktestEngine
|
||||
from easy_tdx.backtest.slippage import (
|
||||
FixedSlippage,
|
||||
PercentSlippage,
|
||||
SquareRootSlippage,
|
||||
VolumeSlippage,
|
||||
)
|
||||
|
||||
# 1. 固定每股滑点(与旧行为一致)
|
||||
model1 = FixedSlippage(per_share=0.01)
|
||||
|
||||
# 2. 按金额百分比
|
||||
model2 = PercentSlippage(rate=0.001)
|
||||
|
||||
# 3. 方根市场冲击模型(Almgren-Chriss 简化版)
|
||||
# impact = sigma * sqrt(participation_rate) * price * size * coeff
|
||||
# A 股量化主流:参与率 >5% 时冲击显著
|
||||
model3 = SquareRootSlippage(impact_coeff=0.1)
|
||||
|
||||
# 4. 成交量比例滑点
|
||||
model4 = VolumeSlippage(base_bps=10.0)
|
||||
|
||||
# 在 BacktestEngine 中使用
|
||||
engine = BacktestEngine(
|
||||
MyStrategy,
|
||||
cash=1_000_000,
|
||||
slippage_model=SquareRootSlippage(impact_coeff=0.1),
|
||||
)
|
||||
result = engine.run(df)
|
||||
```
|
||||
|
||||
**模型选择建议**:
|
||||
|
||||
| 场景 | 推荐模型 | 参数 |
|
||||
|------|---------|------|
|
||||
| 快速原型 | `FixedSlippage` | `per_share=0.01` |
|
||||
| 中频策略 | `PercentSlippage` | `rate=0.001` |
|
||||
| 大额订单 | `SquareRootSlippage` | `impact_coeff=0.1` |
|
||||
| 低流动性股票 | `VolumeSlippage` | `base_bps=10.0` |
|
||||
|
||||
### 5.2 执行仿真
|
||||
|
||||
4 种执行模型,将单笔信号拆分为多笔子交易:
|
||||
|
||||
```python
|
||||
from easy_tdx.backtest.execution import (
|
||||
ImmediateExecution,
|
||||
TWAPExecution,
|
||||
VWAPExecution,
|
||||
LimitExecution,
|
||||
)
|
||||
|
||||
# 1. 即时成交(默认,与旧行为一致)
|
||||
exec1 = ImmediateExecution()
|
||||
|
||||
# 2. TWAP:时间加权平均价格,N 根 K 线均匀拆单
|
||||
exec2 = TWAPExecution(n_bars=5)
|
||||
|
||||
# 3. VWAP:成交量加权平均价格,按历史量分布拆单
|
||||
exec3 = VWAPExecution(n_bars=5, volume_lookback=20)
|
||||
|
||||
# 4. 限价单:目标价挂单,TTL 内未触发则放弃
|
||||
exec4 = LimitExecution(ttl_bars=5)
|
||||
|
||||
# 在 BacktestEngine 中使用
|
||||
engine = BacktestEngine(
|
||||
MyStrategy,
|
||||
cash=1_000_000,
|
||||
execution_model=TWAPExecution(n_bars=3),
|
||||
slippage_model=SquareRootSlippage(),
|
||||
)
|
||||
result = engine.run(df)
|
||||
```
|
||||
|
||||
**执行模型选择**:
|
||||
|
||||
| 场景 | 推荐模型 | 参数 |
|
||||
|------|---------|------|
|
||||
| 小额/快速验证 | `ImmediateExecution` | 默认 |
|
||||
| 大额建仓/平仓 | `TWAPExecution` | `n_bars=3~5` |
|
||||
| 追踪 VWAP 基准 | `VWAPExecution` | `n_bars=5` |
|
||||
| 精确入场价位 | `LimitExecution` | `ttl_bars=5` |
|
||||
|
||||
**TWAP vs VWAP 示例**:
|
||||
|
||||
```python
|
||||
# TWAP: 300 股拆成 3 笔 100 股,在 bar 1/2/3 以 close 执行
|
||||
engine = BacktestEngine(
|
||||
MyStrategy, cash=100_000,
|
||||
execution_model=TWAPExecution(n_bars=3),
|
||||
)
|
||||
|
||||
# VWAP: 按成交量分布拆 300 股 — 成交量大的 bar 分配更多
|
||||
engine = BacktestEngine(
|
||||
MyStrategy, cash=100_000,
|
||||
execution_model=VWAPExecution(n_bars=3, volume_lookback=20),
|
||||
)
|
||||
|
||||
# 限价单:在 50 元挂买入,5 根 K 线内 low <= 50 才成交
|
||||
class LimitBuyStrategy(Strategy):
|
||||
def init(self): pass
|
||||
def next(self):
|
||||
if self._bar_index == 0:
|
||||
self.buy(size=100, price=50.0) # 指定限价
|
||||
|
||||
engine = BacktestEngine(
|
||||
LimitBuyStrategy, cash=100_000,
|
||||
execution_model=LimitExecution(ttl_bars=5),
|
||||
)
|
||||
```
|
||||
|
||||
### 5.3 归因分析
|
||||
|
||||
从回测结果生成归因报告:
|
||||
|
||||
```python
|
||||
from easy_tdx.backtest import BacktestEngine
|
||||
from easy_tdx.backtest.attribution import AttributionAnalyzer
|
||||
|
||||
# 运行回测
|
||||
engine = BacktestEngine(MyStrategy, cash=1_000_000)
|
||||
result = engine.run(df)
|
||||
|
||||
# --- 成本归因 ---
|
||||
analyzer = AttributionAnalyzer(result.trades, result.equity_curve)
|
||||
cost_report = analyzer.cost_attribution()
|
||||
print(f"总收益: {cost_report.total_return:.2%}")
|
||||
print(f"总交易成本: {cost_report.total_trade_cost:.0f} 元")
|
||||
print(f" 佣金: {cost_report.commission_cost:.0f}")
|
||||
print(f" 滑点: {cost_report.slippage_cost:.0f}")
|
||||
print(f" 印花税: {cost_report.stamp_tax_cost:.0f}")
|
||||
|
||||
# --- Brinson 归因(需要基准)---
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
# 构造基准曲线(如沪深300)
|
||||
benchmark = pd.DataFrame({
|
||||
"datetime": result.equity_curve["datetime"],
|
||||
"total": np.linspace(100000, 108000, len(result.equity_curve)),
|
||||
})
|
||||
analyzer = AttributionAnalyzer(result.trades, result.equity_curve, benchmark=benchmark)
|
||||
brinson_report = analyzer.brinson_attribution()
|
||||
print(f"配置贡献: {brinson_report.allocation_return:.2%}")
|
||||
print(f"选股贡献: {brinson_report.selection_return:.2%}")
|
||||
print(f"交叉效应: {brinson_report.interaction_return:.2%}")
|
||||
|
||||
# --- 因子归因(需要因子数据)---
|
||||
exposures = pd.DataFrame({"momentum": [0.5, 0.3, 0.2], "quality": [0.1, -0.1, 0.0]})
|
||||
returns = pd.DataFrame({"momentum": [0.05, 0.03, 0.02], "quality": [0.01, -0.02, 0.0]})
|
||||
analyzer = AttributionAnalyzer(
|
||||
result.trades, result.equity_curve,
|
||||
factor_exposures=exposures, factor_returns=returns,
|
||||
)
|
||||
factor_report = analyzer.factor_attribution()
|
||||
for name, ret in factor_report.factor_returns.items():
|
||||
print(f" {name}: {ret:.4f}")
|
||||
print(f"特质收益: {factor_report.specific_return:.4f}")
|
||||
|
||||
# --- 完整报告(自动选择最佳归因模式)---
|
||||
full_report = analyzer.full_report()
|
||||
```
|
||||
|
||||
**归因模式优先级**:因子归因 > Brinson 归因 > 成本归因。`full_report()` 自动选择数据最完整的模式。
|
||||
|
||||
---
|
||||
|
||||
## 6. CLI 命令
|
||||
|
||||
```bash
|
||||
# 列出所有内置因子
|
||||
easy-tdx factor list --table
|
||||
|
||||
# 因子分析(需要数据,输出示例代码)
|
||||
easy-tdx factor analyze momentum_20d
|
||||
|
||||
# 组合因子回测(需要数据,输出示例代码)
|
||||
easy-tdx pfactor backtest momentum_20d --n-stocks 10 --optimizer factor_weighted
|
||||
```
|
||||
|
||||
CLI 命令输出 Python API 示例代码,方便复制使用。完整的因子计算和组合回测建议通过 Python API 完成。
|
||||
|
||||
---
|
||||
|
||||
## 7. 完整工作流示例
|
||||
|
||||
从数据获取到组合回测再到归因分析的完整管道:
|
||||
|
||||
```python
|
||||
"""
|
||||
easy-tdx 量化研究完整工作流示例。
|
||||
|
||||
依赖: pip install easy-tdx
|
||||
"""
|
||||
|
||||
from easy_tdx import TdxClient, Market, KlineCategory
|
||||
from easy_tdx.factor import FactorEngine, FactorAnalyzer, preprocess
|
||||
from easy_tdx.portfolio import RebalanceEngine, FactorWeightedOptimizer
|
||||
from easy_tdx.backtest import BacktestEngine
|
||||
from easy_tdx.backtest.slippage import SquareRootSlippage
|
||||
from easy_tdx.backtest.execution import TWAPExecution
|
||||
from easy_tdx.backtest.attribution import AttributionAnalyzer
|
||||
|
||||
# ── 1. 数据获取 ──────────────────────────────────────
|
||||
client = TdxClient()
|
||||
stock_pool = ["000001", "000858", "600519", "600036", "601318",
|
||||
"000333", "002415", "601012", "600276", "000568"]
|
||||
|
||||
data = {}
|
||||
for code in stock_pool:
|
||||
market = Market.SH if code.startswith("6") else Market.SZ
|
||||
data[code] = client.get_security_bars(
|
||||
market, code, KlineCategory.DAY, 0, 500
|
||||
)
|
||||
print(f"获取 {len(data)} 只股票数据")
|
||||
|
||||
# ── 2. 因子计算 ──────────────────────────────────────
|
||||
engine = FactorEngine()
|
||||
factor_data = engine.compute_cross_section(
|
||||
data, ["momentum_20d", "volatility_20d", "rsi_14"]
|
||||
)
|
||||
print(f"截面因子数据: {len(factor_data)} 行")
|
||||
|
||||
# ── 3. 因子预处理 ─────────────────────────────────────
|
||||
clean = preprocess(
|
||||
factor_data,
|
||||
factor_names=["momentum_20d", "volatility_20d", "rsi_14"],
|
||||
steps=["winsorize", "zscore", "fill_missing"],
|
||||
)
|
||||
|
||||
# ── 4. 因子分析 ──────────────────────────────────────
|
||||
forward_returns = engine.compute_forward_returns(data, period=5)
|
||||
|
||||
for factor_name in ["momentum_20d", "volatility_20d", "rsi_14"]:
|
||||
analyzer = FactorAnalyzer(clean, forward_returns)
|
||||
report = analyzer.full_report(factor_name)
|
||||
print(f"\n── {factor_name} ──")
|
||||
print(f" IC均值: {report.mean_ic:.4f} ICIR: {report.icir:.4f}")
|
||||
print(f" 多头年化: {report.long_only_annual:.2%}")
|
||||
print(f" 多空夏普: {report.long_short_sharpe:.4f}")
|
||||
|
||||
# ── 5. 组合回测 ──────────────────────────────────────
|
||||
rebalancer = RebalanceEngine(
|
||||
optimizer=FactorWeightedOptimizer(),
|
||||
factor_name="momentum_20d",
|
||||
n_stocks=5,
|
||||
rebalance_freq="M",
|
||||
cash=1_000_000,
|
||||
)
|
||||
result = rebalancer.run(data, start_date=20230101, end_date=20240101)
|
||||
print(f"\n── 组合回测 ──")
|
||||
print(f" 总收益: {result.performance['total_return']:.2%}")
|
||||
print(f" 年化: {result.performance['annual_return']:.2%}")
|
||||
print(f" 最大回撤: {result.performance['max_drawdown']:.2%}")
|
||||
print(f" 夏普: {result.performance['sharpe']:.4f}")
|
||||
|
||||
# ── 6. 高级单策略回测(滑点 + 执行仿真)──────
|
||||
from easy_tdx.backtest import Strategy
|
||||
|
||||
class MomentumStrategy(Strategy):
|
||||
def init(self):
|
||||
pass
|
||||
def next(self):
|
||||
if self._bar_index < 20:
|
||||
return
|
||||
ret = (self.data.close[0] - self.data.close[-20]) / self.data.close[-20]
|
||||
if ret > 0.05 and self.position["size"] == 0:
|
||||
self.buy(size=0)
|
||||
elif ret < -0.03 and self.position["size"] > 0:
|
||||
self.sell(size=0)
|
||||
|
||||
bt_engine = BacktestEngine(
|
||||
MomentumStrategy,
|
||||
cash=500_000,
|
||||
slippage_model=SquareRootSlippage(impact_coeff=0.1),
|
||||
execution_model=TWAPExecution(n_bars=3),
|
||||
)
|
||||
# 选一只股票做回测
|
||||
bt_result = bt_engine.run(data["600519"])
|
||||
print(f"\n── 高级回测(600519)──")
|
||||
print(f" 总收益: {bt_result.performance['total_return']:.2%}")
|
||||
print(f" 夏普: {bt_result.performance['sharpe']:.4f}")
|
||||
|
||||
# ── 7. 归因分析 ──────────────────────────────────────
|
||||
att_analyzer = AttributionAnalyzer(bt_result.trades, bt_result.equity_curve)
|
||||
cost_report = att_analyzer.cost_attribution()
|
||||
print(f"\n── 成本归因 ──")
|
||||
print(f" 总交易成本: {cost_report.total_trade_cost:.0f} 元")
|
||||
print(f" 佣金: {cost_report.commission_cost:.0f}")
|
||||
print(f" 滑点: {cost_report.slippage_cost:.0f}")
|
||||
print(f" 印花税: {cost_report.stamp_tax_cost:.0f}")
|
||||
|
||||
print("\n完成。")
|
||||
client.close()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 向后兼容
|
||||
|
||||
|
||||
Reference in New Issue
Block a user