mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 14:34:18 +08:00
release: v1.8.0 - backtest engine with batch strategy comparison
- Add backtest section to README with CLI usage and run_all_strategies.py demo - Update all version numbers to 1.8.0 (pyproject.toml, __init__.py, cli/__init__.py, docs/conf.py) - Fix turtle_breakout strategy: TAQ returns 3 values (UP, MID, DOWN) - Add run_all_strategies.py batch comparison script - Update README intro to highlight backtest feature - Add backtest to CLI command table and architecture tree Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
70c69c8a66
commit
b5b5d0dc5b
@@ -76,7 +76,9 @@
|
||||
"Bash(mypy src/easy_tdx/backtest/types.py)",
|
||||
"Bash(mypy tests/unit/test_backtest_types.py)",
|
||||
"Bash(mypy src/easy_tdx/backtest/types.py tests/unit/test_backtest_types.py)",
|
||||
"Bash(mypy *)"
|
||||
"Bash(mypy *)",
|
||||
"Bash(git diff *)",
|
||||
"Bash(echo \"exit: $?\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,15 @@ easy-tdx 要做的事很简单:**把机构的数据锁砸开,扔到每个普
|
||||
它是一个完全免费、无需注册、无需 API Key、纯开源的行**情核武器**。
|
||||
一行命令,A股、港股、美股、期货——K线、报价、资金流向、板块轮动、分时明细、逐笔成交,**毫秒级拉满**。
|
||||
|
||||
**32个技术指标**(MACD、KDJ、RSI、BOLL……连“捉妖大师”和“30日乖离率信号”都给你算好)开箱即用。
|
||||
**32个技术指标**(MACD、KDJ、RSI、BOLL……连”捉妖大师”和”30日乖离率信号”都给你算好)开箱即用。
|
||||
**缠论分析**(笔、中枢、买卖点、背驰)一键出结果——你不再需要手画分型、猜线段。
|
||||
**内置回测引擎**——写个策略文件,一行命令跑回测,9 个经典策略自带,批量对比哪个最赚钱一目了然。
|
||||
|
||||
装上就能跑。**Python API + CLI 双通道**,输出 JSON 天然喂给 AI Agent:Claude Code、OpenClaw、Hermes 直接吃。
|
||||
|
||||
**你不懂 TCP 协议?不用。**
|
||||
**你不会写量化框架?不用。**
|
||||
**你想回测验证策略?自带引擎,不用。**
|
||||
**你不想给任何平台付一分钱?完全不用。**
|
||||
|
||||
`pip install easy-tdx`,30秒后——你屏幕上的数据,和机构看到的**是同一份**。
|
||||
@@ -265,6 +267,120 @@ easy-tdx chanlun SZ 000001 --period 30MIN
|
||||
|
||||
`[✓]` 表示确认背驰。趋势背驰(上)代表上涨趋势可能结束,趋势背驰(下)代表下跌趋势可能结束。
|
||||
|
||||
### 回测引擎
|
||||
|
||||
内置向量回测引擎,加载 Python 策略文件即可跑回测。策略继承 `Strategy` 基类,在 `init()` 注册指标,在 `next()` 逐 bar 生成买卖信号,引擎完成订单模拟、持仓跟踪和绩效分析。
|
||||
|
||||
**单策略回测:**
|
||||
|
||||
```bash
|
||||
easy-tdx backtest SZ 300308 --strategy-file strategies/expma_cross.py --count 2000 --cash 1000000 --adjust QFQ --table
|
||||
```
|
||||
|
||||
输出示例:
|
||||
|
||||
```
|
||||
=== 回测绩效概要 ===
|
||||
总收益率: 1413.51%
|
||||
年化收益: 40.85%
|
||||
最大回撤: 76.75%
|
||||
夏普比率: 0.88
|
||||
胜率: 20.8%
|
||||
交易次数: 24
|
||||
```
|
||||
|
||||
**全策略批量对比:**
|
||||
|
||||
项目自带 `run_all_strategies.py`,一次跑完 `strategies/` 下所有策略并排名:
|
||||
|
||||
```bash
|
||||
python -X utf8 run_all_strategies.py SZ 300308 --count 2000 --cash 1000000 --adjust QFQ
|
||||
```
|
||||
|
||||
输出示例(以 SZ 300308 为例):
|
||||
|
||||
```
|
||||
发现 9 个策略文件
|
||||
标的: SZ 300308 | K线: 2000 | 资金: 1,000,000 | 复权: QFQ
|
||||
================================================================================
|
||||
|
||||
>> 运行策略: bias_reversal ... 完成 (2.4s)
|
||||
>> 运行策略: bollinger_breakout ... 完成 (0.6s)
|
||||
>> 运行策略: expma_cross ... 完成 (0.6s)
|
||||
>> 运行策略: kdj_golden ... 完成 (0.1s)
|
||||
>> 运行策略: ma_cross ... 完成 (1.4s)
|
||||
>> 运行策略: macd_cross ... 完成 (2.1s)
|
||||
>> 运行策略: rsi_reversal ... 完成 (0.2s)
|
||||
>> 运行策略: turtle_breakout ... 完成 (0.1s)
|
||||
>> 运行策略: volume_price ... 完成 (6.3s)
|
||||
|
||||
================================================================================
|
||||
[*] 策略绩效排名 (按总收益率降序)
|
||||
================================================================================
|
||||
排名 策略 总收益率 年化收益 最大回撤 夏普 胜率 交易次数 盈亏比
|
||||
----------------------------------------------------------------------------------------------------
|
||||
*1* 1 expma_cross 1413.51% 40.85% 76.75% 0.88 20.8% 24 6.45
|
||||
*2* 2 ma_cross 1258.07% 38.94% 58.01% 0.87 38.2% 55 2.21
|
||||
*3* 3 turtle_breakout 905.07% 33.76% 48.30% 0.83 75.0% 4 10.14
|
||||
4 bias_reversal 504.94% 25.47% 42.25% 0.70 66.3% 95 2.08
|
||||
5 macd_cross 387.67% 22.11% 61.08% 0.60 40.0% 85 2.20
|
||||
6 volume_price 247.72% 17.01% 65.73% 0.50 43.3% 254 1.40
|
||||
7 bollinger_breakout 169.65% 13.32% 49.71% 0.44 66.7% 24 1.93
|
||||
8 rsi_reversal 95.89% 8.85% 56.51% 0.33 57.1% 7 2.48
|
||||
9 kdj_golden 89.10% 8.36% 61.86% 0.32 66.7% 3 10.49
|
||||
```
|
||||
|
||||
综合评分(夏普 × 0.4 + 收益/回撤 × 0.3 + 胜率 × 0.3):
|
||||
|
||||
```
|
||||
*1* 1 turtle_breakout 23.04 0.83 0.70 75.0%
|
||||
*2* 2 bias_reversal 20.35 0.70 0.60 66.3%
|
||||
*3* 3 bollinger_breakout 20.26 0.44 0.27 66.7%
|
||||
```
|
||||
|
||||
换一个标的再跑:
|
||||
|
||||
```bash
|
||||
# 贵州茅台
|
||||
python -X utf8 run_all_strategies.py SH 600519 --count 2000 --cash 1000000 --adjust QFQ
|
||||
```
|
||||
|
||||
#### 自带策略示例
|
||||
|
||||
`strategies/` 目录下有 9 个开箱即用的策略文件,可直接用于 `--strategy-file`:
|
||||
|
||||
| 文件 | 策略 | 类型 | 适合行情 |
|
||||
|------|------|------|----------|
|
||||
| `ma_cross.py` | 双均线交叉(MA5/MA20) | 趋势跟踪 | 单边趋势 |
|
||||
| `expma_cross.py` | EMA12/EMA50 交叉 | 趋势跟踪 | 单边趋势(比 MA 更灵敏) |
|
||||
| `macd_cross.py` | MACD 金叉死叉 | 趋势跟踪 | 中长线趋势 |
|
||||
| `bollinger_breakout.py` | 布林带突破 | 震荡反转 | 横盘震荡 |
|
||||
| `rsi_reversal.py` | RSI 超买超卖 | 反转 | 震荡市 |
|
||||
| `kdj_golden.py` | KDJ 低位金叉/高位死叉 | 反转 | 短线震荡 |
|
||||
| `turtle_breakout.py` | 海龟交易法(唐安奇通道) | 趋势突破 | 牛市启动 |
|
||||
| `bias_reversal.py` | 乖离率反转 | 反转 | 震荡回归 |
|
||||
| `volume_price.py` | 量价配合 | 综合判断 | 放量突破 |
|
||||
|
||||
编写自定义策略只需继承 `Strategy` 基类:
|
||||
|
||||
```python
|
||||
from easy_tdx.backtest import Strategy
|
||||
from easy_tdx import MyTT
|
||||
|
||||
|
||||
class MyStrategy(Strategy):
|
||||
def init(self):
|
||||
self.ma = self.I(MyTT.MA, self.data.close, 10)
|
||||
|
||||
def next(self):
|
||||
if self.data.close[0] > self.ma[self._bar_index]:
|
||||
self.buy(size=0) # size=0 表示全仓
|
||||
elif self.position["size"] > 0:
|
||||
self.sell(size=0) # size=0 表示清仓
|
||||
```
|
||||
|
||||
完整 API 参考:[docs/backtest_usage.md](docs/backtest_usage.md)
|
||||
|
||||
### 捉妖大师(重点)
|
||||
|
||||
捉妖大师是多周期涨幅共振指标,通过 20/60/120 日涨幅及指数平滑判断短中长线趋势是否同向,用于筛选趋势刚启动的强势股。
|
||||
@@ -416,6 +532,7 @@ easy-tdx offline sync-all
|
||||
| `symbol-info` | 个股特征快照 |
|
||||
| `indicator` | 技术指标计算(32 个:MACD/KDJ/RSI/BOLL/DMI/ATR...) |
|
||||
| `indicator-list` | 列出可用技术指标 |
|
||||
| `backtest` | 回测引擎(加载策略文件,输出绩效报告) |
|
||||
| `f10` | F10 公司信息 |
|
||||
| `fund-flow` | 历史资金流向 |
|
||||
| `ex kline` | 扩展市场 K 线 |
|
||||
@@ -935,6 +1052,7 @@ src/easy_tdx/
|
||||
├── commands/ # 标准协议命令(无 IO)
|
||||
├── codec/ # price / volume / datetime / frame / bitmap 编解码
|
||||
├── chanlun/ # 缠论技术分析(K线合并/分型/笔/线段/中枢/买卖点/背驰)
|
||||
├── backtest/ # 回测引擎(Strategy基类/向量化引擎/绩效分析)
|
||||
├── models/ # 纯 dataclass,无业务逻辑
|
||||
├── offline/ # 离线数据读写模块(读取 + 写入同步)
|
||||
└── cli/ # easy-tdx CLI(click)
|
||||
@@ -963,6 +1081,18 @@ ruff format --check src/ tests/ # format check
|
||||
|
||||
## Changelog
|
||||
|
||||
### 1.8.0 (2026-06-09)
|
||||
|
||||
**回测引擎** — 内置向量回测引擎,支持自定义策略回测和全策略批量对比。
|
||||
|
||||
- 新增 `backtest` 子包:Strategy 基类、BacktestEngine、OrderSimulator、PortfolioTracker、PerformanceAnalyzer
|
||||
- 新增 `easy-tdx backtest` CLI 命令,支持 `--strategy-file`、`--cash`、`--commission`、`--adjust` 等参数
|
||||
- 绩效报告包含 19 项指标:总收益率、年化收益、最大回撤、夏普比率、索提诺、卡玛、胜率、盈亏比等
|
||||
- 新增 `strategies/` 目录,包含 9 个开箱即用的策略示例(MA/EMA/MACD/BOLL/RSI/KDJ/BIAS/海龟/量价)
|
||||
- 新增 `run_all_strategies.py` 批量对比脚本,一键跑完全部策略并按收益率和综合评分排名
|
||||
- 自带策略在 SZ 300308 上 3 年回测:收益率最高 1413%(expma_cross),综合最优 turtle_breakout
|
||||
- 30+ 离线单元测试覆盖,零网络依赖
|
||||
|
||||
### 1.7.1 (2026-06-08)
|
||||
|
||||
**Bug 修复** — 修复缠论笔计算在持续下跌/上涨走势中因"分型陷阱"导致近期笔丢失的问题。
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ copyright = "2025, Justin Gu"
|
||||
author = "Justin Gu"
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = "1.7.1"
|
||||
release = "1.8.0"
|
||||
|
||||
# -- Extensions ---------------------------------------------------------------
|
||||
extensions = [
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "easy-tdx"
|
||||
version = "1.7.1"
|
||||
version = "1.8.0"
|
||||
description = "通达信 TCP 协议行情数据客户端,支持在线行情、离线数据读取与写入同步"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
"""批量回测脚本:依次运行 strategies/ 目录下所有策略并比较结果。
|
||||
|
||||
用法::
|
||||
|
||||
python run_all_strategies.py SZ 300308 --count 2000 --cash 1000000 --adjust QFQ
|
||||
|
||||
输出每个策略的绩效指标,并按总收益率排名。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
# 确保 easy_tdx 可导入
|
||||
sys.path.insert(0, str(Path(__file__).parent / "src"))
|
||||
|
||||
import click
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument("market")
|
||||
@click.argument("code")
|
||||
@click.option("--count", default=2000, type=int, help="K线数量")
|
||||
@click.option("--cash", default=1000000.0, type=float, help="初始资金")
|
||||
@click.option("--commission", default=0.0003, type=float, help="佣金率")
|
||||
@click.option("--adjust", default="QFQ", help="复权: NONE/QFQ/HFQ")
|
||||
@click.option("--period", default="DAILY", help="K线周期")
|
||||
def run_all(
|
||||
market: str,
|
||||
code: str,
|
||||
count: int,
|
||||
cash: float,
|
||||
commission: float,
|
||||
adjust: str,
|
||||
period: str,
|
||||
) -> None:
|
||||
"""批量运行 strategies/ 目录下所有策略并比较结果。"""
|
||||
from easy_tdx.backtest.engine import BacktestEngine
|
||||
from easy_tdx.backtest.strategy import Strategy
|
||||
from easy_tdx.cli.parsers import parse_adjust, parse_market, parse_period
|
||||
from easy_tdx.mac.client import MacClient
|
||||
|
||||
# 1. 发现策略文件
|
||||
strategies_dir = Path(__file__).parent / "strategies"
|
||||
strategy_files = sorted(strategies_dir.glob("*.py"))
|
||||
if not strategy_files:
|
||||
click.echo("未找到策略文件 (strategies/*.py)", err=True)
|
||||
raise SystemExit(1)
|
||||
|
||||
click.echo(f"发现 {len(strategy_files)} 个策略文件")
|
||||
click.echo(f"标的: {market} {code} | K线: {count} | 资金: {cash:,.0f} | 复权: {adjust}")
|
||||
click.echo("=" * 80)
|
||||
|
||||
# 2. 获取数据(所有策略共享同一份数据)
|
||||
mkt = parse_market(market)
|
||||
click.echo("正在获取行情数据...")
|
||||
client = MacClient.from_best_host()
|
||||
client.connect()
|
||||
try:
|
||||
df = client.get_stock_kline(
|
||||
mkt,
|
||||
code,
|
||||
period=parse_period(period),
|
||||
start=0,
|
||||
count=count,
|
||||
adjust=parse_adjust(adjust),
|
||||
)
|
||||
finally:
|
||||
client.close()
|
||||
click.echo(f"获取到 {len(df)} 条K线数据")
|
||||
click.echo("=" * 80)
|
||||
|
||||
# 3. 逐个运行策略
|
||||
results: list[dict] = []
|
||||
|
||||
for sf in strategy_files:
|
||||
strategy_name = sf.stem
|
||||
click.echo(f"\n>> 运行策略: {strategy_name} ...", nl=False)
|
||||
|
||||
# 加载策略类
|
||||
import importlib.util
|
||||
|
||||
spec = importlib.util.spec_from_file_location("strategy_module", sf)
|
||||
if spec is None or spec.loader is None:
|
||||
click.echo(" [加载失败]")
|
||||
continue
|
||||
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
# 查找 Strategy 子类
|
||||
strategy_cls = None
|
||||
for attr_name in dir(module):
|
||||
obj = getattr(module, attr_name)
|
||||
try:
|
||||
if isinstance(obj, type) and issubclass(obj, Strategy) and obj is not Strategy:
|
||||
strategy_cls = obj
|
||||
break
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
if strategy_cls is None:
|
||||
click.echo(" [未找到 Strategy 子类]")
|
||||
continue
|
||||
|
||||
# 运行回测
|
||||
t0 = time.perf_counter()
|
||||
try:
|
||||
engine = BacktestEngine(
|
||||
strategy=strategy_cls,
|
||||
cash=cash,
|
||||
commission=commission,
|
||||
)
|
||||
result = engine.run(df)
|
||||
elapsed = time.perf_counter() - t0
|
||||
perf = result.performance
|
||||
click.echo(f" 完成 ({elapsed:.1f}s)")
|
||||
|
||||
results.append({
|
||||
"strategy": strategy_name,
|
||||
"total_return": perf.get("total_return", 0),
|
||||
"annual_return": perf.get("annual_return", 0),
|
||||
"max_drawdown": perf.get("max_drawdown", 0),
|
||||
"sharpe": perf.get("sharpe", 0),
|
||||
"sortino": perf.get("sortino", 0),
|
||||
"calmar": perf.get("calmar", 0),
|
||||
"win_rate": perf.get("win_rate", 0),
|
||||
"total_trades": perf.get("total_trades", 0),
|
||||
"profit_factor": perf.get("profit_factor", 0),
|
||||
"volatility": perf.get("volatility", 0),
|
||||
})
|
||||
except Exception as e:
|
||||
elapsed = time.perf_counter() - t0
|
||||
click.echo(f" 错误 ({elapsed:.1f}s): {e}")
|
||||
results.append({
|
||||
"strategy": strategy_name,
|
||||
"error": str(e),
|
||||
})
|
||||
|
||||
# 4. 输出排名
|
||||
click.echo("\n" + "=" * 80)
|
||||
click.echo("[*] 策略绩效排名 (按总收益率降序)")
|
||||
click.echo("=" * 80)
|
||||
|
||||
# 过滤掉有错误的策略
|
||||
valid = [r for r in results if "error" not in r]
|
||||
errored = [r for r in results if "error" in r]
|
||||
|
||||
if not valid:
|
||||
click.echo("所有策略均运行失败!")
|
||||
for r in errored:
|
||||
click.echo(f" {r['strategy']}: {r['error']}")
|
||||
raise SystemExit(1)
|
||||
|
||||
# 按总收益率排序
|
||||
valid.sort(key=lambda x: x["total_return"], reverse=True)
|
||||
|
||||
# 表头
|
||||
click.echo(
|
||||
f"{'排名':>4} {'策略':<22} {'总收益率':>10} {'年化收益':>10} "
|
||||
f"{'最大回撤':>10} {'夏普':>8} {'胜率':>8} {'交易次数':>8} {'盈亏比':>8}"
|
||||
)
|
||||
click.echo("-" * 100)
|
||||
|
||||
for i, r in enumerate(valid, 1):
|
||||
medal = " *1*" if i == 1 else " *2*" if i == 2 else " *3*" if i == 3 else " "
|
||||
click.echo(
|
||||
f"{medal}{i:>2} {r['strategy']:<22} "
|
||||
f"{r['total_return']:>9.2%} "
|
||||
f"{r['annual_return']:>9.2%} "
|
||||
f"{r['max_drawdown']:>9.2%} "
|
||||
f"{r['sharpe']:>8.2f} "
|
||||
f"{r['win_rate']:>7.1%} "
|
||||
f"{r['total_trades']:>8} "
|
||||
f"{r['profit_factor']:>8.2f}"
|
||||
)
|
||||
|
||||
# 最佳策略详细报告
|
||||
best = valid[0]
|
||||
click.echo("\n" + "=" * 80)
|
||||
click.echo(f"[BEST] 最佳策略: {best['strategy']}")
|
||||
click.echo("=" * 80)
|
||||
click.echo(f" 总收益率: {best['total_return']:.2%}")
|
||||
click.echo(f" 年化收益: {best['annual_return']:.2%}")
|
||||
click.echo(f" 最大回撤: {best['max_drawdown']:.2%}")
|
||||
click.echo(f" 夏普比率: {best['sharpe']:.2f}")
|
||||
click.echo(f" 索提诺: {best['sortino']:.2f}")
|
||||
click.echo(f" 卡玛比率: {best['calmar']:.2f}")
|
||||
click.echo(f" 胜率: {best['win_rate']:.1%}")
|
||||
click.echo(f" 交易次数: {best['total_trades']}")
|
||||
click.echo(f" 盈亏比: {best['profit_factor']:.2f}")
|
||||
click.echo(f" 年化波动: {best['volatility']:.4f}")
|
||||
|
||||
# 综合评分(综合夏普、收益率、回撤)
|
||||
click.echo("\n" + "=" * 80)
|
||||
click.echo("[*] 综合评分排名 (Sharpe*0.4 + Ret/DD*0.3 + WinRate*0.3)")
|
||||
click.echo("=" * 80)
|
||||
|
||||
scored = []
|
||||
for r in valid:
|
||||
# 避免除以零
|
||||
ret_dd_ratio = r["annual_return"] / r["max_drawdown"] if r["max_drawdown"] > 1e-6 else 999.0
|
||||
score = r["sharpe"] * 0.4 + ret_dd_ratio * 0.3 + r["win_rate"] * 100 * 0.3
|
||||
scored.append((r, score))
|
||||
|
||||
scored.sort(key=lambda x: x[1], reverse=True)
|
||||
|
||||
click.echo(
|
||||
f"{'排名':>4} {'策略':<22} {'综合评分':>10} {'夏普':>8} {'收益/回撤':>10} {'胜率':>8}"
|
||||
)
|
||||
click.echo("-" * 70)
|
||||
for i, (r, score) in enumerate(scored, 1):
|
||||
ret_dd_ratio = r["annual_return"] / r["max_drawdown"] if r["max_drawdown"] > 1e-6 else 999.0
|
||||
medal = " *1*" if i == 1 else " *2*" if i == 2 else " *3*" if i == 3 else " "
|
||||
click.echo(
|
||||
f"{medal}{i:>2} {r['strategy']:<22} {score:>10.2f} "
|
||||
f"{r['sharpe']:>8.2f} {ret_dd_ratio:>10.2f} {r['win_rate']:>7.1%}"
|
||||
)
|
||||
|
||||
# 报告错误
|
||||
if errored:
|
||||
click.echo("\n[!] 以下策略运行失败:")
|
||||
for r in errored:
|
||||
click.echo(f" {r['strategy']}: {r['error']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_all()
|
||||
@@ -107,4 +107,4 @@ __all__ = [
|
||||
"save_best_ex_host",
|
||||
]
|
||||
|
||||
__version__ = "1.4.0"
|
||||
__version__ = "1.8.0"
|
||||
|
||||
@@ -46,17 +46,12 @@ class PortfolioTracker:
|
||||
Args:
|
||||
trades: 交易列表
|
||||
"""
|
||||
# 构建 datetime → Trade 映射
|
||||
buy_map: dict[int, Trade] = {}
|
||||
sell_map: dict[int, Trade] = {}
|
||||
|
||||
# 构建 datetime → [Trade] 映射(支持同 bar 多笔交易)
|
||||
trade_map: dict[int, list[Trade]] = {}
|
||||
for trade in trades:
|
||||
if trade.rejected:
|
||||
continue
|
||||
if trade.direction == "BUY":
|
||||
buy_map[trade.datetime] = trade
|
||||
else:
|
||||
sell_map[trade.datetime] = trade
|
||||
trade_map.setdefault(trade.datetime, []).append(trade)
|
||||
|
||||
# 遍历每个 bar
|
||||
for i in range(self._n):
|
||||
@@ -68,15 +63,16 @@ class PortfolioTracker:
|
||||
self._position[i] = self._position[i - 1]
|
||||
self._avg_price[i] = self._avg_price[i - 1]
|
||||
|
||||
# 处理买入
|
||||
if dt in buy_map:
|
||||
trade = buy_map[dt]
|
||||
# 处理该 bar 的所有交易
|
||||
for trade in trade_map.get(dt, []):
|
||||
if trade.direction == "BUY":
|
||||
cost = trade.size * trade.price + trade.commission + trade.slippage
|
||||
self._cash[i] -= cost
|
||||
|
||||
# 更新均价
|
||||
if self._position[i] > 0:
|
||||
total_cost = self._position[i] * self._avg_price[i] + trade.size * trade.price
|
||||
prev_cost = self._position[i] * self._avg_price[i]
|
||||
total_cost = prev_cost + trade.size * trade.price
|
||||
self._position[i] += trade.size
|
||||
self._avg_price[i] = total_cost / self._position[i]
|
||||
else:
|
||||
@@ -84,9 +80,7 @@ class PortfolioTracker:
|
||||
self._position[i] = trade.size
|
||||
self._avg_price[i] = trade.price
|
||||
|
||||
# 处理卖出
|
||||
if dt in sell_map:
|
||||
trade = sell_map[dt]
|
||||
elif trade.direction == "SELL":
|
||||
proceeds = trade.size * trade.price - trade.commission - trade.slippage
|
||||
self._cash[i] += proceeds
|
||||
self._position[i] -= trade.size
|
||||
|
||||
@@ -19,7 +19,7 @@ class Signal:
|
||||
"""策略产生的交易信号。
|
||||
|
||||
Attributes:
|
||||
datetime: 信号时间(Unix timestamp 毫秒)
|
||||
datetime: 信号时间(YYYYMMDD 整数格式,如 20240101)
|
||||
direction: 交易方向
|
||||
size: 交易数量(0 = 全仓/清仓)
|
||||
price: 限价(None = 市价单)
|
||||
@@ -43,7 +43,7 @@ class Trade:
|
||||
"""已成交记录。
|
||||
|
||||
Attributes:
|
||||
datetime: 成交时间(Unix timestamp 毫秒)
|
||||
datetime: 成交时间(YYYYMMDD 整数格式,如 20240101)
|
||||
direction: 交易方向
|
||||
size: 成交数量
|
||||
price: 成交价格
|
||||
@@ -71,7 +71,7 @@ class Position:
|
||||
"""持仓快照。
|
||||
|
||||
Attributes:
|
||||
datetime: 快照时间(Unix timestamp 毫秒)
|
||||
datetime: 快照时间(YYYYMMDD 整数格式,如 20240101)
|
||||
size: 持仓数量(正=多头,负=空头,0=空仓)
|
||||
avg_price: 平均持仓成本
|
||||
market_value: 市值
|
||||
|
||||
@@ -23,7 +23,7 @@ from ..backtest.cli import backtest
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.version_option(version="1.7.0", prog_name="easy-tdx")
|
||||
@click.version_option(version="1.8.0", prog_name="easy-tdx")
|
||||
def cli() -> None:
|
||||
"""easy-tdx -- 通达信行情数据 CLI(默认 JSON 输出,适合 Agent 使用)。
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ class TurtleStrategy(Strategy):
|
||||
"""海龟交易法(唐安奇通道突破)策略。"""
|
||||
|
||||
def init(self) -> None:
|
||||
self.upper, self.lower = self.I(MyTT.TAQ, self.data.high, self.data.low, 20)
|
||||
self.upper, _, self.lower = self.I(MyTT.TAQ, self.data.high, self.data.low, 20)
|
||||
|
||||
def next(self) -> None:
|
||||
cur = self.data.close[0]
|
||||
|
||||
Reference in New Issue
Block a user