本节从一个具体的案例出发,介绍如何在回测策略编写过程中进行性能提升。
在中高频交易中,CTA
策略是一种预测价格走势的策略,可以抓住大单的动向。该策略的核心思想是在分析订单流信息或特定事件后,获取短期价格波动的大致方向,利用速度优势提前建仓,等待价格波动到预期水平后平仓。
基于 Level 2 快照数据和逐笔成交数据,实现以下的 CTA 策略逻辑:
快照数据计算 MACD 指标,当 MACD 指标出现金叉之后,且满足下面两个条件之一时,执行买入:
基于逐笔成交,成交价的过去30秒内的 CCI
指标从下向上突破+100线进入超买区间,并且过去30秒的成交量大于50000股时,买入500股。
当成交价的过去30秒内 CCI 指标从下向上突破-100线时,买入500股。
MACD 指标死叉时,卖出
股票中高频 CTA 策略代码实现
首先,可以确定该策略需要的全局变量以及相应的类型。根据策略逻辑本例需要每次买入的股票数量、待买入和卖出的标的池。这里买入的股票买入数量为整型,待买入和平仓的股票设置为字符数组类型。
context["buyVol"] = 500
context["buyList"] = array(SYMBOL,0)
context["sellList"] = array(SYMBOL,0)
其次,基于 Level2 快照定义 MACD 指标、基于 Level2 逐笔成交行情定义30秒内的 CCI
和成交量两个指标。回测引擎内部创建的是状态响应式引擎,因子指标定义的方式,可以具体参考状态响应式引擎用户手册。在策略初始化函数中,首先订阅基于 Level2 快照行情 MACD 指标、订阅基于成交行情的30秒的 CCI
和成交量指标。
def initialize(mutable context){
// 通过Backtest::setUniverse可以更换当日股票池,
// 如Backtest::setUniverse(context["engine"],["688088.XSHG","688157.XSHG","688208.XSHG"])
print("initialize")
// 订阅快照行情的指标
d = dict(STRING, ANY)
d["macd"] =
d["prevMacd"] =
Backtest::subscribeIndicator(context["engine"], "snapshot", d)
d = dict(STRING, ANY)
d["cci"] =
d["prevcci"] =
d["tradeVol30s"]=
Backtest::subscribeIndicator(context["engine"], "trade", d)
// 记录每日统计量
context["buyVol"] = 500
}
在快照行情回调函数 onSnapshot 中,通过获取订阅的 MACD 指标记录买入卖出信号。
def getOpenQty(openOrders){
qty = 0
for( i in openOrders){
qty = i.openQty + qty
}
return qty
}
def onSnapshot(mutable context, msg, indicator){
// msg为字典,最新时刻的tick数据
// 记录买入卖出信号
if(indicator.prevMacd < 0 and indicator.macd > 0){//MACD 指标出现金叉
pos=Backtest::getPosition(context.engine,msg.symbol).longPosition
if((pos <= 0) and (not msg.symbol in context["buyList"])){
context["buyList"] = context["buyList"].append!(msg.symbol)
}
}
else if((indicator.prevMacd > 0 and indicator.macd < 0) or (msg.symbol in context["sellList"])){//MACD 指标死叉,平仓
// 未成交的订单进行撤单
Backtest::cancelOrder(context.engine, msg.symbol, , "buy")
pos = Backtest::getPosition(context.engine, msg.symbol).longPosition
openQty = getOpenQty(Backtest::getOpenOrders(context.engine, msg.symbol,, "close"))
if(pos-openQty>0){ //卖出持仓
Backtest::submitOrder(context.engine,(
msg.symbol, context["tradeTime"], 5, round(msg.lastPrice-0.02,3), pos-openQty, 3), "close")
}
if(not msg.symbol in context["sellList"]){
context["sellList"]=context["sellList"].append!(msg.symbol)
}
}
}
在进行卖出平仓的操作中,对未成交的订单进行撤单同时平掉当前持仓。平仓操作时,如果上一次平仓委托没有发生完全成交,那么本次还需要继续平仓操作。未成交的订单可以通过接口
getOpenOrders 获取,该接口返回的是一个字典数组,返回所有的相应未成交的每一笔订单。
在逐笔成交行情 onTick 中执行买入操作。具体实现步骤为当基于快照的 MACD
指标出现金叉之后,基于逐笔成交行情的成交价的过去30秒内的 CCI
指标从下向上突破+100线进入超买区间,并且过去30秒的成交量大于50000股时买入500股,或者当成交价的过去30秒内 CCI
指标从下向上突破-100线时买入500股。同时在买入操作执行之后,如果没有发生成交会进行等待成交避免进行多次买入操作。
def onTick(mutable context, msg, indicator){
//...
if(msg.symbol in context["buyList"]){
buyFlag = false
// 指标从下向上突破+100线进入超买区间时,过去30s内的成交量大于10000股是买入
if(indicator.prevcci < 100. and indicator.cci >= 100. and indicator.tradeVol30s > 10000){
buyFlag =true
}
// 指标从下向上突破-100线,买入
if( indicator.prevcci < -100. and indicator.cci >= -100. ){
buyFlag = true
}
if(buyFlag == false){
return
}
// 有持仓
pos=Backtest::getPosition(context["engine"], msg.symbol).longPosition
if(pos > 0){
return
}
//有在途单
opens=Backtest::getOpenOrders(context["engine"], msg.symbol, , "buy")
if(opens.size() > 0){
return
}
Backtest::submitOrder(context["engine"], (
msg.symbol, msg.timestamp, 5, round(msg.price,2), context["buyVol"], 1), "buy")
context["buyList"] = context["buyList"][context["buyList"] != msg.symbol]
context["sellList"] = context["sellList"][context["sellList"] != msg.symbol]
}
}
股票逐笔 CTA 策略回测的完整脚本见附件。
性能测试
以上策略实现的代码可以在2.00.14.1 和 3.00.2.1版本执行,以及可以在 3.00.2.1 JIT版本中开启 JIT。
try{Backtest::dropBacktestEngine(strategyName)}catch(ex){print ex}
// 2.00.14.1 和 3.00.2.1版本
engine = Backtest::createBacktester(strategyName, userConfig, callbacks,false)
// 3.00.2.1 JIT版本
engine = Backtest::createBacktester(strategyName, userConfig, callbacks,true)
单只标的20个交易日共24.1万条行情数据进行回测测试非 JIT 耗时3.9秒,JIT 耗时1.8秒。
反向案例
在策略实现过程中,如果以上两个行情回调函数中,把指标以及待买入和待卖出的股票池的两个全局变量进复制给本地变量,或者再把本地变量复制给全局变量。具体实现代码如下,使用单只标的20个交易日共24.1万条行情数据进行测试非
JIT 共耗时 4.4 秒,比原来减少复制的方式耗时多了10%。
def onSnapshot(mutable context, msg, indicator){
//
buyList = context["buyList"]
sellList = context["sellList"]
istock = msg.symbol
macd = indicator.macd
prevMacd = indicator.prevMacd
if( prevMacd < 0 and macd >0 ){//MACD 指标出现金叉
//...
}
else if((prevMacd > 0 and macd < 0) or (istock in sellList)){//MACD 指标死叉,平仓
//...
}
context["buyList"] = buyList
context["sellList"] = sellList
}
def onTick(mutable context, msg, indicator){
buyList = context["buyList"]
sellList = context["sellList"]
istock = msg.symbol
if(istock in buyList){
cci = indicator.cci
prevcci = indicator.prevcci
tradeVol30s = indicator.tradeVol30s
//指标从下向上突破+100线进入超买区间时,过去30s内的成交量大于10000股是买入
if(prevcci < 100. and cci >= 100. and tradeVol30s > 10000){
buyFlag = true
}
//指标从下向上突破-100线,买入
if( prevcci < -100. and cci >= -100. ){
buyFlag = true
}
}
context["buyList"] = buyList
context["sellList"] = sellList
}
蛇精是什么意思4s店保养如何避免上当