发明者 - 筑就非凡的量化世界
摘要均线策略作为最简单的趋势策略,通常是程序化、量化交易入门的必修课。本篇文章我们不探究策略原理,我们用发明者量化(FMZ.COM)从策略设计层面入手,剖析一个多品种策略的架构设计,学习一些策略架构设计的经验。
1多品种策略优势
多品种策略设计的优点在于使用方便,一个策略程序控制交易多个品种,可以统一信息状态显示。交易多个品种相对分散了风险,增加了交易机会。缺点在于设计比较复杂,各个品种之间不能相互影响,对程序执行效率要求比较高。
所以设计难度远大于设计一个单品种策略。第一种方式比较简单,在发明者量化交易平台可以使用「商品期货交易类库中的CTA函数」轻松实现多品种策略。第二种方式就是直接写策略,好在发明者量化交易平台上提供了大量策略范例,给我们提供了丰富的参考代码,设计思路。
2策略代码
我们就使用「CTP商品期货多品种海龟交易策略」策略作为参考,修改成我们需要的多品种策略,从简单入手,比如修改成一个多品种均线策略。节选部分代码:
- start: 2019-07-01 09:00:00
复制代码- exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}]
复制代码- var _bot = $.NewPositionManager();
复制代码- New: function(needRestore, symbol, keepBalance, fastPeriod, slowPeriod) {
复制代码- var symbolDetail = _C(exchange.SetContractType, symbol);
复制代码- if (symbolDetail.VolumeMultiple == 0 || symbolDetail.MaxLimitOrderVolume == 0 || symbolDetail.MinLimitOrderVolume == 0 || symbolDetail.LongMarginRatio == 0 || symbolDetail.ShortMarginRatio == 0) {
复制代码- Log("合约", symbolDetail.InstrumentName, "一手", symbolDetail.VolumeMultiple, "份, 最大下单量", symbolDetail.MaxLimitOrderVolume, "保证金率:", _N(symbolDetail.LongMarginRatio), _N(symbolDetail.ShortMarginRatio), "交割日期", symbolDetail.StartDelivDate);
复制代码- var errMsg = ["成功", "切换合约失败", "获取订单失败", "获取持仓失败", "交易下单失败", "获取深度失败", "不在交易时间"];
复制代码- keepBalance: keepBalance,
复制代码- obj.symbolDetail = symbolDetail;
复制代码- symbolDetail: symbolDetail,
复制代码- obj.setLastError = function(err) {
复制代码- if (typeof(err) === 'undefined' || err === '') {
复制代码- obj.status.lastErrTime = "";
复制代码- obj.status.lastErr = err;
复制代码- obj.status.lastErrTime = t.toLocaleString();
复制代码- obj.reset = function(marketPosition) {
复制代码- if (typeof(marketPosition) !== 'undefined') {
复制代码- obj.marketPosition = marketPosition;
复制代码- // 例如多品种海龟策略,需要恢复N值,入市周期、离市周期等程序运行中的数据,可以在此处恢复,如不清楚,可以参考原版多品种海龟策略此处的代码
复制代码- var pos = _bot.GetPosition(obj.symbol, marketPosition > 0 ? PD_LONG : PD_SHORT);
复制代码- obj.holdPrice = pos.Price;
复制代码- obj.holdAmount = pos.Amount;
复制代码- Log(obj.symbol, "仓位", pos);
复制代码- throw "恢复" + obj.symbol + "的持仓状态出错, 没有找到仓位信息";
复制代码- // 可以根据策略,打印一些恢复后的数据,参考原版多品种海龟策略
复制代码- Log("恢复", obj.symbol, "持仓均价:", obj.holdPrice, "持仓数量:", obj.holdAmount);
复制代码- obj.status.vm = [obj.marketPosition];
复制代码- obj.Status = function() {
复制代码- obj.status.marketPosition = obj.marketPosition;
复制代码- obj.status.holdPrice = obj.holdPrice;
复制代码- obj.status.holdAmount = obj.holdAmount;
复制代码- obj.status.lastPrice = obj.lastPrice;
复制代码- if (obj.lastPrice > 0 && obj.holdAmount > 0 && obj.marketPosition !== 0) {
复制代码- obj.status.holdProfit = _N((obj.lastPrice - obj.holdPrice) * obj.holdAmount * symbolDetail.VolumeMultiple, 4) * (obj.marketPosition > 0 ? 1 : -1);
复制代码- obj.status.holdProfit = 0;
复制代码- obj.setTask = function(action, amount, onFinish) {
复制代码- obj.processTask = function() {
复制代码- obj.Poll = function(subroutine) {
复制代码- obj.status.isTrading = $.IsTrading(obj.symbol);
复制代码- if (!obj.status.isTrading) {
复制代码- if (obj.task.action != ACT_IDLE) {
复制代码- var retCode = obj.processTask();
复制代码- if (obj.task.action != ACT_IDLE) {
复制代码- obj.setLastError("任务没有处理成功: " + errMsg[retCode] + ", " + obj.task.desc + ", 重试: " + obj.task.retry);
复制代码- // 调用Poll时如果设置了subroutine参数,只运行到此处,这个是程序设计的一个小技巧
复制代码- if (typeof(subroutine) !== 'undefined' && subroutine) {
复制代码- var suffix = WXPush ? '@' : '';
复制代码- _C(exchange.SetContractType, obj.symbol);
复制代码- var records = exchange.GetRecords();
复制代码- obj.setLastError("获取K线失败");
复制代码- obj.status.recordsLen = records.length;
复制代码- if (records.length < (obj.fastPeriod + 2) || records.length < (obj.slowPeriod +2)) {
复制代码- obj.setLastError("K线长度小于 均线周期:" + obj.fastPeriod + "或" + obj.slowPeriod);
复制代码- var opCode = 0; // 0: IDLE 空闲, 1: LONG 做多, 2: SHORT 做空, 3: CoverALL 当前合约全部平仓。opCode 是操作码,以下策略逻辑检测到条件后设置对应的操作码
复制代码- var lastPrice = records[records.length - 1].Close;
复制代码- obj.lastPrice = lastPrice;
复制代码- var fastMA = TA.EMA(records, obj.fastPeriod)
复制代码- var slowMA = TA.EMA(records, obj.slowPeriod)
复制代码- if (obj.marketPosition === 0) { // 不持仓时
复制代码- // 根据交易逻辑赋值opCode信号,程序后续根据信号处理
复制代码- if(fastMA[fastMA.length - 3] < slowMA[slowMA.length - 3] && fastMA[fastMA.length - 2] > slowMA[slowMA.length - 2]) { // long 均线金叉做多
复制代码- } else if (fastMA[fastMA.length - 3] > slowMA[slowMA.length - 3] && fastMA[fastMA.length - 2] < slowMA[slowMA.length - 2]) { // short 均线死叉做空
复制代码- if(obj.marketPosition < 0 && fastMA[fastMA.length - 3] < slowMA[slowMA.length - 3] && fastMA[fastMA.length - 2] > slowMA[slowMA.length - 2]) { // 持空头仓位,金叉,平仓
复制代码- } else if (obj.marketPosition > 0 && fastMA[fastMA.length - 3] > slowMA[slowMA.length - 3] && fastMA[fastMA.length - 2] < slowMA[slowMA.length - 2]) { // 持多头仓位,死叉,平仓
复制代码- // 可以设计更加复杂的交易逻辑,加仓、减仓、止损、止盈等,可以参考原版多品种海龟策略
复制代码- obj.setTask(ACT_COVER, 0, function(ret) { // 设置回调,清空保存的持久化数据
复制代码- if (Math.abs(obj.marketPosition) >= obj.maxLots) {
复制代码- obj.setLastError("禁止开仓, 超过最大持仓 " + obj.maxLots);
复制代码- var account = _bot.GetAccount();
复制代码- var canOpen = parseInt((account.Balance-obj.keepBalance) / (opCode == 1 ? obj.symbolDetail.LongMarginRatio : obj.symbolDetail.ShortMarginRatio) / (lastPrice * 1.2) / obj.symbolDetail.VolumeMultiple);
复制代码- var unit = Math.min(1, canOpen); // 开仓数量处理
复制代码- obj.setTask((opCode == 1 ? ACT_LONG : ACT_SHORT), unit, function(ret) {
复制代码- obj.setLastError("下单失败");
复制代码- // 下单成功后,打印一些信息,保存一些数据,参考原版多品种海龟交易策略
复制代码- obj.holdPrice = ret.position.Price;
复制代码- obj.holdAmount = ret.position.Amount;
复制代码- obj.marketPosition += opCode == 1 ? 1 : -1;
复制代码- obj.status.vm = [obj.marketPosition];
复制代码- _G(obj.symbol, obj.status.vm);
复制代码- vm = JSON.parse(VMStatus)[obj.symbol];
复制代码- Log("准备恢复进度, 当前合约状态为", vm);
复制代码- // 恢复进度,和reset函数传入参数相关,当前策略只有一个持仓参数,可以参考原版多品种海龟策略加以比较学习
复制代码- Log("没有找到" + obj.symbol + "的进度恢复信息");
复制代码- if (exchange.GetName().indexOf('CTP') == -1) {
复制代码- SetErrorFilter("login|ready|流控|连接失败|初始|Timeout");
复制代码- var mode = exchange.IO("mode", 0);
复制代码- if (typeof(mode) !== 'number') {
复制代码- throw "切换模式失败, 请更新到最新托管者!";
复制代码- while (!exchange.IO("status")) {
复制代码- LogStatus("正在等待与交易服务器连接, " + new Date());
复制代码- var positions = _C(exchange.GetPosition);
复制代码- if (positions.length > 0) {
复制代码- Log("检测到当前持有仓位, 系统将开始尝试恢复进度...");
复制代码- var initAccount = _bot.GetAccount();
复制代码- var initMargin = JSON.parse(exchange.GetRawJSON()).CurrMargin;
复制代码- var keepBalance = _N((initAccount.Balance + initMargin) * (KeepRatio/100), 3);
复制代码- Log("资产信息", initAccount, "保留资金:", keepBalance);
复制代码- var arr = Instruments.split(',');
复制代码- var arrFastPeriod = FastPeriodArr.split(',')
复制代码- var arrSlowPeriod = SlowPeriodArr.split(',')
复制代码- if (arr.length != arrFastPeriod.length || arr.length != arrSlowPeriod.length) {
复制代码- throw "均线周期参数与添加合约数量不匹配,请检查参数!"
复制代码- for (var i = 0; i < arr.length; i++) {
复制代码- var symbol = arr[i].replace(/^\s+/g, "").replace(/\s+$/g, "");
复制代码- if (typeof(filter[symbol]) !== 'undefined') {
复制代码- throw symbol + "已经存在,请检查参数!"
复制代码- for (var j = 0; j < positions.length; j++) {
复制代码- if (positions[j].ContractType == symbol) {
复制代码- var fastPeriod = parseInt(arrFastPeriod[i])
复制代码- var slowPeriod = parseInt(arrSlowPeriod[i])
复制代码- var obj = Manager.New(hasPosition, symbol, keepBalance, fastPeriod, slowPeriod)
复制代码- if (GetCommand() === "暂停/继续") {
复制代码- while (GetCommand() !== "暂停/继续") {
复制代码- while (!exchange.IO("status")) {
复制代码- LogStatus("正在等待与交易服务器连接, " + new Date() + "\n" + lastStatus);
复制代码- cols: ["合约名称", "持仓方向", "持仓均价", "持仓数量", "持仓盈亏", "加仓次数", "当前价格"],
复制代码- cols: ["合约名称", "合约乘数", "保证金率", "交易时间", "柱线长度", "异常描述", "发生时间"],
复制代码- var ts = new Date().getTime();
复制代码- for (var i = 0; i < tts.length; i++) {
复制代码- vmStatus[d.symbol] = d.vm;
复制代码- tblStatus.rows.push([d.symbolDetail.InstrumentName, d.holdAmount == 0 ? '--' : (d.marketPosition > 0 ? '多' : '空'), d.holdPrice, d.holdAmount, d.holdProfit, Math.abs(d.marketPosition), d.lastPrice]);
复制代码- tblMarket.rows.push([d.symbolDetail.InstrumentName, d.symbolDetail.VolumeMultiple, _N(d.symbolDetail.LongMarginRatio, 4) + '/' + _N(d.symbolDetail.ShortMarginRatio, 4), (d.isTrading ? '是#0000ff' : '否#ff0000'), d.recordsLen, d.lastErr, d.lastErrTime]);
复制代码- totalHold += Math.abs(d.holdAmount);
复制代码- var elapsed = now.getTime() - ts;
复制代码- var tblAssets = _bot.GetAccount(true);
复制代码- var nowAccount = _bot.Account();
复制代码- if (tblAssets.rows.length > 10) {
复制代码- tblAssets.rows[0] = ["InitAccount", "初始资产", initAccount];
复制代码- tblAssets.rows.unshift(["NowAccount", "当前可用", nowAccount], ["InitAccount", "初始资产", initAccount]);
复制代码- lastStatus = '`' + JSON.stringify([tblStatus, tblMarket, tblAssets]) + '`\n轮询耗时: ' + elapsed + ' 毫秒, 当前时间: ' + now.toLocaleString() + ', 星期' + ['日', '一', '二', '三', '四', '五', '六'][now.getDay()] + ", 持有品种个数: " + holdSymbol;
复制代码- lastStatus += "\n手动恢复字符串: " + JSON.stringify(vmStatus);
复制代码- if (preTotalHold > 0 && totalHold == 0) {
复制代码- LogProfit(nowAccount.Balance - initAccount.Balance - initMargin);
复制代码- preTotalHold = totalHold;
复制代码- Sleep(LoopInterval * 1000);
复制代码
3策略参数
和原版「CTP商品期货多品种海龟交易策略」一样,该策略也要引用商品期货交易类库。策略代码比较长,每段关键位置,都有注释,说明了这段代码的用途。
耐心看完对比下原版的「CTP商品期货多品种海龟交易策略」会发现,其实没有什么改动,仅仅是修改了策略交易部分的逻辑代码,策略其他部分代码完全复用了。这不得不说得益于原版策略设计的巧妙,让策略交易逻辑和策略下单处理逻辑等其它与策略不相关的功能代码分离的很好。
这些代码耦合很低,所以非常容易修改(前提是通读过策略,完全理解策略架构之后)。其实完全可以把原版策略中和交易策略相关的内容分离出来删除掉,只留下一个多品种策略框架,就可以根据自己的需求随意开发了。
4策略回测
随便设置了一组参数,回测最近的行情:
策略仅供参考学习,实盘慎用。策略地址:
https://www.fmz.com/strategy/193043
发明者量化交易之所以运营这个公众号,旨在改变当前量化圈无干货,交流闭塞,骗子横行的现状,打造一个更纯净的量化圈子。 更多内容请访问我们的网站(www.fmz.com)
您的转发,将是支持我们继续创作更多干货的动力!如果您觉得这篇文章对您有帮助,请多多转发,支持我们。分享也是一种智慧!
长按二维码识别关注我们
联系我们微信:fmz_zhangchao邮箱:hi@fmz.com网址:www.fmz.com
|
|