La estrategia de Histograma XDPO adapta el experto MQL5 original Exp_XDPO_Histogram. Construye un oscilador de precio destendenciado con doble suavizado (XDPO) a partir de precios de cierre. El oscilador se obtiene restando una media móvil al precio y suavizando esta diferencia con una segunda media móvil. La dinámica del histograma proporciona señales para abrir y cerrar operaciones.
Lógica de trading
Cuando el oscilador gira hacia arriba, se cierran todas las posiciones cortas. Si el valor actual del oscilador supera el anterior, se abre una nueva posición larga.
Cuando el oscilador gira hacia abajo, se cierran todas las posiciones largas. Si el valor actual del oscilador está por debajo del anterior, se abre una nueva posición corta.
Los cálculos se realizan solo en velas completadas.
Parámetros
FirstMaLength – longitud de la primera media móvil aplicada al precio.
SecondMaLength – longitud de la media móvil aplicada a la diferencia entre el precio y la primera MA.
CandleType – tipo de vela utilizado para todos los cálculos.
Notas
Las medias móviles se implementan con indicadores SimpleMovingAverage.
La estrategia utiliza órdenes de mercado (BuyMarket y SellMarket) y cierra las posiciones opuestas antes de abrir nuevas.
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()