ATR MACD Strategy
ATR MACD uses volatility from the Average True Range to adjust position size while trading MACD crossovers. Larger ATR readings result in smaller trade size, keeping risk consistent across market regimes.
Testing indicates an average annual return of about 154%. It performs best in the stocks market.
Entries occur when MACD crosses its signal line, with exits triggered by the opposite crossover or a volatility-based stop.
This combination seeks to capture momentum while accounting for changing volatility.
Details
- Entry Criteria: indicator signal
- Long/Short: Both
- Exit Criteria: stop-loss or opposite signal
- Stops: Yes, percent based
- Default Values:
CandleType= 15 minuteStopLoss= 2%
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: ATR, MACD
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
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 that uses ATR for volatility detection and MACD for trend direction.
/// Enters when MACD confirms trend direction.
/// </summary>
public class AtrMacdStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private decimal _atrValue;
private int _cooldown;
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Strategy constructor.
/// </summary>
public AtrMacdStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 100)
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
.SetRange(5, 500);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_atrValue = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var atr = new AverageTrueRange { Length = 14 };
var macd = new MovingAverageConvergenceDivergenceSignal();
var subscription = SubscribeCandles(CandleType);
// Bind ATR to capture value
subscription.BindEx(atr, OnAtr);
// Bind MACD for main logic
subscription
.BindEx(macd, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var atrArea = CreateChartArea();
if (atrArea != null)
DrawIndicator(atrArea, atr);
var macdArea = CreateChartArea();
if (macdArea != null)
DrawIndicator(macdArea, macd);
}
}
private void OnAtr(ICandleMessage candle, IIndicatorValue atrValue)
{
if (atrValue.IsFormed)
_atrValue = atrValue.ToDecimal();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (macdValue is not MovingAverageConvergenceDivergenceSignalValue macdTyped)
return;
if (macdTyped.Macd is not decimal macdLine || macdTyped.Signal is not decimal signalLine)
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Entry: MACD bullish crossover
if (macdLine > signalLine && Position == 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
// Entry: MACD bearish crossover
else if (macdLine < signalLine && Position == 0)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit on MACD crossover against position
if (Position > 0 && macdLine < signalLine)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && macdLine > signalLine)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
}
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 AverageTrueRange, MovingAverageConvergenceDivergenceSignal
from StockSharp.Algo.Strategies import Strategy
class atr_macd_strategy(Strategy):
"""
ATR + MACD strategy.
Uses ATR for volatility detection and MACD for trend direction.
Enters when MACD confirms trend direction.
"""
def __init__(self):
super(atr_macd_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 100).SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._atr_value = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(atr_macd_strategy, self).OnReseted()
self._atr_value = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(atr_macd_strategy, self).OnStarted2(time)
self._atr_value = 0.0
self._cooldown = 0
atr = AverageTrueRange()
atr.Length = 14
macd = MovingAverageConvergenceDivergenceSignal()
subscription = self.SubscribeCandles(self.candle_type)
# Bind ATR to capture value
subscription.BindEx(atr, self._on_atr)
# Bind MACD for main logic
subscription.BindEx(macd, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
atr_area = self.CreateChartArea()
if atr_area is not None:
self.DrawIndicator(atr_area, atr)
macd_area = self.CreateChartArea()
if macd_area is not None:
self.DrawIndicator(macd_area, macd)
def _on_atr(self, candle, atr_iv):
if atr_iv.IsFormed:
self._atr_value = float(atr_iv.Value)
def _process_candle(self, candle, macd_iv):
if candle.State != CandleStates.Finished:
return
if macd_iv.Macd is None or macd_iv.Signal is None:
return
macd_line = float(macd_iv.Macd)
signal_line = float(macd_iv.Signal)
cd = self._cooldown_bars.Value
if self._cooldown > 0:
self._cooldown -= 1
return
# Entry: MACD bullish crossover
if macd_line > signal_line and self.Position == 0:
self.BuyMarket()
self._cooldown = cd
# Entry: MACD bearish crossover
elif macd_line < signal_line and self.Position == 0:
self.SellMarket()
self._cooldown = cd
# Exit on MACD crossover against position
if self.Position > 0 and macd_line < signal_line:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and macd_line > signal_line:
self.BuyMarket()
self._cooldown = cd
def CreateClone(self):
return atr_macd_strategy()