MACD with 1D Stochastic Confirmation Reversal Strategy
Strategy that buys on MACD line crossing above signal with confirmation from daily Stochastic oscillator. The trade is closed when price hits an ATR-based stop loss or falls below a trailing EMA take profit.
Details
- Entry Criteria:
- Long:
MACD crosses above Signal && DailyK > DailyD && DailyK < 80
- Long:
- Long/Short: Long only
- Stops: ATR stop loss and trailing EMA take profit
- Default Values:
MacdFastLength= 12MacdSlowLength= 26MacdSignalLength= 9TrailingEmaLength= 20StopLossAtrMultiplier= 3.25mTrailingActivationAtrMultiplier= 4.25mCandleType= TimeSpan.FromHours(1).TimeFrame()
- Filters:
- Category: Reversal
- Direction: Long
- Indicators: MACD, Stochastic, ATR, EMA
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Mid-term
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that goes long on MACD crossover confirmed by daily Stochastic.
/// Uses ATR based stop loss and trailing EMA take profit.
/// </summary>
public class MacdStochasticConfirmationReversalStrategy : Strategy
{
private readonly StrategyParam<int> _macdFast;
private readonly StrategyParam<int> _macdSlow;
private readonly StrategyParam<int> _macdSignal;
private readonly StrategyParam<int> _trailingEmaLength;
private readonly StrategyParam<decimal> _stopLossAtr;
private readonly StrategyParam<decimal> _trailingActivationAtr;
private readonly StrategyParam<DataType> _candleType;
private MovingAverageConvergenceDivergenceSignal _macd;
private AverageTrueRange _atr;
private ExponentialMovingAverage _ema;
private StochasticOscillator _dailyStoch;
private decimal? _dailyK;
private decimal? _dailyD;
private decimal _prevMacd;
private decimal _prevSignal;
private bool _hasPrev;
private decimal _stopLossLevel;
private decimal _activationLevel;
private bool _trailingActive;
private decimal? _takeProfitLevel;
private decimal _entryPrice;
/// <summary>
/// MACD fast EMA length.
/// </summary>
public int MacdFastLength { get => _macdFast.Value; set => _macdFast.Value = value; }
/// <summary>
/// MACD slow EMA length.
/// </summary>
public int MacdSlowLength { get => _macdSlow.Value; set => _macdSlow.Value = value; }
/// <summary>
/// MACD signal EMA length.
/// </summary>
public int MacdSignalLength { get => _macdSignal.Value; set => _macdSignal.Value = value; }
/// <summary>
/// Trailing EMA length.
/// </summary>
public int TrailingEmaLength { get => _trailingEmaLength.Value; set => _trailingEmaLength.Value = value; }
/// <summary>
/// ATR multiplier for stop loss.
/// </summary>
public decimal StopLossAtrMultiplier { get => _stopLossAtr.Value; set => _stopLossAtr.Value = value; }
/// <summary>
/// ATR multiplier to activate trailing.
/// </summary>
public decimal TrailingActivationAtrMultiplier { get => _trailingActivationAtr.Value; set => _trailingActivationAtr.Value = value; }
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Initialize strategy parameters.
/// </summary>
public MacdStochasticConfirmationReversalStrategy()
{
_macdFast = Param(nameof(MacdFastLength), 12)
.SetDisplay("MACD Fast", "Fast EMA length", "MACD");
_macdSlow = Param(nameof(MacdSlowLength), 26)
.SetDisplay("MACD Slow", "Slow EMA length", "MACD");
_macdSignal = Param(nameof(MacdSignalLength), 9)
.SetDisplay("MACD Signal", "Signal EMA length", "MACD");
_trailingEmaLength = Param(nameof(TrailingEmaLength), 20)
.SetDisplay("Trailing EMA", "EMA length for trailing take profit", "Strategy");
_stopLossAtr = Param(nameof(StopLossAtrMultiplier), 3.25m)
.SetDisplay("ATR Stop", "ATR multiplier for stop loss", "Strategy");
_trailingActivationAtr = Param(nameof(TrailingActivationAtrMultiplier), 4.25m)
.SetDisplay("ATR Activate", "ATR multiplier to activate trailing", "Strategy");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Base candle type", "Common");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_macd = null;
_atr = null;
_ema = null;
_dailyStoch = null;
_dailyK = null;
_dailyD = null;
_prevMacd = 0m;
_prevSignal = 0m;
_hasPrev = false;
_stopLossLevel = 0m;
_activationLevel = 0m;
_trailingActive = false;
_takeProfitLevel = null;
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = MacdFastLength },
LongMa = { Length = MacdSlowLength },
},
SignalMa = { Length = MacdSignalLength }
};
_atr = new AverageTrueRange { Length = 14 };
_ema = new EMA { Length = TrailingEmaLength };
_dailyStoch = new StochasticOscillator();
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_macd, _atr, _ema, ProcessCandle)
.Start();
var dailySubscription = SubscribeCandles(TimeSpan.FromDays(1).TimeFrame());
dailySubscription
.BindEx(_dailyStoch, ProcessDaily)
.Start();
}
private void ProcessDaily(ICandleMessage candle, IIndicatorValue stochValue)
{
if (candle.State != CandleStates.Finished)
return;
var stoch = (StochasticOscillatorValue)stochValue;
if (stoch.K is decimal k && stoch.D is decimal d)
{
_dailyK = k;
_dailyD = d;
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue, IIndicatorValue atrValue, IIndicatorValue emaValue)
{
if (candle.State != CandleStates.Finished)
return;
var macd = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
if (macd.Macd is not decimal macdLine || macd.Signal is not decimal signalLine)
return;
var atr = atrValue.ToDecimal();
var ema = emaValue.ToDecimal();
if (!_macd.IsFormed || _dailyK is null || _dailyD is null)
{
_prevMacd = macdLine;
_prevSignal = signalLine;
_hasPrev = true;
return;
}
var crossUp = _hasPrev && _prevMacd <= _prevSignal && macdLine > signalLine;
if (crossUp && _dailyK > _dailyD && _dailyK < 80m && Position <= 0)
{
BuyMarket();
_entryPrice = candle.ClosePrice;
_stopLossLevel = _entryPrice - StopLossAtrMultiplier * atr;
_activationLevel = _entryPrice + TrailingActivationAtrMultiplier * atr;
_trailingActive = false;
_takeProfitLevel = null;
}
if (Position > 0)
{
if (!_trailingActive && candle.HighPrice > _activationLevel)
_trailingActive = true;
if (_trailingActive)
_takeProfitLevel = ema;
if ((_takeProfitLevel is decimal tp && candle.ClosePrice < tp) ||
candle.LowPrice <= _stopLossLevel)
{
SellMarket(Position);
_trailingActive = false;
_takeProfitLevel = null;
}
}
_prevMacd = macdLine;
_prevSignal = signalLine;
_hasPrev = true;
}
}
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,
AverageTrueRange,
ExponentialMovingAverage as EMA,
StochasticOscillator,
MovingAverageConvergenceDivergenceSignalValue,
StochasticOscillatorValue,
)
from StockSharp.Algo.Strategies import Strategy
class macd_stochastic_confirmation_reversal_strategy(Strategy):
def __init__(self):
super(macd_stochastic_confirmation_reversal_strategy, self).__init__()
self._macd_fast = self.Param("MacdFastLength", 12) \
.SetDisplay("MACD Fast", "Fast EMA length", "MACD")
self._macd_slow = self.Param("MacdSlowLength", 26) \
.SetDisplay("MACD Slow", "Slow EMA length", "MACD")
self._macd_signal = self.Param("MacdSignalLength", 9) \
.SetDisplay("MACD Signal", "Signal EMA length", "MACD")
self._trailing_ema_length = self.Param("TrailingEmaLength", 20) \
.SetDisplay("Trailing EMA", "EMA length for trailing take profit", "Strategy")
self._stop_loss_atr = self.Param("StopLossAtrMultiplier", 3.25) \
.SetDisplay("ATR Stop", "ATR multiplier for stop loss", "Strategy")
self._trailing_activation_atr = self.Param("TrailingActivationAtrMultiplier", 4.25) \
.SetDisplay("ATR Activate", "ATR multiplier to activate trailing", "Strategy")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Base candle type", "Common")
self._daily_k = None
self._daily_d = None
self._prev_macd = 0.0
self._prev_signal = 0.0
self._has_prev = False
self._stop_loss_level = 0.0
self._activation_level = 0.0
self._trailing_active = False
self._take_profit_level = None
self._entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(macd_stochastic_confirmation_reversal_strategy, self).OnReseted()
self._daily_k = None
self._daily_d = None
self._prev_macd = 0.0
self._prev_signal = 0.0
self._has_prev = False
self._stop_loss_level = 0.0
self._activation_level = 0.0
self._trailing_active = False
self._take_profit_level = None
self._entry_price = 0.0
def OnStarted2(self, time):
super(macd_stochastic_confirmation_reversal_strategy, self).OnStarted2(time)
self._macd_ind = MovingAverageConvergenceDivergenceSignal()
self._macd_ind.Macd.ShortMa.Length = self._macd_fast.Value
self._macd_ind.Macd.LongMa.Length = self._macd_slow.Value
self._macd_ind.SignalMa.Length = self._macd_signal.Value
self._atr = AverageTrueRange()
self._atr.Length = 14
self._ema = EMA()
self._ema.Length = self._trailing_ema_length.Value
self._daily_stoch = StochasticOscillator()
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(self._macd_ind, self._atr, self._ema, self._process_candle).Start()
daily_sub = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromDays(1)))
daily_sub.BindEx(self._daily_stoch, self._process_daily).Start()
def _process_daily(self, candle, stoch_value):
if candle.State != CandleStates.Finished:
return
stoch = stoch_value
k = stoch.K
d = stoch.D
if k is not None and d is not None:
self._daily_k = float(k)
self._daily_d = float(d)
def _process_candle(self, candle, macd_value, atr_value, ema_value):
if candle.State != CandleStates.Finished:
return
macd = macd_value
macd_line_val = macd.Macd
signal_line_val = macd.Signal
if macd_line_val is None or signal_line_val is None:
return
macd_line = float(macd_line_val)
signal_line = float(signal_line_val)
atr = float(atr_value)
ema = float(ema_value)
if not self._macd_ind.IsFormed or self._daily_k is None or self._daily_d is None:
self._prev_macd = macd_line
self._prev_signal = signal_line
self._has_prev = True
return
cross_up = self._has_prev and self._prev_macd <= self._prev_signal and macd_line > signal_line
if cross_up and self._daily_k > self._daily_d and self._daily_k < 80.0 and self.Position <= 0:
self.BuyMarket()
self._entry_price = float(candle.ClosePrice)
self._stop_loss_level = self._entry_price - float(self._stop_loss_atr.Value) * atr
self._activation_level = self._entry_price + float(self._trailing_activation_atr.Value) * atr
self._trailing_active = False
self._take_profit_level = None
if self.Position > 0:
if not self._trailing_active and float(candle.HighPrice) > self._activation_level:
self._trailing_active = True
if self._trailing_active:
self._take_profit_level = ema
if (self._take_profit_level is not None and float(candle.ClosePrice) < self._take_profit_level) or \
float(candle.LowPrice) <= self._stop_loss_level:
self.SellMarket(self.Position)
self._trailing_active = False
self._take_profit_level = None
self._prev_macd = macd_line
self._prev_signal = signal_line
self._has_prev = True
def CreateClone(self):
return macd_stochastic_confirmation_reversal_strategy()