RMACD Reversal Strategy
Overview
This strategy uses the Moving Average Convergence Divergence (MACD) indicator to generate reversal signals. Four different modes define how entries are detected:
- Breakdown – enters long when the MACD histogram crosses below zero and enters short when it crosses above zero.
- MacdTwist – looks for a change in MACD direction by comparing the last two histogram values.
- SignalTwist – monitors the signal line for direction changes.
- MacdDisposition – enters when the MACD histogram crosses the signal line.
The strategy always uses market orders and reverses positions when a new opposite signal appears.
Parameters
- Fast Length – period for the fast EMA inside MACD.
- Slow Length – period for the slow EMA inside MACD.
- Signal Length – smoothing period for the signal line.
- Candle Type – timeframe of candles used for calculations.
- Mode – selects the entry algorithm described above.
Notes
- Signals are evaluated only on finished candles.
- The strategy stores previous MACD values internally instead of requesting historical data.
- No explicit stop loss or take profit is used; positions are closed only on opposite signals.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on MACD signal crossover with several reversal entry modes.
/// </summary>
public class RmacdReversalStrategy : Strategy
{
private readonly StrategyParam<int> _signalLength;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<AlgModes> _mode;
private decimal _prevMacd;
private decimal _prevMacd2;
private decimal _prevSignal;
private decimal _prevSignal2;
private int _initialized;
private int _barsSinceTrade;
public int SignalLength { get => _signalLength.Value; set => _signalLength.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public AlgModes Mode { get => _mode.Value; set => _mode.Value = value; }
public RmacdReversalStrategy()
{
_signalLength = Param(nameof(SignalLength), 9)
.SetGreaterThanZero()
.SetDisplay("Signal Length", "Signal smoothing period", "Indicator");
_cooldownBars = Param(nameof(CooldownBars), 6)
.SetGreaterThanZero()
.SetDisplay("Cooldown Bars", "Bars between trades", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for analysis", "General");
_mode = Param(nameof(Mode), AlgModes.Breakdown)
.SetDisplay("Mode", "Entry algorithm", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevMacd = 0;
_prevMacd2 = 0;
_prevSignal = 0;
_prevSignal2 = 0;
_initialized = 0;
_barsSinceTrade = CooldownBars;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var macd = new MovingAverageConvergenceDivergenceSignal();
macd.SignalMa.Length = SignalLength;
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(macd, ProcessMacd)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, macd);
DrawOwnTrades(area);
}
}
private void ProcessMacd(ICandleMessage candle, IIndicatorValue macdValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!macdValue.IsFormed)
return;
_barsSinceTrade++;
var typed = (IMovingAverageConvergenceDivergenceSignalValue)macdValue;
if (typed.Macd is not decimal macd || typed.Signal is not decimal signal)
return;
if (_initialized == 0)
{
_prevMacd = macd;
_prevSignal = signal;
_initialized = 1;
return;
}
else if (_initialized == 1)
{
_prevMacd2 = _prevMacd;
_prevSignal2 = _prevSignal;
_prevMacd = macd;
_prevSignal = signal;
_initialized = 2;
return;
}
var buy = false;
var sell = false;
switch (Mode)
{
case AlgModes.Breakdown:
buy = _prevMacd > 0m && macd <= 0m;
sell = _prevMacd < 0m && macd >= 0m;
break;
case AlgModes.MacdTwist:
buy = _prevMacd < _prevMacd2 && macd > _prevMacd;
sell = _prevMacd > _prevMacd2 && macd < _prevMacd;
break;
case AlgModes.SignalTwist:
buy = _prevSignal < _prevSignal2 && signal > _prevSignal;
sell = _prevSignal > _prevSignal2 && signal < _prevSignal;
break;
case AlgModes.MacdDisposition:
buy = _prevMacd > _prevSignal && macd <= signal;
sell = _prevMacd < _prevSignal && macd >= signal;
break;
}
if (buy && Position <= 0 && _barsSinceTrade >= CooldownBars)
{
if (Position < 0) BuyMarket();
BuyMarket();
_barsSinceTrade = 0;
}
else if (sell && Position >= 0 && _barsSinceTrade >= CooldownBars)
{
if (Position > 0) SellMarket();
SellMarket();
_barsSinceTrade = 0;
}
_prevMacd2 = _prevMacd;
_prevSignal2 = _prevSignal;
_prevMacd = macd;
_prevSignal = signal;
}
/// <summary>
/// Entry modes for RMACD strategy.
/// </summary>
public enum AlgModes
{
/// <summary>
/// MACD histogram crossing the zero line.
/// </summary>
Breakdown,
/// <summary>
/// MACD histogram changes direction.
/// </summary>
MacdTwist,
/// <summary>
/// Signal line changes direction.
/// </summary>
SignalTwist,
/// <summary>
/// MACD histogram crosses the signal line.
/// </summary>
MacdDisposition
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import MovingAverageConvergenceDivergenceSignal
from StockSharp.Algo.Strategies import Strategy
class rmacd_reversal_strategy(Strategy):
# Entry modes
BREAKDOWN = 0
MACD_TWIST = 1
SIGNAL_TWIST = 2
MACD_DISPOSITION = 3
def __init__(self):
super(rmacd_reversal_strategy, self).__init__()
self._signal_length = self.Param("SignalLength", 9) \
.SetGreaterThanZero() \
.SetDisplay("Signal Length", "Signal smoothing period", "Indicator")
self._cooldown_bars = self.Param("CooldownBars", 6) \
.SetGreaterThanZero() \
.SetDisplay("Cooldown Bars", "Bars between trades", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for analysis", "General")
self._mode = self.Param("Mode", 0) \
.SetDisplay("Mode", "Entry algorithm (0=Breakdown,1=MacdTwist,2=SignalTwist,3=MacdDisposition)", "Trading")
self._prev_macd = 0.0
self._prev_macd2 = 0.0
self._prev_signal = 0.0
self._prev_signal2 = 0.0
self._initialized = 0
self._bars_since_trade = 0
@property
def signal_length(self):
return self._signal_length.Value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
@property
def candle_type(self):
return self._candle_type.Value
@property
def mode(self):
return self._mode.Value
def OnReseted(self):
super(rmacd_reversal_strategy, self).OnReseted()
self._prev_macd = 0.0
self._prev_macd2 = 0.0
self._prev_signal = 0.0
self._prev_signal2 = 0.0
self._initialized = 0
self._bars_since_trade = self.cooldown_bars
def OnStarted2(self, time):
super(rmacd_reversal_strategy, self).OnStarted2(time)
macd = MovingAverageConvergenceDivergenceSignal()
macd.SignalMa.Length = self.signal_length
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(macd, self.process_macd).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, macd)
self.DrawOwnTrades(area)
def process_macd(self, candle, macd_value):
if candle.State != CandleStates.Finished:
return
if not macd_value.IsFormed:
return
self._bars_since_trade += 1
macd_v = macd_value.Macd
signal_v = macd_value.Signal
if macd_v is None or signal_v is None:
return
macd = float(macd_v)
signal = float(signal_v)
if self._initialized == 0:
self._prev_macd = macd
self._prev_signal = signal
self._initialized = 1
return
elif self._initialized == 1:
self._prev_macd2 = self._prev_macd
self._prev_signal2 = self._prev_signal
self._prev_macd = macd
self._prev_signal = signal
self._initialized = 2
return
buy = False
sell = False
m = self.mode
if m == self.BREAKDOWN:
buy = self._prev_macd > 0.0 and macd <= 0.0
sell = self._prev_macd < 0.0 and macd >= 0.0
elif m == self.MACD_TWIST:
buy = self._prev_macd < self._prev_macd2 and macd > self._prev_macd
sell = self._prev_macd > self._prev_macd2 and macd < self._prev_macd
elif m == self.SIGNAL_TWIST:
buy = self._prev_signal < self._prev_signal2 and signal > self._prev_signal
sell = self._prev_signal > self._prev_signal2 and signal < self._prev_signal
elif m == self.MACD_DISPOSITION:
buy = self._prev_macd > self._prev_signal and macd <= signal
sell = self._prev_macd < self._prev_signal and macd >= signal
if buy and self.Position <= 0 and self._bars_since_trade >= self.cooldown_bars:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._bars_since_trade = 0
elif sell and self.Position >= 0 and self._bars_since_trade >= self.cooldown_bars:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._bars_since_trade = 0
self._prev_macd2 = self._prev_macd
self._prev_signal2 = self._prev_signal
self._prev_macd = macd
self._prev_signal = signal
def CreateClone(self):
return rmacd_reversal_strategy()