Gaussian Detrended Reversion 策略
Gaussian Detrended Reversion 是一种均值回归策略,使用经 ALMA 平滑的去趋势价格振荡器。当平滑后的振荡器在零线下方上穿其滞后线时开多;当振荡器在零线上方下穿滞后线时开空。振荡器反向穿越或穿越零线时平仓。
细节
- 数据: 价格K线。
- 入场条件:
- 多头: 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()