Стратегия переносит логику советника «MACDSimple» Юрия Решетова из MetaTrader в инфраструктуру StockSharp. Она работает с одним инструментом и использует классические сигналы MACD, модифицированные двумя параметрами-сдвигами. Анализ выполняется только после закрытия свечи, поэтому решения принимаются по подтверждённым данным без внутрибара шумов.
Индикаторы и расчёты
MACD (Moving Average Convergence Divergence) – главная линия и линия сигнала рассчитываются по формулам:
Период быстрой EMA = SignalPeriod + DF
Период медленной EMA = SignalPeriod + DS + DF
Период сигнальной линии = SignalPeriod
Параметры DF и DS сохранены из оригинального советника и позволяют растягивать или сжимать компоненты MACD, сохраняя их взаимное расположение.
Параметры
Параметр
Описание
Значение по умолчанию
Volume
Объём каждой рыночной сделки.
2
DF
Сдвиг для периода быстрой EMA MACD. Значение не может быть отрицательным.
1
DS
Дополнительный сдвиг для периода медленной EMA MACD. Значение не может быть отрицательным.
2
SignalPeriod
Базовый период, от которого рассчитываются обе EMA и сигнальная линия.
10
CandleType
Таймфрейм свечей, по которым ведётся анализ и торговля.
30 минут
Правила торговли
Управление позицией
На каждой завершённой свече стратегия обновляет MACD и пропускает обработку, если индикатор ещё не сформирован.
При открытой длинной позиции и значении MACD ниже нуля стратегия полностью закрывает лонг рыночной заявкой.
При открытой короткой позиции и значении MACD выше нуля стратегия полностью закрывает шорт рыночной заявкой.
После закрытия позиции на текущей свече дальнейшая обработка этой свечи прекращается – так работает исходный советник.
Условия входа
Сделки рассматриваются только тогда, когда главная линия MACD и сигнальная линия имеют один знак (обе положительные или обе отрицательные).
Если обе линии положительные и главная линия выше сигнальной, открывается длинная позиция.
Если обе линии отрицательные и главная линия ниже сигнальной, открывается короткая позиция.
Все рыночные заявки выставляются объёмом Volume. Одновременно может быть открыта только одна позиция.
Условия выхода
Выход из рынка осуществляется исключительно при пересечении нулевого уровня линией MACD против открытой позиции. Дополнительные стопы или тейк-профиты не применяются.
Дополнительные замечания
Перед входом стратегия проверяет IsFormedAndOnlineAndAllowTrading(), чтобы убедиться в наличии данных и разрешении на торговлю.
Защита капитала по умолчанию отсутствует. При необходимости можно добавить StartProtection() или внешние механизмы риск-менеджмента.
Поскольку периоды MACD вычисляются от одного базового значения с учётом сдвигов, изменение SignalPeriod, DF или DS синхронно изменяет все компоненты, сохраняя пропорции оригинального советника.
Реализация
Используется высокоуровневый API StockSharp: подписка на свечи и привязка индикатора через SubscribeCandles().Bind().
При переносе соблюдены требования AGENTS.md: табуляция, работа с индикатором напрямую в обработчике, использование BuyMarket и SellMarket для сделок.
Код легко расширить дополнительными фильтрами или блоками риск-менеджмента без нарушения исходной логики MetaTrader.
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>
/// MACD-based strategy adapted from Yury Reshetov's MACDSimple expert advisor.
/// </summary>
public class MacdSimpleReshetovStrategy : Strategy
{
private readonly StrategyParam<int> _df;
private readonly StrategyParam<int> _ds;
private readonly StrategyParam<int> _signalPeriod;
private readonly StrategyParam<DataType> _candleType;
private MovingAverageConvergenceDivergenceSignal _macd;
public int Df { get => _df.Value; set => _df.Value = value; }
public int Ds { get => _ds.Value; set => _ds.Value = value; }
public int SignalPeriod { get => _signalPeriod.Value; set => _signalPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public MacdSimpleReshetovStrategy()
{
_df = Param(nameof(Df), 1)
.SetNotNegative()
.SetDisplay("DF", "Offset for the fast EMA", "Indicators");
_ds = Param(nameof(Ds), 2)
.SetNotNegative()
.SetDisplay("DS", "Offset for the slow EMA", "Indicators");
_signalPeriod = Param(nameof(SignalPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Signal Period", "Signal line period", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// The MQL version derives MACD periods from the signal period with DF and DS offsets.
var fastPeriod = SignalPeriod + Df;
var slowPeriod = SignalPeriod + Ds + Df;
_macd = new MovingAverageConvergenceDivergenceSignal
{
Macd = { ShortMa = { Length = fastPeriod }, LongMa = { Length = slowPeriod } },
SignalMa = { Length = SignalPeriod }
};
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var result = _macd.Process(candle);
if (!_macd.IsFormed)
return;
var macdValue = result as MovingAverageConvergenceDivergenceSignalValue;
if (macdValue == null)
return;
var macdLine = macdValue.Macd ?? 0m;
var signalLine = macdValue.Signal ?? 0m;
// Manage existing positions before evaluating new signals.
if (Position > 0)
{
if (macdLine < 0m)
SellMarket(Position);
return;
}
if (Position < 0)
{
if (macdLine > 0m)
BuyMarket(-Position);
return;
}
// Enter only when MACD and signal lines share the same sign.
if (macdLine * signalLine <= 0m)
return;
if (macdLine > 0m && macdLine > signalLine)
{
BuyMarket(Volume);
}
else if (macdLine < 0m && macdLine < signalLine)
{
SellMarket(Volume);
}
}
}
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 MovingAverageConvergenceDivergenceSignal, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class macd_simple_reshetov_strategy(Strategy):
def __init__(self):
super(macd_simple_reshetov_strategy, self).__init__()
self._df = self.Param("Df", 1)
self._ds = self.Param("Ds", 2)
self._signal_period = self.Param("SignalPeriod", 10)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._macd = None
@property
def Df(self):
return self._df.Value
@property
def Ds(self):
return self._ds.Value
@property
def SignalPeriod(self):
return self._signal_period.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(macd_simple_reshetov_strategy, self).OnStarted2(time)
fast_period = self.SignalPeriod + self.Df
slow_period = self.SignalPeriod + self.Ds + self.Df
self._macd = MovingAverageConvergenceDivergenceSignal()
self._macd.Macd.ShortMa.Length = fast_period
self._macd.Macd.LongMa.Length = slow_period
self._macd.SignalMa.Length = self.SignalPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
civ = CandleIndicatorValue(self._macd, candle)
civ.IsFinal = True
result = self._macd.Process(civ)
if not self._macd.IsFormed:
return
try:
macd_line = float(result.Macd) if result.Macd is not None else 0.0
signal_line = float(result.Signal) if result.Signal is not None else 0.0
except:
return
pos = float(self.Position)
if pos > 0:
if macd_line < 0:
self.SellMarket(pos)
return
if pos < 0:
if macd_line > 0:
self.BuyMarket(abs(pos))
return
if macd_line * signal_line <= 0:
return
if macd_line > 0 and macd_line > signal_line:
self.BuyMarket(float(self.Volume))
elif macd_line < 0 and macd_line < signal_line:
self.SellMarket(float(self.Volume))
def OnReseted(self):
super(macd_simple_reshetov_strategy, self).OnReseted()
self._macd = None
def CreateClone(self):
return macd_simple_reshetov_strategy()