Notes & Explanation of Futures Reverse Doubling Algorithm Strategy

Author: Ninabadass, Created: 2022-04-27 15:44:04, Updated: 2022-04-28 12:05:02

Notes & Explanation of Futures Reverse Doubling Algorithm Strategy

This strategy is a strategy that was applicable to the cryptocurrency 796 Futures Exchange a long time ago. The futures contract is crypto-margined, that is, the margin is deducted from currency (for example, in BTC contracts, it is deducted from BTC), and the contract order amount can be decimal, similar to Binance crypto-margined contract. The strategy is taken out for strategy design study, the logic processing is still very good, so the strategy is mainly for learning.

Strategy code notes:

var FirstTradeType = [ORDER_TYPE_BUY, ORDER_TYPE_SELL][OpType];   // the opening direction of the first trade is determined by the opening direction of the parameter OpType, and the value of the global variable FirstTradeType is ORDER_TYPE_BUY or ORDER_TYPE_SELL
var OrgAccount = null;                                            // the global variable, to record the account assets  
var Counter = {s : 0, f: 0};                                      // declare the variable Counter; its value is initialized as an object (similar structure in Python is called dictionary); "s" represents the number of win, and "f" represents the number of failure
var LastProfit = 0;                                               // the recent profit and loss 
var AllProfit = 0;                                                // the total rofit and loss 
var _Failed = 0;                                                  // the number of stop loss 

// Cancel all orders except the order of a certain ID in the pending order list. When the orderId parameter is not passed, all pending orders of the current trading pair are cancelled. The parameter "e" is the reference of the exchange object. For example, import exchange as parameter, "e" is now the alias of exchange
function StripOrders(e, orderId) {                                
    var order = null;                               // the initialization variable "order" is null  
    if (typeof(orderId) == 'undefined') {           // If the parameter orderId is not written, then typeof(orderId) == 'undefined' is true, and the code block of the if statement is executed, and the orderId is assigned to null
        orderId = null;
    }
    while (true) {                                  // processing loop 
        var dropped = 0;                            // the number of processing marks 
        var orders = _C(e.GetOrders);               // call GetOrders to obtain the current pending orders (not executed orders), and assign to orders
        for (var i = 0; i < orders.length; i++) {   // traverse the list of unexecuted orders "orders"
            if (orders[i].Id == orderId) {          // if the order ID is the same as the ID in the parameter "orderId", the local variable "order" assign a value to orders[i], which is the current order structure when traversing 
                order = orders[i];
            } else {                                // if the order ID is not the same as the ID in the parameter "orderId", execute the cancellation operation  
                var extra = "";                     // according to part of executed situation, set the extending information "extra"
                if (orders[i].DealAmount > 0) {
                    extra = "Executed:" + orders[i].DealAmount;
                } else {
                    extra = "Unexecuted:";
                }
                e.SetDirection(orders[i].Type == ORDER_TYPE_BUY ? "buy" : "sell");
                e.CancelOrder(orders[i].Id, orders[i].Type == ORDER_TYPE_BUY ? "buy order" : "sell order", extra);    // cancellation operation along with exporting "extra" information, which will be displayed in logs 
                dropped++;                           // dropped, accumulated counts
            }
        }
        if (dropped == 0) {                          // when the traverse is done, "dropped" is equal to 0, namely no cancellation processing during the traverse (which means no order to be canceled); that is to say the cancellation processing is done, and break out of the loop 
            break;
        }
        Sleep(300);                                  // prevent over fast rotating frequency; create a time interval 
    }
    return order;                                    // return the order that needs to be found 
}


var preMsg = "";                                      // the variable to record the cached information 
function GetAccount(e, waitFrozen) {                  // to get account asset information; parameter e is also a reference to exchange, and parameter waitFrozen controls whether to wait for freezing
    if (typeof(waitFrozen) == 'undefined') {          // if the parameter waitFrozen is not passed in the call, assign false to the parameter waitFrozen, that is, the default is no waiting for freezing
        waitFrozen = false;
    }
    var account = null;
    var alreadyAlert = false;                         // the varibale to mark whether it already alerted 
    while (true) {                                    // to obtain the account information and detect frozen; if no wait for freezing, break the while loop directly 
        account = _C(e.GetAccount);
        if (!waitFrozen || account.FrozenStocks < MinStock) {
            break;
        }
        if (!alreadyAlert) {
            alreadyAlert = true;                      // trigger the alert once, and then reset alreadyAlert to avoid repeating alert 
            Log("frozen assets or symbols are found in the account", account);       // export the alert log 
        }
        Sleep(Interval);
    }
    msg = "Success: " + Counter.s + " times, failure: " + Counter.f + " times, the current account symbols: " + account.Stocks;
    if (account.FrozenStocks > 0) {
        msg += " frozen symbols: " + account.FrozenStocks;
    }

    if (msg != preMsg) {                              // detect whether the current information is the same as that last time; if not, update the informtaion on the status bar 
        preMsg = msg;
        LogStatus(msg, "#ff0000");
    }
    return account;                                   // the function returns the account stucture of the account information 
}

function GetPosition(e, orderType) {                  // obtain positions, or obtain the positions with specified direction (orderType)
    var positions = _C(e.GetPosition);                // obtain positions 
    if (typeof(orderType) == 'undefined') {           // the parameter orderType is to specify the position type; if the parameter is not imported, directly return all positions
        return positions;
    }
    for (var i = 0; i < positions.length; i++) {      // traverse the list of positions 
        if (positions[i].Type == orderType) {         // if the traversed current position data is the direction (orderType) we need 
            return positions[i];                      // return positions of orderType
        }
    }
    return null;
}

function GetTicker(e) {                               // obtain ticker market data 
    while (true) {
        var ticker = _C(e.GetTicker);                 // obtain tick market data
        if (ticker.Buy > 0 && ticker.Sell > 0 && ticker.Sell > ticker.Buy) {   // detect the reliability of the market data 
            return ticker;                            // return ticker data 
        }
        Sleep(100);
    }
}
// mode = 0 : direct buy, 1 : buy as buy1
function Trade(e, tradeType, tradeAmount, mode, slidePrice, maxSpace, retryDelay) {      // trading function 
    // e for exchange object reference, tradeType for trade direction (buy/sell), tradeAmount for the trading amount, mode for trading mode, slidePrice for slippoint price, maxSpace for maximum pending order distance, and retryDelay for retry interval
    var initPosition = GetPosition(e, tradeType);      // obtain the position data with a specified direction, recorded as initPosition
    var nowPosition = initPosition;                    // declare another variable "nowPosition", assigned by "initPosition
    var orderId = null;
    var prePrice = 0;            // the ordering price in the loop last time 
    var dealAmount = 0;          // executed trading amount 
    var diffMoney = 0;           
    var isFirst = true;          // mark of initially executing the loop 
    var tradeFunc = tradeType == ORDER_TYPE_BUY ? e.Buy : e.Sell;     // the function of placing orders; according to the parameter tradeType, determine to call e.Buy or e.Sell
    var isBuy = tradeType == ORDER_TYPE_BUY;                          // whether it is the buy mark
    while (true) {                                                    // while loop
        var account = _C(e.GetAccount);                               // obtain the current account asset data 
        var ticker = GetTicker(e);                                    // obtain the current market data 
        var tradePrice = 0;                                           // determine a trading price based on the parameter 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) {                                            // according to the  mark variable isFirst to judge, if it is the first execution, nothing will be done 
                isFirst = false;                                      // if the mark isFirst is set to false, it means it is not the first execution 
            } else {                                                  // not the first executin, so update the position data 
                nowPosition = GetPosition(e, tradeType);
            }
            dealAmount = _N((nowPosition ? nowPosition.Amount : 0) - (initPosition ? initPosition.Amount : 0), 6);   // by the initial position data and the current position data, calculate the executed amount 
            var doAmount = Math.min(tradeAmount - dealAmount, account.Stocks * MarginLevel, 4);      // by the executed amount and the account available assets, calculate the rest amount that needs to be traded 
            if (doAmount < MinStock) {                                                               // if the calculated trading amount is less than the minimum of the executed amount, terminate the logic and break the while loop 
                break;
            }
            prePrice = tradePrice;                                                                   // cache the trading price in the current round of loop 
            e.SetDirection(tradeType == ORDER_TYPE_BUY ? "buy" : "sell");                            // set futures trading direction 
            orderId = tradeFunc(tradePrice, doAmount);                                               // trade of placing orders; its parameters are the calculated price and the order amount of this time
        } else {                                                                                     // when the order recording variable "orderId" is not null, it means the orders has been placed  
            if (mode == 0 || Math.abs(tradePrice - prePrice) > maxSpace) {                           // if it is the mode of pending orders, the current price and the price cached last time exceed the maximum pending order range  
                orderId = null;                                                                      // reset orderId to null, and restart to place orders in next round of loop 
            }
            var order = StripOrders(exchange, orderId);                                              // call StripOrders to find the order with ID of orderId in the pending order list 
            if (order == null) {                                                                     // if not found, reset orderId to null, and continue the next round of placing orders  
                orderId = null;
            }
        }
        Sleep(retryDelay);                                                                           // temporarily specify a certain time, to control the loop frequency 
    }

    if (dealAmount <= 0) {                                                                           // after the while loop is finished, if the executed amount "dealAmount" is less than or equal to 0, that means the trade is a failure, and return null
        return null;
    }

    return nowPosition;                                                                              // under normal circumstances, return the latest position data 
}

function coverFutures(e, orderType) {                               // function of closing positions 
    var coverAmount = 0;                                            // declare a variable coverAmount, with the default of 0, to record the amount already closed 
    while (true) {
        var positions = _C(e.GetPosition);                          // obtain positions 
        var ticker = GetTicker(e);                                  // obtain the current market quotes 
        var found = 0;                                              // search for marks 
        for (var i = 0; i < positions.length; i++) {                // traverse the array of holding positions, namely "positions"
            if (positions[i].Type == orderType) {                   // find the positions that are needed 
                if (coverAmount == 0) {                             
                    coverAmount = positions[i].Amount;              // initially record the position amount, namely the amount to be closed 
                }
                if (positions[i].Type == ORDER_TYPE_BUY) {          // execute the operation of closing positions, according to position type 
                    e.SetDirection("closebuy");                     // set the futures trading direction 
                    e.Sell(ticker.Buy, positions[i].Amount);        // function of placing orders 
                } else {
                    e.SetDirection("closesell");
                    e.Buy(ticker.Sell, positions[i].Amount);
                }
                found++;                                            // mark the accumulation 
            }
        }
        if (found == 0) {                                           // if the mark variable found is 0, it means no position to be processed, and then break out of the while loop
            break;
        }
        Sleep(2000);                                                // interval time of 2 seconds
        StripOrders(e);                                             // cancel all pending orders 
    }
    return coverAmount;                                             // return the amount of closing positions 
}


function loop(pos) {
    var tradeType = null;                         // initialize the trading direction 
    if (typeof(pos) == 'undefined' || !pos) {     //judge whether it is the initial round of execution
        tradeType = FirstTradeType;
        pos = Trade(exchange, tradeType, OpAmount, OpMode, SlidePrice, MaxSpace, Interval);     // initial trade 
        if (!pos) {
            throw "Rough start, fail to open positions";
        } else {
            Log(tradeType == ORDER_TYPE_BUY ? "opening long positions completed" : "Opening short positions completed", "Average price:", pos.Price, "Amount:", pos.Amount);
        }
    } else {
        tradeType = pos.Type;        // continue to specify the trading direction according to position direction 
    }
    var holdPrice = pos.Price;       // position price 
    var holdAmount = pos.Amount;     // position amount 

    var openFunc = tradeType == ORDER_TYPE_BUY ? exchange.Buy : exchange.Sell;     // long positions; open positions to buy; if not, open positions to sell
    var coverFunc = tradeType == ORDER_TYPE_BUY ? exchange.Sell : exchange.Buy;    // long positions; close positions to sell; if not, close positions to buy 

    var reversePrice = 0;           // reverse price
    var coverPrice = 0;             // closing position price
    var canOpen = true;             //  mark for enabling opening positions 

    if (tradeType == ORDER_TYPE_BUY) {                         
        reversePrice = _N(holdPrice * (1 - StopLoss), 4);     // stop loss price
        coverPrice = _N(holdPrice * (1 + StopProfit), 4);     // take profit price
    } else {
        reversePrice = _N(holdPrice * (1 + StopLoss), 4);
        coverPrice = _N(holdPrice * (1 - StopProfit), 4);
    }

    var coverId = null;
    var msg = "position price:" + holdPrice + " stopLoss price:" + reversePrice;

    for (var i = 0; i < 10; i++) {               // control to order 10 times maximum
        if (coverId) {                           // when the order ID is null, and break will not be triggered, continue the loop, until to the maximum of 10 times 
            break;
        }
        if (tradeType == ORDER_TYPE_BUY) {       // according to the order direction, pend orders to close, namely the take profit orders 
            exchange.SetDirection("closebuy");
            coverId = exchange.Sell(coverPrice, holdAmount, msg);
        } else {
            exchange.SetDirection("closesell");
            coverId = exchange.Buy(coverPrice, holdAmount, msg);
        }

        Sleep(Interval);
    }

    if (!coverId) {                // raise the error after 10 times of order failure, and stop the strategy 
        StripOrders(exchange);     // cancel all pending orders
        Log("fail to order", "@")        // add push alert 
        throw "fail to order";           // raise the error, and stop the bot 
    }


    while (true) {                 // enter to detect the reverse loop
        Sleep(Interval);           
        var ticker = GetTicker(exchange);                                // obtain the latest market quotes 
        if ((tradeType == ORDER_TYPE_BUY && ticker.Last < reversePrice) || (tradeType == ORDER_TYPE_SELL && ticker.Last > reversePrice)) {   // detect the trigger of stoploss, namely reverse 
            StripOrders(exchange);                                       // cancel all pending orders 
            var coverAmount = coverFutures(exchange, tradeType);         // close all positions 
            if (_Failed >= MaxLoss) {                                    // if the maximum number of stoploss (reverse times) is exceeded, break the loop and restart again  
                Counter.f++;                                             // counted as one failure 
                Log("exceed the maximum number of failures", MaxLoss);
                break;                                                   // break out of the loop
            }
            var reverseAmount = _N(coverAmount * ReverseRate, 4);        // according to the closing position amount, double the trading amount  

            var account = GetAccount(exchange, true);                    // update the account information, and here frozen assets are forbidden  
            // detect whether the assets are enough; if not, break the loop, and restart, such as _Failed >= MaxLoss
            if (_N(account.Stocks * MarginLevel, 4) < reverseAmount) {   // detect whether the assets are enough 
                Log("open positions without currency, need to open positions:", reverseAmount, "currency symbols, and there are only ", account.Stocks, "currency symbols");
                Counter.f++;
                break;
            }
            var reverseType = tradeType;                                 // record the type of the reverse operation; the default is forward
            if (ReverseMode == 0) {                                      // the adjustment of the reverse mode, that is, if the parameter is set to reverse, the adjustment is needed here  
                reverseType = tradeType == ORDER_TYPE_BUY ? ORDER_TYPE_SELL : ORDER_TYPE_BUY; // reverse indicates if the positions just now are long, the reverse now will do short; if the positions just now are short, the reverse now will do long 
            }
            var pos = Trade(exchange, reverseType, reverseAmount, OpMode, SlidePrice, MaxSpace, Interval);    // double the operation of placing orders 
            if (pos) {                                                                                        // detect the positions after the execution of the trading logic  
                Log(reverseType == ORDER_TYPE_BUY ? "long position" : "short position", "double open positions completed");
            }
            return pos;                                                                                       // return the position structure 
        } else {                                          // the trading logic executed when the reverse is not triggered 
            var orders = _C(exchange.GetOrders);          // when the take-profit pending orders are executed, add 1 to the number of win 
            if (orders.length == 0) {
                Counter.s++;
                var account = GetAccount(exchange, true); // update the account assets 
                LogProfit(account.Stocks, account);       // print the account assets 
                break;
            }
        }
    }

    // if the return is normal in a non-while loop, null is returned, such as successful take-profit, exceeding the number of failures, insufficient assets
    return null;
}

function onexit() {          // when the bot is stopped, execute the onexit function
    StripOrders(exchange);   // cancel all pending orders 
    Log("Exit");
}

function main() {
    if (exchange.GetName().indexOf("Futures") == -1) {    // detect whether the currently first added exchange object is a futures platform 
        throw "only support futures, and spot not supported temporarily";
    }
    // EnableLogLocal(SaveLocal);
    if (exchange.GetRate() != 1) {                        // disable the exchange rateconverting  
        Log("disable exchange rate converting");
        exchange.SetRate(1);
    }

    StopProfit /= 100;                                    // the parameter is processed to be decimal; assuming that StopProfit is 1, namely 1% of take-profit, recalculate the assignment and the value of StopProfit is 0.01, namely 1%.
    StopLoss /= 100;                                      // stop loss (reverse), as above 

    var eName = exchange.GetName();
    if (eName == "Futures_CTP") {                         // detect whether the currently first added exchange object is commodity futures ; if it is, raise an error to stop the bot 
        throw "temporarily only support cryptocurrency futures"
    }
    exchange.SetContractType(Symbol);                     // set the code of the cryptocurrency contract, namely the contract to be traded and operated 
    exchange.SetMarginLevel(MarginLevel);                 // set margin level 
    Interval *= 1000;                                     // change the polling interval parameter from second to millisecond 
    SetErrorFilter("502:|503:|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF");    // set the type of the error filtered 
    StripOrders(exchange);                                // cancel all pending orders 
    OrgAccount = GetAccount(exchange, true);              // obtain the current account information 
    LogStatus("start successfully");                                 // update the status bar information 
    var pos = null;                                       // initialize the variable pos to null in the main function, to record the position data structure 
    var positions = GetPosition(exchange);                // obtaining the current positions is to call the encapsulated GetPosition without orderType, to obtain all positions held; note that here the called function is not "exchange.GetPosition" of API interface 
    if (positions.length == 1) {                          // if there are positions at the beginning, assign the value to the variable pos 
        pos = positions[0];
        Log("found 1 position, and already rcovered the process automatically");
    } else if (positions.length > 1) {                    // when there are multiple positions, that is, the status in which the strategy cannot run, the strategy raises an error to make the bot stop  
        throw "found over 1 position ";
    }
    while (true) {                                        // strategy main loop 
        pos = loop(pos);                                  // execute the main function loop in the trading logic; take pos as parameter; return the new position data structure 
        if (!pos) {                                       // under the circumstance if the condition is triggered, and return null, such as the success of take-profit, exceeding the number of failures, and inadequate assets 
            _Failed = 0;                                  // reset the number of stop loss to 0, and restart it 
        } else {
            _Failed++;                                    // the accumulated number of stop loss
        }
        Sleep(Interval);
    }
}

Strategy Logic

After reading the strategy code, you can find that the strategy logic is not complicated, and the code is not too much, but the design is ingenious and many parts of it can be used for reference. The main function of the strategy trading logic is the loop function, which is repeatedly called in the main loop of the main function. When the loop function starts, first place orders to hold positions, and then pend the orders to take profit and wait for the stop- profit orders to be executed. Then, enter the detection status to detect the following two contents.

  • To detect whether the pending take-profit orders are executed; if the take-profit orders are executed, that means the profit is earned, so break out of the detection loop, reset the logic, and restart again.
  • To detect whether the stop-loss (reverse) is triggered; it it is triggered, that means cancelling all pending orders, and close the positions, and then double the reverse ordering trades according to whether the parameter is set to reverse or forward. There are positions held, continue to pend take-profit orders, and enter the detection status again (monitoring take-profit, reverse).

The strategy logic is simply described as such, but there are still some other details, such as the setting of the maximum number of reverse, the detection of the available account assets, and the processing of the maximum number of failures to 10 times. Some functions in the strategy are designed to perform differently according to different parameters, such as: StripOrders function, GetAccount function, GetPosition function. Those functions have different actions depending on the differences of the imported parameters. That makes good use of the code, and avoids code redundancy, and makes the strategy design concise and easy to understand.

Original strategy: https://www.fmz.com/strategy/3648

Doubling reverse has certain risks, especially in futures; the strategy is only for study, and please use it with caution. Welcome to leave messages for discussion.


More