在做商品期货交易时,并非都是全自动的交易策略,有很多半自动的程序化交易工具代替人工盯盘。这类工具虽然算不上完整的策略,但是也是基于使用者的交易意图,有条理的进行交易,也算是一种最简单的交易策略吧。下面我们就一起来实现一个交易工具。
对于半自动的交易工具可能会有很多需求,我们简单整理一些需求实现出来,对于更加高级、复杂的需求可以后续优化升级。
商品期货计划委托工具(教学)
-
计划委托:
制定委托任务,由策略参数设置的价格线,下单手数,多空方向,触发方式,确定任务。 -
止盈
计划委托订单成交以后,根据设置的止盈价格,创建计划止盈任务。 -
止损
计划委托订单成交以后,根据设置的止损价格,创建计划止损任务。 -
止损反手
根据止损任务触发后,制定反手任务。
有了以上需求,我们就可以逐一把功能实现,首先分析一下,止盈、止损动作是建立在开始的计划委托订单成交,有持仓以后,再产生的动作,所以止盈、止损是基于,第一个计划委托订单成交以后再创建。止损反手同样也是基于止损完成以后再产生的动作。
大致是这样的一个流程。
所以这里遇到第一个问题,我们设计时,如何让一个任务完成以后,自动创建另一个后续任务呢?
这个问题解决很简单,FMZ量化交易平台提供了强大的模板类库,用自带的商品期货交易类库就可以轻松解决。
每个策略创建时都会在模板栏中自带这个类库,勾选上,保存策略即可引用。
回到我们的问题,如何让一个任务完成后,自动创建预先设置好的另一个任务,我们使用「商品期货交易类库」中的$.NewTaskQueue()
函数创建的交易队列控制对象就可以。
策略中定义的全局变量
// 全局变量 var _TaskQueue = [] // 任务队列,数据结构是一个数组。 var ENTRUST = 0 // 定义的任务类型,ENTRUST 代表计划委托 var STOPLOSS = 1 // 代表止损委托 var STOPPROFIT = 2 // 代表止盈委托 var BACKHAND = 3 // 代表反手委托 var dictTaskType = ["委托任务", "止损任务", "止盈任务", "反手任务"] // 委托类型的中文参照,用于程序中显示这些名字 var q = $.NewTaskQueue() // 创建 商品期货交易类库 交易队列控制对象。
如何使用这个对象 q
呢?很简单,如下代码。task.taskSymbol
为当前任务的合约代码,task.taskDirection
为当前任务的交易方向,task.taskAmount
为当前任务的下单数量。最后function(tradeTask, ret) {...
就是解决我们问题的回调函数。可以在当前任务完成后,触发执行这个回调函数,这样我们把后续任务的创建操作写在这个回调函数中就可以轻松实现。
q.pushTask(exchange, task.taskSymbol, task.taskDirection, task.taskAmount, function(tradeTask, ret) { Log(tradeTask.desc, ret, "XX委托完成") if (ret) { // 回调,创建后续任务 // ... // .. // . } })
我们先看完整的策略代码:
// 全局变量 var _TaskQueue = [] var ENTRUST = 0 var STOPLOSS = 1 var STOPPROFIT = 2 var BACKHAND = 3 var dictTaskType = ["委托任务", "止损任务", "止盈任务", "反手任务"] var q = $.NewTaskQueue() function closeTask (taskType) { for (var i = 0; i < _TaskQueue.length; i++) { if (taskType == _TaskQueue[i].taskType) { _TaskQueue[i].taskFinished = true Log("关闭任务:", dictTaskType[taskType]) } } } // 任务处理对象 function TaskQueueProcess () { // 获取行情 exchange.SetContractType(_EntrustSymbol) var ticker = _C(exchange.GetTicker) for (var i = 0; i < _TaskQueue.length; i++) { var task = _TaskQueue[i] if (task.taskFinished == false && task.taskType == ENTRUST && task.taskTrigger * ticker.Last >= task.taskTrigger * task.taskPrice) { q.pushTask(exchange, task.taskSymbol, task.taskDirection, task.taskAmount, function(tradeTask, ret) { Log(tradeTask.desc, ret, "委托完成") if (ret) { // 回调,创建后续任务 if (_EntrustStopLossPrice != -1) { // 创建止损任务 var newTask = { taskType : STOPLOSS, taskSymbol : task.taskSymbol, taskPrice : _EntrustStopLossPrice, taskAmount : task.taskAmount, taskDirection : task.taskDirection == "buy" ? "closebuy" : "closesell", taskTrigger : -1, // 低于 价格触发 taskFinished : false } _TaskQueue.push(newTask) Log("创建止损任务", newTask, "#FF0000") } if (_EntrustStopProfitPrice != -1) { // 创建止盈任务 var newTask = { taskType : STOPPROFIT, taskSymbol : task.taskSymbol, taskPrice : _EntrustStopProfitPrice, taskAmount : task.taskAmount, taskDirection : task.taskDirection == "buy" ? "closebuy" : "closesell", taskTrigger : 1, // 高于 价格触发 taskFinished : false } _TaskQueue.push(newTask) Log("创建止盈任务", newTask, "#FF0000") } } }) task.taskFinished = true break } else if (task.taskFinished == false && task.taskType == STOPLOSS && ticker.Last * task.taskTrigger >= task.taskPrice * task.taskTrigger) { q.pushTask(exchange, task.taskSymbol, task.taskDirection, task.taskAmount, function(tradeTask, ret) { Log(tradeTask.desc, ret, "止损完成") // 关闭止盈任务 closeTask(STOPPROFIT) if (ret) { // 回调,创建后续任务 if (_EntrustStopLossBackhandPrice != -1) { var newTask = { taskType : BACKHAND, taskSymbol : task.taskSymbol, taskPrice : _EntrustStopLossBackhandPrice, taskAmount : task.taskAmount, taskDirection : task.taskDirection == "closebuy" ? "sell" : "buy", taskTrigger : task.taskDirection == "closebuy" ? -1 : 1, // -1 小于时触发, 1大于时触发 taskFinished : false } _TaskQueue.push(newTask) Log("创建反手任务", newTask, "#FF0000") } } }) task.taskFinished = true break } else if (task.taskFinished == false && task.taskType == STOPPROFIT && ticker.Last * task.taskTrigger >= task.taskPrice * task.taskTrigger) { q.pushTask(exchange, task.taskSymbol, task.taskDirection, task.taskAmount, function(tradeTask, ret) { Log(tradeTask.desc, ret, "止盈完成") // 关闭止损任务 closeTask(STOPLOSS) }) task.taskFinished = true break } else if (task.taskFinished == false && task.taskType == BACKHAND && ticker.Last * task.taskTrigger >= task.taskPrice * task.taskTrigger) { q.pushTask(exchange, task.taskSymbol, task.taskDirection, task.taskAmount, function(tradeTask, ret) { Log(tradeTask.desc, ret, "反手完成") }) task.taskFinished = true break } } q.poll() } function main() { if (_IsRecovery) { recoveryData = _G("_TaskQueue") if (!recoveryData) { _TaskQueue = recoveryData } else { Log("没有可用于恢复的数据") } } // 根据参数生成任务 if (_EntrustSymbol == "null" || _EntrustPrice <= 0 || _EntrustAmount <= 0) { throw "没有设置委托合约或者委托价格无效或者委托数量无效" } else { var task = { taskType : ENTRUST, taskSymbol : _EntrustSymbol, taskPrice : _EntrustPrice, taskAmount : _EntrustAmount, taskDirection : _EntrustMode == 0 ? "buy" : "sell", taskTrigger : _EntrustTriggerType == 0 ? 1 : -1, taskFinished : false } Log("请注意,创建委托任务", task, "#FF0000") _TaskQueue.push(task) } while (true) { if (exchange.IO("status")) { TaskQueueProcess() // 状态栏显示 LogStatus(_D(), "已连接") } else { LogStatus(_D(), "未连接") } Sleep(500) } } // 扫尾取消所有挂单 function onexit(){ Log("策略停止,保存数据:", _TaskQueue) _G("_TaskQueue", _TaskQueue) } function onerror(){ Log("策略异常停止,保存数据:", _TaskQueue) _G("_TaskQueue", _TaskQueue) }
通篇看下策略代码,其实最核心的就是TaskQueueProcess
这个函数,在该函数中对_TaskQueue
数组中储存的任务进行逐一判断,判断任务是否生效,判断当前的行情条件结合具体的任务参数,是否达到我们的委托触发条件。达到触发条件,然后使用模板的q
对象,执行任务,根据预先设置好的回调函数,在任务执行完成时,创建后续任务,从而完成委托流程。
回测测试:
参数设置时:
参数默认为无效参数,例如「委托价格」为-1时,即为不委托。同理,止损、止盈、反手价格也一样,设置-1即不启用该功能。在代码中可以看到:
判断,设置的参数如果是-1的话,是不会在回调函数中创建后续任务的。
教学策略链接:https://www.fmz.com/strategy/175150
策略仅用于教学,实盘慎用。