The essence of the strategy described in this article is a dynamic balance strategy, that is, the value of the balance currency is always equal to the value of the valuation currency. However, the strategy logic is very simple when it is designed as pre-pending order. The main purpose of writing this strategy is to show all aspects of strategy design.

  • Strategy logic encapsulation
    Encapsulate the strategy logic with some data and tag variables at runtime (encapsulated as objects).
  • Code for strategy processing initialization
    The initial account information is recorded in the initial run for profit calculation. At the initial runtime, you can choose whether to restore data according to the parameters.
  • Code for strategy interaction processing
    A simple interactive processing of pause and resume is designed.
  • Code for strategy profit calculation
    The currency standard calculation method is used to calculate the profit.
  • Mechanism of key data persistence in the strategy
    Designing mechanisms for recovering data
  • Code for displaying strategy processing information
    Status bar data display.

Strategy code

var Shannon = {
    // member
    e : exchanges[0],
    arrPlanOrders : [],
    distance : BalanceDistance,
    account : null,
    ticker : null, 
    initAccount : null,
    isAskPending : false,
    isBidPending : false,

    // function 
    CancelAllOrders : function (e) {
        while(true) {
            var orders = _C(e.GetOrders)
            if(orders.length == 0) {
                return 
            }
            Sleep(500)
            for(var i = 0; i < orders.length; i++) {
                e.CancelOrder(orders[i].Id, orders[i])
                Sleep(500)
            }
        }
    },

    Balance : function () {
        if (this.arrPlanOrders.length == 0) {
            this.CancelAllOrders(this.e)
            var acc = _C(this.e.GetAccount)
            this.account = acc
            var askPendingPrice = (this.distance + acc.Balance) / acc.Stocks
            var bidPendingPrice = (acc.Balance - this.distance) / acc.Stocks
            var askPendingAmount = this.distance / 2 / askPendingPrice
            var bidPendingAmount = this.distance / 2 / bidPendingPrice

            this.arrPlanOrders.push({tradeType : "ask", price : askPendingPrice, amount : askPendingAmount}) 
            this.arrPlanOrders.push({tradeType : "bid", price : bidPendingPrice, amount : bidPendingAmount})
        } else if(this.isAskPending == false && this.isBidPending == false) {
            for(var i = 0; i < this.arrPlanOrders.length; i++) {
                var tradeFun = this.arrPlanOrders[i].tradeType == "ask" ? this.e.Sell : this.e.Buy
                var id = tradeFun(this.arrPlanOrders[i].price, this.arrPlanOrders[i].amount)
                if(id) {
                    this.isAskPending = this.arrPlanOrders[i].tradeType == "ask" ? true : this.isAskPending
                    this.isBidPending = this.arrPlanOrders[i].tradeType == "bid" ? true : this.isBidPending
                } else {
                    Log("Pending order failed, clear!")
                    this.CancelAllOrders(this.e)
                    return 
                }
            }
        }

        if(this.isBidPending || this.isAskPending) {
            var orders = _C(this.e.GetOrders)
            Sleep(1000)
            var ticker = _C(this.e.GetTicker)
            this.ticker = ticker
            if(this.isAskPending) {
                var isFoundAsk = false 
                for (var i = 0; i < orders.length; i++) {
                    if(orders[i].Type == ORDER_TYPE_SELL) {
                        isFoundAsk = true
                    }
                }
                if(!isFoundAsk) {
                    Log("Selling order filled, cancel the order, reset")
                    this.CancelAllOrders(this.e)
                    this.arrPlanOrders = []
                    this.isAskPending = false 
                    this.isBidPending = false 
                    LogProfit(this.CalcProfit(ticker))
                    return 
                }
            }
            if(this.isBidPending) {
                var isFoundBid = false 
                for(var i = 0; i < orders.length; i++) {
                    if(orders[i].Type == ORDER_TYPE_BUY) {
                        isFoundBid = true
                    }
                }
                if(!isFoundBid) {
                    Log("Buying order filled, cancel the order, reset")
                    this.CancelAllOrders(this.e)
                    this.arrPlanOrders = []
                    this.isAskPending = false 
                    this.isBidPending = false 
                    LogProfit(this.CalcProfit(ticker))
                    return 
                }
            }        
        }
    }, 
    ShowTab : function() {
        var tblPlanOrders = {
            type : "table", 
            title : "Plan pending orders", 
            cols : ["direction", "price", "amount"], 
            rows : []
        }
        for(var i = 0; i < this.arrPlanOrders.length; i++) {
            tblPlanOrders.rows.push([this.arrPlanOrders[i].tradeType, this.arrPlanOrders[i].price, this.arrPlanOrders[i].amount])
        }

        var tblAcc = {
            type : "table", 
            title : "Account information", 
            cols : ["type", "Stocks", "FrozenStocks", "Balance", "FrozenBalance"], 
            rows : []            
        }
        tblAcc.rows.push(["Initial", this.initAccount.Stocks, this.initAccount.FrozenStocks, this.initAccount.Balance, this.initAccount.FrozenBalance])
        tblAcc.rows.push(["This", this.account.Stocks, this.account.FrozenStocks, this.account.Balance, this.account.FrozenBalance])

        return "Time:" + _D() + "\n `" + JSON.stringify([tblPlanOrders, tblAcc]) + "`" + "\n" + "ticker:" + JSON.stringify(this.ticker)
    },
    CalcProfit : function(ticker) {
        var acc = _C(this.e.GetAccount)
        this.account = acc
        return (this.account.Balance - this.initAccount.Balance) + (this.account.Stocks - this.initAccount.Stocks) * ticker.Last
    },
    Init : function() {
        this.initAccount = _C(this.e.GetAccount)
        if(IsReset) {
            var acc = _G("account")
            if(acc) {
                this.initAccount = acc 
            } else {
                Log("Failed to restore initial account information! Running in initial state!")
                _G("account", this.initAccount)
            }
        } else {
            _G("account", this.initAccount)
            LogReset(1)
            LogProfitReset()
        }
    },
    Exit : function() {
        Log("Cancel all pending orders before stopping...")
        this.CancelAllOrders(this.e)
    }
}

function main() {
    // Initialization
    Shannon.Init()

    // Main loop
    while(true) {
        Shannon.Balance()        
        LogStatus(Shannon.ShowTab())
        // Interaction
        var cmd = GetCommand()
        if(cmd) {
            if(cmd == "stop") {
                while(true) {
                    LogStatus("Pause", Shannon.ShowTab())
                    cmd = GetCommand()
                    if(cmd) {
                        if(cmd == "continue") {
                            break
                        }
                    }
                    Sleep(1000)
                }
            }
        }
        Sleep(5000)
    }
}

function onexit() {
    Shannon.Exit()
}

Backtesting

Optimization and extension

  • A virtual pending order mechanism can be added. Some exchanges have limited price on pending orders, so the order may not be actually placed. You need to wait until the price is close to the real pending order.
  • Add futures trading
  • Expand a multi-species and multi-exchange version

Strategies are for educational purposes only and should be used with caution in real bot trading.
Strategy address: https://www.fmz.com/strategy/225746

Leave a Reply

Your email address will not be published. Required fields are marked *