Laguerre ADX Strategy
Эта стратегия применяет фильтр Лагерра к компонентам +DI и –DI индикатора Average Directional Index (ADX). Сглаживание уменьшает шум направленного движения и показывает резкие изменения доминирования между покупателями и продавцами. Когда сглаженный +DI пересекает сверху вниз сглаженный –DI, система открывает длинную позицию, ожидая разворот вверх. Когда сглаженный +DI поднимается выше сглаженного –DI, открывается короткая позиция.
Позиции закрываются, когда текущие сглаженные значения показывают, что контроль перешёл к противоположной стороне. Метод построен как контртрендовый подход, использующий краткосрочные экстремумы направленного индекса.
Подробности
- Условия входа:
- Long: Laguerre +DI пересекает сверху вниз Laguerre –DI.
- Short: Laguerre +DI пересекает снизу вверх Laguerre –DI.
- Long/Short: обе стороны.
- Условия выхода:
- Long: Laguerre –DI поднимается выше Laguerre +DI.
- Short: Laguerre +DI опускается ниже Laguerre –DI.
- Стопы: фиксированные стопы отсутствуют, используется базовая защита позиции.
- Значения по умолчанию:
ADX Period= 14.Gamma= 0.764 (коэффициент сглаживания Лагерра).Candle Type= четырёхчасовой таймфрейм.
- Фильтры:
- Категория: Контртренд
- Направление: Обе стороны
- Индикаторы: ADX
- Стопы: Нет
- Сложность: Средняя
- Таймфрейм: Среднесрочный
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Да
- Уровень риска: Средний
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>
/// Strategy that uses Laguerre-filtered DI+ and DI- to trade directional shifts.
/// </summary>
public class LaguerreAdxStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _gamma;
private readonly StrategyParam<decimal> _stopLossPct;
private readonly StrategyParam<decimal> _takeProfitPct;
private readonly StrategyParam<DataType> _candleType;
private AverageDirectionalIndex _adx;
private decimal _prevUp;
private decimal _prevDown;
private decimal _l0Up, _l1Up, _l2Up, _l3Up;
private decimal _l0Down, _l1Down, _l2Down, _l3Down;
private bool _isInitialized;
public int AdxPeriod { get => _adxPeriod.Value; set => _adxPeriod.Value = value; }
public decimal Gamma { get => _gamma.Value; set => _gamma.Value = value; }
public decimal StopLossPct { get => _stopLossPct.Value; set => _stopLossPct.Value = value; }
public decimal TakeProfitPct { get => _takeProfitPct.Value; set => _takeProfitPct.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public LaguerreAdxStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetDisplay("ADX Period", "Period for ADX calculation", "Indicators");
_gamma = Param(nameof(Gamma), 0.764m)
.SetDisplay("Gamma", "Laguerre smoothing factor", "Indicators");
_stopLossPct = Param(nameof(StopLossPct), 2m)
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk");
_takeProfitPct = Param(nameof(TakeProfitPct), 3m)
.SetDisplay("Take Profit %", "Take profit percentage", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_adx = default;
_prevUp = _prevDown = 0;
_l0Up = _l1Up = _l2Up = _l3Up = 0;
_l0Down = _l1Down = _l2Down = _l3Down = 0;
_isInitialized = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_adx = new AverageDirectionalIndex { Length = AdxPeriod };
Indicators.Add(_adx);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(TakeProfitPct, UnitTypes.Percent),
stopLoss: new Unit(StopLossPct, UnitTypes.Percent),
useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var adxResult = _adx.Process(candle);
if (!adxResult.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var adxVal = (AverageDirectionalIndexValue)adxResult;
var plus = adxVal.Dx.Plus ?? 0m;
var minus = adxVal.Dx.Minus ?? 0m;
var up = LaguerreRsi(plus, ref _l0Up, ref _l1Up, ref _l2Up, ref _l3Up);
var down = LaguerreRsi(minus, ref _l0Down, ref _l1Down, ref _l2Down, ref _l3Down);
if (!_isInitialized)
{
_prevUp = up;
_prevDown = down;
_isInitialized = true;
return;
}
// Crossover signals
if (_prevUp <= _prevDown && up > down && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
else if (_prevUp >= _prevDown && up < down && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
}
_prevUp = up;
_prevDown = down;
}
private decimal LaguerreRsi(decimal value, ref decimal l0, ref decimal l1, ref decimal l2, ref decimal l3)
{
var l0Prev = l0;
var l1Prev = l1;
var l2Prev = l2;
l0 = (1m - Gamma) * value + Gamma * l0;
l1 = -Gamma * l0 + l0Prev + Gamma * l1;
l2 = -Gamma * l1 + l1Prev + Gamma * l2;
l3 = -Gamma * l2 + l2Prev + Gamma * l3;
decimal cu = 0, cd = 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;
return cu + cd == 0 ? 0 : cu / (cu + cd);
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import AverageDirectionalIndex, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class laguerre_adx_strategy(Strategy):
def __init__(self):
super(laguerre_adx_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 14) \
.SetDisplay("ADX Period", "Period for ADX calculation", "Indicators")
self._gamma = self.Param("Gamma", 0.764) \
.SetDisplay("Gamma", "Laguerre smoothing factor", "Indicators")
self._stop_loss_pct = self.Param("StopLossPct", 2.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
self._take_profit_pct = self.Param("TakeProfitPct", 3.0) \
.SetDisplay("Take Profit %", "Take profit percentage", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for candles", "General")
self._adx = None
self._prev_up = 0.0
self._prev_down = 0.0
self._l0_up = 0.0
self._l1_up = 0.0
self._l2_up = 0.0
self._l3_up = 0.0
self._l0_down = 0.0
self._l1_down = 0.0
self._l2_down = 0.0
self._l3_down = 0.0
self._is_initialized = False
@property
def adx_period(self):
return self._adx_period.Value
@property
def gamma(self):
return self._gamma.Value
@property
def stop_loss_pct(self):
return self._stop_loss_pct.Value
@property
def take_profit_pct(self):
return self._take_profit_pct.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(laguerre_adx_strategy, self).OnReseted()
self._adx = None
self._prev_up = 0.0
self._prev_down = 0.0
self._l0_up = 0.0
self._l1_up = 0.0
self._l2_up = 0.0
self._l3_up = 0.0
self._l0_down = 0.0
self._l1_down = 0.0
self._l2_down = 0.0
self._l3_down = 0.0
self._is_initialized = False
def OnStarted2(self, time):
super(laguerre_adx_strategy, self).OnStarted2(time)
self._adx = AverageDirectionalIndex()
self._adx.Length = self.adx_period
self.Indicators.Add(self._adx)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.process_candle).Start()
self.StartProtection(
takeProfit=Unit(self.take_profit_pct, UnitTypes.Percent),
stopLoss=Unit(self.stop_loss_pct, UnitTypes.Percent),
useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _laguerre_rsi(self, value, l_state):
g = float(self.gamma)
l0_prev = l_state[0]
l1_prev = l_state[1]
l2_prev = l_state[2]
l_state[0] = (1.0 - g) * value + g * l_state[0]
l_state[1] = -g * l_state[0] + l0_prev + g * l_state[1]
l_state[2] = -g * l_state[1] + l1_prev + g * l_state[2]
l_state[3] = -g * l_state[2] + l2_prev + g * l_state[3]
cu = 0.0
cd = 0.0
if l_state[0] >= l_state[1]:
cu = l_state[0] - l_state[1]
else:
cd = l_state[1] - l_state[0]
if l_state[1] >= l_state[2]:
cu += l_state[1] - l_state[2]
else:
cd += l_state[2] - l_state[1]
if l_state[2] >= l_state[3]:
cu += l_state[2] - l_state[3]
else:
cd += l_state[3] - l_state[2]
return 0.0 if cu + cd == 0 else cu / (cu + cd)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
cv = CandleIndicatorValue(self._adx, candle)
adx_result = self._adx.Process(cv)
if not adx_result.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
plus = adx_result.Dx.Plus
minus = adx_result.Dx.Minus
plus = float(plus) if plus is not None else 0.0
minus = float(minus) if minus is not None else 0.0
up_state = [self._l0_up, self._l1_up, self._l2_up, self._l3_up]
up = self._laguerre_rsi(plus, up_state)
self._l0_up, self._l1_up, self._l2_up, self._l3_up = up_state
down_state = [self._l0_down, self._l1_down, self._l2_down, self._l3_down]
down = self._laguerre_rsi(minus, down_state)
self._l0_down, self._l1_down, self._l2_down, self._l3_down = down_state
if not self._is_initialized:
self._prev_up = up
self._prev_down = down
self._is_initialized = True
return
if self._prev_up <= self._prev_down and up > down and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_up >= self._prev_down and up < down and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_up = up
self._prev_down = down
def CreateClone(self):
return laguerre_adx_strategy()