The Dual Moving Average Reversion Strategy is a typical short-term mean reversion trading strategy. The strategy generates trading signals by two moving averages with different parameter settings. It aims to gain profits when trend reversal happens.
The strategy uses two moving averages to generate trading signals. The first MA maopening is used to determine the trend direction. The second MA maclosing is used to generate trading signals.
When maopening goes up, it indicates the current market is in an uptrend. When maopening goes down, it indicates the current market is in a downtrend. maclosing is multiplied by a coefficient greater than 1 to make it more sensitive for generating early reversal signals.
Specifically, when maopening goes up and maclosing crosses below maopening, it indicates a trend reversal. The strategy will open short position. When maopening goes down and maclosing crosses above maopening, it indicates a trend reversal. The strategy will open long position.
The parameters of the strategy include MA type, length, data source etc. The trading performance can be optimized by adjusting these parameters. There are also some configurable options such as entry rule, stop loss etc.
The main advantages of the Dual MA Reversion Strategy are:
Small drawdown, suitable for short-term trading. The fast moving averages can quickly capture short-term reversals with smaller drawdowns.
Simple to implement and easy to grasp. The crossover of two MAs generates clear trading signals.
Highly configurable with multiple adjustable parameters. The parameters of two MAs and coefficients can be optimized.
Easy to automate with clear logic flow. The simple logic and high frequency trading make it very suitable for automated trading.
Controllable risk with stop loss mechanism. Moving stop loss or value stop loss can limit the loss of single trade.
There are also some risks of the strategy:
Lagging of MA crossover signals. The MAs themselves lag behind the price. The crossover may happen after the trend has reversed for some time.
Risk of whipsaw trades. The reversed trend may quickly reverse again, causing consecutive losses.
Drawdown still exists. Though stop loss limits the single loss, consecutive stop loss can still lead to large drawdowns.
Overfitting risk. Excessive parameter optimization may lead to overfitting and poor performance in live trading.
The solutions include:
Optimize parameters to find faster MAs.
Add filters, like volume and volatility indicators, to avoid whipsaw trades.
Adjust stop loss position to lower the probability of consecutive stop loss.
Robustness test of parameter sets to evaluate overfitting risks.
The strategy can be further optimized in the following aspects:
Test different types of MAs to find more sensitive ones, like KAMA, ZLEMA etc.
Optimize MA lengths to find the optimal combination. Usually shorter periods have better performance.
Test different data sources, like close, median price, typical price etc.
Add trend filter to avoid unsuitable reversal signals, like Donchian Channel.
Add other indicators for confirmation, like MACD, OBV etc.
Enhance risk management mechanisms, like moving stop loss, maximum account loss etc.
Portfolio optimization to find the optimal asset allocation.
Robustness test of parameters to evaluate overfitting risks.
The Dual MA Reversion is a simple and practical short-term trading strategy. It is suitable for capturing short-term reversals with quantitative trading. However, risks like lagging and whipsaw trades exist. The strategy can be improved by optimizing parameters, adding filters, enhancing risk control etc. to develop a stable and efficient strategy with good real trading performance.
/*backtest start: 2023-10-17 00:00:00 end: 2023-11-16 00:00:00 period: 1h basePeriod: 15m exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}] */ //@version=5 strategy(title = "hamster-bot MRS 2", overlay = true, default_qty_type = strategy.percent_of_equity, initial_capital = 100, default_qty_value = 100, pyramiding = 9, commission_value = 0.045, backtest_fill_limits_assumption = 1) info_options = "Options" on_close = input(false, title = "Entry on close", inline=info_options, group=info_options) OFFS = input.int(0, minval = 0, maxval = 1, title = "| Offset View", inline=info_options, group=info_options) trade_offset = input.int(0, minval = 0, maxval = 1, title = "Trade", inline=info_options, group=info_options) use_kalman_filter = input.bool(false, title="Use Kalman filter", group=info_options) //MA Opening info_opening = "MA Opening" maopeningtyp = input.string("SMA", title="Type", options=["SMA", "EMA", "TEMA", "DEMA", "ZLEMA", "WMA", "Hma", "Thma", "Ehma", "H", "L", "DMA"], title = "", inline=info_opening, group=info_opening) maopeningsrc = input.source(ohlc4, title = "", inline=info_opening, group=info_opening) maopeninglen = input.int(3, minval = 1, maxval = 200, title = "", inline=info_opening, group=info_opening) //MA Closing info_closing = "MA Closing" maclosingtyp = input.string("SMA", title="Type", options=["SMA", "EMA", "TEMA", "DEMA", "ZLEMA", "WMA", "Hma", "Thma", "Ehma", "H", "L", "DMA"], title = "", inline=info_closing, group=info_closing) maclosingsrc = input.source(ohlc4, title = "", inline=info_closing, group=info_closing) maclosinglen = input.int(3, minval = 1, maxval = 200, title = "", inline=info_closing, group=info_closing) maclosingmul = input.float(1, step = 0.005, title = "mul", inline=info_closing, group=info_closing) long1on = input(true, title = "", inline = "long1") long1shift = input.float(0.96, step = 0.005, title = "Long", inline = "long1") long1lot = input.int(10, minval = 0, maxval = 10000, step = 10, title = "Lot 1", inline = "long1") short1on = input(true, title = "", inline = "short1") short1shift = input.float(1.04, step = 0.005, title = "short", inline = "short1") short1lot = input.int(10, minval = 0, maxval = 10000, step = 10, title = "Lot 1", inline = "short1") startTime = input(timestamp("01 Jan 2010 00:00 +0000"), "Start date", inline = "period") finalTime = input(timestamp("31 Dec 2030 23:59 +0000"), "Final date", inline = "period") HMA(_src, _length) => ta.wma(2 * ta.wma(_src, _length / 2) - ta.wma(_src, _length), math.round(math.sqrt(_length))) EHMA(_src, _length) => ta.ema(2 * ta.ema(_src, _length / 2) - ta.ema(_src, _length), math.round(math.sqrt(_length))) THMA(_src, _length) => ta.wma(ta.wma(_src,_length / 3) * 3 - ta.wma(_src, _length / 2) - ta.wma(_src, _length), _length) tema(sec, length)=> tema1= ta.ema(sec, length) tema2= ta.ema(tema1, length) tema3= ta.ema(tema2, length) tema_r = 3*tema1-3*tema2+tema3 donchian(len) => math.avg(ta.lowest(len), ta.highest(len)) ATR_func(_src, _len)=> atrLow = low - ta.atr(_len) trailAtrLow = atrLow trailAtrLow := na(trailAtrLow[1]) ? trailAtrLow : atrLow >= trailAtrLow[1] ? atrLow : trailAtrLow[1] supportHit = _src <= trailAtrLow trailAtrLow := supportHit ? atrLow : trailAtrLow trailAtrLow f_dema(src, len)=> EMA1 = ta.ema(src, len) EMA2 = ta.ema(EMA1, len) DEMA = (2*EMA1)-EMA2 f_zlema(src, period) => lag = math.round((period - 1) / 2) ema_data = src + (src - src[lag]) zl= ta.ema(ema_data, period) f_kalman_filter(src) => float value1= na float value2 = na value1 := 0.2 * (src - src[1]) + 0.8 * nz(value1[1]) value2 := 0.1 * (ta.tr) + 0.8 * nz(value2[1]) lambda = math.abs(value1 / value2) alpha = (-math.pow(lambda, 2) + math.sqrt(math.pow(lambda, 4) + 16 * math.pow(lambda, 2)))/8 value3 = float(na) value3 := alpha * src + (1 - alpha) * nz(value3[1]) //SWITCH ma_func(modeSwitch, src, len, use_k_f=true) => modeSwitch == "SMA" ? use_kalman_filter and use_k_f ? f_kalman_filter(ta.sma(src, len)) : ta.sma(src, len) : modeSwitch == "RMA" ? use_kalman_filter and use_k_f ? f_kalman_filter(ta.rma(src, len)) : ta.rma(src, len) : modeSwitch == "EMA" ? use_kalman_filter and use_k_f ? f_kalman_filter(ta.ema(src, len)) : ta.ema(src, len) : modeSwitch == "TEMA" ? use_kalman_filter and use_k_f ? f_kalman_filter(tema(src, len)) : tema(src, len): modeSwitch == "DEMA" ? use_kalman_filter and use_k_f ? f_kalman_filter(f_dema(src, len)) : f_dema(src, len): modeSwitch == "ZLEMA" ? use_kalman_filter and use_k_f ? f_kalman_filter(f_zlema(src, len)) : f_zlema(src, len): modeSwitch == "WMA" ? use_kalman_filter and use_k_f ? f_kalman_filter(ta.wma(src, len)) : ta.wma(src, len): modeSwitch == "VWMA" ? use_kalman_filter and use_k_f ? f_kalman_filter(ta.vwma(src, len)) : ta.vwma(src, len): modeSwitch == "Hma" ? use_kalman_filter and use_k_f ? f_kalman_filter(HMA(src, len)) : HMA(src, len): modeSwitch == "Ehma" ? use_kalman_filter and use_k_f ? f_kalman_filter(EHMA(src, len)) : EHMA(src, len): modeSwitch == "Thma" ? use_kalman_filter and use_k_f ? f_kalman_filter(THMA(src, len/2)) : THMA(src, len/2): modeSwitch == "ATR" ? use_kalman_filter and use_k_f ? f_kalman_filter(ATR_func(src, len)): ATR_func(src, len) : modeSwitch == "L" ? use_kalman_filter and use_k_f ? f_kalman_filter(ta.lowest(len)): ta.lowest(len) : modeSwitch == "H" ? use_kalman_filter and use_k_f ? f_kalman_filter(ta.highest(len)): ta.highest(len) : modeSwitch == "DMA" ? donchian(len) : na //Var sum = 0.0 maopening = 0.0 maclosing = 0.0 os = maopeningsrc cs = maclosingsrc pos = strategy.position_size p = 0.0 p := pos == 0 ? (strategy.equity / 100) / close : p[1] truetime = true loss = 0.0 maxloss = 0.0 equity = 0.0 //MA Opening maopening := ma_func(maopeningtyp, maopeningsrc, maopeninglen) //MA Closing maclosing := ma_func(maclosingtyp, maclosingsrc, maclosinglen) * maclosingmul long1 = long1on == false ? 0 : long1shift == 0 ? 0 : long1lot == 0 ? 0 : maopening == 0 ? 0 : maopening * long1shift short1 = short1on == false ? 0 : short1shift == 0 ? 0 : short1lot == 0 ? 0 : maopening == 0 ? 0 : maopening * short1shift //Colors maopeningcol = maopening == 0 ? na : color.blue maclosingcol = maclosing == 0 ? na : color.fuchsia long1col = long1 == 0 ? na : color.green short1col = short1 == 0 ? na : color.red //Lines plot(maopening, offset = OFFS, color = maopeningcol) plot(maclosing, offset = OFFS, color = maclosingcol) long1line = long1 == 0 ? close : long1 short1line = short1 == 0 ? close : short1 plot(long1line, offset = OFFS, color = long1col) plot(short1line, offset = OFFS, color = short1col) //Lots lotlong1 = p * long1lot lotshort1 = p * short1lot //Entry if maopening > 0 and maclosing > 0 and truetime //Long sum := 0 strategy.entry("L", strategy.long, lotlong1, limit = on_close ? na : long1, when = long1 > 0 and pos <= sum and (on_close ? close <= long1[trade_offset] : true)) sum := lotlong1 //Short sum := 0 pos := -1 * pos strategy.entry("S", strategy.short, lotshort1, limit = on_close ? na : short1, when = short1 > 0 and pos <= sum and (on_close ? close >= short1[trade_offset] : true)) sum := lotshort1 strategy.exit("Exit", na, limit = maclosing) if time > finalTime strategy.close_all()