回测模式与撮合
回测分为模拟级Tick和实盘级Tick两种模式。两者都基于真实的历史数据:模拟级Tick由K线生成tick,实盘级Tick回放真实记录的tick,后者更精确,也更慢。
模拟级Tick
回测引擎在每根底层K线的开盘价、最高价、最低价、收盘价构成的价格框架内,沿 开盘→最低/最高→收盘 的路径生成2~14个模拟tick,K线的成交量分摊到这些tick上;策略调用行情接口时得到的是当前模拟tick的数据。因此每根底层K线上有多个回测时间点,策略可以在一根K线内多次交易,而不是只能按收盘价成交。底层K线周期越小,生成的tick越接近真实走势,回测也越慢。机制详见回测系统模拟级别机制说明、回测系统机制说明。
模拟盘口:卖一价 = 当前tick收盘价 + 一跳 + 滑点,买一价 = 收盘价 − 一跳 − 滑点(滑点以跳数计);GetDepth()返回按此间隔排列的若干档模拟深度,每档数量为配置中的「每档数量」。
实盘级Tick
使用平台真实记录的逐秒tick数据,包含盘口深度(档位可设置,最多20档),可以选择回放逐笔成交数据;GetDepth()、GetTrades()返回回放的真实数据。由于数据量大、回测速度慢,单次回测的数据上限为50MB,可回测的时间范围因此受限;需要更长的时间范围时,可以降低深度档位、不使用逐笔成交数据。较早的时间段可能没有实盘级数据,时间范围不宜选得过早。
在某个行情时刻,GetTicker()、GetDepth()、GetTrades()、GetRecords()各调用一次不会推动回测时间;再次调用其中同一个函数时,回测时间跳到下一个行情时刻。实盘级Tick模式下,策略循环中的Sleep()宜设得短一些(例如100毫秒)。
撮合规则
两种模式使用相同的撮合规则:
- 按价格触及成交,并且一次全部成交,回测中不会出现部分成交。
- 市价单在当前tick按卖一价/买一价成交;现货市价买单的数量是计价币金额。
- 限价买单价格大于等于卖一价、限价卖单价格小于等于买一价时成交,挂单后的每个tick都会检查。下单时立即成交的,按市场价成交并收取吃单(taker)手续费;挂在盘口之后被价格触及而成交的,按委托价成交并收取挂单(maker)手续费。
- 实盘级Tick模式下,挂在买一/卖一价位上的订单,要等排在它前面的挂单量被消耗后才成交。
- 期货按 名义价值 ÷ 杠杆 冻结保证金;行情数据中包含资金费率时,永续合约按资金费率结算资金费。
数据粒度的影响
同一个策略在不同的数据粒度下(实盘级Tick、底层K线周期较小的模拟级Tick、底层K线周期较大的模拟级Tick等)回测,交易次数和盈亏都会不同。数据粒度大时回测快,但结果可能失真,回测时应尽量使用较小的数据粒度。可以用下面的策略在几种粒度下分别回测对比:
javascript
/*backtest
start: 2025-04-01 08:00:00
end: 2025-04-18 00:00:00
period: 1m
exchanges: [{"eid":"Binance","currency":"BTC_USDT","balance":1000000}]
mode: 1
*/
var delta = 50
var lotSize = 0.001
var lastPrice = null
var direction = null
function main() {
while (true) {
var ticker = _C(exchange.GetTicker)
if (!lastPrice) {
lastPrice = ticker.Last
}
var diff = ticker.Last - lastPrice
if ((!direction || direction == "long") && diff >= delta) {
// 价格上涨超过阈值 -> 做空
exchange.Sell(ticker.Last, lotSize)
Log("Short @", ticker.Last)
direction = "short"
} else if ((!direction || direction == "short") && diff <= -delta) {
// 价格下跌超过阈值 -> 做多
exchange.Buy(ticker.Last, lotSize)
Log("Long @", ticker.Last)
direction = "long"
}
// Tick 模式中尽量短,K线模式中没有影响
Sleep(100)
}
}