JavaScript version SuperTrend strategy
There are many versions of the SuperTrend indicator on the TV. I found a relatively easy-to-understand algorithm and transplanted it. Compared with the SuperTrend indicator loaded on the TV chart of FMZ trading platform backtest system, I found a slight difference and did not understand the reason for the causes, I'm looking forward to the guidance of our readers. I will first show my understanding as follow.
SuperTrend indicator JavaScript version algorithm
Copy code// VIA: https://github.com/freqtrade/freqtrade-strategies/issues/30functionSuperTrend(r, period, multiplier) { // atrvar atr = talib.ATR(r, period) // baseUp , baseDownvar baseUp = [] var baseDown = [] for (var i = 0; i < r.length; i++) { if (isNaN(atr[i])) { baseUp.push(NaN) baseDown.push(NaN) continue } baseUp.push((r[i].High + r[i].Low) / 2 + multiplier * atr[i]) baseDown.push((r[i].High + r[i].Low) / 2 - multiplier * atr[i]) } // fiUp , fiDownvar fiUp = [] var fiDown = [] var prevFiUp = 0var prevFiDown = 0for (var i = 0; i < r.length; i++) { if (isNaN(baseUp[i])) { fiUp.push(NaN) } else { fiUp.push(baseUp[i] < prevFiUp || r[i - 1].Close > prevFiUp ? baseUp[i] : prevFiUp) prevFiUp = fiUp[i] } if (isNaN(baseDown[i])) { fiDown.push(NaN) } else { fiDown.push(baseDown[i] > prevFiDown || r[i - 1].Close < prevFiDown ? baseDown[i] : prevFiDown) prevFiDown = fiDown[i] } } var st = [] var prevSt = NaNfor (var i = 0; i < r.length; i++) { if (i < period) { st.push(NaN) continue } var nowSt = 0if (((isNaN(prevSt) && isNaN(fiUp[i - 1])) || prevSt == fiUp[i - 1]) && r[i].Close <= fiUp[i]) { nowSt = fiUp[i] } elseif (((isNaN(prevSt) && isNaN(fiUp[i - 1])) || prevSt == fiUp[i - 1]) && r[i].Close > fiUp[i]) { nowSt = fiDown[i] } elseif (((isNaN(prevSt) && isNaN(fiDown[i - 1])) || prevSt == fiDown[i - 1]) && r[i].Close >= fiDown[i]) { nowSt = fiDown[i] } elseif (((isNaN(prevSt) && isNaN(fiDown[i - 1])) || prevSt == fiDown[i - 1]) && r[i].Close < fiDown[i]) { nowSt = fiUp[i] } st.push(nowSt) prevSt = st[i] } var up = [] var down = [] for (var i = 0; i < r.length; i++) { if (isNaN(st[i])) { up.push(st[i]) down.push(st[i]) } if (r[i].Close < st[i]) { down.push(st[i]) up.push(NaN) } else { down.push(NaN) up.push(st[i]) } } return [up, down] } // The main function for testing indicators is not a trading strategyfunctionmain() { while (1) { var r = _C(exchange.GetRecords) var st = SuperTrend(r, 10, 3) $.PlotRecords(r, "K") $.PlotLine("L", st[0][st[0].length - 2], r[r.length - 2].Time) $.PlotLine("S", st[1][st[1].length - 2], r[r.length - 2].Time) Sleep(2000) } }
Test code backtest comparison:
A simple strategy using SuperTrend indicator
The trading logic part is relatively simple, that is, when the short trend turns into a long trend, long positions are opened.
Open a short position when the long trend turns into a short trend.
Strategy parameters:
SuperTrend trading strategy
Copy code/*backtest start: 2019-08-01 00:00:00 end: 2020-03-11 00:00:00 period: 15m basePeriod: 5m exchanges: [{"eid":"Futures_OKCoin","currency":"BTC_USD"}] */// Global variablesvarOpenAmount = 0// The number of open positions after openingvarKeepAmount = 0// Reserved positionvarIDLE = 0varLONG = 1varSHORT = 2varCOVERLONG = 3varCOVERSHORT = 4varCOVERLONG_PART = 5varCOVERSHORT_PART = 6varOPENLONG = 7varOPENSHORT = 8varState = IDLE// Trading logic partfunctionGetPosition(posType) { var positions = _C(exchange.GetPosition) /* if(positions.length > 1){ throw "positions error:" + JSON.stringify(positions) } */var count = 0for(var j = 0; j < positions.length; j++){ if(positions[j].ContractType == Symbol){ count++ } } if(count > 1){ throw"positions error:" + JSON.stringify(positions) } for (var i = 0; i < positions.length; i++) { if (positions[i].ContractType == Symbol && positions[i].Type === posType) { return [positions[i].Price, positions[i].Amount]; } } Sleep(TradeInterval); return [0, 0] } functionCancelPendingOrders() { while (true) { var orders = _C(exchange.GetOrders) for (var i = 0; i < orders.length; i++) { exchange.CancelOrder(orders[i].Id); Sleep(TradeInterval); } if (orders.length === 0) { break; } } } functionTrade(Type, Price, Amount, CurrPos, OnePriceTick){ // Processing transactionsif(Type == OPENLONG || Type == OPENSHORT){ // Handling open positions exchange.SetDirection(Type == OPENLONG ? "buy" : "sell") var pfnOpen = Type == OPENLONG ? exchange.Buy : exchange.Sellvar idOpen = pfnOpen(Price, Amount, CurrPos, OnePriceTick, Type) Sleep(TradeInterval) if(idOpen) { exchange.CancelOrder(idOpen) } else { CancelPendingOrders() } } elseif(Type == COVERLONG || Type == COVERSHORT){ // Deal with closing positions exchange.SetDirection(Type == COVERLONG ? "closebuy" : "closesell") var pfnCover = Type == COVERLONG ? exchange.Sell : exchange.Buyvar idCover = pfnCover(Price, Amount, CurrPos, OnePriceTick, Type) Sleep(TradeInterval) if(idCover){ exchange.CancelOrder(idCover) } else { CancelPendingOrders() } } else { throw"Type error:" + Type } } functionSuperTrend(r, period, multiplier) { // atrvar atr = talib.ATR(r, period) // baseUp , baseDownvar baseUp = [] var baseDown = [] for (var i = 0; i < r.length; i++) { if (isNaN(atr[i])) { baseUp.push(NaN) baseDown.push(NaN) continue } baseUp.push((r[i].High + r[i].Low) / 2 + multiplier * atr[i]) baseDown.push((r[i].High + r[i].Low) / 2 - multiplier * atr[i]) } // fiUp , fiDownvar fiUp = [] var fiDown = [] var prevFiUp = 0var prevFiDown = 0for (var i = 0; i < r.length; i++) { if (isNaN(baseUp[i])) { fiUp.push(NaN) } else { fiUp.push(baseUp[i] < prevFiUp || r[i - 1].Close > prevFiUp ? baseUp[i] : prevFiUp) prevFiUp = fiUp[i] } if (isNaN(baseDown[i])) { fiDown.push(NaN) } else { fiDown.push(baseDown[i] > prevFiDown || r[i - 1].Close < prevFiDown ? baseDown[i] : prevFiDown) prevFiDown = fiDown[i] } } var st = [] var prevSt = NaNfor (var i = 0; i < r.length; i++) { if (i < period) { st.push(NaN) continue } var nowSt = 0if (((isNaN(prevSt) && isNaN(fiUp[i - 1])) || prevSt == fiUp[i - 1]) && r[i].Close <= fiUp[i]) { nowSt = fiUp[i] } elseif (((isNaN(prevSt) && isNaN(fiUp[i - 1])) || prevSt == fiUp[i - 1]) && r[i].Close > fiUp[i]) { nowSt = fiDown[i] } elseif (((isNaN(prevSt) && isNaN(fiDown[i - 1])) || prevSt == fiDown[i - 1]) && r[i].Close >= fiDown[i]) { nowSt = fiDown[i] } elseif (((isNaN(prevSt) && isNaN(fiDown[i - 1])) || prevSt == fiDown[i - 1]) && r[i].Close < fiDown[i]) { nowSt = fiUp[i] } st.push(nowSt) prevSt = st[i] } var up = [] var down = [] for (var i = 0; i < r.length; i++) { if (isNaN(st[i])) { up.push(st[i]) down.push(st[i]) } if (r[i].Close < st[i]) { down.push(st[i]) up.push(NaN) } else { down.push(NaN) up.push(st[i]) } } return [up, down] } var preTime = 0functionmain() { exchange.SetContractType(Symbol) while (1) { var r = _C(exchange.GetRecords) var currBar = r[r.length - 1] if (r.length < pd) { Sleep(5000) continue } var st = SuperTrend(r, pd, factor) $.PlotRecords(r, "K") $.PlotLine("L", st[0][st[0].length - 2], r[r.length - 2].Time) $.PlotLine("S", st[1][st[1].length - 2], r[r.length - 2].Time) if(!isNaN(st[0][st[0].length - 2]) && isNaN(st[0][st[0].length - 3])){ if (State == SHORT) { State = COVERSHORT } elseif(State == IDLE) { State = OPENLONG } } if(!isNaN(st[1][st[1].length - 2]) && isNaN(st[1][st[1].length - 3])){ if (State == LONG) { State = COVERLONG } elseif (State == IDLE) { State = OPENSHORT } } // Execution signal var pos = nullvar price = nullif(State == OPENLONG){ // Open long positions pos = GetPosition(PD_LONG) // Check positions// Determine whether the status is satisfied, if it is satisfied, modify the statusif(pos[1] >= Amount){ // Open positions exceed or equal to the open positions set by the parametersSleep(1000) $.PlotFlag(currBar.Time, "Open long positions", 'OL') // markOpenAmount = pos[1] // Record the number of open positionsState = LONG// Mark as longcontinue } price = currBar.Close - (currBar.Close % PriceTick) + PriceTick * 2// Calculate the priceTrade(OPENLONG, price, Amount - pos[1], pos, PriceTick) // Placing Order function (Type, Price, Amount, CurrPos, PriceTick) } if(State == OPENSHORT){ // Open short position pos = GetPosition(PD_SHORT) // Check positionsif(pos[1] >= Amount){ Sleep(1000) $.PlotFlag(currBar.Time, "Open short position", 'OS') OpenAmount = pos[1] State = SHORTcontinue } price = currBar.Close - (currBar.Close % PriceTick) - PriceTick * 2Trade(OPENSHORT, price, Amount - pos[1], pos, PriceTick) } if(State == COVERLONG){ // Handling long positions pos = GetPosition(PD_LONG) // Get position informationif(pos[1] == 0){ // Determine if the position is 0 $.PlotFlag(currBar.Time, "Close long position", '----CL') // markState = IDLEcontinue } price = currBar.Close - (currBar.Close % PriceTick) - PriceTick * 2Trade(COVERLONG, price, pos[1], pos, PriceTick) } if(State == COVERSHORT){ // Deal with long positions pos = GetPosition(PD_SHORT) if(pos[1] == 0){ $.PlotFlag(currBar.Time, "Close short position", '----CS') State = IDLEcontinue } price = currBar.Close - (currBar.Close % PriceTick) + PriceTick * 2Trade(COVERSHORT, price, pos[1], pos, PriceTick) } if(State == COVERLONG_PART) { // Partially close long positions pos = GetPosition(PD_LONG) // Get positionsif(pos[1] <= KeepAmount){ // The position is less than or equal to the holding amount, this time the closing action is completed $.PlotFlag(currBar.Time, "Close long positions, keep:" + KeepAmount, '----CL') // markState = pos[1] == 0 ? IDLE : LONG// update statuscontinue } price = currBar.Close - (currBar.Close % PriceTick) - PriceTick * 2Trade(COVERLONG, price, pos[1] - KeepAmount, pos, PriceTick) } if(State == COVERSHORT_PART){ pos = GetPosition(PD_SHORT) if(pos[1] <= KeepAmount){ $.PlotFlag(currBar.Time, "Close short positions, keep:" + KeepAmount, '----CS') State = pos[1] == 0 ? IDLE : SHORTcontinue } price = currBar.Close - (currBar.Close % PriceTick) + PriceTick * 2Trade(COVERSHORT, price, pos[1] - KeepAmount, pos, PriceTick) } LogStatus(_D()) Sleep(1000) } }
Strategy address: https://www.fmz.com/strategy/201837
Backtest performance
Parameter setting, K line period, reference: homily SuperTrend V.1--Super trend line system
The K-line period is set to 15 minutes, and the SuperTrend parameter is set to 45, 3. Backtest the OKEX futures quarter contract for the most recent year, and set a contract to trade at a time. Due to the setting to trade only one contract at a time, the utilization rate of funds is very low and you don’t need to care about the Sharpe value.