Die XDPO-Histogramm-Strategie adaptiert den ursprünglichen MQL5-Experten Exp_XDPO_Histogram. Sie erstellt einen doppelt geglätteten detrendierten Preisoszillator (XDPO) aus Schlusskursen. Der Oszillator wird ermittelt, indem ein gleitender Durchschnitt vom Preis subtrahiert und diese Differenz mit einem zweiten gleitenden Durchschnitt geglättet wird. Die Histogrammdynamik liefert Signale für das Öffnen und Schließen von Positionen.
Handelslogik
Wenn der Oszillator nach oben dreht, werden alle Short-Positionen geschlossen. Wenn der aktuelle Oszillatorwert den vorherigen übersteigt, wird eine neue Long-Position eröffnet.
Wenn der Oszillator nach unten dreht, werden alle Long-Positionen geschlossen. Wenn der aktuelle Oszillatorwert unter dem vorherigen liegt, wird eine neue Short-Position eröffnet.
Berechnungen werden nur auf abgeschlossenen Kerzen durchgeführt.
Parameter
FirstMaLength – Länge des ersten gleitenden Durchschnitts, der auf den Preis angewendet wird.
SecondMaLength – Länge des gleitenden Durchschnitts, der auf die Differenz zwischen Preis und erstem MA angewendet wird.
CandleType – Kerzentyp, der für alle Berechnungen verwendet wird.
Hinweise
Gleitende Durchschnitte werden mit SimpleMovingAverage-Indikatoren implementiert.
Die Strategie verwendet Marktorders (BuyMarket und SellMarket) und schließt entgegengesetzte Positionen, bevor neue eröffnet werden.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// XDPO Histogram strategy built on double smoothed detrended price oscillator.
/// </summary>
public class XdpoHistogramStrategy : Strategy
{
private readonly StrategyParam<int> _firstMaLength;
private readonly StrategyParam<int> _secondMaLength;
private readonly StrategyParam<DataType> _candleType;
private decimal _prev1;
private decimal _prev2;
private bool _initialized;
public XdpoHistogramStrategy()
{
_firstMaLength = Param(nameof(FirstMaLength), 12)
.SetDisplay("First MA Length", "Length of the initial moving average.", "Indicators");
_secondMaLength = Param(nameof(SecondMaLength), 5)
.SetDisplay("Second MA Length", "Length of the moving average applied to the difference.", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for strategy calculations.", "General");
}
public int FirstMaLength
{
get => _firstMaLength.Value;
set => _firstMaLength.Value = value;
}
public int SecondMaLength
{
get => _secondMaLength.Value;
set => _secondMaLength.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prev1 = 0m;
_prev2 = 0m;
_initialized = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ma1 = new ExponentialMovingAverage { Length = FirstMaLength };
var ma2 = new ExponentialMovingAverage { Length = SecondMaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ma1, ma2, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal ma1Value, decimal ma2Value)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// DPO = close - MA1, then smooth with MA2
// Since we bind both MAs on close price, approximate: xdpo ~ close - ma1
// But we need the smoothed version. Use difference between the two MAs as oscillator.
var xdpo = ma1Value - ma2Value;
if (!_initialized)
{
_prev1 = xdpo;
_prev2 = xdpo;
_initialized = true;
return;
}
if (_prev1 < _prev2 && xdpo > _prev1 && Position <= 0)
{
BuyMarket();
}
else if (_prev1 > _prev2 && xdpo < _prev1 && Position >= 0)
{
SellMarket();
}
_prev2 = _prev1;
_prev1 = xdpo;
}
}
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
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class xdpo_histogram_strategy(Strategy):
def __init__(self):
super(xdpo_histogram_strategy, self).__init__()
self._first_ma_length = self.Param("FirstMaLength", 12) \
.SetDisplay("First MA Length", "Length of the initial moving average.", "Indicators")
self._second_ma_length = self.Param("SecondMaLength", 5) \
.SetDisplay("Second MA Length", "Length of the moving average applied to the difference.", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles for strategy calculations.", "General")
self._prev1 = 0.0
self._prev2 = 0.0
self._initialized = False
@property
def first_ma_length(self):
return self._first_ma_length.Value
@property
def second_ma_length(self):
return self._second_ma_length.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(xdpo_histogram_strategy, self).OnReseted()
self._prev1 = 0.0
self._prev2 = 0.0
self._initialized = False
def OnStarted2(self, time):
super(xdpo_histogram_strategy, self).OnStarted2(time)
ma1 = ExponentialMovingAverage()
ma1.Length = self.first_ma_length
ma2 = ExponentialMovingAverage()
ma2.Length = self.second_ma_length
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ma1, ma2, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle, ma1_value, ma2_value):
if candle.State != CandleStates.Finished:
return
ma1_value = float(ma1_value)
ma2_value = float(ma2_value)
xdpo = ma1_value - ma2_value
if not self._initialized:
self._prev1 = xdpo
self._prev2 = xdpo
self._initialized = True
return
if self._prev1 < self._prev2 and xdpo > self._prev1 and self.Position <= 0:
self.BuyMarket()
elif self._prev1 > self._prev2 and xdpo < self._prev1 and self.Position >= 0:
self.SellMarket()
self._prev2 = self._prev1
self._prev1 = xdpo
def CreateClone(self):
return xdpo_histogram_strategy()