前言
用 TqBacktest 跑历史区间时,有人遇到两种相反现象:一种是时间到了程序还卡在 wait_update 里出不来;另一种是回测明明结束了,却不知道去哪看最终权益和成交明细。天勤 TqSdk 在回测时间耗尽时会抛出 BacktestFinished 异常,这是正常的结束信号,不是程序坏了。
下面说明如何在主循环外捕获它、如何 close() 释放连接、以及如何导出权益和成交供复盘。把回测结束当成和“策略逻辑错误”不同的分支,后面批量参数测试会轻松很多。
一、BacktestFinished 表示什么
回测模式下,wait_update() 会把行情按历史时间推进。当推进到 end_dt 之后,再调用会触发 BacktestFinished,提示回测已完成。若不用 try/except 捕获,脚本可能直接报错退出,看起来像“崩溃”。
正确心态:这是正常完成,要在捕获块里做收尾工作。
二、最小回测骨架
from datetime import date
from tqsdk import TqApi, TqAuth, TqBacktest, BacktestFinished
api = TqApi(
backtest=TqBacktest(
start_dt=date(2024, 1, 1),
end_dt=date(2024, 6, 1),
),
auth=TqAuth("账户", "密码"),
)
symbol = "SHFE.rb2510"
kl = api.get_kline_serial(symbol, 300, data_length=200)
try:
while True:
api.wait_update()
if api.is_changing(kl.iloc[–1], "datetime"):
pass # 策略逻辑
except BacktestFinished as e:
print("回测正常结束:", e)
finally:
api.close()
三、结束后读账户与成交
回测结束后、close() 之前,可读 get_account() 看权益,并按文档访问回测账户的成交记录或 trade_log:
except BacktestFinished:
acc = api.get_account()
print("结束权益", acc.balance)
# 成交明细导出方式见 usage/backtest.rst
批量回测时,把“结束权益、最大回撤、成交笔数”等写入 CSV,比只看控制台最后一行靠谱。
四、为什么回测“跑完不退出”
若没捕获 BacktestFinished,异常栈会停在 wait_update,初学者以为死循环。若捕获了却又在 except 里继续 while True 且不换模式,也会卡住。捕获后应退出循环或结束脚本,并 close()。
另一种情况是策略里写了 while True 但没有用 TqBacktest,那是实盘/模拟逻辑,不属于回测结束问题。
五、与手续费、滑点配置的关系
回测结果是否可信,取决于是否在回测账户上配置了合理手续费。BacktestFinished 之后导出的权益,已经包含了回测假设下的成交成本;若手续费为 0,结束权益会偏乐观。
改手续费后应重新跑完整区间,不要只改参数不重跑。
总结
回测结束靠捕获 BacktestFinished 识别,在 finally 里 api.close()。结束后读 account 和成交记录,把结果落盘,才算完成一次回测实验。
建议每个策略仓库里保留一个最小回测脚本,只验证“能正常开始、能正常结束、能打印结束权益”。这个脚本通过,再上大段策略逻辑,能少很多“回测跑一夜不知道停没停”的焦虑。
FAQ
1)能否不用异常而用标志位判断结束?
官方推荐捕获 BacktestFinished,与文档保持一致最省心。
2)批量回测每个进程都要 close 吗?
要,否则多进程会占满连接和资源。
3)回测里能用 TargetPosTask 吗?
可以,结束处理一样要捕获 BacktestFinished。
4)结束权益和客户端不一致?
检查手续费、合约代码、回测区间是否一致。
风险提示
本文用于期货量化技术实践讨论,不构成任何投资建议。回测结果不代表实盘表现。





