Laguerre ROC Strategy
This strategy uses the Laguerre rate-of-change oscillator to capture trend reversals.
The Laguerre ROC oscillator smooths rate of change through a four-stage Laguerre filter.
Values are normalized between 0 and 1. Two thresholds define overbought and oversold zones:
- Up Level – values above this level indicate strong upward momentum.
- Down Level – values below this level indicate strong downward momentum.
Trading logic:
- When the oscillator falls from the overbought zone (previous value above Up Level
and current value below) the strategy enters a long position.
- When the oscillator rises from the oversold zone (previous value below Down Level
and current value above) the strategy enters a short position.
- If a long position is open and the oscillator turns bearish (previous value below the neutral
level of 0.5) the position is closed.
- If a short position is open and the oscillator turns bullish (previous value above 0.5)
the position is closed.
Parameters:
- Period – lookback length for the rate-of-change calculation.
- Gamma – smoothing factor for the Laguerre filter.
- Up Level – overbought threshold.
- Down Level – oversold threshold.
- Candle Type – timeframe used for candle data.
The example demonstrates how custom indicator logic can be recreated within a high-level
StockSharp strategy using built-in rate-of-change and manual Laguerre filtering.
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>
/// Strategy based on the Laguerre rate of change oscillator.
/// </summary>
public class LaguerreRocStrategy : Strategy
{
private readonly StrategyParam<int> _period;
private readonly StrategyParam<decimal> _gamma;
private readonly StrategyParam<decimal> _upLevel;
private readonly StrategyParam<decimal> _downLevel;
private readonly StrategyParam<DataType> _candleType;
private RateOfChange _roc;
private decimal _l0, _l1, _l2, _l3;
private bool _isFirst = true;
private int _prevColor = 2;
public int Period { get => _period.Value; set => _period.Value = value; }
public decimal Gamma { get => _gamma.Value; set => _gamma.Value = value; }
public decimal UpLevel { get => _upLevel.Value; set => _upLevel.Value = value; }
public decimal DownLevel { get => _downLevel.Value; set => _downLevel.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public LaguerreRocStrategy()
{
_period = Param(nameof(Period), 5)
.SetGreaterThanZero()
.SetDisplay("Period", "Rate of change lookback", "Indicators");
_gamma = Param(nameof(Gamma), 0.5m)
.SetDisplay("Gamma", "Laguerre smoothing factor", "Indicators");
_upLevel = Param(nameof(UpLevel), 0.75m)
.SetDisplay("Up Level", "Overbought threshold", "Indicators");
_downLevel = Param(nameof(DownLevel), 0.25m)
.SetDisplay("Down Level", "Oversold threshold", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_isFirst = true;
_prevColor = 2;
_l0 = _l1 = _l2 = _l3 = 0m;
_roc = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_roc = new RateOfChange { Length = Period };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_roc, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _roc);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rocValue)
{
if (candle.State != CandleStates.Finished)
return;
decimal l0, l1, l2, l3;
if (_isFirst)
{
l0 = l1 = l2 = l3 = rocValue;
_isFirst = false;
}
else
{
l0 = (1 - Gamma) * rocValue + Gamma * _l0;
l1 = -Gamma * l0 + _l0 + Gamma * _l1;
l2 = -Gamma * l1 + _l1 + Gamma * _l2;
l3 = -Gamma * l2 + _l2 + Gamma * _l3;
}
var cu = 0m;
var cd = 0m;
if (l0 >= l1) cu += l0 - l1; else cd += l1 - l0;
if (l1 >= l2) cu += l1 - l2; else cd += l2 - l1;
if (l2 >= l3) cu += l2 - l3; else cd += l3 - l2;
var denom = cu + cd;
var lroc = denom != 0m ? cu / denom : 0m;
int color = 2;
if (lroc > UpLevel) color = 4;
else if (lroc > 0.5m) color = 3;
if (lroc < DownLevel) color = 0;
else if (lroc < 0.5m) color = 1;
if (_prevColor > 2 && color <= 2 && Position < 0)
BuyMarket();
if (_prevColor < 2 && color >= 2 && Position > 0)
SellMarket();
if (_prevColor == 4 && color < 4 && Position <= 0)
BuyMarket();
if (_prevColor == 0 && color > 0 && Position >= 0)
SellMarket();
_prevColor = color;
_l0 = l0; _l1 = l1; _l2 = l2; _l3 = l3;
}
}
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 RateOfChange
from StockSharp.Algo.Strategies import Strategy
class laguerre_roc_strategy(Strategy):
def __init__(self):
super(laguerre_roc_strategy, self).__init__()
self._period = self.Param("Period", 5) \
.SetDisplay("Period", "Rate of change lookback", "Indicators")
self._gamma = self.Param("Gamma", 0.5) \
.SetDisplay("Gamma", "Laguerre smoothing factor", "Indicators")
self._up_level = self.Param("UpLevel", 0.75) \
.SetDisplay("Up Level", "Overbought threshold", "Indicators")
self._down_level = self.Param("DownLevel", 0.25) \
.SetDisplay("Down Level", "Oversold threshold", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._roc = None
self._l0 = 0.0
self._l1 = 0.0
self._l2 = 0.0
self._l3 = 0.0
self._is_first = True
self._prev_color = 2
@property
def period(self):
return self._period.Value
@property
def gamma(self):
return self._gamma.Value
@property
def up_level(self):
return self._up_level.Value
@property
def down_level(self):
return self._down_level.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(laguerre_roc_strategy, self).OnReseted()
self._is_first = True
self._prev_color = 2
self._l0 = self._l1 = self._l2 = self._l3 = 0.0
self._roc = None
def OnStarted2(self, time):
super(laguerre_roc_strategy, self).OnStarted2(time)
self._roc = RateOfChange()
self._roc.Length = self.period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._roc, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._roc)
self.DrawOwnTrades(area)
def process_candle(self, candle, roc_value):
if candle.State != CandleStates.Finished:
return
roc_value = float(roc_value)
g = float(self.gamma)
if self._is_first:
l0 = l1 = l2 = l3 = roc_value
self._is_first = False
else:
l0 = (1.0 - g) * roc_value + g * self._l0
l1 = -g * l0 + self._l0 + g * self._l1
l2 = -g * l1 + self._l1 + g * self._l2
l3 = -g * l2 + self._l2 + g * self._l3
cu = 0.0
cd = 0.0
if l0 >= l1:
cu += l0 - l1
else:
cd += l1 - l0
if l1 >= l2:
cu += l1 - l2
else:
cd += l2 - l1
if l2 >= l3:
cu += l2 - l3
else:
cd += l3 - l2
denom = cu + cd
lroc = cu / denom if denom != 0 else 0.0
up = float(self.up_level)
down = float(self.down_level)
color = 2
if lroc > up:
color = 4
elif lroc > 0.5:
color = 3
if lroc < down:
color = 0
elif lroc < 0.5:
color = 1
if self._prev_color > 2 and color <= 2 and self.Position < 0:
self.BuyMarket()
if self._prev_color < 2 and color >= 2 and self.Position > 0:
self.SellMarket()
if self._prev_color == 4 and color < 4 and self.Position <= 0:
self.BuyMarket()
if self._prev_color == 0 and color > 0 and self.Position >= 0:
self.SellMarket()
self._prev_color = color
self._l0 = l0
self._l1 = l1
self._l2 = l2
self._l3 = l3
def CreateClone(self):
return laguerre_roc_strategy()