Стратегия MACD Not So Sample — это порт MetaTrader-советника MACD_Not_So_Sample. Исходный робот работал на графике EURUSD
с таймфреймом H4, используя пересечения MACD и сигнальной линии, подтверждённые EMA-трендом, и сопровождал позицию крупным
тейк-профитом и трейлинг-стопом. Версия для StockSharp сохраняет эту идею: отрицательный MACD, который пересекает сигнал снизу
вверх при растущей EMA, открывает длинную позицию; положительный MACD, который пересекает сигнал сверху вниз при падающей EMA,
открывает короткую позицию.
Перенос реализует все элементы управления позицией: установка тейк-профита, пересчёт трейлинг-стопа после достаточного движения
цены и выход по обратному пересечению MACD, когда модуль значения превышает заданные уровни. Все вычисления выполняются на
закрытых свечах H4, что полностью повторяет поведение MetaTrader.
Торговая логика
Подписаться на таймфрейм CandleType (по умолчанию четырёхчасовые свечи) и обрабатывать только свечи в состоянии Finished.
Рассчитать индикатор MovingAverageConvergenceDivergenceSignal с параметрами FastPeriod, SlowPeriod, SignalPeriod, чтобы
получить значения MACD и сигнальной линии.
Построить EMA-тренд с периодом TrendPeriod для фильтрации направлений сделок.
Перевести точечные параметры (MacdOpenLevelPips, MacdCloseLevelPips, TakeProfitPips, TrailingStopPips) в ценовые
расстояния с учётом минимального шага цены инструмента.
При отсутствии позиции:
Открыть лонг, если MACD ниже нуля и пересёк сигнал снизу вверх, предыдущее значение было ниже сигнала, EMA растёт, а
модуль MACD превышает MacdOpenLevelPips.
Открыть шорт, если MACD выше нуля и пересёк сигнал сверху вниз, предыдущее значение было выше сигнала, EMA снижается, а
модуль MACD превышает MacdOpenLevelPips.
При длинной позиции:
Закрыть сделку, когда MACD становится положительным, пересекает сигнал сверху вниз и его величина больше MacdCloseLevelPips.
Выйти досрочно при достижении тейк-профита или при срабатывании трейлинг-стопа.
При короткой позиции:
Закрыть сделку, когда MACD становится отрицательным, пересекает сигнал снизу вверх и его модуль больше MacdCloseLevelPips.
Выйти досрочно при достижении тейк-профита или при срабатывании трейлинг-стопа.
Трейлинг-стоп активируется только после того, как цена пройдёт порог TrailingStopPips, затем уровень сопровождает новые
экстремумы свечей, фиксируя прибыль.
Параметры
Название
Тип
Значение по умолчанию
Описание
FastPeriod
int
47
Период быстрой EMA в расчёте MACD.
SlowPeriod
int
166
Период медленной EMA в расчёте MACD.
SignalPeriod
int
11
Период EMA сигнальной линии MACD.
TrendPeriod
int
8
Период EMA, используемой как трендовый фильтр.
MacdOpenLevelPips
decimal
1
Минимальный модуль MACD (в пунктах) для открытия позиции.
MacdCloseLevelPips
decimal
3
Минимальный модуль MACD (в пунктах) для закрытия позиции.
TakeProfitPips
decimal
550
Расстояние тейк-профита в пунктах.
TrailingStopPips
decimal
19
Шаг трейлинг-стопа в пунктах. Значение 0 отключает трейлинг.
TradeVolume
decimal
1
Объём, используемый при рыночных заявках.
CandleType
DataType
H4
Свечной ряд, который обрабатывает стратегия.
RequiredSecurityCode
string
EURUSD
Ожидаемый тикер инструмента, проверка повторяет поведение MetaTrader.
Отличия от оригинального MetaTrader-советника
В MetaTrader каждая сделка — отдельный ордер с MagicNumber. В StockSharp используется чистая позиция, поэтому стратегия закрывает
текущую позицию и открывает новую без управления множеством ордеров.
В MQL-версии объём зависел от свободной маржи (AccountFreeMargin). В порте используется параметр TradeVolume; управление
рисками следует настраивать во внешних компонентах.
Трейлинг-стоп реализован через анализ экстремумов свечи, а не модификацию ордеров, но момент срабатывания совпадает с
оригинальной логикой.
Индикаторы рассчитываются через высокоуровневые классы StockSharp и подписку на свечи, без прямых вызовов iMACD и iMA.
Рекомендации по использованию
Перед запуском убедитесь, что код инструмента совпадает с RequiredSecurityCode. При несоответствии стратегия немедленно
остановится, чтобы избежать торгов по неверному активу.
Параметр TradeVolume копируется в Strategy.Volume в методе OnStarted, поэтому вспомогательные методы (BuyMarket,
SellMarket) используют заданный объём.
Трейлинг-стоп начинает действовать только после прохождения ценой указанного расстояния; до этого момента закрытие происходит по
MACD или тейк-профиту.
При добавлении стратегии на график выводятся свечи, оба индикатора и сделки, что упрощает визуальную проверку сигналов.
Индикаторы
MovingAverageConvergenceDivergenceSignal (линия MACD и сигнальная линия).
ExponentialMovingAverage (трендовый фильтр).
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// MACD Not So Sample: Dual EMA crossover with RSI confirmation and ATR stops.
/// </summary>
public class MacdNotSoSampleStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public MacdNotSoSampleStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 12)
.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 26)
.SetDisplay("Slow EMA Length", "Slow EMA period.", "Indicators");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastEmaLength { get => _fastEmaLength.Value; set => _fastEmaLength.Value = value; }
public int SlowEmaLength { get => _slowEmaLength.Value; set => _slowEmaLength.Value = value; }
public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fastEma, slowEma, rsi, atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fastEma); DrawIndicator(area, slowEma); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal rsiVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished) return;
if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0) { _prevFast = fastVal; _prevSlow = slowVal; return; }
var close = candle.ClosePrice;
if (Position > 0)
{
if ((fastVal < slowVal && _prevFast >= _prevSlow) || close <= _entryPrice - atrVal * 2m) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if ((fastVal > slowVal && _prevFast <= _prevSlow) || close >= _entryPrice + atrVal * 2m) { BuyMarket(); _entryPrice = 0; }
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow && rsiVal > 50) { _entryPrice = close; BuyMarket(); }
else if (fastVal < slowVal && _prevFast >= _prevSlow && rsiVal < 50) { _entryPrice = close; SellMarket(); }
}
_prevFast = fastVal; _prevSlow = slowVal;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex, AverageTrueRange
class macd_not_so_sample_strategy(Strategy):
def __init__(self):
super(macd_not_so_sample_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._fast_ema_length = self.Param("FastEmaLength", 12) \
.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 26) \
.SetDisplay("Slow EMA Length", "Slow EMA period.", "Indicators")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastEmaLength(self):
return self._fast_ema_length.Value
@property
def SlowEmaLength(self):
return self._slow_ema_length.Value
@property
def RsiLength(self):
return self._rsi_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(macd_not_so_sample_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastEmaLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowEmaLength
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._rsi, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
rv = float(rsi_val)
av = float(atr_val)
if self._prev_fast == 0 or self._prev_slow == 0 or av <= 0:
self._prev_fast = fv
self._prev_slow = sv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if (fv < sv and self._prev_fast >= self._prev_slow) or close <= self._entry_price - av * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if (fv > sv and self._prev_fast <= self._prev_slow) or close >= self._entry_price + av * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if fv > sv and self._prev_fast <= self._prev_slow and rv > 50:
self._entry_price = close
self.BuyMarket()
elif fv < sv and self._prev_fast >= self._prev_slow and rv < 50:
self._entry_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(macd_not_so_sample_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def CreateClone(self):
return macd_not_so_sample_strategy()