Berlin Range Index Strategy
The Berlin Range Index strategy filters the standard Choppiness Index with an ATR based factor to highlight trending and ranging phases. When the filtered index falls below a minimum threshold the strategy opens a position in the direction of the current candle. Positions are closed when the index indicates a ranging or weakening trend.
Details
- Entry Criteria:
- Filtered range index below
ChopMinand candle direction defines long or short.
- Filtered range index below
- Exit Criteria:
- Range index above
ChopMaxor weakening trend.
- Range index above
- Stops: None.
- Default Values:
Length= 9ChopMax= 40ChopMin= 10AtrLength= 14LowLookback= 14UseNormalized= trueStdDevLength= 14
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: Choppiness Index, ATR, Standard Deviation
- Complexity: Medium
- Timeframe: Any
- 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>
/// Berlin Range Index strategy.
/// Uses choppiness index to detect trending vs ranging markets.
/// Enters in trend direction when choppiness is low (strong trend).
/// Exits when choppiness is high (choppy/ranging market).
/// </summary>
public class BerlinRangeIndexStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<decimal> _chopThreshold;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevChop;
public int Length { get => _length.Value; set => _length.Value = value; }
public decimal ChopThreshold { get => _chopThreshold.Value; set => _chopThreshold.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public BerlinRangeIndexStrategy()
{
_length = Param(nameof(Length), 7)
.SetGreaterThanZero()
.SetDisplay("Length", "Choppiness index period", "General")
.SetOptimize(5, 30, 5);
_chopThreshold = Param(nameof(ChopThreshold), 55m)
.SetDisplay("Chop Threshold", "Threshold for trend vs range", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_prevChop = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var choppiness = new ChoppinessIndex { Length = Length };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(choppiness, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, choppiness);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal chopValue)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevChop == 0)
{
_prevChop = chopValue;
return;
}
// Low choppiness = strong trend; enter in candle direction
if (chopValue < ChopThreshold && _prevChop >= ChopThreshold)
{
if (candle.ClosePrice > candle.OpenPrice && Position <= 0)
BuyMarket();
else if (candle.ClosePrice < candle.OpenPrice && Position >= 0)
SellMarket();
}
// High choppiness = choppy market; exit positions
else if (chopValue > ChopThreshold && _prevChop <= ChopThreshold)
{
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
}
_prevChop = chopValue;
}
}
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 ChoppinessIndex
from StockSharp.Algo.Strategies import Strategy
class berlin_range_index_strategy(Strategy):
def __init__(self):
super(berlin_range_index_strategy, self).__init__()
self._length = self.Param("Length", 7) \
.SetGreaterThanZero() \
.SetDisplay("Length", "Choppiness index period", "General")
self._chop_threshold = self.Param("ChopThreshold", 55.0) \
.SetDisplay("Chop Threshold", "Threshold for trend vs range", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_chop = 0.0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(berlin_range_index_strategy, self).OnReseted()
self._prev_chop = 0.0
def OnStarted2(self, time):
super(berlin_range_index_strategy, self).OnStarted2(time)
chop = ChoppinessIndex()
chop.Length = self._length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(chop, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, chop)
self.DrawOwnTrades(area)
def OnProcess(self, candle, chop_val):
if candle.State != CandleStates.Finished:
return
chop_v = float(chop_val)
if self._prev_chop == 0:
self._prev_chop = chop_v
return
threshold = float(self._chop_threshold.Value)
close = float(candle.ClosePrice)
open_p = float(candle.OpenPrice)
if chop_v < threshold and self._prev_chop >= threshold:
if close > open_p and self.Position <= 0:
self.BuyMarket()
elif close < open_p and self.Position >= 0:
self.SellMarket()
elif chop_v > threshold and self._prev_chop <= threshold:
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._prev_chop = chop_v
def CreateClone(self):
return berlin_range_index_strategy()