回测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()(本地模拟),不能用 TqAccountTqKq

TqBacktest 参数

TqBacktest(start_dt=date(2024, 1, 1), end_dt=date(2024, 6, 1))
参数含义
start_dt回测起始时间(date 为交易日,datetime 为精确时间点)
end_dt回测结束时间

时间默认按北京时间。用 date 精确到天,用 datetime 精确到秒。

回测模式下的行情推进

回测和实盘有一个关键区别——行情怎么推进

  1. K 线在创建时和结束时各更新一次,中间不更新。
  2. quote 的更新频率取决于你订阅了什么数据:
    • 订阅了 Tick → quote 用 Tick 生成,频率和 Tick 一致
    • 只订阅了 K 线 → quote 用 K 线生成,频率等于 K 线周期
    • K 线周期大于分钟线 → 自动订阅分钟线来生成 quote
  3. 模拟撮合规则:限价单要求报单价达到或超过对手价才成交;市价单用对手价成交。
  4. 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()

回测注意事项

  1. 回测不等于实盘:回测中的撮合是理想化的(特别是市价单),实盘有滑点、部分成交、网络延迟等情况。
  2. 避免未来函数:策略中使用的数据应该是当前时间点可获取的。取最后一根已完成的 K 线数据计算指标,不要用正在形成的 K 线。
  3. 手续费和滑点TqSim 默认不收手续费。用 TqSim.set_commission(symbol, fee) 设置,使回测更接近实盘。
  4. 主连合约回测:使用 KQ.m@SHFE.rb 等主连合约可以避免换月问题,适合做长期回测。
  5. 组合合约:回测组合合约时只能订阅 Tick,不能订阅 K 线(因为组合合约无最新价)。

小结

  • 加一个 backtest=TqBacktest(...) 参数,策略代码原封不动就能回测。
  • 回测必须用 TqSim() 账户。
  • 必须捕获 BacktestFinished 异常,其中包含回测报告。
  • 回测是理想环境,实盘会有滑点和延迟,不要过度乐观。

回测通过后,下一步上模拟盘验证,最终走向实盘: 模拟与实盘