Dual MA Momentum Breakout Strategy

Author: ChaoZhang, Date: 2024-01-31 10:33:21
Tags:

img

Overview

The Dual MA Momentum Breakout Strategy is a quantitative trading strategy that combines dual moving average lines and RSI indicators. It calculates fast moving average, slow moving average and RSI to set overbought/oversold thresholds for the momentum indicator RSI. It goes long when the dual MAs have golden crosses and goes short when there are death crosses, in order to capture the trending moves in the market.

Logic

The Dual MA Momentum Breakout Strategy is mainly based on dual moving averages and RSI indicator. It first calculates one fast and one slow moving average line, with the fast MA being the 10-day weighted moving average and the slow MA being the 100-day linear adaptive moving average. Then it calculates the 14-day RSI and sets overbought/oversold thresholds. When the fast MA crosses above the slow MA, it signals an uptrend, and when the fast MA crosses below the slow MA, it signals a downtrend. In addition to determining the trend direction, the strategy also requires the RSI to be above the overbought threshold or below the oversold threshold to effectively filter out fake breakouts.

Specifically, when an uptrend is identified, if the RSI is above the overbought threshold at this time, a long position will be opened. When a downtrend is identified and the RSI is below the oversold threshold, a short position will be opened. After opening a position, the opposite position will be opened when the trading signal is reversed.

Advantage

The Dual MA Momentum Breakout Strategy combines dual MAs and RSI to effectively identify market trends and uses the RSI to filter out fake breakouts, thereby improving the reliability of trading signals. Compared to a single MA system, this strategy can greatly reduce the occurrence of ineffective trades. In addition, the parameter optimization of the RSI also provides flexibility for the strategy.

Risk

The Dual MA Momentum Breakout Strategy also carries some risks. The dual MA system is very sensitive to parameters and different parameter combinations need to be carefully tested for different markets. In addition, improperly set thresholds for RSI may also lead to missing trading opportunities. Finally, aggressive trailing stops may be penetrated in certain market conditions, so stop loss points should be adjusted based on backtesting results.

Optimization

The Dual MA Momentum Breakout Strategy can be optimized in the following aspects:

  1. Optimize fast and slow MA parameters to find the best parameter combination;
  2. Optimize RSI parameters and adjust overbought/oversold thresholds;
  3. Add adaptive trailing stop mechanisms to control risks;
  4. Add position sizing optimization module to improve capital use efficiency.

Conclusion

The Dual MA Momentum Breakout Strategy determines trend direction through the dual MA system and uses RSI to filter signals, which can effectively improve the shortcomings of a single MA system. This strategy has large optimization space for parameters and can achieve adaptive adjustment. It is an excellent trend following strategy.


/*backtest
start: 2023-12-01 00:00:00
end: 2023-12-10 23:59:59
period: 1h
basePeriod: 15m
exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}]
*/

// This work is licensed under a Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) https://creativecommons.org/licenses/by-nc-sa/4.0/
// © Salman4sgd

//@version=5
strategy("MAConverging + QQE Threshold Strategy", overlay = true)
//------------------------------------------------------------------------------
//Settings
//-----------------------------------------------------------------------------{
length = input(100)

incr   = input(10, "Increment")

fast   = input(10)

src    = input(close)

//-----------------------------------------------------------------------------}
//Calculations
//-----------------------------------------------------------------------------{
var ma    = 0.
var fma   = 0.
var alpha = 0.
var k     = 1 / incr

upper = ta.highest(length)
lower = ta.lowest(length)
init_ma = ta.sma(src, length)

cross = ta.cross(src,ma)

alpha := cross ? 2 / (length + 1)
  : src > ma and upper > upper[1] ? alpha + k
  : src < ma and lower < lower[1] ? alpha + k
  : alpha

ma := nz(ma[1] + alpha[1] * (src - ma[1]), init_ma)
  
fma := nz(cross ? math.avg(src, fma[1])
  : src > ma ? math.max(src, fma[1]) + (src - fma[1]) / fast
  : math.min(src, fma[1]) + (src - fma[1]) / fast,src)

//-----------------------------------------------------------------------------}
//Plots
//-----------------------------------------------------------------------------{
css = fma > ma ? color.teal : color.red

plot0 = plot(fma, "Fast MA" 
  , color = #ff5d00
  , transp = 100)

plot1 = plot(ma, "Converging MA"
  , color = css)

fill(plot0, plot1, css
  , "Fill"
  , transp = 80)
  
//-----------------------------------------------------------------------------}

RSI_Period = input(14, title='RSI Length')
SF = input(5, title='RSI Smoothing')
QQE = input(4.238, title='Fast QQE Factor')
ThreshHold = input(10, title='Thresh-hold')
//
sQQEx = input(false, title='Show Smooth RSI, QQE Signal crosses')
sQQEz = input(false, title='Show Smooth RSI Zero crosses')
sQQEc = input(false, title='Show Smooth RSI Thresh Hold Channel Exits')
ma_type = input.string(title='MA Type', defval='EMA', options=['ALMA', 'EMA', 'DEMA', 'TEMA', 'WMA', 'VWMA', 'SMA', 'SMMA', 'HMA', 'LSMA', 'PEMA'])
lsma_offset = input.int(defval=0, title='* Least Squares (LSMA) Only - Offset Value', minval=0)
alma_offset = input.float(defval=0.85, title='* Arnaud Legoux (ALMA) Only - Offset Value', minval=0, step=0.01)
alma_sigma = input.int(defval=6, title='* Arnaud Legoux (ALMA) Only - Sigma Value', minval=0)
inpDrawBars = input(true, title='color bars?')


ma(type, src, len) =>
    float result = 0
    if type == 'SMA'  // Simple
        result := ta.sma(src, len)
        result
    if type == 'EMA'  // Exponential
        result := ta.ema(src, len)
        result
    if type == 'DEMA'  // Double Exponential
        e = ta.ema(src, len)
        result := 2 * e - ta.ema(e, len)
        result
    if type == 'TEMA'  // Triple Exponential
        e = ta.ema(src, len)
        result := 3 * (e - ta.ema(e, len)) + ta.ema(ta.ema(e, len), len)
        result
    if type == 'WMA'  // Weighted
        result := ta.wma(src, len)
        result
    if type == 'VWMA'  // Volume Weighted
        result := ta.vwma(src, len)
        result
    if type == 'SMMA'  // Smoothed
        w = ta.wma(src, len)
        result := na(w[1]) ? ta.sma(src, len) : (w[1] * (len - 1) + src) / len
        result
    if type == 'HMA'  // Hull
        result := ta.wma(2 * ta.wma(src, len / 2) - ta.wma(src, len), math.round(math.sqrt(len)))
        result
    if type == 'LSMA'  // Least Squares
        result := ta.linreg(src, len, lsma_offset)
        result
    if type == 'ALMA'  // Arnaud Legoux
        result := ta.alma(src, len, alma_offset, alma_sigma)
        result
    if type == 'PEMA'
        // Copyright (c) 2010-present, Bruno Pio
        // Copyright (c) 2019-present, Alex Orekhov (everget)
        // Pentuple Exponential Moving Average script may be freely distributed under the MIT license.
        ema1 = ta.ema(src, len)
        ema2 = ta.ema(ema1, len)
        ema3 = ta.ema(ema2, len)
        ema4 = ta.ema(ema3, len)
        ema5 = ta.ema(ema4, len)
        ema6 = ta.ema(ema5, len)
        ema7 = ta.ema(ema6, len)
        ema8 = ta.ema(ema7, len)
        pema = 8 * ema1 - 28 * ema2 + 56 * ema3 - 70 * ema4 + 56 * ema5 - 28 * ema6 + 8 * ema7 - ema8
        result := pema
        result
    result

src := input(close, title='RSI Source')
//

//
Wilders_Period = RSI_Period * 2 - 1


Rsi = ta.rsi(src, RSI_Period)
RsiMa = ma(ma_type, Rsi, SF)
AtrRsi = math.abs(RsiMa[1] - RsiMa)
MaAtrRsi = ma(ma_type, AtrRsi, Wilders_Period)
dar = ma(ma_type, MaAtrRsi, Wilders_Period) * QQE

longband = 0.0
shortband = 0.0
trend = 0

DeltaFastAtrRsi = dar
RSIndex = RsiMa
newshortband = RSIndex + DeltaFastAtrRsi
newlongband = RSIndex - DeltaFastAtrRsi
longband := RSIndex[1] > longband[1] and RSIndex > longband[1] ? math.max(longband[1], newlongband) : newlongband
shortband := RSIndex[1] < shortband[1] and RSIndex < shortband[1] ? math.min(shortband[1], newshortband) : newshortband
cross_1 = ta.cross(longband[1], RSIndex)
trend := ta.cross(RSIndex, shortband[1]) ? 1 : cross_1 ? -1 : nz(trend[1], 1)
FastAtrRsiTL = trend == 1 ? longband : shortband

//
// Find all the QQE Crosses
QQExlong = 0
QQExlong := nz(QQExlong[1])
QQExshort = 0
QQExshort := nz(QQExshort[1])
QQExlong := sQQEx and FastAtrRsiTL < RSIndex ? QQExlong + 1 : 0
QQExshort := sQQEx and FastAtrRsiTL > RSIndex ? QQExshort + 1 : 0
// Zero cross
QQEzlong = 0
QQEzlong := nz(QQEzlong[1])
QQEzshort = 0
QQEzshort := nz(QQEzshort[1])
QQEzlong := sQQEz and RSIndex >= 50 ? QQEzlong + 1 : 0
QQEzshort := sQQEz and RSIndex < 50 ? QQEzshort + 1 : 0
//  
// Thresh Hold channel Crosses give the BUY/SELL alerts.
QQEclong = 0
QQEclong := nz(QQEclong[1])
QQEcshort = 0
QQEcshort := nz(QQEcshort[1])
QQEclong := sQQEc and RSIndex > 50 + ThreshHold ? QQEclong + 1 : 0
QQEcshort := sQQEc and RSIndex < 50 - ThreshHold ? QQEcshort + 1 : 0


// // QQE exit from Thresh Hold Channel
// plotshape(sQQEc and QQEclong == 1 ? RsiMa - 50 : na, title='QQE XC Over Channel', style=shape.diamond, location=location.absolute, color=color.new(color.olive, 0), size=size.small, offset=0)
// plotshape(sQQEc and QQEcshort == 1 ? RsiMa - 50 : na, title='QQE XC Under Channel', style=shape.diamond, location=location.absolute, color=color.new(color.red, 0), size=size.small, offset=0)
// // QQE crosses
// plotshape(sQQEx and QQExlong == 1 ? FastAtrRsiTL[1] - 50 : na, title='QQE XQ Cross Over', style=shape.circle, location=location.absolute, color=color.new(color.lime, 0), size=size.small, offset=-1)
// plotshape(sQQEx and QQExshort == 1 ? FastAtrRsiTL[1] - 50 : na, title='QQE XQ Cross Under', style=shape.circle, location=location.absolute, color=color.new(color.blue, 0), size=size.small, offset=-1)
// // Signal crosses zero line
// plotshape(sQQEz and QQEzlong == 1 ? RsiMa - 50 : na, title='QQE XZ Zero Cross Over', style=shape.square, location=location.absolute, color=color.new(color.aqua, 0), size=size.small, offset=0)
// plotshape(sQQEz and QQEzshort == 1 ? RsiMa - 50 : na, title='QQE XZ Zero Cross Under', style=shape.square, location=location.absolute, color=color.new(color.fuchsia, 0), size=size.small, offset=0)

// hcolor = RsiMa - 50 > ThreshHold ? color.green : RsiMa - 50 < 0 - ThreshHold ? color.red : color.orange
// plot(FastAtrRsiTL - 50, color=color.new(color.blue, 0), linewidth=2)
// p1 = plot(RsiMa - 50, color=color.new(color.orange, 0), linewidth=2)
// plot(RsiMa - 50, color=hcolor, style=plot.style_columns, transp=50)


// hZero = hline(0, color=color.black, linestyle=hline.style_dashed, linewidth=1)
// hUpper = hline(ThreshHold, color=color.green, linestyle=hline.style_dashed, linewidth=2)
// hLower = hline(0 - ThreshHold, color=color.red, linestyle=hline.style_dashed, linewidth=2)
// fill(hUpper, hLower, color=color.new(color.gray, 80))
//EOF

length := input.int(title='ATR Length', defval=14, minval=1)
smoothing = input.string(title='ATR Smoothing', defval='RMA', options=['RMA', 'SMA', 'EMA', 'WMA'])
m = input(0.3, 'ATR Multiplier')
src1 = input(high)
src2 = input(low)
pline = input(true, 'Show Price Lines')
col1 = input(color.blue, 'ATR Text Color')
col2 = input.color(color.teal, 'Low Text Color', inline='1')
col3 = input.color(color.red, 'High Text Color', inline='2')

collong = input.color(color.teal, 'Low Line Color', inline='1')
colshort = input.color(color.red, 'High Line Color', inline='2')

ma_function(source, length) =>
    if smoothing == 'RMA'
        ta.rma(source, length)
    else
        if smoothing == 'SMA'
            ta.sma(source, length)
        else
            if smoothing == 'EMA'
                ta.ema(source, length)
            else
                ta.wma(source, length)

a = ma_function(ta.tr(true), length) * m
s_sl = ma_function(ta.tr(true), length) * m + src1
l_sl = src2 - ma_function(ta.tr(true), length) * m

p1 = plot(s_sl, title='ATR Short Stop Loss', color=colshort, trackprice=pline ? true : false, transp=20)
p2 = plot(l_sl, title='ATR Long Stop Loss', color=collong, trackprice=pline ? true : false, transp=20)


bgc = RsiMa - 50 > ThreshHold ? color.green : Rsi - 50 < 0 - ThreshHold ? color.red : color.orange
barcolor(inpDrawBars ? bgc : na)
prebuy = RsiMa - 50 > ThreshHold
buy=prebuy and not(prebuy[1]) and fma > ma

var long_tp=0.0
var long_sl=0.0
var short_tp=0.0
var short_sl=0.0

if prebuy
    strategy.close("Short")



if buy and strategy.position_size<=0
    strategy.entry("Long", strategy.long)
    long_sl:=l_sl
    long_tp:=close+(close-long_sl)*2
    
    
//if strategy.position_size>0
strategy.exit("L_SL","Long",stop=long_sl)
    //strategy.exit("L_SL","Long",stop=long_sl)
// if low<long_sl[1]
//     strategy.close("Long")
    
presell=RsiMa - 50 < 0 - ThreshHold // RsiMa - 50 < 0 - ThreshHold
sell= presell and not(presell[1]) and fma < ma

//plotshape(presell)

if presell
    strategy.close("Long")

if sell and strategy.position_size>=0
    strategy.entry("Short", strategy.short)
    short_sl:=s_sl
    short_tp:=close-(short_sl-close)*2
   
//if strategy.position_size<0
strategy.exit("S_SL","Short",stop=short_sl)
    //strategy.exit("S_SL","Short",stop=short_sl) 


    



More