Loading ...

期货反手加倍算法策略注释详解

Author: 小小梦, Created: 2020-10-28 17:56:09, Updated: 2020-10-30 10:03:28

期货反手加倍算法策略注释详解

该策略是很久以前适用数字货币796期货交易所的一个策略,期货合约是币本位,即保证金扣除的是币(例如BTC合约扣除BTC),合约下单量可以是小数,类似币安的币本位合约。 该策略拿出来学习策略设计,逻辑处理还是很不错的,策略以学习为主。

策略代码注释

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函数开始执行时,首先下单持仓,然后挂单止盈,等待止盈订单成交。之后进入检测状态,检测两项内容。

  • 检测挂出的止盈单是否成交,止盈单成交,即盈利,退出检测循环,重置逻辑,重新开始。
  • 检测是否触发止损(反手),触发止损,即取消所有挂单,平掉仓位,然后根据参数设置是反手顺仓还是反手逆仓进行加倍反手下单交易。产生持仓,继续挂出止盈单,并且再次进入检测状态(监测止盈、反手)。

策略逻辑简单描述如此,但是还是有一些其它细节的,比如最大反手次数的设置,账户资产可用的检测,下单失败最大次数10次的处理等。 策略中有些函数都做了根据参数不同而行为差异化的设计,例如:StripOrders函数,GetAccount函数,GetPosition函数。这些函数根据参数传入差异,有不同的行为。这样很好的复用了代码,避免了代码冗余,让策略设计简洁易懂。

原策略:https://www.fmz.com/strategy/3648

反手加倍有一定的风险,特别是在期货上,策略仅为学习,实盘慎用,欢迎留言讨论。


More