Exp HLRSign Strategy
This strategy implements the HLRSign indicator logic in StockSharp. It opens and closes positions when the High-Low Ratio (HLR) crosses predefined levels.
How It Works
- Calculates Donchian Channel values over a configurable range.
- Computes the HLR value as a percentage position of the mid-price within the channel.
- Generates buy or sell signals when the HLR crosses the upper or lower thresholds depending on the selected mode:
- ModeIn – buy on crossing above the upper level and sell on crossing below the lower level.
- ModeOut – sell on crossing below the upper level and buy on crossing above the lower level.
- Allows enabling or disabling opening and closing of long and short positions separately.
Parameters
| Name | Description |
|---|---|
Mode |
Indicator operation mode (ModeIn or ModeOut). |
Range |
Lookback period for highest and lowest prices. |
UpLevel |
Upper threshold in percent for HLR. |
DnLevel |
Lower threshold in percent for HLR. |
CandleType |
Timeframe of candles used for calculations. |
BuyOpen |
Allow opening long positions. |
SellOpen |
Allow opening short positions. |
BuyClose |
Allow closing long positions. |
SellClose |
Allow closing short positions. |
Notes
- The strategy uses the high-level API with
DonchianChannelsindicator. - It processes only finished candles and checks position permissions before trading.
- No stop-loss or take-profit levels are defined; position protection can be added manually.
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>
/// HLRSign based strategy.
/// Opens and closes positions based on HLR level crossings.
/// </summary>
public class ExpHlrSignStrategy : Strategy
{
public enum AlgMethods
{
ModeIn,
ModeOut,
}
private readonly StrategyParam<AlgMethods> _mode;
private readonly StrategyParam<int> _range;
private readonly StrategyParam<decimal> _upLevel;
private readonly StrategyParam<decimal> _dnLevel;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<bool> _buyOpen;
private readonly StrategyParam<bool> _sellOpen;
private readonly StrategyParam<bool> _buyClose;
private readonly StrategyParam<bool> _sellClose;
private decimal _previousHlr;
private bool _isFirst = true;
private int _barsSinceTrade;
/// <summary>
/// Indicator mode.
/// </summary>
public AlgMethods Mode
{
get => _mode.Value;
set => _mode.Value = value;
}
/// <summary>
/// Lookback range for highest and lowest values.
/// </summary>
public int Range
{
get => _range.Value;
set => _range.Value = value;
}
/// <summary>
/// Upper level in percent for HLR crossing.
/// </summary>
public decimal UpLevel
{
get => _upLevel.Value;
set => _upLevel.Value = value;
}
/// <summary>
/// Lower level in percent for HLR crossing.
/// </summary>
public decimal DnLevel
{
get => _dnLevel.Value;
set => _dnLevel.Value = value;
}
/// <summary>
/// Bars to wait after a completed trade.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Type of candles to use.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Allow opening long positions.
/// </summary>
public bool BuyOpen
{
get => _buyOpen.Value;
set => _buyOpen.Value = value;
}
/// <summary>
/// Allow opening short positions.
/// </summary>
public bool SellOpen
{
get => _sellOpen.Value;
set => _sellOpen.Value = value;
}
/// <summary>
/// Allow closing long positions on sell signal.
/// </summary>
public bool BuyClose
{
get => _buyClose.Value;
set => _buyClose.Value = value;
}
/// <summary>
/// Allow closing short positions on buy signal.
/// </summary>
public bool SellClose
{
get => _sellClose.Value;
set => _sellClose.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="ExpHlrSignStrategy"/>.
/// </summary>
public ExpHlrSignStrategy()
{
_mode = Param(nameof(Mode), AlgMethods.ModeIn)
.SetDisplay("Mode", "Indicator operation mode", "General");
_range = Param(nameof(Range), 40)
.SetDisplay("Range", "Lookback period for HLR", "Indicator")
.SetOptimize(20, 80, 10)
;
_upLevel = Param(nameof(UpLevel), 80m)
.SetDisplay("Up Level", "Upper level for HLR", "Indicator")
.SetOptimize(60m, 90m, 5m)
;
_dnLevel = Param(nameof(DnLevel), 20m)
.SetDisplay("Down Level", "Lower level for HLR", "Indicator")
.SetOptimize(10m, 40m, 5m)
;
_cooldownBars = Param(nameof(CooldownBars), 1)
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for analysis", "General");
_buyOpen = Param(nameof(BuyOpen), true)
.SetDisplay("Buy Open", "Allow opening long positions", "Trading");
_sellOpen = Param(nameof(SellOpen), true)
.SetDisplay("Sell Open", "Allow opening short positions", "Trading");
_buyClose = Param(nameof(BuyClose), true)
.SetDisplay("Buy Close", "Allow closing long positions", "Trading");
_sellClose = Param(nameof(SellClose), true)
.SetDisplay("Sell Close", "Allow closing short positions", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousHlr = 0;
_isFirst = true;
_barsSinceTrade = CooldownBars;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var donchian = new DonchianChannels { Length = Range };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(donchian, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, donchian);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_barsSinceTrade < CooldownBars)
_barsSinceTrade++;
var donchian = (DonchianChannelsValue)value;
var upper = donchian.UpperBand;
var lower = donchian.LowerBand;
if (upper == null || lower == null)
return;
var mid = (candle.HighPrice + candle.LowPrice) / 2m;
var range = (decimal)(upper - lower);
var hlr = range != 0m ? 100m * (mid - lower.Value) / range : 0m;
bool buySignal = false;
bool sellSignal = false;
if (_isFirst)
{
_previousHlr = hlr;
_isFirst = false;
return;
}
if (Mode == AlgMethods.ModeIn)
{
if (hlr > UpLevel && _previousHlr <= UpLevel)
buySignal = true;
if (hlr < DnLevel && _previousHlr >= DnLevel)
sellSignal = true;
}
else
{
if (hlr < UpLevel && _previousHlr >= UpLevel)
sellSignal = true;
if (hlr > DnLevel && _previousHlr <= DnLevel)
buySignal = true;
}
if (_barsSinceTrade >= CooldownBars && buySignal)
{
if (BuyOpen && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_barsSinceTrade = 0;
}
}
if (_barsSinceTrade >= CooldownBars && sellSignal)
{
if (SellOpen && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_barsSinceTrade = 0;
}
}
_previousHlr = hlr;
}
}
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, CandleStates
from StockSharp.Algo.Indicators import DonchianChannels
from StockSharp.Algo.Strategies import Strategy
# Mode constants
MODE_IN = 0
MODE_OUT = 1
class exp_hlr_sign_strategy(Strategy):
def __init__(self):
super(exp_hlr_sign_strategy, self).__init__()
self._mode = self.Param("Mode", MODE_IN) \
.SetDisplay("Mode", "Indicator operation mode", "General")
self._range = self.Param("Range", 40) \
.SetDisplay("Range", "Lookback period for HLR", "Indicator")
self._up_level = self.Param("UpLevel", 80.0) \
.SetDisplay("Up Level", "Upper level for HLR", "Indicator")
self._dn_level = self.Param("DnLevel", 20.0) \
.SetDisplay("Down Level", "Lower level for HLR", "Indicator")
self._cooldown_bars = self.Param("CooldownBars", 1) \
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for analysis", "General")
self._buy_open = self.Param("BuyOpen", True) \
.SetDisplay("Buy Open", "Allow opening long positions", "Trading")
self._sell_open = self.Param("SellOpen", True) \
.SetDisplay("Sell Open", "Allow opening short positions", "Trading")
self._buy_close = self.Param("BuyClose", True) \
.SetDisplay("Buy Close", "Allow closing long positions", "Trading")
self._sell_close = self.Param("SellClose", True) \
.SetDisplay("Sell Close", "Allow closing short positions", "Trading")
self._previous_hlr = 0.0
self._is_first = True
self._bars_since_trade = 0
@property
def Mode(self):
return self._mode.Value
@Mode.setter
def Mode(self, value):
self._mode.Value = value
@property
def Range(self):
return self._range.Value
@Range.setter
def Range(self, value):
self._range.Value = value
@property
def UpLevel(self):
return self._up_level.Value
@UpLevel.setter
def UpLevel(self, value):
self._up_level.Value = value
@property
def DnLevel(self):
return self._dn_level.Value
@DnLevel.setter
def DnLevel(self, value):
self._dn_level.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def BuyOpen(self):
return self._buy_open.Value
@BuyOpen.setter
def BuyOpen(self, value):
self._buy_open.Value = value
@property
def SellOpen(self):
return self._sell_open.Value
@SellOpen.setter
def SellOpen(self, value):
self._sell_open.Value = value
@property
def BuyClose(self):
return self._buy_close.Value
@BuyClose.setter
def BuyClose(self, value):
self._buy_close.Value = value
@property
def SellClose(self):
return self._sell_close.Value
@SellClose.setter
def SellClose(self, value):
self._sell_close.Value = value
def OnStarted2(self, time):
super(exp_hlr_sign_strategy, self).OnStarted2(time)
donchian = DonchianChannels()
donchian.Length = self.Range
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.BindEx(donchian, self.ProcessCandle) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, donchian)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, value):
if candle.State != CandleStates.Finished:
return
if self._bars_since_trade < self.CooldownBars:
self._bars_since_trade += 1
upper_raw = value.UpperBand
lower_raw = value.LowerBand
if upper_raw is None or lower_raw is None:
return
upper = float(upper_raw)
lower = float(lower_raw)
mid = (float(candle.HighPrice) + float(candle.LowPrice)) / 2.0
range_val = upper - lower
if range_val != 0.0:
hlr = 100.0 * (mid - lower) / range_val
else:
hlr = 0.0
buy_signal = False
sell_signal = False
if self._is_first:
self._previous_hlr = hlr
self._is_first = False
return
up_level = float(self.UpLevel)
dn_level = float(self.DnLevel)
if self.Mode == MODE_IN:
if hlr > up_level and self._previous_hlr <= up_level:
buy_signal = True
if hlr < dn_level and self._previous_hlr >= dn_level:
sell_signal = True
else:
if hlr < up_level and self._previous_hlr >= up_level:
sell_signal = True
if hlr > dn_level and self._previous_hlr <= dn_level:
buy_signal = True
if self._bars_since_trade >= self.CooldownBars and buy_signal:
if self.BuyOpen and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._bars_since_trade = 0
if self._bars_since_trade >= self.CooldownBars and sell_signal:
if self.SellOpen and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._bars_since_trade = 0
self._previous_hlr = hlr
def OnReseted(self):
super(exp_hlr_sign_strategy, self).OnReseted()
self._previous_hlr = 0.0
self._is_first = True
self._bars_since_trade = self.CooldownBars
def CreateClone(self):
return exp_hlr_sign_strategy()