回测TqBacktester
策略写好后,实盘前一定要先回测。TqSdk 内置了 TqBacktest 回测引擎——同一份策略代码,只需在创建 TqApi 时多传一个 backtest 参数,就能用历史数据验证策略表现。这是 TqSdk 「一套代码三种模式」设计哲学的核心体现。
最简回测
from datetime import date
from tqsdk import TqApi, TqAuth, TqSim, TqBacktest
# 创建回测实例,指定起止时间
api = TqApi(
TqSim(),
backtest=TqBacktest(start_dt=date(2024, 1, 1), end_dt=date(2024, 6, 1)),
auth=TqAuth("快期账户", "账户密码")
)
# 下面正常写策略代码——和实盘一模一样
klines = api.get_kline_serial("SHFE.rb2410", 60, 200)
# ... 策略逻辑 ...
回测模式下,TqBacktest 会连接回测服务器,按时间顺序逐根推进历史 K 线和 Tick。你的策略代码完全不用改。
重要:回测模式下账户必须是
TqSim()(本地模拟),不能用TqAccount或TqKq。
TqBacktest 参数
TqBacktest(start_dt=date(2024, 1, 1), end_dt=date(2024, 6, 1))
| 参数 | 含义 |
|---|---|
start_dt | 回测起始时间(date 为交易日,datetime 为精确时间点) |
end_dt | 回测结束时间 |
时间默认按北京时间。用 date 精确到天,用 datetime 精确到秒。
回测模式下的行情推进
回测和实盘有一个关键区别——行情怎么推进:
- K 线在创建时和结束时各更新一次,中间不更新。
- quote 的更新频率取决于你订阅了什么数据:
- 订阅了 Tick → quote 用 Tick 生成,频率和 Tick 一致
- 只订阅了 K 线 → quote 用 K 线生成,频率等于 K 线周期
- K 线周期大于分钟线 → 自动订阅分钟线来生成 quote
- 模拟撮合规则:限价单要求报单价达到或超过对手价才成交;市价单用对手价成交。
wait_update()每次最多推进一个行情时间点。
这意味着:回测中使用 Tick 数据比单纯用 K 线更接近实盘,但回测速度更慢。
BacktestFinished 异常
回测结束时,TqSdk 会抛出 BacktestFinished 异常。必须捕获它,否则程序会报错退出:
from tqsdk import TqApi, TqAuth, TqSim, TqBacktest, BacktestFinished
from datetime import date
api = TqApi(
TqSim(),
backtest=TqBacktest(start_dt=date(2024, 1, 1), end_dt=date(2024, 6, 1)),
auth=TqAuth("快期账户", "账户密码")
)
try:
klines = api.get_kline_serial("SHFE.rb2410", 60, 200)
while True:
api.wait_update()
# ... 策略逻辑 ...
except BacktestFinished as e:
# 回测结束,获取统计报告
account = api.get_account()
print("回测结束")
print("最终权益:", account.balance)
print("累计盈亏:", account.float_profit)
api.close()
BacktestFinished 异常中包含了回测的统计报告(收益率、最大回撤、夏普比率等),可以直接打印查看。
完整回测示例
from datetime import date
from tqsdk import TqApi, TqAuth, TqSim, TqBacktest, TargetPosTask, BacktestFinished
from tqsdk.ta import MA
api = TqApi(
TqSim(init_balance=100000), # 自定义初始资金 10 万
backtest=TqBacktest(start_dt=date(2024, 1, 1), end_dt=date(2024, 6, 1)),
auth=TqAuth("快期账户", "账户密码")
)
klines = api.get_kline_serial("SHFE.rb2410", 60, 200)
target_pos = TargetPosTask(api, "SHFE.rb2410")
try:
while True:
api.wait_update()
if api.is_changing(klines.iloc[-1], "datetime"):
ma5 = MA(klines, 5)
ma20 = MA(klines, 20)
if ma5["ma"].iloc[-1] > ma20["ma"].iloc[-1]:
target_pos.set_target_volume(1)
else:
target_pos.set_target_volume(0)
except BacktestFinished:
account = api.get_account()
print("=" * 40)
print("回测统计报告")
print("=" * 40)
api.close()
回测注意事项
- 回测不等于实盘:回测中的撮合是理想化的(特别是市价单),实盘有滑点、部分成交、网络延迟等情况。
- 避免未来函数:策略中使用的数据应该是当前时间点可获取的。取最后一根已完成的 K 线数据计算指标,不要用正在形成的 K 线。
- 手续费和滑点:
TqSim默认不收手续费。用TqSim.set_commission(symbol, fee)设置,使回测更接近实盘。 - 主连合约回测:使用
KQ.m@SHFE.rb等主连合约可以避免换月问题,适合做长期回测。 - 组合合约:回测组合合约时只能订阅 Tick,不能订阅 K 线(因为组合合约无最新价)。
小结
- 加一个
backtest=TqBacktest(...)参数,策略代码原封不动就能回测。 - 回测必须用
TqSim()账户。 - 必须捕获
BacktestFinished异常,其中包含回测报告。 - 回测是理想环境,实盘会有滑点和延迟,不要过度乐观。
回测通过后,下一步上模拟盘验证,最终走向实盘: 模拟与实盘。