The main idea of this strategy is to combine Kalman filter and tracking stop loss to build a dynamically adjusted stop loss rail. The Kalman filter is used to track prices and give predicted values. The stop loss rail is constructed based on predictions at a certain percentage to achieve dynamic tracking of prices. This allows maximum profit during the trend phase while timely stop loss during reversal.
The whole strategy can achieve good results in trending markets.
The strategy consists of the following main parts:
Kalman filter
Stop loss rail
Pyramiding and taking profit
The main operating flow of the whole strategy is:
The main advantages of this strategy:
The main risks of this strategy:
Risks can be reduced through:
The strategy can be further optimized through:
In summary, this adaptive stop loss rail strategy uniquely combines Kalman prediction and dynamic stop loss. With proper parameter tuning, it can achieve good results. Further modularization and optimization can make this strategy more complete for application in more markets.
/*backtest start: 2023-06-01 00:00:00 end: 2024-01-01 00:00:00 period: 1d basePeriod: 1h exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}] */ // This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/ // © BigCoinHunter // ____ _ _____ _ _ _ _ // | _ \(_) / ____| (_) | | | | | | // | |_) |_ __ _| | ___ _ _ __ | |__| |_ _ _ __ | |_ ___ _ __ // | _ <| |/ _` | | / _ \| | '_ \| __ | | | | '_ \| __/ _ \ '__| // | |_) | | (_| | |___| (_) | | | | | | | | |_| | | | | || __/ | // |____/|_|\__, |\_____\___/|_|_| |_|_| |_|\__,_|_| |_|\__\___|_| // __/ | // |___/ //@version=5 strategy(title='Loft Strategy V4', overlay=true, pyramiding=0, default_qty_type=strategy.cash, default_qty_value=100, initial_capital=10000, currency=currency.USD, commission_value=0.05, commission_type=strategy.commission.percent, process_orders_on_close=true) //-------------- fetch user inputs ------------------ gain = input.float(title="Kalman Gain:", defval=100.0, minval=1, maxval=10000.0, step=1) src = input(defval=close, title='Source:') stopPercentBase = input.float(title='Beginning Approach(%)', defval=5.0, minval=0.1, maxval=30.0, step=0.1) stopPercentMin = input.float(title='Final Approach(%)', defval=1.0, minval=0.1, maxval=30.0, step=0.1) downStep = input.float(title='Approach Decrease Step', defval=0.001, minval=0.0, maxval = 5, step=0.001) //stopPercentDeviation = input.float(title="Approach Deviation", defval=1.0, minval=0.1, maxval = 5.0, step=0.1) baseOrderQty = input.float(title="Base Order Quantity", defval=100.0, minval=0.001) maxOrderCount = input.int(title="Max Safe Order Attemp", defval=4, minval=1) priceDeviation = input.float(title="Safe Order Deviation", defval=3, minval=1.0, step=0.1) profitDeviation = input.float(title="Profit Deviation", defval=1.0, minval=1.0, maxval=10, step=0.1) maxTakeProfit = input.float(title="Max Take Profit(%)", defval=25.0, maxval=100, step=0.1) maxOrderQty = input.float(title="Max Order Quantity", defval=1.0, minval=0.01) baseTP1 = input.float(title="TP1(%)", defval=1.0, minval=0.0, maxval=100.0, step=0.1, inline="0") qt1 = input.int(title="QT1(%):", defval=40, minval=1, maxval=100, step=5, inline="0") baseTP2 = input.float(title="TP2(%)", defval=3.0, minval=0.0, maxval=100.0, step=0.1, inline="1") qt2 = input.int(title="QT2(%):", defval=30, minval=1, maxval=100, step=5, inline="1") baseTP3 = input.float(title="TP3(%)", defval=5.0, minval=0.0, maxval=100.0, step=0.1, inline="2") qt3 = input.int(title="QT3(%):", defval=30, minval=1, maxval=100, step=5, inline="2") initialStopLoss = input.float(title="Stop Loss(%)", defval=0.0, minval=0.0, maxval=100.0, step=0.1) longEntry = input.bool(defval=true, title= 'Long Entry', inline="3") shortEntry = input.bool(defval=true, title='Short Entry', inline="3") useSafeStop2 = input.bool(defval = true, title="Safe Stop After TP2", inline="6") useSafeStop1 = input.bool(defval = false, title="Safe Stop After TP1", inline="6") //---------- backtest range setup ------------ fromDay = input.int(defval = 1, title = "From Date:", minval = 1, maxval = 31, inline="4") fromMonth = input.int(defval = 1, title = "/", minval = 1, maxval = 12, inline="4") fromYear = input.int(defval = 2021, title = "/", minval = 2010, inline="4") toDay = input.int(defval = 30, title = "To__ Date:", minval = 1, maxval = 31, inline="5") toMonth = input.int(defval = 12, title = "/", minval = 1, maxval = 12, inline="5") toYear = input.int(defval = 2022, title = "/", minval = 2010, inline="5") //------------ time interval setup ----------- start = timestamp(fromYear, fromMonth, fromDay, 00, 00) // backtest start window finish = timestamp(toYear, toMonth, toDay, 23, 59) // backtest finish window window() => true // create function "within window of time" //------- define the order comments ------ enterLongComment = "" exitLongComment = "" enterShortComment = "" exitShortComment = "" longTPSL = "" longTP = "" longSL = "" shortTPSL = "" shortTP = "" shortSL = "" //--------- Define global variables ----------- var bool long = true var bool stoppedOutLong = false var bool stoppedOutShort = false var float kf = 0.0 var float velo = 0.0 var float orderQty = baseOrderQty var float stopLoss = initialStopLoss var bool isProfit = false var int barindex = 1 var int winCounter = 0 var int winCounterBuffer = 0 var int failCounter = 0 var float tp1 = baseTP1 var float tp2 = baseTP2 var float tp3 = baseTP3 var bool isTakeTP1 = false var bool isTakeTP2 = false var bool isTakeTP3 = false var bool isLastProfit = true var float stopPercentMax = stopPercentBase var float stopPercent = stopPercentBase var float stopLine = 0.0 var labelColor = color.blue //------ kalman filter calculation -------- dk = src - nz(kf[1], src) smooth = nz(kf[1], src) + dk * math.sqrt(gain / 10000 * 2) velo := nz(velo[1], 0) + gain / 10000 * dk kf := smooth + velo //--------- calculate the loft stopLoss line --------- //stopPercentMax := isLastProfit ? stopPercentBase : (stopPercentBase * stopPercentDeviation) if long == true stopLine := kf - (kf * (stopPercent / 100)) if long[1] == true and stopLine <= stopLine[1] stopLine := stopLine[1] else if (long[1] == true) stopPercent := stopPercent - downStep if(stopPercent < stopPercentMin) stopPercent := stopPercentMin if(kf < stopLine) long := false stopPercent := stopPercentMax stopLine := kf + (kf * (stopPercent / 100)) else stopLine := kf + (kf * (stopPercent / 100)) if long[1] == false and stopLine >= stopLine[1] stopLine := stopLine[1] else if(long[1] == false) stopPercent := stopPercent - downStep if(stopPercent < stopPercentMin) stopPercent := stopPercentMin if(kf > stopLine) long := true stopPercent := stopPercentMax stopLine := kf - (kf * (stopPercent / 100)) //------------------- determine buy and sell points --------------------- buySignall = window() and long and (not stoppedOutLong) sellSignall = window() and (not long) and (not stoppedOutShort) if longEntry and shortEntry if buySignall and baseTP1 <= 0.0 if strategy.position_size < 0 if close < strategy.position_avg_price isLastProfit := true else if strategy.position_size == 0 if strategy.wintrades > winCounter //strategy.wintrades[ barindex ] isLastProfit := true else isLastProfit := false else if sellSignall and baseTP1 <= 0.0 if strategy.position_size > 0 if close > strategy.position_avg_price isLastProfit := true else if strategy.position_size == 0 if strategy.wintrades > winCounter //strategy.wintrades[ barindex ] isLastProfit := true else isLastProfit := false else if isTakeTP2 == true isLastProfit := true else isLastProfit := false else if longEntry if sellSignall winCounterBuffer := winCounter if buySignall if winCounter > winCounterBuffer isLastProfit := true else isLastProfit := false else if shortEntry if buySignall winCounterBuffer := winCounter if sellSignall if winCounter > winCounterBuffer isLastProfit := true else isLastProfit := false //------------- set the deviations ------------ var float maxOrderSize = (baseOrderQty * math.pow(priceDeviation, maxOrderCount - 1)) if buySignall or sellSignall if isLastProfit == false orderQty := orderQty * priceDeviation tp1 := tp1 * profitDeviation tp2 := tp2 * profitDeviation tp3 := tp3 * profitDeviation tp1 := math.min(tp1, maxTakeProfit) tp2 := math.min(tp2, maxTakeProfit) tp3 := math.min(tp3, maxTakeProfit) if orderQty > maxOrderSize failCounter := failCounter + 1 orderQty := baseOrderQty tp1 := baseTP1 tp2 := baseTP2 tp3 := baseTP3 else orderQty := baseOrderQty tp1 := baseTP1 tp2 := baseTP2 tp3 := baseTP3 // ----------------- put debug labels ------------------- if orderQty == maxOrderSize labelColor := color.red else labelColor := isLastProfit ? color.lime : color.yellow if longEntry and shortEntry if buySignall or sellSignall label.new( x=bar_index, y=high, text="Qty:"+str.tostring(math.min(orderQty, maxOrderQty))+" | Worst Case:"+str.tostring(failCounter) ,color = labelColor ) else if longEntry if buySignall label.new( x=bar_index, y=high, text="Qty:"+str.tostring(math.min(orderQty, maxOrderQty))+" | Worst Case:"+str.tostring(failCounter) ,color = labelColor ) else if shortEntry if sellSignall label.new( x=bar_index, y=high, text="Qty:"+str.tostring(math.min(orderQty, maxOrderQty))+" | Worst Case:"+str.tostring(failCounter) ,color = labelColor ) //---------- execute the strategy ----------------- nz(orderQty, baseOrderQty) if longEntry and shortEntry if long strategy.close_all( when = buySignall, comment = exitShortComment) strategy.entry("LONG", strategy.long, when = buySignall, qty=math.min(orderQty, maxOrderQty), comment = enterLongComment) stoppedOutLong := true stoppedOutShort := false else strategy.close_all(when=sellSignall, comment = exitLongComment) strategy.entry("SHORT", strategy.short, when = sellSignall, qty=math.min(orderQty, maxOrderQty), comment = enterShortComment) stoppedOutLong := false stoppedOutShort := true else if(longEntry) strategy.entry("LONG", strategy.long, when = buySignall, qty=math.min(orderQty, maxOrderQty), comment = enterLongComment) strategy.close("LONG", when = sellSignall, comment = exitLongComment) if long stoppedOutLong := true stoppedOutShort := false else stoppedOutLong := false stoppedOutShort := true else if(shortEntry) strategy.entry("SHORT", strategy.short, when = sellSignall, qty=math.min(orderQty, maxOrderQty), comment = enterShortComment) strategy.close("SHORT", when = buySignall, comment = exitShortComment) if not long stoppedOutShort := true stoppedOutLong := false else stoppedOutShort := false stoppedOutLong := true //--------- calculate the TP/SL entries ----------- longProfitPrice1 = strategy.position_avg_price * (1 + tp1 * 0.01) longProfitPrice2 = strategy.position_avg_price * (1 + tp2 * 0.01) longProfitPrice3 = strategy.position_avg_price * (1 + tp3 * 0.01) shortProfitPrice1 = strategy.position_avg_price * (1 - tp1 * 0.01) shortProfitPrice2 = strategy.position_avg_price * (1 - tp2 * 0.01) shortProfitPrice3 = strategy.position_avg_price * (1 - tp3 * 0.01) longStopPrice = strategy.position_avg_price * (1 - stopLoss * 0.01) shortStopPrice = strategy.position_avg_price * (1 + stopLoss * 0.01) shortSafeStopPrice2 = strategy.position_avg_price * (1 - 0.2 * 0.01) longSafeStopPrice2 = strategy.position_avg_price * (1 + 0.2 * 0.01) longSafeStopPrice1 = stopLine shortSafeStopPrice1 = stopLine //----------- calculate TP quantity values ----------- takeQty1 = math.min(orderQty, maxOrderQty) * qt1 / 100 takeQty2 = math.min(orderQty, maxOrderQty) * qt2 / 100 takeQty3 = math.min(orderQty, maxOrderQty) * qt3 / 100 //----------------- take profit and stop loss processes ----------------- if strategy.position_size > 0 if close > longProfitPrice1 and tp1 > 0 and isTakeTP1 == false strategy.close(id="LONG", qty=takeQty1, comment = "longTP 1") isTakeTP1 := true if close > longProfitPrice2 and tp2 > 0 and isTakeTP2 == false strategy.close(id="LONG", qty=takeQty2, comment = "longTP 2") isTakeTP2 := true if close > longProfitPrice3 and tp3 > 0 and isTakeTP3 == false strategy.close(id="LONG", qty=takeQty3, comment = "longTP 3") isTakeTP3 := true if isTakeTP2 == true and useSafeStop2 strategy.exit(id="LONG", stop=longSafeStopPrice2, comment = "Long Safe Stop2") if isTakeTP1 == true and useSafeStop1 strategy.exit(id="LONG", stop=longSafeStopPrice1, comment = "Long Safe Stop1") if strategy.position_size < 0 if close < shortProfitPrice1 and tp1 > 0 and isTakeTP1 == false strategy.close(id="SHORT", qty=takeQty1, comment = "Short TP 1") isTakeTP1 := true if close < shortProfitPrice2 and tp2 > 0 and isTakeTP2 == false strategy.close(id="SHORT", qty=takeQty2, comment = "Short TP 2") isTakeTP2 := true if close < shortProfitPrice3 and tp3 > 0 and isTakeTP3 == false strategy.close(id="SHORT", qty=takeQty3, comment = "Short TP 3") isTakeTP3 := true if isTakeTP2 == true and useSafeStop2 strategy.exit(id="SHORT", stop=shortSafeStopPrice2, comment = "Short Safe Stop2") if isTakeTP1 == true and useSafeStop1 strategy.exit(id="SHORT", stop=shortSafeStopPrice1, comment = "Short Safe Stop1") if(initialStopLoss>0.0) if ( strategy.position_size > 0 ) strategy.exit(id="LONG", stop=longStopPrice, comment = "Long Stop Loss") else if ( strategy.position_size < 0 ) strategy.exit(id="SHORT", stop=shortStopPrice, comment = "Short Stop Loss") if buySignall or sellSignall isTakeTP1 := false isTakeTP2 := false isTakeTP3 := false // winCounter := strategy.wintrades //------------- plot charts --------------------- lineColor1 = long ? color.green : color.red lineColor2 = long ? color.aqua : color.fuchsia kalmanPlot = plot(kf, color=lineColor1, linewidth=3, title = "Kalman Filter") stopPlot = plot(stopLine, color=lineColor2, linewidth=2, title = "Stop Loss Line")template: strategy.tpl:40:21: executing "strategy.tpl" at <.api.GetStrategyListByName>: wrong number of args for GetStrategyListByName: want 7 got 6