Gaussian Detrended 平均回帰戦略
Gaussian Detrended Reversionは、Arnaud Legoux移動平均(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()