该策略是好久之前适用数字货币796期货交易所的一个策略,期货合约是币本位,即保证金扣除的是币(例如BTC合约扣除BTC),合约下单量能够是小数,相似币安的币本位合约。
该策略拿出来学习策略设计,逻辑处理仍是很不错的,策略以学习为主。javascript
策略代码注释
var FirstTradeType = [ORDER_TYPE_BUY, ORDER_TYPE_SELL][OpType]; // 首次开仓方向,根据参数OpType开仓方向肯定,全局变量FirstTradeType的值为ORDER_TYPE_BUY或者ORDER_TYPE_SELL var OrgAccount = null; // 全局变量,记录帐户资产 var Counter = {s : 0, f: 0}; // 声明一个变量Counter,值初始化为一个对象(相似的结构python叫字典),s表明胜的次数,f表明负的次数 var LastProfit = 0; // 最近盈亏 var AllProfit = 0; // 总盈亏 var _Failed = 0; // 止损次数 // 取消挂单列表中某个ID的订单以外的全部订单,当不传orderId参数时,取消当前交易对全部挂单,参数e为交易所对象的引用,例如传入exchange做为参数,此刻e就是exchange的别名 function StripOrders(e, orderId) { var order = null; // 初始化变量 order 为空 if (typeof(orderId) == 'undefined') { // 若是参数 orderId 传入时,没有写,则typeof(orderId) == 'undefined'成立,执行if语句的代码块,orderId 赋值为null orderId = null; } while (true) { // 处理循环 var dropped = 0; // 处理标记次数 var orders = _C(e.GetOrders); // 调用GetOrders获取当前挂单(未彻底成交的订单),赋值给orders for (var i = 0; i < orders.length; i++) { // 遍历未成交订单列表 orders if (orders[i].Id == orderId) { // 若是订单ID和参数上传入的订单ID orderId 相同则给函数内局部变量order赋值orders[i],orders[i]即遍历时当前的订单结构 order = orders[i]; } else { // 若是ID不相同,执行撤销操做 var extra = ""; // 根据部分红交状况,设置扩展信息extra if (orders[i].DealAmount > 0) { extra = "成交: " + orders[i].DealAmount; } else { extra = "未成交"; } e.SetDirection(orders[i].Type == ORDER_TYPE_BUY ? "buy" : "sell"); e.CancelOrder(orders[i].Id, orders[i].Type == ORDER_TYPE_BUY ? "买单" : "卖单", extra); // 撤单操做,附带输出extra信息,在日志上会显示 dropped++; // dropped 计数累计 } } if (dropped == 0) { // 当遍历完成时,dropped 等于0,即遍历时没有一次撤销处理(没有须要撤销的订单了),即为撤销处理工做完成,跳出while循环 break; } Sleep(300); // 防止轮转频率过快,每次间隔必定时间 } return order; // 返回要查找的订单order } var preMsg = ""; // 记录缓存信息的变量 function GetAccount(e, waitFrozen) { // 获取帐户资产信息,参数e亦是exchange的引用,参数waitFrozen控制是否等待冻结 if (typeof(waitFrozen) == 'undefined') { // 若是调用时不传入waitFrozen参数,给参数waitFrozen赋值false,即默认不等待冻结 waitFrozen = false; } var account = null; var alreadyAlert = false; // 标记是否已经提醒过的变量 while (true) { // 获取当前帐户信息,检测冻结,若是不等待冻结,则会直接跳出while循环 account = _C(e.GetAccount); if (!waitFrozen || account.FrozenStocks < MinStock) { break; } if (!alreadyAlert) { alreadyAlert = true; // 触发提醒一次,就重置alreadyAlert,避免重复不停的提醒 Log("发现帐户有冻结的钱或币", account); // 输出提醒日志 } Sleep(Interval); } msg = "成功: " + Counter.s + " 次, 失败: " + Counter.f + " 次, 当前帐户 币: " + account.Stocks; if (account.FrozenStocks > 0) { msg += " 冻结的币: " + account.FrozenStocks; } if (msg != preMsg) { // 检测当前信息是否和上次信息不一样,不一样的话更新在状态栏上 preMsg = msg; LogStatus(msg, "#ff0000"); } return account; // 函数返回帐户信息 account结构 } function GetPosition(e, orderType) { // 获取持仓,或者获取指定方向的持仓 var positions = _C(e.GetPosition); // 获取持仓 if (typeof(orderType) == 'undefined') { // orderType 参数为指定要获取的持仓类型,若是没有传入orderType参数,直接返回全部持仓 return positions; } for (var i = 0; i < positions.length; i++) { // 遍历持仓列表 if (positions[i].Type == orderType) { // 若是当前遍历的持仓数据是须要找的方向(orderType) return positions[i]; // 返回 orderType 类型的持仓 } } return null; } function GetTicker(e) { // 获取ticker 行情数据 while (true) { var ticker = _C(e.GetTicker); // 获取tick行情 if (ticker.Buy > 0 && ticker.Sell > 0 && ticker.Sell > ticker.Buy) { // 检查行情数据可靠性 return ticker; // 返回 ticker数据 } Sleep(100); } } // mode = 0 : direct buy, 1 : buy as buy1 function Trade(e, tradeType, tradeAmount, mode, slidePrice, maxSpace, retryDelay) { // 交易函数 // e 交易所对象引用, tradeType 交易方向(买/卖), tradeAmount 交易数量, mode 交易模式, slidePrice 滑价, maxSpace 最大挂单距离, retryDelay 重试时间间隔 var initPosition = GetPosition(e, tradeType); // 获取指定方向的持仓数据,记做 initPosition var nowPosition = initPosition; // 声明另外一个变量nowPosition 用initPosition赋值 var orderId = null; var prePrice = 0; // 上次循环时的下单价格 var dealAmount = 0; // 已经交易的数量 var diffMoney = 0; var isFirst = true; // 循环首次执行标记 var tradeFunc = tradeType == ORDER_TYPE_BUY ? e.Buy : e.Sell; // 下单函数,根据参数 tradeType 而定是调用 e.Buy 仍是 e.Sell var isBuy = tradeType == ORDER_TYPE_BUY; // 是不是买入的标记 while (true) { // while循环 var account = _C(e.GetAccount); // 获取当前帐户资产数据 var ticker = GetTicker(e); // 获取当前行情数据 var tradePrice = 0; // 根据 mode 参数制定交易价格 if (isBuy) { tradePrice = _N((mode == 0 ? ticker.Sell : ticker.Buy) + slidePrice, 4); } else { tradePrice = _N((mode == 0 ? ticker.Buy : ticker.Sell) - slidePrice, 4); } if (orderId == null) { if (isFirst) { // 根据 isFirst 标记变量判断,若是是第一次执行,什么都不作 isFirst = false; // isFirst 标记设置为false ,表明已经不是第一次执行 } else { // 非第一次执行,更新持仓数据 nowPosition = GetPosition(e, tradeType); } dealAmount = _N((nowPosition ? nowPosition.Amount : 0) - (initPosition ? initPosition.Amount : 0), 6); // 根据最初的持仓数据和当前的持仓数据,计算已经成交的数量 var doAmount = Math.min(tradeAmount - dealAmount, account.Stocks * MarginLevel, 4); // 根据已经成交的数量、帐户可用资产,计算剩余须要交易的数量 if (doAmount < MinStock) { // 若是算出的交易数量小于最小交易数量,终止逻辑,跳出while循环 break; } prePrice = tradePrice; // 缓存当前循环时的交易价格 e.SetDirection(tradeType == ORDER_TYPE_BUY ? "buy" : "sell"); // 设置期货交易方向 orderId = tradeFunc(tradePrice, doAmount); // 下单交易,参数为算出的价格,本次下单数量 } else { // 当记录订单的变量 orderId 不为null时,则说明已经下过订单 if (mode == 0 || Math.abs(tradePrice - prePrice) > maxSpace) { // 若是是挂单模式,当前价格与上一次缓存的价格超出最大挂单区间 orderId = null; // 重置orderId 为空值,就会在下一轮循环从新下单 } var order = StripOrders(exchange, orderId); // 调用StripOrders查找挂单列表中的ID为orderId的订单 if (order == null) { // 若是查找不到,也重置orderId 为空值,继续下一轮的下单操做 orderId = null; } } Sleep(retryDelay); // 暂定必定时间,起到控制循环频率的效果 } if (dealAmount <= 0) { // 在while循环结束后,若是已经交易的量dealAmount小于等于0,说明交易失败返回空值 return null; } return nowPosition; // 正常状况返回最新的持仓数据 } function coverFutures(e, orderType) { // 平仓函数 var coverAmount = 0; // 声明一个变量coverAmount,初始赋值0,用来记录已经平仓的数量 while (true) { var positions = _C(e.GetPosition); // 获取持仓 var ticker = GetTicker(e); // 获取当前行情 var found = 0; // 查找标记 for (var i = 0; i < positions.length; i++) { // 遍历持仓数组positions if (positions[i].Type == orderType) { // 找到须要的持仓 if (coverAmount == 0) { coverAmount = positions[i].Amount; // 初始时记录持仓数量,即要平仓的数量 } if (positions[i].Type == ORDER_TYPE_BUY) { // 根据持仓类型,执行平仓操做 e.SetDirection("closebuy"); // 设置期货交易方向 e.Sell(ticker.Buy, positions[i].Amount); // 下单函数 } else { e.SetDirection("closesell"); e.Buy(ticker.Sell, positions[i].Amount); } found++; // 标记累计 } } if (found == 0) { // 若是标记变量found为0,则没有仓位须要处理,跳出while循环 break; } Sleep(2000); // 间隔2秒 StripOrders(e); // 撤销当前全部挂单 } return coverAmount; // 返回平仓的数量 } function loop(pos) { var tradeType = null; // 初始化交易方向 if (typeof(pos) == 'undefined' || !pos) { // 判断是不是首轮执行 tradeType = FirstTradeType; pos = Trade(exchange, tradeType, OpAmount, OpMode, SlidePrice, MaxSpace, Interval); // 首笔交易 if (!pos) { throw "出师不利, 开仓失败"; } else { Log(tradeType == ORDER_TYPE_BUY ? "开多仓完成" : "开空仓完成", "均价:", pos.Price, "数量:", pos.Amount); } } else { tradeType = pos.Type; // 根据持仓方向继续指定交易方向 } var holdPrice = pos.Price; // 持仓价格 var holdAmount = pos.Amount; // 持仓数量 var openFunc = tradeType == ORDER_TYPE_BUY ? exchange.Buy : exchange.Sell; // 多头持仓,开仓为买,不然开仓为卖 var coverFunc = tradeType == ORDER_TYPE_BUY ? exchange.Sell : exchange.Buy; // 多头持仓,平仓为卖,不然平仓位买 var reversePrice = 0; // 反手价格 var coverPrice = 0; // 平仓价格 var canOpen = true; // 可开仓标记 if (tradeType == ORDER_TYPE_BUY) { reversePrice = _N(holdPrice * (1 - StopLoss), 4); // 止损价格 coverPrice = _N(holdPrice * (1 + StopProfit), 4); // 止盈价格 } else { reversePrice = _N(holdPrice * (1 + StopLoss), 4); coverPrice = _N(holdPrice * (1 - StopProfit), 4); } var coverId = null; var msg = "持仓价: " + holdPrice + " 止损价: " + reversePrice; for (var i = 0; i < 10; i++) { // 控制最多下单10次 if (coverId) { // 订单ID为空,不触发break,继续循环,直到10次 break; } if (tradeType == ORDER_TYPE_BUY) { // 根据方向下单,挂平仓单,即止盈的订单 exchange.SetDirection("closebuy"); coverId = exchange.Sell(coverPrice, holdAmount, msg); } else { exchange.SetDirection("closesell"); coverId = exchange.Buy(coverPrice, holdAmount, msg); } Sleep(Interval); } if (!coverId) { // 10次下单还失败抛出错误,策略中止 StripOrders(exchange); // 撤销全部挂单 Log("下单失败", "@") // 增长推送提醒 throw "下单失败"; // 抛出错误,让机器人中止 } while (true) { // 进入检测反手的循环 Sleep(Interval); var ticker = GetTicker(exchange); // 获取最新的行情 if ((tradeType == ORDER_TYPE_BUY && ticker.Last < reversePrice) || (tradeType == ORDER_TYPE_SELL && ticker.Last > reversePrice)) { // 检测触发止损即反手 StripOrders(exchange); // 挂单所有撤单 var coverAmount = coverFutures(exchange, tradeType); // 持仓全平 if (_Failed >= MaxLoss) { // 若是超过最大止损次数(反手次数),跳出循环从新开始 Counter.f++; // 计一次失败 Log("超过最大失败次数", MaxLoss); break; // 跳出循环 } var reverseAmount = _N(coverAmount * ReverseRate, 4); // 根据平仓的量,进行交易量加倍 var account = GetAccount(exchange, true); // 更新帐户信息,此时不能有资产冻结 // 检测帐户资产是否足够,不足跳出循环,从新开始,如同_Failed >= MaxLoss if (_N(account.Stocks * MarginLevel, 4) < reverseAmount) { // 检测资产是否足够 Log("没有币反手开仓, 须要开仓: ", reverseAmount, "个, 只有", account.Stocks, "个币"); Counter.f++; break; } var reverseType = tradeType; // 记录反转操做类型,默认顺仓 if (ReverseMode == 0) { // 反手模式影响的调整,即若是参数设置了反仓,这里调整 reverseType = tradeType == ORDER_TYPE_BUY ? ORDER_TYPE_SELL : ORDER_TYPE_BUY; // 反仓就是指,刚才持仓是多,此次反手就作空,刚才是作空,此次就作多 } var pos = Trade(exchange, reverseType, reverseAmount, OpMode, SlidePrice, MaxSpace, Interval); // 加倍的下单操做 if (pos) { // 检测交易逻辑执行以后的持仓 Log(reverseType == ORDER_TYPE_BUY ? "多仓" : "空仓", "加倍开仓完成"); } return pos; // 返回持仓结构 } else { // 没有触发反手时执行的逻辑 var orders = _C(exchange.GetOrders); // 当止盈挂单成交,记录胜次数加1 if (orders.length == 0) { Counter.s++; var account = GetAccount(exchange, true); // 更新帐户资产 LogProfit(account.Stocks, account); // 打印帐户资产 break; } } } // 非while循环内正常return 的,返回null,例如止盈成功,超过失败次数,资产不足 return null; } function onexit() { // 机器人中止时,执行扫尾函数onexit StripOrders(exchange); // 撤销全部挂单 Log("Exit"); } function main() { if (exchange.GetName().indexOf("Futures") == -1) { // 检测当前添加的第一个交易所对象是否是期货交易所 throw "只支持期货, 现货暂不支持"; } // EnableLogLocal(SaveLocal); if (exchange.GetRate() != 1) { // 不启用汇率转换 Log("已禁用汇率转换"); exchange.SetRate(1); } StopProfit /= 100; // 参数处理为小数,假设StopProfit为1表示要1%止盈,从新计算赋值,StopProfit的值为0.01即1% StopLoss /= 100; // 止损(反手)同上 var eName = exchange.GetName(); if (eName == "Futures_CTP") { // 检测添加的第一个交易所对象是否为商品期货,若是是,抛出错误信息,让机器人中止 throw "暂只支持数字货币期货" } exchange.SetContractType(Symbol); // 设置数字货币合约代码,即要交易、操做的合约 exchange.SetMarginLevel(MarginLevel); // 设置杠杆 Interval *= 1000; // 轮询间隔参数由秒转换为毫秒 SetErrorFilter("502:|503:|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF"); // 设置屏蔽的错误类型 StripOrders(exchange); // 取消全部挂单 OrgAccount = GetAccount(exchange, true); // 获取当前帐户信息 LogStatus("启动成功"); // 更新状态栏信息 var pos = null; // 初始化main函数内的局部变量pos为null,用来记录持仓数据结构 var positions = GetPosition(exchange); // 获取当前持仓,调用的是封装后的GetPosition不带orderType参数是要获取所有持仓,注意调用的并不是是API接口exchange.GetPosition if (positions.length == 1) { // 若是开始时有持仓,赋值给pos变量 pos = positions[0]; Log("发现一个仓位, 已经自动恢复进度"); } else if (positions.length > 1) { // 有多个持仓时,为策略不可运行状态,策略抛出错误让机器人中止 throw "发现持仓超过1个"; } while (true) { // 策略主循环 pos = loop(pos); // 执行交易逻辑主要的函数loop,pos做为参数,而且返回新的持仓数据结构 if (!pos) { // 该条件触发,返回null的状况,例如止盈成功,超过失败次数,资产不足 _Failed = 0; // 重置止损次数为0,重来 } else { _Failed++; // 累计止损次数 } Sleep(Interval); } }
策略逻辑
读完策略代码能够发现,策略逻辑其实并不复杂,代码也并不算多,可是设计上可谓匠心独运,不少地方能够借鉴参考。
策略交易逻辑的主要函数为loop
函数,在main
函数的主循环中反复调用,当loop
函数开始执行时,首先下单持仓,而后挂单止盈,等待止盈订单成交。以后进入检测状态,检测两项内容。java
- 检测挂出的止盈单是否成交,止盈单成交,即盈利,退出检测循环,重置逻辑,从新开始。
- 检测是否触发止损(反手),触发止损,即取消全部挂单,平掉仓位,而后根据参数设置是反手顺仓仍是反手逆仓进行加倍反手下单交易。产生持仓,继续挂出止盈单,而且再次进入检测状态(监测止盈、反手)。
策略逻辑简单描述如此,可是仍是有一些其它细节的,好比最大反手次数的设置,帐户资产可用的检测,下单失败最大次数10次的处理等。
策略中有些函数都作了根据参数不一样而行为差别化的设计,例如:StripOrders
函数,GetAccount
函数,GetPosition
函数。这些函数根据参数传入差别,有不一样的行为。这样很好的复用了代码,避免了代码冗余,让策略设计简洁易懂。python
原策略:https://www.fmz.com/strategy/3648数组
反手加倍有必定的风险,特别是在期货上,策略仅为学习,实盘慎用,欢迎留言讨论。缓存