Bar Range Strategy
The Bar Range strategy enters long when the current bar's range is among the highest of recent bars and the candle closes below its open. The position is closed after a fixed number of bars.
Details
- Entry Criteria:
- Range = High − Low
- Percent rank of range over
LookbackPeriod≥PercentRankThreshold - Close < Open
- Exit Criteria: Close position after
ExitBarsbars. - Default Values:
LookbackPeriod= 50PercentRankThreshold= 95ExitBars= 1
- Filters:
- Category: Volatility
- Direction: Long
- Indicators: Percent Rank
- Stops: No
- Complexity: Low
- Timeframe: Any
- 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>
/// Bar Range strategy.
/// Long only: buys a bearish candle (close below open) whose high-low range has a percent rank of at least PercentRankThreshold
/// among the previous LookbackPeriod ranges, and closes the long after ExitBars bars.
/// </summary>
public class BarRangeStrategy : Strategy
{
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<decimal> _percentRankThreshold;
private readonly StrategyParam<int> _exitBars;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _ranges = new();
private int _barsInPosition;
/// <summary>
/// Previous bars the range is ranked against.
/// </summary>
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
/// <summary>
/// Minimum percent rank of the range that allows an entry.
/// </summary>
public decimal PercentRankThreshold
{
get => _percentRankThreshold.Value;
set => _percentRankThreshold.Value = value;
}
/// <summary>
/// Bars after which the long is closed.
/// </summary>
public int ExitBars
{
get => _exitBars.Value;
set => _exitBars.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public BarRangeStrategy()
{
_lookbackPeriod = Param(nameof(LookbackPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Lookback Period", "Previous bars the range is ranked against", "Indicators");
_percentRankThreshold = Param(nameof(PercentRankThreshold), 95m)
.SetRange(0m, 100m)
.SetDisplay("Percent Rank Threshold", "Minimum percent rank of the range that allows an entry", "Indicators");
_exitBars = Param(nameof(ExitBars), 1)
.SetGreaterThanZero()
.SetDisplay("Exit Bars", "Bars after which the long is closed", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).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();
_ranges.Clear();
_barsInPosition = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ranges.Clear();
_barsInPosition = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var range = candle.HighPrice - candle.LowPrice;
// Percent rank: share of the previous LookbackPeriod ranges that do not exceed the current one.
decimal? rank = null;
if (_ranges.Count == LookbackPeriod)
rank = 100m * _ranges.Count(r => r <= range) / LookbackPeriod;
_ranges.Enqueue(range);
while (_ranges.Count > LookbackPeriod)
_ranges.Dequeue();
if (Position > 0)
_barsInPosition++;
else
_barsInPosition = 0;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position > 0)
{
if (_barsInPosition >= ExitBars)
SellMarket(Position);
return;
}
if (Position == 0 && rank is decimal r && r >= PercentRankThreshold && candle.ClosePrice < candle.OpenPrice)
BuyMarket(Volume);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from collections import deque
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class bar_range_strategy(Strategy):
"""
Bar Range strategy.
Long only: buys a bearish candle (close below open) whose high-low range has a percent rank of at least PercentRankThreshold
among the previous LookbackPeriod ranges, and closes the long after ExitBars bars.
"""
def __init__(self):
super(bar_range_strategy, self).__init__()
self._lookback_period = self.Param("LookbackPeriod", 50).SetGreaterThanZero().SetDisplay("Lookback Period", "Previous bars the range is ranked against", "Indicators")
self._percent_rank_threshold = self.Param("PercentRankThreshold", 95.0).SetRange(0.0, 100.0).SetDisplay("Percent Rank Threshold", "Minimum percent rank of the range that allows an entry", "Indicators")
self._exit_bars = self.Param("ExitBars", 1).SetGreaterThanZero().SetDisplay("Exit Bars", "Bars after which the long is closed", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._ranges = deque()
self._bars_in_position = 0
def OnReseted(self):
super(bar_range_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(bar_range_strategy, self).OnStarted2(time)
self._reset_state()
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
lookback = self._lookback_period.Value
rng = candle.HighPrice - candle.LowPrice
# Percent rank: share of the previous LookbackPeriod ranges that do not exceed the current one.
rank = None
if len(self._ranges) == lookback:
rank = 100.0 * sum(1 for r in self._ranges if r <= rng) / lookback
self._ranges.append(rng)
while len(self._ranges) > lookback:
self._ranges.popleft()
if self.Position > 0:
self._bars_in_position += 1
else:
self._bars_in_position = 0
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self.Position > 0:
if self._bars_in_position >= self._exit_bars.Value:
self.SellMarket(self.Position)
return
if self.Position == 0 and rank is not None and rank >= float(self._percent_rank_threshold.Value) and candle.ClosePrice < candle.OpenPrice:
self.BuyMarket(self.Volume)
def CreateClone(self):
return bar_range_strategy()