Стратегия XDPO Histogram основана на оригинальном эксперте MQL5 Exp_XDPO_Histogram. Она строит двойной сглаженный детрендовый осциллятор цены (XDPO) по закрытиям. Осциллятор получен вычитанием скользящей средней из цены и последующим сглаживанием разности второй скользящей средней. Изменение гистограммы используется для открытия и закрытия позиций.
Логика торговли
При развороте осциллятора вверх закрываются все короткие позиции. Если текущий уровень осциллятора превышает предыдущий, открывается длинная позиция.
При развороте осциллятора вниз закрываются все длинные позиции. Если текущий уровень осциллятора ниже предыдущего, открывается короткая позиция.
Расчёты выполняются только по завершённым свечам.
Параметры
FirstMaLength – период первой скользящей средней, применяемой к цене.
SecondMaLength – период скользящей средней, применяемой к разности между ценой и первой средней.
CandleType – тип свечей для всех расчётов.
Примечания
В качестве скользящих средних используются индикаторы SimpleMovingAverage.
Стратегия использует рыночные заявки (BuyMarket и SellMarket) и закрывает противоположные позиции перед открытием новых.
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()