Volatility Breakout Strategy
The Volatility Breakout strategy seeks strong directional moves when price escapes from its average range. By measuring the distance from a simple moving average using the Average True Range, the algorithm defines breakout thresholds that scale with volatility.
Testing indicates an average annual return of about 97%. It performs best in the crypto market.
A buy order is triggered when the close rises above the SMA by more than Multiplier times the ATR. A sell signal appears when the close falls below the SMA by the same distance. Positions remain open until an opposite breakout occurs or a protective stop is hit.
This technique caters to intraday traders who thrive on momentum surges. Using ATR-based thresholds helps filter out noise so only significant moves generate trades.
Details
- Entry Criteria:
- Long: Close > SMA + Multiplier * ATR
- Short: Close < SMA - Multiplier * ATR
- Long/Short: Both sides.
- Exit Criteria:
- Long: Exit when an opposite breakout triggers or stop-loss hits
- Short: Exit when an opposite breakout triggers or stop-loss hits
- Stops: Yes, stop-loss at
Multiplier * ATRfrom entry. - Default Values:
Period= 20Multiplier= 2.0mCandleType= TimeSpan.FromMinutes(5)
- Filters:
- Category: Breakout
- Direction: Both
- Indicators: SMA, ATR
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday
- 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;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Volatility Breakout strategy. Enters trades when price breaks out from average price with volatility threshold.
/// </summary>
public class VolatilityBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _periodParam;
private readonly StrategyParam<decimal> _multiplierParam;
private readonly StrategyParam<DataType> _candleTypeParam;
private SimpleMovingAverage _sma;
private AverageTrueRange _atr;
private decimal _prevSma;
private decimal _prevAtr;
/// <summary>
/// Period for SMA and ATR calculations.
/// </summary>
public int Period
{
get => _periodParam.Value;
set => _periodParam.Value = value;
}
/// <summary>
/// Volatility multiplier for breakout threshold.
/// </summary>
public decimal Multiplier
{
get => _multiplierParam.Value;
set => _multiplierParam.Value = value;
}
/// <summary>
/// Candle type for strategy.
/// </summary>
public DataType CandleType
{
get => _candleTypeParam.Value;
set => _candleTypeParam.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public VolatilityBreakoutStrategy()
{
_periodParam = Param(nameof(Period), 20)
.SetGreaterThanZero()
.SetDisplay("Period", "Period for SMA and ATR", "Parameters")
.SetOptimize(10, 50, 5);
_multiplierParam = Param(nameof(Multiplier), 2.0m)
.SetRange(0.1m, decimal.MaxValue)
.SetDisplay("Multiplier", "Volatility multiplier for breakout threshold", "Parameters")
.SetOptimize(1.0m, 3.0m, 0.5m);
_candleTypeParam = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle type for strategy", "Common");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_sma = null;
_atr = null;
_prevSma = 0;
_prevAtr = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
_sma = new SMA { Length = Period };
_atr = new AverageTrueRange { Length = Period };
// Create subscription and bind indicators
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_sma, _atr, ProcessCandle)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _sma);
DrawOwnTrades(area);
}
// Enable position protection
StartProtection(
takeProfit: new Unit(0, UnitTypes.Absolute), // No take profit
stopLoss: new Unit(Multiplier, UnitTypes.Absolute) // Stop loss at 2*ATR
);
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Save values for the next candle
var currentSma = smaValue;
var currentAtr = atrValue;
// Skip first candle after indicators become formed
if (_prevSma == 0 || _prevAtr == 0)
{
_prevSma = currentSma;
_prevAtr = currentAtr;
return;
}
// Calculate volatility threshold
var threshold = Multiplier * currentAtr;
// Check for long setup - price breaks above SMA + threshold
if (candle.ClosePrice > currentSma + threshold && Position <= 0)
{
// Close any short position and open long
BuyMarket(Volume + Math.Abs(Position));
}
// Check for short setup - price breaks below SMA - threshold
else if (candle.ClosePrice < currentSma - threshold && Position >= 0)
{
// Close any long position and open short
SellMarket(Volume + Math.Abs(Position));
}
// Update previous values for next candle
_prevSma = currentSma;
_prevAtr = currentAtr;
}
}
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, UnitTypes, Unit, ICandleMessage, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class volatility_breakout_strategy(Strategy):
"""
Volatility Breakout strategy. Enters trades when price breaks out from average price with volatility threshold.
"""
def __init__(self):
super(volatility_breakout_strategy, self).__init__()
# Period for SMA and ATR calculations.
self._period_param = self.Param("Period", 20) \
.SetGreaterThanZero() \
.SetDisplay("Period", "Period for SMA and ATR", "Parameters") \
.SetCanOptimize(True) \
.SetOptimize(10, 50, 5)
# Volatility multiplier for breakout threshold.
self._multiplier_param = self.Param("Multiplier", 2.0) \
.SetRange(0.1, float('inf')) \
.SetDisplay("Multiplier", "Volatility multiplier for breakout threshold", "Parameters") \
.SetCanOptimize(True) \
.SetOptimize(1.0, 3.0, 0.5)
# Candle type for strategy.
self._candle_type_param = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Candle type for strategy", "Common")
# Indicators and state variables
self._sma = None
self._atr = None
self._prev_sma = 0.0
self._prev_atr = 0.0
@property
def Period(self):
"""Period for SMA and ATR calculations."""
return self._period_param.Value
@Period.setter
def Period(self, value):
self._period_param.Value = value
@property
def Multiplier(self):
"""Volatility multiplier for breakout threshold."""
return self._multiplier_param.Value
@Multiplier.setter
def Multiplier(self, value):
self._multiplier_param.Value = value
@property
def CandleType(self):
"""Candle type for strategy."""
return self._candle_type_param.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type_param.Value = value
def GetWorkingSecurities(self):
"""See base class for details."""
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(volatility_breakout_strategy, self).OnReseted()
self._sma = None
self._atr = None
self._prev_sma = 0.0
self._prev_atr = 0.0
def OnStarted2(self, time):
"""Called when the strategy starts."""
super(volatility_breakout_strategy, self).OnStarted2(time)
# Create indicators
self._sma = SimpleMovingAverage()
self._sma.Length = self.Period
self._atr = AverageTrueRange()
self._atr.Length = self.Period
# Create subscription and bind indicators
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._sma, self._atr, self.ProcessCandle).Start()
# Setup chart visualization if available
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._sma)
self.DrawOwnTrades(area)
# Enable position protection
self.StartProtection(
takeProfit=Unit(0, UnitTypes.Absolute),
stopLoss=Unit(self.Multiplier, UnitTypes.Absolute)
)
def ProcessCandle(self, candle, sma_value, atr_value):
"""Process candle with SMA and ATR values."""
# Skip unfinished candles
if candle.State != CandleStates.Finished:
return
# Save values for the next candle
current_sma = sma_value
current_atr = atr_value
# Skip first candle after indicators become formed
if self._prev_sma == 0 or self._prev_atr == 0:
self._prev_sma = current_sma
self._prev_atr = current_atr
return
# Calculate volatility threshold
threshold = self.Multiplier * current_atr
# Check for long setup - price breaks above SMA + threshold
if candle.ClosePrice > current_sma + threshold and self.Position <= 0:
# Close any short position and open long
self.BuyMarket(self.Volume + Math.Abs(self.Position))
# Check for short setup - price breaks below SMA - threshold
elif candle.ClosePrice < current_sma - threshold and self.Position >= 0:
# Close any long position and open short
self.SellMarket(self.Volume + Math.Abs(self.Position))
# Update previous values for next candle
self._prev_sma = current_sma
self._prev_atr = current_atr
def CreateClone(self):
"""!! REQUIRED!! Creates a new instance of the strategy."""
return volatility_breakout_strategy()