Стратегия Gaussian Detrended Reversion
Gaussian Detrended Reversion — контртрендовая стратегия, использующая детрендированный ценовой осциллятор, сглаженный средней ALMA. Лонг открывается, когда сглаженный осциллятор пересекает свою запаздывающую линию снизу и находится ниже нуля. Шорт открывается при обратном пересечении выше нуля. Выход осуществляется при противоположном пересечении линий или пересечении нулевой отметки.
Подробности
- Данные: ценовые свечи.
- Условия входа:
- Лонг: ALMA‑сглаженный DPO пересекает линию задержки снизу и находится ниже нуля.
- Шорт: ALMA‑сглаженный DPO пересекает линию задержки сверху и находится выше нуля.
- Условия выхода: противоположное пересечение или пересечение нулевой линии.
- Стопы: нет.
- Параметры по умолчанию:
PriceLength= 52SmoothingLength= 52LagLength= 26
- Фильтры:
- Категория: контртренд
- Направление: лонг и шорт
- Индикаторы: EMA, ALMA
- Сложность: низкая
- Уровень риска: средний
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>
/// Gaussian detrended reversion strategy.
/// The detrended price oscillator is the close minus the PriceLength EMA from PriceLength / 2 + 1 candles ago. It is smoothed with an
/// ALMA of SmoothingLength, and the lag line is that value LagLength candles ago. The smoothed oscillator crossing above the lag line
/// below zero goes long, crossing below it above zero goes short. A long closes on a cross below the lag line or above zero, a short
/// on a cross above the lag line or below zero.
/// </summary>
public class GaussianDetrendedReversionStrategy : Strategy
{
private readonly StrategyParam<int> _priceLength;
private readonly StrategyParam<int> _smoothingLength;
private readonly StrategyParam<int> _lagLength;
private readonly StrategyParam<DataType> _candleType;
private ArnaudLegouxMovingAverage _alma;
private readonly List<decimal> _emaHistory = [];
private readonly List<decimal> _smoothHistory = [];
/// <summary>
/// EMA length used to detrend the price.
/// </summary>
public int PriceLength
{
get => _priceLength.Value;
set => _priceLength.Value = value;
}
/// <summary>
/// ALMA smoothing length.
/// </summary>
public int SmoothingLength
{
get => _smoothingLength.Value;
set => _smoothingLength.Value = value;
}
/// <summary>
/// Candles back for the lag line.
/// </summary>
public int LagLength
{
get => _lagLength.Value;
set => _lagLength.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public GaussianDetrendedReversionStrategy()
{
_priceLength = Param(nameof(PriceLength), 52)
.SetGreaterThanZero()
.SetDisplay("Price Length", "EMA length used to detrend the price", "Indicators");
_smoothingLength = Param(nameof(SmoothingLength), 52)
.SetGreaterThanZero()
.SetDisplay("Smoothing Length", "ALMA smoothing length", "Indicators");
_lagLength = Param(nameof(LagLength), 26)
.SetGreaterThanZero()
.SetDisplay("Lag Length", "Candles back for the lag line", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_emaHistory.Clear();
_smoothHistory.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_emaHistory.Clear();
_smoothHistory.Clear();
var ema = new ExponentialMovingAverage { Length = PriceLength };
_alma = new ArnaudLegouxMovingAverage { Length = SmoothingLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal ema)
{
if (candle.State != CandleStates.Finished)
return;
var barsBack = PriceLength / 2 + 1;
_emaHistory.Add(ema);
if (_emaHistory.Count > barsBack + 1)
_emaHistory.RemoveAt(0);
if (_emaHistory.Count <= barsBack)
return;
var dpo = candle.ClosePrice - _emaHistory[0];
var almaValue = _alma.Process(new DecimalIndicatorValue(_alma, dpo, candle.OpenTime) { IsFinal = true });
if (!_alma.IsFormed)
return;
// The history keeps the current value, the previous one and the lag values for both.
_smoothHistory.Add(almaValue.GetValue<decimal>());
if (_smoothHistory.Count > LagLength + 2)
_smoothHistory.RemoveAt(0);
if (_smoothHistory.Count < LagLength + 2)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var count = _smoothHistory.Count;
var smooth = _smoothHistory[count - 1];
var prevSmooth = _smoothHistory[count - 2];
var lag = _smoothHistory[count - 1 - LagLength];
var prevLag = _smoothHistory[count - 2 - LagLength];
var crossUp = prevSmooth <= prevLag && smooth > lag;
var crossDown = prevSmooth >= prevLag && smooth < lag;
var zeroUp = prevSmooth <= 0 && smooth > 0;
var zeroDown = prevSmooth >= 0 && smooth < 0;
if (crossUp && smooth < 0 && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (crossDown && smooth > 0 && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && (crossDown || zeroUp))
SellMarket(Position);
else if (Position < 0 && (crossUp || zeroDown))
BuyMarket(Math.Abs(Position));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, ArnaudLegouxMovingAverage, DecimalIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class gaussian_detrended_reversion_strategy(Strategy):
"""
Gaussian detrended reversion strategy.
The detrended price oscillator is the close minus the PriceLength EMA from PriceLength / 2 + 1 candles ago. It is smoothed with an
ALMA of SmoothingLength, and the lag line is that value LagLength candles ago. The smoothed oscillator crossing above the lag line
below zero goes long, crossing below it above zero goes short. A long closes on a cross below the lag line or above zero, a short
on a cross above the lag line or below zero.
"""
def __init__(self):
super(gaussian_detrended_reversion_strategy, self).__init__()
self._price_length = self.Param("PriceLength", 52).SetGreaterThanZero().SetDisplay("Price Length", "EMA length used to detrend the price", "Indicators")
self._smoothing_length = self.Param("SmoothingLength", 52).SetGreaterThanZero().SetDisplay("Smoothing Length", "ALMA smoothing length", "Indicators")
self._lag_length = self.Param("LagLength", 26).SetGreaterThanZero().SetDisplay("Lag Length", "Candles back for the lag line", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._alma = None
self._ema_history = []
self._smooth_history = []
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(gaussian_detrended_reversion_strategy, self).OnReseted()
self._ema_history = []
self._smooth_history = []
def OnStarted2(self, time):
super(gaussian_detrended_reversion_strategy, self).OnStarted2(time)
self._ema_history = []
self._smooth_history = []
ema = ExponentialMovingAverage()
ema.Length = self._price_length.Value
self._alma = ArnaudLegouxMovingAverage()
self._alma.Length = self._smoothing_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ema):
if candle.State != CandleStates.Finished:
return
bars_back = self._price_length.Value // 2 + 1
lag_length = self._lag_length.Value
self._ema_history.append(ema)
if len(self._ema_history) > bars_back + 1:
self._ema_history.pop(0)
if len(self._ema_history) <= bars_back:
return
dpo = candle.ClosePrice - self._ema_history[0]
alma_input = DecimalIndicatorValue(self._alma, dpo, candle.OpenTime)
alma_input.IsFinal = True
alma_value = self._alma.Process(alma_input)
if not self._alma.IsFormed:
return
# The history keeps the current value, the previous one and the lag values for both.
self._smooth_history.append(alma_value.GetValue[Decimal](None))
if len(self._smooth_history) > lag_length + 2:
self._smooth_history.pop(0)
if len(self._smooth_history) < lag_length + 2:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
count = len(self._smooth_history)
smooth = self._smooth_history[count - 1]
prev_smooth = self._smooth_history[count - 2]
lag = self._smooth_history[count - 1 - lag_length]
prev_lag = self._smooth_history[count - 2 - lag_length]
cross_up = prev_smooth <= prev_lag and smooth > lag
cross_down = prev_smooth >= prev_lag and smooth < lag
zero_up = prev_smooth <= 0 and smooth > 0
zero_down = prev_smooth >= 0 and smooth < 0
if cross_up and smooth < 0 and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif cross_down and smooth > 0 and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and (cross_down or zero_up):
self.SellMarket(self.Position)
elif self.Position < 0 and (cross_up or zero_down):
self.BuyMarket(abs(self.Position))
def CreateClone(self):
return gaussian_detrended_reversion_strategy()