Данная стратегия представляет собой порт MetaTrader 4 эксперта AG.mq4 на платформу StockSharp. Алгоритм использует две пары индикаторов MACD с различными параметрами: основная пара формирует торговые сигналы, а вторичная (масштабированная) фильтрует входы и управляет выходами. Обработка ведётся только по закрытым свечам, что полностью повторяет оригинальное поведение советника.
Логика торговли
Индикаторы
Основной MACD: быстрая EMA = FastEmaLength, медленная EMA = SlowEmaLength, сигнальная SMA = SignalSmaLength.
Второй MACD: быстрая EMA = SlowEmaLength * 2, медленная EMA = FastEmaLength * 2, сигнальная SMA = SignalSmaLength * 2.
Условия для покупки
Главная линия основного MACD находится выше сигнальной.
Сигнальная линия основного MACD меньше нуля.
Главная линия вторичного MACD выше сигнальной.
Сигнальная линия вторичного MACD меньше нуля.
Условия для продажи
Главная линия основного MACD ниже сигнальной.
Сигнальная линия основного MACD больше нуля.
Главная линия вторичного MACD ниже сигнальной.
Сигнальная линия вторичного MACD больше нуля.
Выходы
Длинные позиции закрываются, когда вторичный MACD переходит в медвежий режим, а сигнальная линия основного MACD остаётся выше нуля.
Короткие позиции закрываются, когда вторичный MACD становится бычьим, а сигнальная линия основного MACD остаётся ниже нуля.
Стратегия игнорирует незавершённые свечи, чтобы избежать перерисовки.
Управление позицией
Все сделки выполняются рыночными ордерами объёмом OrderVolume.
Параметр MaxOpenOrders соответствует входному ORDER в MQL4 и ограничивает суммарное количество активных ордеров и позиций (значение 0 снимает ограничение).
В методе OnStarted вызывается StartProtection(), чтобы задействовать модуль защиты StockSharp.
Параметры
Имя
Описание
OrderVolume
Базовый объём для новых сделок.
FastEmaLength
Период быстрой EMA основного MACD.
SlowEmaLength
Период медленной EMA основного MACD.
SignalSmaLength
Период сигнальной SMA для обеих пар MACD.
MaxOpenOrders
Максимальное число активных ордеров и открытых позиций, 0 — без ограничений.
CandleType
Тип или таймфрейм свечей, используемых для расчётов.
Примечания
Во второй паре MACD сохранён оригинальный порядок «быстрая/медленная» EMA, даже если быстрая становится длиннее медленной, чтобы поведение совпадало с MQL-версией.
Стратегия не использует отложенные ордера — вход и выход выполняются по рынку при появлении сигнала.
Дополнительные стоп-лоссы и тейк-профиты не устанавливаются, поскольку исходный советник полагался на разворот сигналов.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// AG MACD Dual strategy - MACD histogram crossover with EMA trend filter.
/// Buys when MACD histogram crosses above zero while price is above EMA.
/// Sells when MACD histogram crosses below zero while price is below EMA.
/// </summary>
public class AgMacdDualStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevMacd;
private decimal _prevSignal;
private bool _hasPrev;
private decimal _currentEma;
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public AgMacdDualStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 50)
.SetDisplay("EMA Period", "EMA trend filter", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
protected override void OnReseted() { base.OnReseted(); _prevMacd = 0m; _prevSignal = 0m; _hasPrev = false; _currentEma = 0m; }
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var macd = new MovingAverageConvergenceDivergenceSignal();
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(macd, ProcessMacd)
.Bind(ema, ProcessEma)
.Start();
}
private void ProcessEma(ICandleMessage candle, decimal ema)
{
if (candle.State != CandleStates.Finished)
return;
_currentEma = ema;
}
private void ProcessMacd(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished)
return;
if (!value.IsFinal || value.IsEmpty)
return;
var macdVal = value as MovingAverageConvergenceDivergenceSignalValue;
if (macdVal == null)
return;
var macdLine = macdVal.Macd;
var signalLine = macdVal.Signal;
if (macdLine == null || signalLine == null)
return;
var histogram = macdLine.Value - signalLine.Value;
if (!_hasPrev)
{
_prevMacd = macdLine.Value;
_prevSignal = signalLine.Value;
_hasPrev = true;
return;
}
var prevHist = _prevMacd - _prevSignal;
var close = candle.ClosePrice;
if (prevHist <= 0 && histogram > 0 && close > _currentEma && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (prevHist >= 0 && histogram < 0 && close < _currentEma && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevMacd = macdLine.Value;
_prevSignal = signalLine.Value;
}
}
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, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ag_macd_dual_strategy(Strategy):
def __init__(self):
super(ag_macd_dual_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 50).SetDisplay("EMA Period", "EMA trend filter", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_macd = 0.0; self._prev_signal = 0.0; self._has_prev = False; self._current_ema = 0.0
@property
def ema_period(self): return self._ema_period.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(ag_macd_dual_strategy, self).OnReseted()
self._prev_macd = 0.0; self._prev_signal = 0.0; self._has_prev = False; self._current_ema = 0.0
def OnStarted2(self, time):
super(ag_macd_dual_strategy, self).OnStarted2(time)
self._has_prev = False
macd = MovingAverageConvergenceDivergenceSignal()
ema = ExponentialMovingAverage(); ema.Length = self.ema_period
sub = self.SubscribeCandles(self.candle_type)
sub.BindEx(macd, self.process_macd).Bind(ema, self.process_ema).Start()
def process_ema(self, candle, ema):
if candle.State != CandleStates.Finished: return
self._current_ema = float(ema)
def process_macd(self, candle, value):
if candle.State != CandleStates.Finished: return
if not value.IsFinal or value.IsEmpty: return
if value.Macd is None or value.Signal is None: return
ml = float(value.Macd); sl = float(value.Signal); hist = ml - sl
if not self._has_prev: self._prev_macd = ml; self._prev_signal = sl; self._has_prev = True; return
prev_hist = self._prev_macd - self._prev_signal; close = float(candle.ClosePrice)
if prev_hist <= 0 and hist > 0 and close > self._current_ema and self.Position <= 0:
if self.Position < 0: self.BuyMarket()
self.BuyMarket()
elif prev_hist >= 0 and hist < 0 and close < self._current_ema and self.Position >= 0:
if self.Position > 0: self.SellMarket()
self.SellMarket()
self._prev_macd = ml; self._prev_signal = sl
def CreateClone(self): return ag_macd_dual_strategy()