Livermore Seykota Breakout
Breakout system combining Livermore pivot points with Seykota trend filter and ATR-based exits.
Testing indicates an average annual return around 87%. It performs best in the stocks market.
The strategy looks for breakouts above or below the most recent pivot while confirming trend direction with EMA alignment and volume strength. ATR-based stops manage risk.
Details
- Entry Criteria: Price breaks last pivot with trend and volume confirmation.
- Long/Short: Both directions.
- Exit Criteria: ATR stop or trailing stop.
- Stops: ATR-based stop & trailing.
- Default Values:
MainEmaLength= 50FastEmaLength= 20SlowEmaLength= 200PivotLength= 3AtrLength= 14StopAtrMultiplier= 3TrailAtrMultiplier= 2VolumeSmaLength= 20CandleType= TimeSpan.FromMinutes(15)
- Filters:
- Category: Breakout
- Direction: Both
- Indicators: EMA, Volume, ATR, Pivot
- Stops: ATR trailing
- Complexity: Basic
- Timeframe: Intraday (15m)
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Livermore Seykota breakout strategy.
/// Tracks the most recent confirmed pivot high and pivot low (PivotLength bars on each side). A close above the last pivot high goes long
/// when price is above the main EMA, the fast EMA is above the slow EMA and volume is above its average; a close below the last pivot low
/// goes short under the mirrored conditions. Positions exit on an ATR stop placed at entry or on an ATR trailing stop.
/// </summary>
public class LivermoreSeykotaBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _mainEmaLength;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _pivotLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _stopAtrMultiplier;
private readonly StrategyParam<decimal> _trailAtrMultiplier;
private readonly StrategyParam<int> _volumeSmaLength;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _volumeSma;
private readonly List<decimal> _highs = [];
private readonly List<decimal> _lows = [];
private decimal? _pivotHigh;
private decimal? _pivotLow;
private decimal? _stopPrice;
private decimal? _trailPrice;
/// <summary>
/// Main EMA trend filter length.
/// </summary>
public int MainEmaLength
{
get => _mainEmaLength.Value;
set => _mainEmaLength.Value = value;
}
/// <summary>
/// Fast EMA length.
/// </summary>
public int FastEmaLength
{
get => _fastEmaLength.Value;
set => _fastEmaLength.Value = value;
}
/// <summary>
/// Slow EMA length.
/// </summary>
public int SlowEmaLength
{
get => _slowEmaLength.Value;
set => _slowEmaLength.Value = value;
}
/// <summary>
/// Bars on each side that confirm a pivot.
/// </summary>
public int PivotLength
{
get => _pivotLength.Value;
set => _pivotLength.Value = value;
}
/// <summary>
/// ATR period.
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// ATR multiplier of the initial stop.
/// </summary>
public decimal StopAtrMultiplier
{
get => _stopAtrMultiplier.Value;
set => _stopAtrMultiplier.Value = value;
}
/// <summary>
/// ATR multiplier of the trailing stop.
/// </summary>
public decimal TrailAtrMultiplier
{
get => _trailAtrMultiplier.Value;
set => _trailAtrMultiplier.Value = value;
}
/// <summary>
/// Volume SMA length.
/// </summary>
public int VolumeSmaLength
{
get => _volumeSmaLength.Value;
set => _volumeSmaLength.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public LivermoreSeykotaBreakoutStrategy()
{
_mainEmaLength = Param(nameof(MainEmaLength), 50)
.SetGreaterThanZero()
.SetDisplay("Main EMA", "Main EMA trend filter length", "Indicators");
_fastEmaLength = Param(nameof(FastEmaLength), 20)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA length", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 200)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA length", "Indicators");
_pivotLength = Param(nameof(PivotLength), 3)
.SetGreaterThanZero()
.SetDisplay("Pivot Length", "Bars on each side that confirm a pivot", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR period", "Indicators");
_stopAtrMultiplier = Param(nameof(StopAtrMultiplier), 3m)
.SetNotNegative()
.SetDisplay("Stop ATR Mult", "ATR multiplier of the initial stop", "Risk");
_trailAtrMultiplier = Param(nameof(TrailAtrMultiplier), 2m)
.SetNotNegative()
.SetDisplay("Trail ATR Mult", "ATR multiplier of the trailing stop", "Risk");
_volumeSmaLength = Param(nameof(VolumeSmaLength), 20)
.SetGreaterThanZero()
.SetDisplay("Volume SMA", "Volume SMA length", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_highs.Clear();
_lows.Clear();
_pivotHigh = null;
_pivotLow = null;
_stopPrice = null;
_trailPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var mainEma = new ExponentialMovingAverage { Length = MainEmaLength };
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
_volumeSma = new SimpleMovingAverage { Length = VolumeSmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(mainEma, fastEma, slowEma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, mainEma);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal mainEma, decimal fastEma, decimal slowEma, decimal atr)
{
if (candle.State != CandleStates.Finished)
return;
var volumeValue = _volumeSma.Process(new DecimalIndicatorValue(_volumeSma, candle.TotalVolume, candle.OpenTime) { IsFinal = true });
UpdatePivots(candle);
if (Position > 0 && _stopPrice is decimal longStop)
{
if (TrailAtrMultiplier > 0)
_trailPrice = Math.Max(_trailPrice ?? decimal.MinValue, candle.ClosePrice - atr * TrailAtrMultiplier);
var exitLevel = Math.Max(longStop, _trailPrice ?? decimal.MinValue);
if (candle.LowPrice <= exitLevel)
{
SellMarket(Position);
_stopPrice = null;
_trailPrice = null;
return;
}
}
else if (Position < 0 && _stopPrice is decimal shortStop)
{
if (TrailAtrMultiplier > 0)
_trailPrice = Math.Min(_trailPrice ?? decimal.MaxValue, candle.ClosePrice + atr * TrailAtrMultiplier);
var exitLevel = Math.Min(shortStop, _trailPrice ?? decimal.MaxValue);
if (candle.HighPrice >= exitLevel)
{
BuyMarket(-Position);
_stopPrice = null;
_trailPrice = null;
return;
}
}
if (!volumeValue.IsFormed || _pivotHigh is not decimal pivotHigh || _pivotLow is not decimal pivotLow)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var volumeStrong = candle.TotalVolume > volumeValue.GetValue<decimal>();
var upTrend = close > mainEma && fastEma > slowEma;
var downTrend = close < mainEma && fastEma < slowEma;
if (close > pivotHigh && upTrend && volumeStrong && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = StopAtrMultiplier > 0 ? close - atr * StopAtrMultiplier : decimal.MinValue;
_trailPrice = null;
}
else if (close < pivotLow && downTrend && volumeStrong && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = StopAtrMultiplier > 0 ? close + atr * StopAtrMultiplier : decimal.MaxValue;
_trailPrice = null;
}
}
private void UpdatePivots(ICandleMessage candle)
{
var window = PivotLength * 2 + 1;
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
if (_highs.Count > window)
{
_highs.RemoveAt(0);
_lows.RemoveAt(0);
}
if (_highs.Count < window)
return;
// The middle bar is a pivot once PivotLength bars on each side have closed.
var centerHigh = _highs[PivotLength];
var centerLow = _lows[PivotLength];
if (centerHigh == _highs.Max())
_pivotHigh = centerHigh;
if (centerLow == _lows.Min())
_pivotLow = centerLow;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange, SimpleMovingAverage, DecimalIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class livermore_seykota_breakout_strategy(Strategy):
"""
Livermore Seykota breakout strategy.
Tracks the most recent confirmed pivot high and pivot low (PivotLength bars on each side). A close above the last pivot high goes long
when price is above the main EMA, the fast EMA is above the slow EMA and volume is above its average; a close below the last pivot low
goes short under the mirrored conditions. Positions exit on an ATR stop placed at entry or on an ATR trailing stop.
"""
def __init__(self):
super(livermore_seykota_breakout_strategy, self).__init__()
self._main_ema_length = self.Param("MainEmaLength", 50).SetGreaterThanZero().SetDisplay("Main EMA", "Main EMA trend filter length", "Indicators")
self._fast_ema_length = self.Param("FastEmaLength", 20).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast EMA length", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 200).SetGreaterThanZero().SetDisplay("Slow EMA", "Slow EMA length", "Indicators")
self._pivot_length = self.Param("PivotLength", 3).SetGreaterThanZero().SetDisplay("Pivot Length", "Bars on each side that confirm a pivot", "Indicators")
self._atr_length = self.Param("AtrLength", 14).SetGreaterThanZero().SetDisplay("ATR Length", "ATR period", "Indicators")
self._stop_atr_multiplier = self.Param("StopAtrMultiplier", 3.0).SetNotNegative().SetDisplay("Stop ATR Mult", "ATR multiplier of the initial stop", "Risk")
self._trail_atr_multiplier = self.Param("TrailAtrMultiplier", 2.0).SetNotNegative().SetDisplay("Trail ATR Mult", "ATR multiplier of the trailing stop", "Risk")
self._volume_sma_length = self.Param("VolumeSmaLength", 20).SetGreaterThanZero().SetDisplay("Volume SMA", "Volume SMA length", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._volume_sma = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._highs = []
self._lows = []
self._pivot_high = None
self._pivot_low = None
self._stop_price = None
self._trail_price = None
def OnReseted(self):
super(livermore_seykota_breakout_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(livermore_seykota_breakout_strategy, self).OnStarted2(time)
self._reset_state()
main_ema = ExponentialMovingAverage()
main_ema.Length = self._main_ema_length.Value
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self._fast_ema_length.Value
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self._slow_ema_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
self._volume_sma = SimpleMovingAverage()
self._volume_sma.Length = self._volume_sma_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(main_ema, fast_ema, slow_ema, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, main_ema)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, slow_ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, main_ema, fast_ema, slow_ema, atr):
if candle.State != CandleStates.Finished:
return
volume_input = DecimalIndicatorValue(self._volume_sma, candle.TotalVolume, candle.OpenTime)
volume_input.IsFinal = True
volume_value = self._volume_sma.Process(volume_input)
self._update_pivots(candle)
trail_mult = Decimal(self._trail_atr_multiplier.Value)
stop_mult = Decimal(self._stop_atr_multiplier.Value)
if self.Position > 0 and self._stop_price is not None:
if trail_mult > 0:
level = candle.ClosePrice - atr * trail_mult
self._trail_price = level if self._trail_price is None else max(self._trail_price, level)
exit_level = self._stop_price if self._trail_price is None else max(self._stop_price, self._trail_price)
if candle.LowPrice <= exit_level:
self.SellMarket(self.Position)
self._stop_price = None
self._trail_price = None
return
elif self.Position < 0 and self._stop_price is not None:
if trail_mult > 0:
level = candle.ClosePrice + atr * trail_mult
self._trail_price = level if self._trail_price is None else min(self._trail_price, level)
exit_level = self._stop_price if self._trail_price is None else min(self._stop_price, self._trail_price)
if candle.HighPrice >= exit_level:
self.BuyMarket(-self.Position)
self._stop_price = None
self._trail_price = None
return
if not volume_value.IsFormed or self._pivot_high is None or self._pivot_low is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = candle.ClosePrice
volume_strong = candle.TotalVolume > volume_value.GetValue[Decimal](None)
up_trend = close > main_ema and fast_ema > slow_ema
down_trend = close < main_ema and fast_ema < slow_ema
if close > self._pivot_high and up_trend and volume_strong and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - atr * stop_mult if stop_mult > 0 else Decimal.MinValue
self._trail_price = None
elif close < self._pivot_low and down_trend and volume_strong and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = close + atr * stop_mult if stop_mult > 0 else Decimal.MaxValue
self._trail_price = None
def _update_pivots(self, candle):
pivot_length = self._pivot_length.Value
window = pivot_length * 2 + 1
self._highs.append(candle.HighPrice)
self._lows.append(candle.LowPrice)
if len(self._highs) > window:
self._highs.pop(0)
self._lows.pop(0)
if len(self._highs) < window:
return
# The middle bar is a pivot once PivotLength bars on each side have closed.
center_high = self._highs[pivot_length]
center_low = self._lows[pivot_length]
if center_high == max(self._highs):
self._pivot_high = center_high
if center_low == min(self._lows):
self._pivot_low = center_low
def CreateClone(self):
return livermore_seykota_breakout_strategy()