Adaptive EMA Breakout
The Adaptive EMA Breakout strategy is built around Adaptive EMA breakout with trend confirmation.
Testing indicates an average annual return of about 166%. It performs best in the stocks market.
Signals trigger when its indicators confirms breakout opportunities on intraday (5m) data. This makes the method suitable for active traders.
Stops rely on ATR multiples and factors like Fast, Slow. Adjust these defaults to balance risk and reward.
Details
- Entry Criteria: see implementation for indicator conditions.
- Long/Short: Both directions.
- Exit Criteria: opposite signal or stop logic.
- Stops: Yes, using indicator-based calculations.
- Default Values:
Fast = 2Slow = 30Lookback = 10StopMultiplier = 2mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: multiple indicators
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday (5m)
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Breakout strategy that trades in the direction of a rising or falling adaptive moving average when price extends beyond an ATR buffer.
/// </summary>
public class AdaptiveEmaBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _fast;
private readonly StrategyParam<int> _slow;
private readonly StrategyParam<int> _lookback;
private readonly StrategyParam<decimal> _breakoutAtrMultiplier;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private KaufmanAdaptiveMovingAverage _adaptiveEma;
private AverageTrueRange _atr;
private decimal _previousAdaptiveEmaValue;
private bool _isInitialized;
private int _cooldown;
/// <summary>
/// Fast period for KAMA smoothing.
/// </summary>
public int Fast
{
get => _fast.Value;
set => _fast.Value = value;
}
/// <summary>
/// Slow period for KAMA smoothing.
/// </summary>
public int Slow
{
get => _slow.Value;
set => _slow.Value = value;
}
/// <summary>
/// Main lookback period for KAMA.
/// </summary>
public int Lookback
{
get => _lookback.Value;
set => _lookback.Value = value;
}
/// <summary>
/// Minimum ATR multiple required above or below KAMA for entry.
/// </summary>
public decimal BreakoutAtrMultiplier
{
get => _breakoutAtrMultiplier.Value;
set => _breakoutAtrMultiplier.Value = value;
}
/// <summary>
/// Stop loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Bars to wait after each order.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public AdaptiveEmaBreakoutStrategy()
{
_fast = Param(nameof(Fast), 2)
.SetRange(1, 20)
.SetDisplay("Fast Period", "Fast period for KAMA smoothing", "KAMA");
_slow = Param(nameof(Slow), 30)
.SetRange(5, 100)
.SetDisplay("Slow Period", "Slow period for KAMA smoothing", "KAMA");
_lookback = Param(nameof(Lookback), 10)
.SetRange(2, 100)
.SetDisplay("Lookback", "Main lookback period for KAMA", "KAMA");
_breakoutAtrMultiplier = Param(nameof(BreakoutAtrMultiplier), 0.75m)
.SetRange(0.1m, 5m)
.SetDisplay("Breakout ATR", "ATR multiple required for entry", "Signals");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetRange(0.5m, 10m)
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk");
_cooldownBars = Param(nameof(CooldownBars), 72)
.SetRange(1, 500)
.SetDisplay("Cooldown Bars", "Bars to wait after each order", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for the strategy", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (Security != null)
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_adaptiveEma = null;
_atr = null;
_previousAdaptiveEmaValue = 0m;
_isInitialized = false;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (Security == null)
throw new InvalidOperationException("Security is not specified.");
_adaptiveEma = new KaufmanAdaptiveMovingAverage
{
Length = Lookback,
FastSCPeriod = Fast,
SlowSCPeriod = Slow,
};
_atr = new AverageTrueRange { Length = 14 };
_cooldown = 0;
_isInitialized = false;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_adaptiveEma, _atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _adaptiveEma);
DrawOwnTrades(area);
}
StartProtection(new Unit(0, UnitTypes.Absolute), new Unit(StopLossPercent, UnitTypes.Percent), false);
}
private void ProcessCandle(ICandleMessage candle, decimal adaptiveEmaValue, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_adaptiveEma.IsFormed || !_atr.IsFormed)
return;
if (ProcessState != ProcessStates.Started)
return;
if (!_isInitialized)
{
_previousAdaptiveEmaValue = adaptiveEmaValue;
_isInitialized = true;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_previousAdaptiveEmaValue = adaptiveEmaValue;
return;
}
var isTrendUp = adaptiveEmaValue > _previousAdaptiveEmaValue;
var isTrendDown = adaptiveEmaValue < _previousAdaptiveEmaValue;
var breakoutDistance = candle.ClosePrice - adaptiveEmaValue;
var requiredDistance = atrValue * BreakoutAtrMultiplier;
if (Position == 0)
{
if (isTrendUp && breakoutDistance >= requiredDistance)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (isTrendDown && breakoutDistance <= -requiredDistance)
{
SellMarket();
_cooldown = CooldownBars;
}
}
else if (Position > 0)
{
if (candle.ClosePrice <= adaptiveEmaValue || isTrendDown)
{
SellMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
}
else if (Position < 0)
{
if (candle.ClosePrice >= adaptiveEmaValue || isTrendUp)
{
BuyMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
}
_previousAdaptiveEmaValue = adaptiveEmaValue;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import KaufmanAdaptiveMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class adaptive_ema_breakout_strategy(Strategy):
"""
Breakout strategy that trades in the direction of a rising or falling adaptive
moving average when price extends beyond an ATR buffer.
"""
def __init__(self):
super(adaptive_ema_breakout_strategy, self).__init__()
self._fast = self.Param("Fast", 2) \
.SetDisplay("Fast Period", "Fast period for KAMA smoothing", "KAMA")
self._slow = self.Param("Slow", 30) \
.SetDisplay("Slow Period", "Slow period for KAMA smoothing", "KAMA")
self._lookback = self.Param("Lookback", 10) \
.SetDisplay("Lookback", "Main lookback period for KAMA", "KAMA")
self._breakout_atr_multiplier = self.Param("BreakoutAtrMultiplier", 0.75) \
.SetDisplay("Breakout ATR", "ATR multiple required for entry", "Signals")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
self._cooldown_bars = self.Param("CooldownBars", 72) \
.SetDisplay("Cooldown Bars", "Bars to wait after each order", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles for the strategy", "General")
self._adaptive_ema = None
self._atr = None
self._previous_adaptive_ema_value = 0.0
self._is_initialized = False
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(adaptive_ema_breakout_strategy, self).OnReseted()
self._adaptive_ema = None
self._atr = None
self._previous_adaptive_ema_value = 0.0
self._is_initialized = False
self._cooldown = 0
def OnStarted2(self, time):
super(adaptive_ema_breakout_strategy, self).OnStarted2(time)
self._adaptive_ema = KaufmanAdaptiveMovingAverage()
self._adaptive_ema.Length = int(self._lookback.Value)
self._adaptive_ema.FastSCPeriod = int(self._fast.Value)
self._adaptive_ema.SlowSCPeriod = int(self._slow.Value)
self._atr = AverageTrueRange()
self._atr.Length = 14
self._cooldown = 0
self._is_initialized = False
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._adaptive_ema, self._atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._adaptive_ema)
self.DrawOwnTrades(area)
self.StartProtection(Unit(0, UnitTypes.Absolute), Unit(self._stop_loss_percent.Value, UnitTypes.Percent), False)
def _process_candle(self, candle, adaptive_ema_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not self._adaptive_ema.IsFormed or not self._atr.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
ae = float(adaptive_ema_value)
av = float(atr_value)
if not self._is_initialized:
self._previous_adaptive_ema_value = ae
self._is_initialized = True
return
if self._cooldown > 0:
self._cooldown -= 1
self._previous_adaptive_ema_value = ae
return
is_trend_up = ae > self._previous_adaptive_ema_value
is_trend_down = ae < self._previous_adaptive_ema_value
close_price = float(candle.ClosePrice)
breakout_distance = close_price - ae
bam = float(self._breakout_atr_multiplier.Value)
required_distance = av * bam
cd = int(self._cooldown_bars.Value)
if self.Position == 0:
if is_trend_up and breakout_distance >= required_distance:
self.BuyMarket()
self._cooldown = cd
elif is_trend_down and breakout_distance <= -required_distance:
self.SellMarket()
self._cooldown = cd
elif self.Position > 0:
if close_price <= ae or is_trend_down:
self.SellMarket(Math.Abs(self.Position))
self._cooldown = cd
elif self.Position < 0:
if close_price >= ae or is_trend_up:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown = cd
self._previous_adaptive_ema_value = ae
def CreateClone(self):
return adaptive_ema_breakout_strategy()