经典的多品种海龟交易策略升级了,新增加了移仓模块,再也不用每次临近交割时,手忙脚乱的N个品种平仓开仓处理了。对于策略设计,架构设计很重要,一个良好的架构,升级功能,调试测试,扩展优化都是非常方便,并且不易出现潜藏BUG的。
策略代码中增加的移仓部分代码:
// switch symbol
var insDetail = exchange.SetContractType(obj.symbol); // obj.symbol 为策略参数设置的rb888
if (!insDetail) {
return
}
var records = exchange.GetRecords();
if (!records) {
obj.setLastError("获取K线失败");
return;
}
// 上面几行为了方便说明,以下部分为新增代码
// update tradeSymbol
var tradeSymbol = insDetail.InstrumentID; // insDetail 变量中记录的是rb888 映射的真正的合约信息
if (tradeSymbol != obj.tradeSymbol) {
var oldSymbol = obj.tradeSymbol;
var pos = _q.GetPosition(exchange, oldSymbol);
if (pos && pos.Amount > 0) {
Log("开始移仓", oldSymbol, "->", tradeSymbol, "数量:", pos.Amount, "#ff0000");
obj.status.switchCount++;
_q.pushTask(exchange, oldSymbol, (pos.Type == PD_LONG ? "closebuy" : "closesell"), pos.Amount, function(task, ret) {
if (!ret) {
Log(oldSymbol, "移仓平仓失败 #ff0000");
return;
}
Log("移仓进度平仓成功, 开始开仓", oldSymbol, "->", tradeSymbol, "数量:", pos.Amount, "#0000ff");
obj.tradeSymbol = tradeSymbol;
obj.symbolDetail = insDetail;
_q.pushTask(exchange, tradeSymbol, (pos.Type == PD_LONG ? "buy" : "sell"), pos.Amount, function(task, ret) {
if (!ret) {
Log(tradeSymbol, "移仓开仓失败, 重置品种进度 #ff0000");
obj.marketPosition = 0;
return;
}
Log("移仓成功", oldSymbol, "->", tradeSymbol, "#0000ff");
});
});
return;
} else {
obj.tradeSymbol = tradeSymbol;
obj.symbolDetail = insDetail;
}
}
移仓逻辑其实非常简单,当策略设置参与海龟交易的合约代码为主力合约ID时,例如设置了rb888
,其实对应的是当前的合约ID:rb2010
。
策略就是在每次执行交易逻辑前,调用函数:
var insDetail = exchange.SetContractType(obj.symbol); // obj.symbol 为策略参数设置的rb888
...
var tradeSymbol = insDetail.InstrumentID; // insDetail 变量中记录的是rb888 映射的真正的合约信息
其中insDetail.InstrumentID
就是当前真正的主力合约ID,拿到当前的主力合约ID,也就是tradeSymbol
变量存储的字符串。
检查当前合约真正的主力合约IDtradeSymbol
和策略记录的操作的合约ID:obj.tradeSymbol
是不是一致。如果不同,说明主力合约已经切换了,就触发移仓操作,移仓操作第一步先平掉之前仓位。第二部操作是当第一步平仓完成时,在新的主力合约开仓相同的头寸。移仓时,使用了「商品期货交易类库」中的任务队列对象去处理下单操作。这样做的好处是把当前新主力合约开仓的交易任务作为旧主力合约平仓任务的回调函数,待平仓完成后,自动触发,可以看下「商品期货交易类库」的$.NewTaskQueue()
设计。
策略代码通篇看来,内容不少,容易让初学者望而生畏。但是不要被吓到,其实不难,整篇策略可以分为几个部分,逐个部分阅读方便理解。
function main() {
if (exchange.GetName().indexOf('CTP') == -1) {
throw "只支持商品期货CTP";
}
SetErrorFilter("login|ready|流控|连接失败|初始|Timeout");
var mode = exchange.IO("mode", 0);
if (typeof(mode) !== 'number') {
throw "切换模式失败, 请更新到最新托管者!";
}
while (!exchange.IO("status")) {
Sleep(3000);
LogStatus("正在等待与交易服务器连接, " + _D());
}
var positions = _C(exchange.GetPosition);
if (positions.length > 0) {
Log("检测到当前持有仓位, 系统将开始尝试恢复进度...");
Log("持仓信息", positions);
}
Log("风险系数:", RiskRatio, "N值周期:", ATRLength, "系统1: 入市周期", EnterPeriodA, "离市周期", LeavePeriodA, "系统二: 入市周期", EnterPeriodB, "离市周期", LeavePeriodB, "加仓系数:", IncSpace, "止损系数:", StopLossRatio, "单品种最多开仓:", MaxLots, "次");
var initAccount = _q.GetAccount(exchange);
var initMargin = JSON.parse(exchange.GetRawJSON()).CurrMargin;
var realInitBalance = initAccount.Balance + initMargin;
if (CustomBalance) {
realInitBalance = InitBalance;
Log("自定义启动资产为", realInitBalance);
}
var keepBalance = _N(realInitBalance * (KeepRatio/100), 3);
Log("当前资产信息", initAccount, "保留资金:", keepBalance);
var tts = [];
var filter = [];
var arr = Instruments.split(',');
for (var i = 0; i < arr.length; i++) {
var symbol = arr[i].replace(/^\s+/g, "").replace(/\s+$/g, "");
if (typeof(filter[symbol]) !== 'undefined') {
Log(symbol, "已经存在, 系统已自动过滤");
continue;
}
filter[symbol] = true;
var hasPosition = false;
for (var j = 0; j < positions.length; j++) {
if (positions[j].ContractType == symbol) {
hasPosition = true;
break;
}
}
var obj = TTManager.New(hasPosition, symbol, realInitBalance, keepBalance, RiskRatio, ATRLength, EnterPeriodA, LeavePeriodA, EnterPeriodB, LeavePeriodB, UseEnterFilter, IncSpace, StopLossRatio, MaxLots);
tts.push(obj);
}
// 其它代码...
这部分代码,主要做了策略程序运行起始,对配置设置,对于账户仓位的检查,对参数上设置的要做海龟交易的合约ID做检查、过滤重复的合约ID。然后对应每个要做的合约,构造海龟交易逻辑对象:
var obj = TTManager.New(hasPosition, symbol, realInitBalance, keepBalance, RiskRatio, ATRLength, EnterPeriodA, LeavePeriodA, EnterPeriodB, LeavePeriodB, UseEnterFilter, IncSpace, StopLossRatio, MaxLots);
这部分主要是做交易逻辑真正执行前的一些初始化工作。
该循环为策略的主要循环,这一部分主要是处理策略运行时的界面显示,交互检测,遍历所有的海龟交易逻辑对象,调用每个海龟交易逻辑对象的处理函数tts[i].Poll();
。可以看到,这里把海龟交易逻辑相关的操作都完全独立了出来,让整个策略层次比较分明。所有海龟交易逻辑相关的内容都在Poll()
函数内执行。
其余的代码主要就是var TTManager = {...}
这个海龟交易逻辑对象的构造函数了,这个函数主要就是用来构造海龟交易逻辑对象的。在程序初始化部分中,调用了这个函数,构造每个要交易的合约对应的海龟交易逻辑对象。整个的「海龟交易法则」用代码表达的部分都封装在这个部分。
这个策略在往期的一些文章里面也详细介绍过,就不做过多的赘述,策略代码只要认真阅读下,可以学习到不少策略设计方面的经验。我的另一篇文章:https://www.youquant.com/digest-topic/5235 ,商品期货多品种均线策略,也是修改自该策略。实际上这个策略整个框架设计的非常易于扩展、修改,仅仅是改动很小部分就可以,不会牵一发动全身的引起各种问题。
我们本篇的另一个重心,讲解一下我们的回测系统。
模拟级别回测
根据设置的底层K线周期,依据类似MT4的tick
模拟算法,在K线数据框架内模拟出tick数据,这样的好处是,回测行情数据大大减少,回测速度较快,适用于低频率交易的趋势跟踪策略。但是不适用于高频率交易的策略,不适用于基于盘口、tick数据算法的策略。
实盘级别回测
实盘级别回测则是回放逐个真实的tick数据,每个tick数据都是真实记录的。回测速度会比较慢。实盘级别回测并且支持,真实深度数据回放、真实市场分笔交易记录回放。
回测配置保存
可以点击「保存回测设置」,让回测配置代码化,保存在策略开头部分,回测时就可以自动加载这个回测配置。
支持任何自定义K线周期
可以设置任意K线周期进行回测。
容错模式回测
对于策略设计初学者,可能编程基础各不一样,难免写出一些潜在BUG,可以用回测系统的「容错模式」回测,初步找出一些潜藏的程序BUG,非常方便。 容错模式回测时,回测系统会随机给各个调用的接口返回错误,检验策略程序的容错能力、程序健壮性。在策略开发设计完时,不妨用容错模式回测,初步检验一下。
策略能经受容错模式回测,策略逻辑回测正常完成,说明策略具备了最基本的容错处理。
可以看到多品种策略回测时,所有订阅的合约都加载到了回测系统。
回测系统可以自动生成丰富的绩效图表,用来衡量策略绩效。
回测日志中可以清楚的看到,新增的移仓模块正常的处理了移仓任务。
感谢阅读,欢迎留言。