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