namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Starter V6 Mod E strategy using dual EMA crossover with Laguerre RSI filter.
/// Buy when fast EMA crosses above slow EMA and Laguerre is oversold.
/// Sell when fast EMA crosses below slow EMA and Laguerre is overbought.
/// </summary>
public class StarterV6ModEStrategy : Strategy
{
private readonly StrategyParam<int> _slowEmaPeriod;
private readonly StrategyParam<int> _fastEmaPeriod;
private readonly StrategyParam<decimal> _laguerreGamma;
private readonly StrategyParam<decimal> _laguerreOversold;
private readonly StrategyParam<decimal> _laguerreOverbought;
private readonly StrategyParam<DataType> _candleType;
private decimal _lagL0;
private decimal _lagL1;
private decimal _lagL2;
private decimal _lagL3;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _prevLaguerre;
private bool _hasPrev;
public int SlowEmaPeriod
{
get => _slowEmaPeriod.Value;
set => _slowEmaPeriod.Value = value;
}
public int FastEmaPeriod
{
get => _fastEmaPeriod.Value;
set => _fastEmaPeriod.Value = value;
}
public decimal LaguerreGamma
{
get => _laguerreGamma.Value;
set => _laguerreGamma.Value = value;
}
public decimal LaguerreOversold
{
get => _laguerreOversold.Value;
set => _laguerreOversold.Value = value;
}
public decimal LaguerreOverbought
{
get => _laguerreOverbought.Value;
set => _laguerreOverbought.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public StarterV6ModEStrategy()
{
_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 26)
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
_fastEmaPeriod = Param(nameof(FastEmaPeriod), 12)
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_laguerreGamma = Param(nameof(LaguerreGamma), 0.7m)
.SetDisplay("Laguerre Gamma", "Smoothing factor for Laguerre RSI", "Indicators");
_laguerreOversold = Param(nameof(LaguerreOversold), 0.5m)
.SetDisplay("Laguerre Oversold", "Oversold level (0-1)", "Indicators");
_laguerreOverbought = Param(nameof(LaguerreOverbought), 0.5m)
.SetDisplay("Laguerre Overbought", "Overbought level (0-1)", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_lagL0 = 0m;
_lagL1 = 0m;
_lagL2 = 0m;
_lagL3 = 0m;
_prevFast = 0m;
_prevSlow = 0m;
_prevLaguerre = 0m;
_hasPrev = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
_lagL0 = _lagL1 = _lagL2 = _lagL3 = 0m;
var fastEma = new ExponentialMovingAverage { Length = FastEmaPeriod };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
var laguerre = CalculateLaguerre(candle.ClosePrice);
if (!_hasPrev)
{
_prevFast = fast;
_prevSlow = slow;
_prevLaguerre = laguerre;
_hasPrev = true;
return;
}
// EMA crossover signals
var bullishCross = _prevFast <= _prevSlow && fast > slow;
var bearishCross = _prevFast >= _prevSlow && fast < slow;
// Long: fast EMA crosses above slow + Laguerre was oversold
if (Position <= 0 && bullishCross && _prevLaguerre <= LaguerreOversold)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Short: fast EMA crosses below slow + Laguerre was overbought
else if (Position >= 0 && bearishCross && _prevLaguerre >= LaguerreOverbought)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevFast = fast;
_prevSlow = slow;
_prevLaguerre = laguerre;
}
private decimal CalculateLaguerre(decimal price)
{
var gamma = LaguerreGamma;
var l0Prev = _lagL0;
var l1Prev = _lagL1;
var l2Prev = _lagL2;
var l3Prev = _lagL3;
_lagL0 = (1m - gamma) * price + gamma * l0Prev;
_lagL1 = -gamma * _lagL0 + l0Prev + gamma * l1Prev;
_lagL2 = -gamma * _lagL1 + l1Prev + gamma * l2Prev;
_lagL3 = -gamma * _lagL2 + l2Prev + gamma * l3Prev;
decimal cu = 0m;
decimal cd = 0m;
if (_lagL0 >= _lagL1) cu = _lagL0 - _lagL1; else cd = _lagL1 - _lagL0;
if (_lagL1 >= _lagL2) cu += _lagL1 - _lagL2; else cd += _lagL2 - _lagL1;
if (_lagL2 >= _lagL3) cu += _lagL2 - _lagL3; else cd += _lagL3 - _lagL2;
var denom = cu + cd;
return denom == 0m ? 0m : cu / denom;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class starter_v6_mod_e_strategy(Strategy):
"""Dual EMA crossover strategy with Laguerre RSI filter.
Buy when fast EMA crosses above slow EMA and Laguerre was oversold.
Sell when fast EMA crosses below slow EMA and Laguerre was overbought."""
def __init__(self):
super(starter_v6_mod_e_strategy, self).__init__()
self._slow_ema_period = self.Param("SlowEmaPeriod", 26) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._fast_ema_period = self.Param("FastEmaPeriod", 12) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._laguerre_gamma = self.Param("LaguerreGamma", 0.7) \
.SetDisplay("Laguerre Gamma", "Smoothing factor for Laguerre RSI", "Indicators")
self._laguerre_oversold = self.Param("LaguerreOversold", 0.5) \
.SetDisplay("Laguerre Oversold", "Oversold level (0-1)", "Indicators")
self._laguerre_overbought = self.Param("LaguerreOverbought", 0.5) \
.SetDisplay("Laguerre Overbought", "Overbought level (0-1)", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._lag_l0 = 0.0
self._lag_l1 = 0.0
self._lag_l2 = 0.0
self._lag_l3 = 0.0
self._prev_fast = 0.0
self._prev_slow = 0.0
self._prev_laguerre = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def SlowEmaPeriod(self):
return self._slow_ema_period.Value
@property
def FastEmaPeriod(self):
return self._fast_ema_period.Value
@property
def LaguerreGamma(self):
return self._laguerre_gamma.Value
@property
def LaguerreOversold(self):
return self._laguerre_oversold.Value
@property
def LaguerreOverbought(self):
return self._laguerre_overbought.Value
def OnReseted(self):
super(starter_v6_mod_e_strategy, self).OnReseted()
self._lag_l0 = 0.0
self._lag_l1 = 0.0
self._lag_l2 = 0.0
self._lag_l3 = 0.0
self._prev_fast = 0.0
self._prev_slow = 0.0
self._prev_laguerre = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(starter_v6_mod_e_strategy, self).OnStarted2(time)
self._has_prev = False
self._lag_l0 = 0.0
self._lag_l1 = 0.0
self._lag_l2 = 0.0
self._lag_l3 = 0.0
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.FastEmaPeriod
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.SlowEmaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_ema, slow_ema, self._process_candle).Start()
def _calculate_laguerre(self, price):
gamma = float(self.LaguerreGamma)
l0_prev = self._lag_l0
l1_prev = self._lag_l1
l2_prev = self._lag_l2
l3_prev = self._lag_l3
self._lag_l0 = (1.0 - gamma) * price + gamma * l0_prev
self._lag_l1 = -gamma * self._lag_l0 + l0_prev + gamma * l1_prev
self._lag_l2 = -gamma * self._lag_l1 + l1_prev + gamma * l2_prev
self._lag_l3 = -gamma * self._lag_l2 + l2_prev + gamma * l3_prev
cu = 0.0
cd = 0.0
if self._lag_l0 >= self._lag_l1:
cu = self._lag_l0 - self._lag_l1
else:
cd = self._lag_l1 - self._lag_l0
if self._lag_l1 >= self._lag_l2:
cu += self._lag_l1 - self._lag_l2
else:
cd += self._lag_l2 - self._lag_l1
if self._lag_l2 >= self._lag_l3:
cu += self._lag_l2 - self._lag_l3
else:
cd += self._lag_l3 - self._lag_l2
denom = cu + cd
if denom == 0.0:
return 0.0
return cu / denom
def _process_candle(self, candle, fast, slow):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast)
slow_val = float(slow)
close = float(candle.ClosePrice)
laguerre = self._calculate_laguerre(close)
if not self._has_prev:
self._prev_fast = fast_val
self._prev_slow = slow_val
self._prev_laguerre = laguerre
self._has_prev = True
return
# EMA crossover signals
bullish_cross = self._prev_fast <= self._prev_slow and fast_val > slow_val
bearish_cross = self._prev_fast >= self._prev_slow and fast_val < slow_val
# Long: fast EMA crosses above slow + Laguerre was oversold
if self.Position <= 0 and bullish_cross and self._prev_laguerre <= float(self.LaguerreOversold):
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# Short: fast EMA crosses below slow + Laguerre was overbought
elif self.Position >= 0 and bearish_cross and self._prev_laguerre >= float(self.LaguerreOverbought):
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_fast = fast_val
self._prev_slow = slow_val
self._prev_laguerre = laguerre
def CreateClone(self):
return starter_v6_mod_e_strategy()