Color JLaguerre
Strategy based on the color-coded Laguerre oscillator.
The indicator smooths price movement with a Jurik filter and paints its line according to position within predefined levels. A change of color marks a potential trend shift.
The strategy enters long when the oscillator crosses the middle level upward and short when it crosses downward. Positions are closed when the oscillator reaches extreme levels or an opposite signal appears.
Details
- Entry Criteria: Color change of Laguerre oscillator around middle level.
- Long/Short: Both directions.
- Exit Criteria: Opposite signal or reaching extreme level.
- Stops: Yes.
- Default Values:
RsiLength= 14HighLevel= 85MiddleLevel= 50LowLevel= 15StopLossPercent= 2mCandleType= TimeSpan.FromHours(1)
- Filters:
- Category: Oscillator
- Direction: Both
- Indicators: RSI
- Stops: Yes
- Complexity: Basic
- Timeframe: Hourly (1h)
- 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>
/// RSI oscillator smoothed through Jurik MA. Middle-level reversals and
/// extreme-level exits are based on formed indicators and completed candles.
/// </summary>
public class ColorJLaguerreStrategy : Strategy
{
private readonly StrategyParam<int> _rsiLength, _jmaLength;
private readonly StrategyParam<decimal> _highLevel, _middleLevel, _lowLevel, _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private JurikMovingAverage _jma;
private decimal? _previous;
private Order _pending;
public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
public int JmaLength { get => _jmaLength.Value; set => _jmaLength.Value = value; }
public decimal HighLevel { get => _highLevel.Value; set => _highLevel.Value = value; }
public decimal MiddleLevel { get => _middleLevel.Value; set => _middleLevel.Value = value; }
public decimal LowLevel { get => _lowLevel.Value; set => _lowLevel.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ColorJLaguerreStrategy()
{
_rsiLength = Param(nameof(RsiLength), 14).SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI lookback period", "Indicator");
_jmaLength = Param(nameof(JmaLength), 14).SetGreaterThanZero()
.SetDisplay("JMA Length", "Jurik smoothing of RSI", "Indicator");
_highLevel = Param(nameof(HighLevel), 85m)
.SetDisplay("High Level", "Close long at this threshold", "Levels");
_middleLevel = Param(nameof(MiddleLevel), 50m)
.SetDisplay("Middle Level", "Entry crossover threshold", "Levels");
_lowLevel = Param(nameof(LowLevel), 15m)
.SetDisplay("Low Level", "Close short at this threshold", "Levels");
_stopLossPercent = Param(nameof(StopLossPercent), 2m).SetNotNegative()
.SetDisplay("Stop Loss %", "Native percent stop distance", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_jma = null;
_previous = null;
_pending = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_previous = null;
_pending = null;
var rsi = new RelativeStrengthIndex { Length = RsiLength };
_jma = new JurikMovingAverage { Length = JmaLength };
Indicators.Add(_jma);
SubscribeCandles(CandleType)
.Bind(rsi, (candle, value) =>
{
if (candle.State == CandleStates.Finished && rsi.IsFormed)
ProcessCandle(candle, value);
}).Start();
StartProtection(
takeProfit: new Unit(0m, UnitTypes.Percent),
stopLoss: new Unit(StopLossPercent, UnitTypes.Percent),
useMarketOrders: true);
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
var result = _jma.Process(rsiValue, candle.OpenTime, true);
if (!result.IsFormed)
return;
var value = result.ToDecimal();
var crossUp = _previous is decimal prior && prior <= MiddleLevel && value > MiddleLevel;
var crossDown = _previous is decimal before && before >= MiddleLevel && value < MiddleLevel;
_previous = value;
if (!IsFormedAndOnlineAndAllowTrading()
|| (_pending is not null && _pending.State is not (OrderStates.Done or OrderStates.Failed)))
return;
if (Position > 0m && value >= HighLevel)
{
_pending = SellMarket(Position);
return;
}
if (Position < 0m && value <= LowLevel)
{
_pending = BuyMarket(-Position);
return;
}
if (crossUp)
{
if (Position < 0m)
_pending = BuyMarket(-Position + Volume);
else if (Position == 0m)
_pending = BuyMarket(Volume);
}
else if (crossDown)
{
if (Position > 0m)
_pending = SellMarket(Position + Volume);
else if (Position == 0m)
_pending = SellMarket(Volume);
}
}
}
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, OrderStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import RelativeStrengthIndex, JurikMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import process_float
class color_j_laguerre_strategy(Strategy):
"""RSI-JMA oscillator crosses middle and exits at extreme levels on finished candles."""
def __init__(self):
super(color_j_laguerre_strategy, self).__init__()
self._rsi_length = self.Param("RsiLength", 14).SetGreaterThanZero() \
.SetDisplay("RSI Length", "RSI lookback", "Indicator")
self._jma_length = self.Param("JmaLength", 14).SetGreaterThanZero() \
.SetDisplay("JMA Length", "Jurik smoothing of RSI", "Indicator")
self._high_level = self.Param("HighLevel", 85.0) \
.SetDisplay("High Level", "Close long at upper threshold", "Levels")
self._middle_level = self.Param("MiddleLevel", 50.0) \
.SetDisplay("Middle Level", "Middle crossover threshold", "Levels")
self._low_level = self.Param("LowLevel", 15.0) \
.SetDisplay("Low Level", "Close short at lower threshold", "Levels")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative() \
.SetDisplay("Stop Loss %", "Native percent stop", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._rsi = None
self._jma = None
self._previous = None
self._pending = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(color_j_laguerre_strategy, self).OnReseted()
self._rsi = self._jma = self._previous = self._pending = None
def OnStarted2(self, time):
super(color_j_laguerre_strategy, self).OnStarted2(time)
self._previous = self._pending = None
self._rsi = RelativeStrengthIndex()
self._rsi.Length = int(self._rsi_length.Value)
self._jma = JurikMovingAverage()
self._jma.Length = int(self._jma_length.Value)
self.Indicators.Add(self._jma)
self.SubscribeCandles(self.CandleType).Bind(self._rsi, self.ProcessCandle).Start()
self.StartProtection(
takeProfit=Unit(0.0, UnitTypes.Percent),
stopLoss=Unit(self._stop_loss_percent.Value, UnitTypes.Percent),
useMarketOrders=True)
def ProcessCandle(self, candle, rsi_value):
if candle.State != CandleStates.Finished or not self._rsi.IsFormed:
return
smooth = process_float(self._jma, rsi_value, candle.OpenTime, True)
if not smooth.IsFormed:
return
value = float(smooth)
middle = float(self._middle_level.Value)
up = self._previous is not None and self._previous <= middle and value > middle
down = self._previous is not None and self._previous >= middle and value < middle
self._previous = value
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._pending is not None and self._pending.State not in (OrderStates.Done, OrderStates.Failed):
return
if self.Position > 0 and value >= float(self._high_level.Value):
self._pending = self.SellMarket(self.Position)
return
if self.Position < 0 and value <= float(self._low_level.Value):
self._pending = self.BuyMarket(-self.Position)
return
if up:
if self.Position < 0:
self._pending = self.BuyMarket(-self.Position + self.Volume)
elif self.Position == 0:
self._pending = self.BuyMarket(self.Volume)
elif down:
if self.Position > 0:
self._pending = self.SellMarket(self.Position + self.Volume)
elif self.Position == 0:
self._pending = self.SellMarket(self.Volume)
def CreateClone(self):
return color_j_laguerre_strategy()