This strategy detects quiet market phases where three moving averages converge within a narrow band. When price finally breaks out above or below this range, the strategy enters in the direction of the breakout and aims to capture the emerging trend.
The system observes the spread between the Fast, Mid and Slow SMAs. If the maximum difference between these averages stays below the configured threshold for a specified number of bars, the market is considered "range bound". The highest high and lowest low of that period define the breakout levels.
Trades are opened when price closes beyond these extremes. Positions are protected by reversing conditions: if price falls back into the range or reaches a multiple of the range width in profit, the position is closed.
Details
Entry Criteria:
Long: After a range of RangeLength bars where SMA spread is below ShakeThreshold, enter when price closes above the highest high of the range.
Short: Under the same range conditions, enter when price closes below the lowest low of the range.
Long/Short: Both directions.
Exit Criteria:
Long: Close if price returns below the range low or profit exceeds 4 * (range high - range low).
Short: Close if price returns above the range high or profit exceeds 4 * (range high - range low).
Stops: Implicit exits based on range boundaries and profit multiple.
Default Values:
FastSma = 38
MidSma = 140
SlowSma = 210
ShakeThreshold = 250
RangeLength = 200
CandleType = TimeSpan.FromMinutes(1)
Filters:
Category: Breakout
Direction: Both
Indicators: SMA, Highest, Lowest
Stops: Yes
Complexity: Basic
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>
/// Breakout strategy that waits for converging SMAs and trades on breakout.
/// </summary>
public class BreakTheRangeBoundStrategy : Strategy
{
private readonly StrategyParam<int> _fastSma;
private readonly StrategyParam<int> _slowSma;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _prevClose;
private bool _hasPrev;
public int FastSma { get => _fastSma.Value; set => _fastSma.Value = value; }
public int SlowSma { get => _slowSma.Value; set => _slowSma.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public BreakTheRangeBoundStrategy()
{
_fastSma = Param(nameof(FastSma), 10)
.SetGreaterThanZero()
.SetDisplay("Fast SMA", "Fast moving average period", "Parameters");
_slowSma = Param(nameof(SlowSma), 50)
.SetGreaterThanZero()
.SetDisplay("Slow SMA", "Slow moving average period", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0;
_prevSlow = 0;
_prevClose = 0;
_hasPrev = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fast = new ExponentialMovingAverage { Length = FastSma };
var slow = new ExponentialMovingAverage { Length = SlowSma };
SubscribeCandles(CandleType).Bind(fast, slow, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
if (!_hasPrev)
{
_prevFast = fastValue;
_prevSlow = slowValue;
_prevClose = close;
_hasPrev = true;
return;
}
// Cross above slow SMA => buy breakout
if (_prevClose <= _prevSlow && close > slowValue && fastValue > slowValue && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
// Cross below slow SMA => sell breakout
else if (_prevClose >= _prevSlow && close < slowValue && fastValue < slowValue && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
}
_prevFast = fastValue;
_prevSlow = slowValue;
_prevClose = close;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class break_the_range_bound_strategy(Strategy):
def __init__(self):
super(break_the_range_bound_strategy, self).__init__()
self._fast_sma = self.Param("FastSma", 10) \
.SetDisplay("Fast SMA", "Fast moving average period", "Parameters")
self._slow_sma = self.Param("SlowSma", 50) \
.SetDisplay("Slow SMA", "Slow moving average period", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._prev_close = 0.0
self._has_prev = False
@property
def fast_sma(self):
return self._fast_sma.Value
@property
def slow_sma(self):
return self._slow_sma.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(break_the_range_bound_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._prev_close = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(break_the_range_bound_strategy, self).OnStarted2(time)
fast = ExponentialMovingAverage()
fast.Length = self.fast_sma
slow = ExponentialMovingAverage()
slow.Length = self.slow_sma
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def on_process(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
if not self._has_prev:
self._prev_fast = fast_value
self._prev_slow = slow_value
self._prev_close = close
self._has_prev = True
return
# Cross above slow SMA => buy breakout
if self._prev_close <= self._prev_slow and close > slow_value and fast_value > slow_value and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# Cross below slow SMA => sell breakout
elif self._prev_close >= self._prev_slow and close < slow_value and fast_value < slow_value and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_fast = fast_value
self._prev_slow = slow_value
self._prev_close = close
def CreateClone(self):
return break_the_range_bound_strategy()