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
pinevar 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
From: https://blog.mathquant.com/2022/12/19/balanced-pending-order-strategy-teaching-strategy.html