mirror of
https://ghfast.top/https://github.com/aeroxw/easy-tdx.git
synced 2026-09-12 15:44:15 +08:00
docs: add backtest engine design spec
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
626d0aae44
commit
8d68e9c094
@@ -17,7 +17,62 @@
|
||||
"Bash(pip list *)",
|
||||
"Bash(uv run *)",
|
||||
"Bash(git stash *)",
|
||||
"Bash(dir /s /b src\\\\xmtdx)"
|
||||
"Bash(dir /s /b src\\\\xmtdx)",
|
||||
"Bash(git remote *)",
|
||||
"Bash(uv --version)",
|
||||
"Bash(git rm *)",
|
||||
"mcp__plugin_context7_context7__resolve-library-id",
|
||||
"mcp__plugin_context7_context7__query-docs",
|
||||
"Bash(grep -E \"\\\\.py$\")",
|
||||
"Bash(awk '{added+=$1; deleted+=$2} END {print \"+\" added \" -\" deleted}')",
|
||||
"Bash(find D:/python/easty_tdx/examples/1[1-7]* -name \"*.py\")",
|
||||
"Bash(easy-tdx ping *)",
|
||||
"Bash(easy-tdx version *)",
|
||||
"Bash(easy-tdx kline *)",
|
||||
"Bash(easy-tdx quote *)",
|
||||
"Bash(easy-tdx tick *)",
|
||||
"Bash(easy-tdx transaction *)",
|
||||
"Bash(easy-tdx auction *)",
|
||||
"Bash(easy-tdx quote-list *)",
|
||||
"Bash(easy-tdx board-list *)",
|
||||
"Bash(easy-tdx board-members *)",
|
||||
"Bash(easy-tdx belong-board *)",
|
||||
"Bash(easy-tdx capital-flow *)",
|
||||
"Bash(echo \"EXIT CODE: $?\")",
|
||||
"Bash(easy-tdx unusual *)",
|
||||
"Bash(easy-tdx market-stat *)",
|
||||
"Bash(easy-tdx server-info *)",
|
||||
"Bash(easy-tdx symbol-info *)",
|
||||
"Bash(easy-tdx ex *)",
|
||||
"WebSearch",
|
||||
"mcp__zread__search_doc",
|
||||
"mcp__zread__read_file",
|
||||
"Bash(git status *)",
|
||||
"Bash(pip show *)",
|
||||
"Bash(git tag *)",
|
||||
"Bash(twine upload *)",
|
||||
"Bash(pip index *)",
|
||||
"Bash(curl -s https://pypi.org/pypi/easy-tdx/json)",
|
||||
"Bash(curl -sI \"https://files.pythonhosted.org/packages/py3/e/easy-tdx/easy_tdx-1.2.0-py3-none-any.whl\")",
|
||||
"Bash(gh run *)",
|
||||
"Bash(easy-tdx *)",
|
||||
"mcp__web-search-prime__web_search_prime",
|
||||
"mcp__plugin_compound-engineering_context7__resolve-library-id",
|
||||
"mcp__plugin_compound-engineering_context7__query-docs",
|
||||
"Bash(pip uninstall *)",
|
||||
"Bash(xargs grep -l \"Command\\\\|Request\\\\|Response\")",
|
||||
"Bash(quotes/stock_xdxr_info.go)",
|
||||
"Bash(mypy src/)",
|
||||
"Bash(ruff format *)",
|
||||
"mcp__plugin_episodic-memory_episodic-memory__search",
|
||||
"Bash(echo \"FK/\")",
|
||||
"Bash(git pull *)",
|
||||
"mcp__playwright__browser_navigate",
|
||||
"mcp__playwright__browser_snapshot",
|
||||
"Bash(gh auth *)",
|
||||
"Bash(rm -rf easy_tdx.wiki)",
|
||||
"Bash(gh repo *)",
|
||||
"Bash(gh api *)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,3 +13,4 @@ venv/
|
||||
.omc/
|
||||
uv.lock
|
||||
docs/_build/
|
||||
FK/
|
||||
|
||||
@@ -39,6 +39,8 @@ easy-tdx 要做的事很简单:**把机构的数据锁砸开,扔到每个普
|
||||
随便用,随便改,随便分发。
|
||||
**数据面前,人人平等。**
|
||||
|
||||
📖 **详细用法请查看 [GitHub Wiki](https://github.com/handsomejustin/easy_tdx/wiki)**
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
|
||||
@@ -0,0 +1,424 @@
|
||||
# Backtest Engine Design — easy-tdx
|
||||
|
||||
**Date:** 2026-06-09
|
||||
**Status:** Approved
|
||||
**Module:** `easy_tdx.backtest`
|
||||
**Priority:** P0 — 量化工具链全栈的第一块拼图
|
||||
|
||||
---
|
||||
|
||||
## 1. 目标
|
||||
|
||||
为 easy-tdx 新增自建回测引擎模块,让用户能基于 easy-tdx 获取的 K 线数据执行策略回测、查看绩效报告。
|
||||
|
||||
### 核心约束
|
||||
|
||||
- 纯计算模块,与 `chanlun` 同级,零网络依赖
|
||||
- 仅依赖 `pandas`/`numpy`(项目已有),不引入第三方回测库
|
||||
- 接收 easy-tdx 标准 DataFrame(`datetime, open, close, high, low, vol, amount`)
|
||||
- 双模式策略定义:Python 类继承 + DSL 公式语法
|
||||
- v1 只实现向量化执行路径(日级策略),架构预留事件驱动扩展点
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构
|
||||
|
||||
### 2.1 文件结构
|
||||
|
||||
```
|
||||
src/easy_tdx/backtest/
|
||||
├── __init__.py # 公开 API 导出
|
||||
├── strategy.py # Strategy 基类 + StrategyDataProxy + IndicatorProvider
|
||||
├── dsl.py # DSL 解析器 + @dsl_strategy 装饰器 + 字符串 DSL 编译
|
||||
├── engine.py # BacktestEngine(向量化执行路径)
|
||||
├── orders.py # OrderSimulator(撮合规则)
|
||||
├── portfolio.py # PortfolioTracker(持仓/资金曲线)
|
||||
├── performance.py # PerformanceAnalyzer(绩效指标计算)
|
||||
├── types.py # Trade / Position / Signal / BacktestResult 数据类
|
||||
└── cli.py # CLI 集成(easy-tdx backtest ...)
|
||||
```
|
||||
|
||||
### 2.2 模块交互流
|
||||
|
||||
```
|
||||
easy_tdx MacClient.get_stock_kline() → DataFrame
|
||||
│
|
||||
▼
|
||||
BacktestEngine(strategy, cash=100000)
|
||||
│
|
||||
┌───────────┼───────────┐
|
||||
▼ ▼ ▼
|
||||
Strategy DSL Parser OrderSimulator
|
||||
(Python类) (公式语法) (撮合规则)
|
||||
│ │ │
|
||||
└─────┬─────┘ │
|
||||
▼ │
|
||||
Signal (bool mask) │
|
||||
│ │
|
||||
▼ ▼
|
||||
PortfolioTracker ←────┘
|
||||
│
|
||||
▼
|
||||
PerformanceAnalyzer
|
||||
│
|
||||
▼
|
||||
BacktestResult
|
||||
(绩效指标 + 资金曲线 + 交易记录)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心数据类型
|
||||
|
||||
### 3.1 Signal
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class Signal:
|
||||
datetime: int
|
||||
direction: Literal["BUY", "SELL"]
|
||||
size: float # 0 = 全仓/清仓
|
||||
price: float | None # None = 市价
|
||||
stop_loss: float | None
|
||||
take_profit: float | None
|
||||
```
|
||||
|
||||
### 3.2 Trade
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class Trade:
|
||||
datetime: int
|
||||
direction: Literal["BUY", "SELL"]
|
||||
size: float
|
||||
price: float
|
||||
commission: float
|
||||
slippage: float
|
||||
pnl: float # 仅平仓时计算
|
||||
```
|
||||
|
||||
### 3.3 Position
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class Position:
|
||||
datetime: int
|
||||
size: float # 正=多头,负=空头,0=空仓
|
||||
avg_price: float
|
||||
market_value: float
|
||||
unrealized_pnl: float
|
||||
```
|
||||
|
||||
### 3.4 BacktestResult
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class BacktestResult:
|
||||
performance: dict[str, float]
|
||||
equity_curve: pd.DataFrame # datetime, cash, position_value, total, drawdown, drawdown_pct
|
||||
trades: pd.DataFrame # datetime, direction, size, price, commission, pnl
|
||||
positions: pd.DataFrame # datetime, size, avg_price, market_value, unrealized_pnl
|
||||
config: dict
|
||||
```
|
||||
|
||||
方法:
|
||||
- `to_json() → str`
|
||||
- `to_dict() → dict`
|
||||
- `summary() → None`(打印概要)
|
||||
|
||||
---
|
||||
|
||||
## 4. Strategy 基类
|
||||
|
||||
### 4.1 接口定义
|
||||
|
||||
```python
|
||||
class Strategy(ABC):
|
||||
def init(self) -> None:
|
||||
"""注册指标。策略初始化时调用一次。"""
|
||||
pass
|
||||
|
||||
def next(self) -> None:
|
||||
"""每根 K 线调用。在此生成买卖信号。"""
|
||||
pass
|
||||
|
||||
def I(self, func: Callable, *args, **kwargs) -> np.ndarray:
|
||||
"""注册指标函数。init() 后一次性计算,返回完整数组。"""
|
||||
...
|
||||
|
||||
def buy(self, size: float = 0, price: float | None = None,
|
||||
stop_loss: float | None = None, take_profit: float | None = None) -> None:
|
||||
"""买入。size=0 全仓。"""
|
||||
...
|
||||
|
||||
def sell(self, size: float = 0, price: float | None = None,
|
||||
stop_loss: float | None = None, take_profit: float | None = None) -> None:
|
||||
"""卖出。size=0 清仓。"""
|
||||
...
|
||||
|
||||
@property
|
||||
def data(self) -> StrategyDataProxy: ...
|
||||
|
||||
@property
|
||||
def position(self) -> Position: ...
|
||||
```
|
||||
|
||||
### 4.2 StrategyDataProxy
|
||||
|
||||
```python
|
||||
class StrategyDataProxy:
|
||||
"""K 线数据代理。支持 .close[0](当前)、.close[-1](前一根)。"""
|
||||
@property
|
||||
def open(self) -> _SeriesAccessor: ...
|
||||
@property
|
||||
def close(self) -> _SeriesAccessor: ...
|
||||
@property
|
||||
def high(self) -> _SeriesAccessor: ...
|
||||
@property
|
||||
def low(self) -> _SeriesAccessor: ...
|
||||
@property
|
||||
def vol(self) -> _SeriesAccessor: ...
|
||||
@property
|
||||
def amount(self) -> _SeriesAccessor: ...
|
||||
|
||||
class _SeriesAccessor:
|
||||
"""[0] 当前值、[-1] 前一根、切片。"""
|
||||
def __getitem__(self, key: int) -> float: ...
|
||||
def __len__(self) -> int: ...
|
||||
```
|
||||
|
||||
### 4.3 Python 类策略示例
|
||||
|
||||
```python
|
||||
class MACrossStrategy(Strategy):
|
||||
def init(self):
|
||||
self.ma5 = self.I(MA, self.data.close, 5)
|
||||
self.ma20 = self.I(MA, self.data.close, 20)
|
||||
|
||||
def next(self):
|
||||
if crossover(self.ma5, self.ma20):
|
||||
self.buy(size=100)
|
||||
elif crossover(self.ma20, self.ma5):
|
||||
self.sell(size=100)
|
||||
|
||||
engine = BacktestEngine(strategy=MACrossStrategy, cash=100000)
|
||||
result = engine.run(df)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. DSL 策略定义
|
||||
|
||||
### 5.1 设计边界
|
||||
|
||||
| 能做 | 不做 |
|
||||
|------|------|
|
||||
| 指标交叉、比较、逻辑组合 | 循环、变量赋值、函数定义 |
|
||||
| 内置常用函数(CROSS, ABOVE, BELOW, BETWEEN) | 自定义控制流 |
|
||||
| 参数化(可调窗口期) | 图灵完备 |
|
||||
|
||||
超出 DSL 能力的——直接用 Python 类。
|
||||
|
||||
### 5.2 两种 DSL 模式
|
||||
|
||||
**Python 装饰器模式**:
|
||||
|
||||
```python
|
||||
from easy_tdx.backtest import dsl_strategy
|
||||
|
||||
@dsl_strategy
|
||||
def dual_ma(df):
|
||||
buy = CROSS(MA(df.close, 5), MA(df.close, 20))
|
||||
sell = CROSS(MA(df.close, 20), MA(df.close, 5))
|
||||
return buy, sell
|
||||
```
|
||||
|
||||
**字符串模式**(CLI 用):
|
||||
|
||||
```bash
|
||||
easy-tdx backtest SH 600519 --strategy "CROSS(MA(5),MA(20))" --cash 100000 --table
|
||||
```
|
||||
|
||||
### 5.3 内置 DSL 函数
|
||||
|
||||
复用 `MyTT.py` 已有实现:
|
||||
|
||||
| 函数 | 签名 | 含义 |
|
||||
|------|------|------|
|
||||
| `MA(series, n)` | `(ndarray, int) → ndarray` | 简单移动平均 |
|
||||
| `EMA(series, n)` | `(ndarray, int) → ndarray` | 指数移动平均 |
|
||||
| `RSI(series, n)` | `(ndarray, int) → ndarray` | 相对强弱 |
|
||||
| `BOLL(series, n, k)` | `(ndarray, int, float) → tuple` | 布林带 |
|
||||
| `MACD(series, fast, slow, signal)` | `(ndarray, ...) → tuple` | MACD |
|
||||
| `CROSS(a, b)` | `(ndarray, ndarray) → ndarray[bool]` | 上穿检测 |
|
||||
| `REF(series, n)` | `(ndarray, int) → ndarray` | 前 n 期值 |
|
||||
| `HHV(series, n)` | `(ndarray, int) → ndarray` | n 期最高 |
|
||||
| `LLV(series, n)` | `(ndarray, int) → ndarray` | n 期最低 |
|
||||
| `BETWEEN(x, a, b)` | `(ndarray, ...) → ndarray[bool]` | 区间判断 |
|
||||
| `COUNT(cond, n)` | `(ndarray[bool], int) → ndarray` | n 期满足条件次数 |
|
||||
|
||||
### 5.4 DSL 编译器
|
||||
|
||||
`DSLCompiler.compile(func)` 流程:
|
||||
1. 调用 `func(mock_df)` 捕获 DSL 函数调用
|
||||
2. 记录 `(buy_mask, sell_mask)` 信号生成规则
|
||||
3. 动态生成 Strategy 子类
|
||||
|
||||
引擎侧优化:DSL 策略不逐 Bar 调用 `next()`,直接用 bool mask 一次性生成全部 Signal。
|
||||
|
||||
---
|
||||
|
||||
## 6. 引擎执行流
|
||||
|
||||
### 6.1 BacktestEngine 构造参数
|
||||
|
||||
```python
|
||||
class BacktestEngine:
|
||||
def __init__(
|
||||
self,
|
||||
strategy: type[Strategy] | Strategy,
|
||||
cash: float = 100000.0,
|
||||
commission: float = 0.0003,
|
||||
min_commission: float = 5.0,
|
||||
stamp_tax: float = 0.001,
|
||||
slippage: float = 0.0,
|
||||
execution: str = "next_open", # "next_open" | "next_close" | "this_close" | "worst" | "best"
|
||||
position_mode: str = "full", # "full" | "fixed" | "percent" | "signal_only"
|
||||
benchmark: pd.DataFrame | None = None,
|
||||
):
|
||||
...
|
||||
```
|
||||
|
||||
### 6.2 四步执行管道
|
||||
|
||||
1. **信号生成**:DSL → bool mask;Python 类 → trace next() 生成 mask
|
||||
2. **信号→订单**(OrderSimulator):根据 execution 规则确定成交价,根据仓位模式确定量
|
||||
3. **持仓追踪**(PortfolioTracker):逐 Bar 更新现金/持仓/市值/回撤
|
||||
4. **绩效分析**(PerformanceAnalyzer):从资金曲线计算全部指标
|
||||
|
||||
Step 2 是唯一需要逐行处理的步骤(仓位依赖前一 Bar 状态)。其余步骤全向量化。
|
||||
|
||||
### 6.3 OrderSimulator 成交价规则
|
||||
|
||||
| 模式 | 说明 |
|
||||
|------|------|
|
||||
| `next_open`(默认) | 下一根 K 线开盘价成交,最真实 |
|
||||
| `next_close` | 下一根 K 线收盘价成交 |
|
||||
| `this_close` | 当根 K 线收盘价成交(有未来函数风险,标注警告) |
|
||||
| `worst` | 对投资者最差价格(买入取 high,卖出取 low) |
|
||||
| `best` | 对投资者最优价格(买入取 low,卖出取 high) |
|
||||
|
||||
### 6.4 仓位管理规则
|
||||
|
||||
| 模式 | 说明 |
|
||||
|------|------|
|
||||
| `full`(默认) | 买入用全部现金,卖出清仓 |
|
||||
| `fixed` | 每次固定股数 |
|
||||
| `percent` | 每次用总资产的 N% |
|
||||
| `signal_only` | 只生成信号,不模拟仓位 |
|
||||
|
||||
### 6.5 费用模型
|
||||
|
||||
- 佣金:`max(size * price * commission_rate, min_commission)`,买卖双向
|
||||
- 印花税:`size * price * stamp_tax_rate`,仅卖出
|
||||
- 滑点:`size * slippage_per_share`
|
||||
|
||||
### 6.6 多策略批量回测
|
||||
|
||||
```python
|
||||
# 多只股票
|
||||
results = engine.run_many({
|
||||
"SH600519": df_519,
|
||||
"SZ000858": df_858,
|
||||
})
|
||||
# → dict[str, BacktestResult]
|
||||
|
||||
# 参数扫描
|
||||
results = engine.run_grid(df, params={
|
||||
"short": [5, 10, 15],
|
||||
"long": [20, 30, 60],
|
||||
})
|
||||
# → list[GridResult],支持 .sort_by("sharpe").to_table()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 绩效指标
|
||||
|
||||
| 指标 | key | 算法 |
|
||||
|------|-----|------|
|
||||
| 总收益率 | `total_return` | `(total[-1] / total[0]) - 1` |
|
||||
| 年化收益率 | `annual_return` | `(1 + r) ** (252/n) - 1` |
|
||||
| 最大回撤 | `max_drawdown` | `max((peak - total) / peak)` |
|
||||
| 最大回撤天数 | `max_dd_duration` | 首次新高 - 回撤起点 |
|
||||
| 夏普比率 | `sharpe` | `(mean(ret) - rf/252) / std(ret) * sqrt(252)` |
|
||||
| 索提诺比率 | `sortino` | 分母只用负收益标准差 |
|
||||
| 卡玛比率 | `calmar` | `annual_return / max_drawdown` |
|
||||
| 总交易次数 | `total_trades` | `len(trades)` |
|
||||
| 盈利/亏损次数 | `win_trades` / `lose_trades` | `trade_pnl > 0 / <= 0` |
|
||||
| 胜率 | `win_rate` | `win_trades / total_trades` |
|
||||
| 盈亏比 | `profit_factor` | `sum(win_pnl) / abs(sum(lose_pnl))` |
|
||||
| 平均盈利/亏损 | `avg_win` / `avg_loss` | 盈利/亏损交易均值 |
|
||||
| 最大单笔盈亏 | `max_win` / `max_loss` | 单笔极值 |
|
||||
| 平均持仓天数 | `avg_holding_days` | 买入到卖出的 Bar 数均值 |
|
||||
| 收益波动率 | `volatility` | `std(daily_ret) * sqrt(252)` |
|
||||
| 基准超额收益 | `alpha` | 策略收益 - 基准收益(需 benchmark) |
|
||||
| 信息比率 | `information_ratio` | 超额收益均值 / 跟踪误差(需 benchmark) |
|
||||
|
||||
---
|
||||
|
||||
## 8. CLI 集成
|
||||
|
||||
### 8.1 命令
|
||||
|
||||
```bash
|
||||
# DSL 字符串模式
|
||||
easy-tdx backtest SH 600519 --strategy "CROSS(MA(5),MA(20))" --cash 100000 --table
|
||||
|
||||
# DSL 文件模式
|
||||
easy-tdx backtest SH 600519 --strategy-file my_strategy.py --cash 100000
|
||||
|
||||
# 参数化
|
||||
easy-tdx backtest SH 600519 --strategy "CROSS(MA({short}),MA({long}))" --params short=5,long=20
|
||||
|
||||
# 指定周期/复权
|
||||
easy-tdx backtest SH 600519 --strategy "CROSS(MA(5),MA(20))" --period 5MIN --adjust QFQ
|
||||
|
||||
# 参数扫描
|
||||
easy-tdx backtest SH 600519 --strategy "CROSS(MA({short}),MA({long}))" \
|
||||
--grid short=5,10,15 --grid long=20,30,60 --sort-by sharpe --table
|
||||
|
||||
# 输出 CSV
|
||||
easy-tdx backtest SH 600519 --strategy "CROSS(MA(5),MA(20))" --output csv
|
||||
```
|
||||
|
||||
### 8.2 输出格式
|
||||
|
||||
默认 JSON,`--table` 切换表格,`--output csv` 输出 CSV。与现有 CLI 行为一致。
|
||||
|
||||
---
|
||||
|
||||
## 9. 测试计划
|
||||
|
||||
```
|
||||
tests/unit/test_backtest_strategy.py # Strategy 基类 + 指标注入
|
||||
tests/unit/test_backtest_dsl.py # DSL 解析 + 编译
|
||||
tests/unit/test_backtest_engine.py # 引擎核心(信号→成交→持仓)
|
||||
tests/unit/test_backtest_orders.py # 撮合规则(5 种 execution 模式)
|
||||
tests/unit/test_backtest_portfolio.py # 持仓追踪 + 资金曲线
|
||||
tests/unit/test_backtest_performance.py # 绩效计算(手工验证已知结果)
|
||||
tests/unit/test_backtest_cli.py # CLI 命令(click test runner)
|
||||
```
|
||||
|
||||
全部离线测试,使用手工构造的 DataFrame fixture,零网络依赖。
|
||||
|
||||
---
|
||||
|
||||
## 10. 未来扩展点(v1 不实现,架构不堵死)
|
||||
|
||||
- 事件驱动执行路径(支持日内策略、逐 tick 推演)
|
||||
- 多品种组合回测(Portfolio 级别,同时持有多只股票)
|
||||
- 风控模块(最大回撤止损、单笔止损、仓位上限)
|
||||
- 实时模拟交易(Strategy 基类接口可直接迁移)
|
||||
- 与缠论模块深度集成(策略可直接引用笔/中枢/买卖点信号)
|
||||
- 可视化(K 线 + 买卖点标注 + 资金曲线)
|
||||
@@ -321,4 +321,14 @@ def BIAS_SIGNAL(CLOSE, P=10, M=30): #乖离率信号指标:M日乖离 + 短/
|
||||
X_LMA = MA(X, M) #长周期信号线 MA(X,M)
|
||||
return RD(X), RD(S_SMA), RD(X_LMA)
|
||||
|
||||
def FK(CLOSE): #FK趋势指标:快线EMA(2)与斜率外推慢线EMA(42)比较
|
||||
fast = EMA(CLOSE, 2)
|
||||
slow = EMA(SLOPE(CLOSE, 21) * 20 + CLOSE, 42)
|
||||
return fast > slow
|
||||
|
||||
def OUTPERFORM_20D(CLOSE, INDEX_CLOSE): #20日相对强度:个股涨幅跑赢大盘返回1,否则返回0
|
||||
stock_ret = (CLOSE - REF(CLOSE, 20)) / REF(CLOSE, 20)
|
||||
index_ret = (INDEX_CLOSE - REF(INDEX_CLOSE, 20)) / REF(INDEX_CLOSE, 20)
|
||||
return IF(stock_ret > index_ret, 1, 0)
|
||||
|
||||
#望大家能提交更多指标和函数 https://github.com/mpquant/MyTT
|
||||
|
||||
+14
-10
@@ -1,12 +1,16 @@
|
||||
"""响应帧头解析与 zlib 解压。
|
||||
|
||||
响应帧格式(16 字节固定头 + body):
|
||||
struct "<IIIHH"
|
||||
偏移 0: I (4字节) — 未知
|
||||
偏移 4: I (4字节) — 未知
|
||||
偏移 8: I (4字节) — 未知
|
||||
响应帧格式(16 字节固定头 + body),字节级结构(gotdx 交叉验证):
|
||||
偏移 0: I (4字节) — magic = 7654321 (0x0074CBB1),协议标识
|
||||
偏移 4: B (1字节) — ZipFlag:bit4=1 表示 body 已压缩,0x0C=未压缩, 0x1C=已压缩
|
||||
偏移 5: I (4字节) — SeqID:请求 bytes 1-4 的回显(命令标识)
|
||||
偏移 9: B (1字节) — 保留(观察到恒为 0x00)
|
||||
偏移 10: H (2字节) — Method:请求 bytes 10-11 的回显
|
||||
偏移 12: H (2字节) — zipsize(body 实际长度)
|
||||
偏移 14: H (2字节) — unzipsize(解压后长度;等于 zipsize 表示未压缩)
|
||||
|
||||
兼容说明:使用 IIIHH 解码可正确提取 zipsize/unzipsize。前三个 uint32 中:
|
||||
u0 = magic, u1 = ZipFlag(1B) + SeqID(3B 低字节), u2 = SeqID(1B 高字节) + 保留(1B) + Method(2B)
|
||||
"""
|
||||
|
||||
import zlib
|
||||
@@ -21,22 +25,22 @@ _HEADER_FMT = "<IIIHH"
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FrameHeader:
|
||||
unknown_0: int
|
||||
unknown_1: int
|
||||
unknown_2: int
|
||||
magic: int # 协议魔数,恒为 7654321
|
||||
seq_id: int # ZipFlag(1B) + 请求 bytes 1-4 回显(3B)
|
||||
method: int # 请求回显(1B) + 保留(1B) + Method(2B)
|
||||
zipsize: int
|
||||
unzipsize: int
|
||||
|
||||
|
||||
def parse_header(buf: bytes) -> FrameHeader:
|
||||
"""解析 16 字节响应帧头。"""
|
||||
u0, u1, u2, zipsize, unzipsize = unpack_from(
|
||||
magic, seq_id, method, zipsize, unzipsize = unpack_from(
|
||||
_HEADER_FMT,
|
||||
buf,
|
||||
0,
|
||||
"frame header",
|
||||
)
|
||||
return FrameHeader(u0, u1, u2, zipsize, unzipsize)
|
||||
return FrameHeader(magic, seq_id, method, zipsize, unzipsize)
|
||||
|
||||
|
||||
def decompress_body(header: FrameHeader, raw_body: bytes) -> bytes:
|
||||
|
||||
@@ -39,10 +39,10 @@ class GetSecurityListCmd(BaseCommand[list[SecurityInfo]]):
|
||||
code_bytes,
|
||||
volunit,
|
||||
name_bytes,
|
||||
_unknown1, # 4字节,含义未明
|
||||
_unknown1, # 4字节,排序/分组字段(非用户可见数据)
|
||||
decimal_point,
|
||||
pre_close_raw,
|
||||
_unknown2, # 4字节,含义未明
|
||||
_unknown2, # 4字节,私有时间戳(非用户可见数据)
|
||||
) = struct.unpack("<6sH8s4sBI4s", raw)
|
||||
|
||||
code = code_bytes.decode("utf-8", errors="replace").rstrip("\x00")
|
||||
|
||||
@@ -100,8 +100,8 @@ class GetSecurityQuotesCmd(BaseCommand[list[SecurityQuote]]):
|
||||
s_vol, pos = get_price(body, pos)
|
||||
b_vol, pos = get_price(body, pos)
|
||||
|
||||
unknown_2, pos = get_price(body, pos)
|
||||
unknown_3, pos = get_price(body, pos)
|
||||
unknown_2, pos = get_price(body, pos) # IndexOpenAmount(指数)/舍入残差(个股)
|
||||
unknown_3, pos = get_price(body, pos) # StockOpenAmount(个股)/负值(指数)
|
||||
|
||||
# 五档买盘
|
||||
bid1_d, pos = get_price(body, pos)
|
||||
@@ -129,8 +129,8 @@ class GetSecurityQuotesCmd(BaseCommand[list[SecurityQuote]]):
|
||||
bv5, pos = get_price(body, pos)
|
||||
av5, pos = get_price(body, pos)
|
||||
|
||||
# 尾部:2字节 H + 4个 get_price + 2字节 h + 2字节 H
|
||||
(unknown_4,) = unpack_from("<H", body, pos, "security_quotes tail flag")
|
||||
# 尾部:2字节 H(交易状态标志,0x8020=停牌)+ 4个 get_price + 2字节 h + 2字节 H
|
||||
(trading_status,) = unpack_from("<H", body, pos, "security_quotes tail flag")
|
||||
pos += 2
|
||||
unknown_5, pos = get_price(body, pos)
|
||||
unknown_6, pos = get_price(body, pos)
|
||||
@@ -196,6 +196,8 @@ class GetSecurityQuotesCmd(BaseCommand[list[SecurityQuote]]):
|
||||
unknown_7=unknown_7,
|
||||
unknown_8=unknown_8,
|
||||
server_time=_format_server_time(unknown_0),
|
||||
trading_status=trading_status,
|
||||
open_amount=unknown_3 * 100.0,
|
||||
_raw=body[record_start:pos],
|
||||
)
|
||||
)
|
||||
|
||||
@@ -9,7 +9,10 @@ from .enums import Market
|
||||
class SecurityQuote:
|
||||
"""单只股票实时五档行情。
|
||||
|
||||
带 unknown_ 前缀的字段为协议中尚未明确含义的字段,保留以供逆向分析。
|
||||
带 unknown_ 前缀的字段保留原始协议值,其含义已在逆向分析中确认:
|
||||
unknown_2: 指数→集合竞价成交金额/100;个股→舍入残差≈0
|
||||
unknown_3: 个股→集合竞价成交金额/100;指数→负值/无意义
|
||||
unknown_5-8: 保留字段,恒为 0
|
||||
_raw 为该股票记录的原始字节切片。
|
||||
"""
|
||||
|
||||
@@ -17,18 +20,18 @@ class SecurityQuote:
|
||||
code: str
|
||||
|
||||
# 价格
|
||||
price: float # 现价
|
||||
price: float # 现价
|
||||
pre_close: float # 昨收
|
||||
open: float # 今开
|
||||
high: float # 最高
|
||||
low: float # 最低
|
||||
open: float # 今开
|
||||
high: float # 最高
|
||||
low: float # 最低
|
||||
|
||||
# 量额
|
||||
vol: float # 总成交量(手)
|
||||
cur_vol: float # 当前成交量
|
||||
amount: float # 成交额(元)
|
||||
s_vol: float # 内盘(主动卖)
|
||||
b_vol: float # 外盘(主动买)
|
||||
vol: float # 总成交量(手)
|
||||
cur_vol: float # 当前成交量
|
||||
amount: float # 成交额(元)
|
||||
s_vol: float # 内盘(主动卖)
|
||||
b_vol: float # 外盘(主动买)
|
||||
|
||||
# 活跃度指标(含义来自社区逆向,仅供参考)
|
||||
active1: int
|
||||
@@ -60,21 +63,25 @@ class SecurityQuote:
|
||||
|
||||
# 价格指标
|
||||
rise_speed: float # 涨速(原 reversed_bytes9 / 100)
|
||||
limit_up: float | None # 涨停价(业务规则计算)
|
||||
limit_up: float | None # 涨停价(业务规则计算)
|
||||
limit_down: float | None # 跌停价(业务规则计算)
|
||||
|
||||
# 未知字段:买卖量之后的两个变长整数(保留供进一步分析)
|
||||
unknown_2: int = field(default=0, repr=False) # 未知变长整数 2
|
||||
unknown_3: int = field(default=0, repr=False) # 未知变长整数 3
|
||||
# 协议原始值(含义已确认,保留以供高级分析)
|
||||
unknown_2: int = field(default=0, repr=False) # 指数: IndexOpenAmount/100; 个股: 舍入残差
|
||||
unknown_3: int = field(default=0, repr=False) # 个股: StockOpenAmount/100; 指数: 负值
|
||||
|
||||
# 未知字段:尾部四个变长整数
|
||||
unknown_5: int = field(default=0, repr=False) # 原 reversed_bytes5
|
||||
unknown_6: int = field(default=0, repr=False) # 原 reversed_bytes6
|
||||
unknown_7: int = field(default=0, repr=False) # 原 reversed_bytes7
|
||||
unknown_8: int = field(default=0, repr=False) # 原 reversed_bytes8
|
||||
# 尾部保留字段
|
||||
unknown_5: int = field(default=0, repr=False) # 保留,恒为 0
|
||||
unknown_6: int = field(default=0, repr=False) # 保留,恒为 0
|
||||
unknown_7: int = field(default=0, repr=False) # 保留,恒为 0
|
||||
unknown_8: int = field(default=0, repr=False) # 保留,恒为 0
|
||||
|
||||
# 服务器时间字符串(从 unknown_0 原始整数解析,格式 HH:MM:SS.mmm)
|
||||
server_time: str = field(default="", repr=True)
|
||||
|
||||
# 已确认语义的新字段
|
||||
trading_status: int = field(default=0, repr=False) # 交易状态标志,0x8020=停牌
|
||||
open_amount: float = field(default=0.0, repr=False) # 集合竞价成交金额(元),个股有效
|
||||
|
||||
# 原始字节(该股票记录切片)
|
||||
_raw: bytes = field(default=b"", repr=False, compare=False)
|
||||
|
||||
@@ -158,6 +158,11 @@ def test_security_quotes_parse():
|
||||
assert q.unknown_2 == -1
|
||||
assert q.unknown_3 == 22694
|
||||
|
||||
# confirmed semantic fields
|
||||
assert isinstance(q.trading_status, int)
|
||||
assert isinstance(q.open_amount, float)
|
||||
assert q.open_amount == 22694 * 100.0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# minute_time
|
||||
|
||||
Reference in New Issue
Block a user