Стратегия MACD Signal — это порт советника MetaTrader MACD_signal.mq4 на платформу StockSharp. В исходном коде
сравнивался гистограммный столбик MACD с волатильностным диапазоном на основе ATR: когда столбик пробивал границу
диапазона, открывалась единственная рыночная позиция. C#-версия воспроизводит тот же импульсный подход на высокоуровневом
API StockSharp, хранит предыдущие значения гистограммы и ATR в приватных полях и снабжена подробными англоязычными
комментариями.
В отличие от MetaTrader, где ордера модифицировались напрямую, StockSharp работает с неттинговым учётом. Поэтому
стратегия сначала закрывает текущую позицию перед сменой направления и управляет трейлинг-стопом внутри кода, не
полагаясь на внешние OrderModify.
Логика торговли
Подписаться на свечную серию, указанную параметром CandleType, и обрабатывать только закрытые свечи.
Подать цены закрытия в индикатор MovingAverageConvergenceDivergenceSignal с заданными периодами EMA. Значение
гистограммы (MACD - signal) фиксируется на каждой завершённой свече.
Рассчитать AverageTrueRange на тех же свечах. Значение предыдущей свечи умножается на ThresholdMultiplier, что
соответствует формуле rr = ATR * LEVEL из MQL.
Сформировать бычий сигнал, когда текущая гистограмма выше +threshold, а предыдущая была ниже. Если счёт пуст или
находится в шорте, а свойство Direction разрешает покупки, отправить рыночный ордер объёмом TradeVolume.
Сформировать медвежий сигнал, когда гистограмма уходит ниже -threshold, оставаясь выше на прошлой свече. Если счёт
пуст или в лонге и разрешены продажи, отправить рыночный ордер на TradeVolume.
Управлять открытой позицией на каждом баре:
закрывать лонг при переходе гистограммы в отрицательную зону и шорт — при переходе в положительную;
контролировать фиксированный тейк-профит TakeProfitPoints, сравнивая его с максимумом/минимумом свечи;
активировать трейлинг-стоп после прохождения цены на TrailingStopPoints в прибыльную сторону и закрывать позицию,
если свеча возвращается к пересчитанному уровню. Для лонга используется цена закрытия как аналог бид-цены, для шорта —
как аналог аск-цены.
Если TakeProfitPoints меньше исторического порога в 10 пунктов, стратегия не торгует, повторяя защитную проверку
из MQL.
Управление рисками
Одна позиция за раз. Стратегия всегда выходит в ноль перед открытием нового направления, что копирует условие
OrdersTotal() < 1.
Фиксированный объём. Параметр TradeVolume заменяет mql-переменную Lots и также назначается в Strategy.Volume,
чтобы ручные действия в интерфейсе использовали тот же размер.
Фиксированный тейк-профит.TakeProfitPoints переводит шаги цены в реальные величины через Security.PriceStep.
Выход по индикатору. Смена знака гистограммы приводит к мгновенному рыночному закрытию позиции.
Трейлинг-стоп. После достаточного движения стоп подтягивается внутрь зоны прибыли и никогда не отступает назад.
Параметры
Имя
Тип
Значение по умолчанию
Описание
TradeVolume
decimal
10
Объём каждой рыночной заявки; дополнительно назначается в Strategy.Volume.
TakeProfitPoints
int
10
Расстояние до фиксированного тейк-профита в шагах цены. Значения ниже 10 блокируют
торговлю.
TrailingStopPoints
int
25
Шаги цены для трейлинг-стопа. 0 отключает сопровождение.
FastPeriod
int
9
Период быстрой EMA в MACD.
SlowPeriod
int
15
Период медленной EMA в MACD.
SignalPeriod
int
8
Период сглаживания сигнальной линии MACD.
ThresholdMultiplier
decimal
0.004
Множитель для ATR предыдущей свечи, формирующий границу прорыва.
AtrPeriod
int
200
Количество свечей для расчёта ATR.
CandleType
DataType
таймфрейм 30 минут
Основной используемый временной интервал.
Отличия от оригинального советника
В MetaTrader использовалась проверка AccountFreeMargin(). В StockSharp аналогичной информации нет, поэтому проверка
опущена, а контроль риска рекомендуется вынести на уровень портфеля.
В MQL стоп-заявки обновлялись через OrderModify. В портированной версии управление выходами выполняется внутри
стратегии по данным свечи и внутренним переменным трейлинга.
MQL следил за количеством исторических баров (Bars < 100). В StockSharp индикаторы через BindEx автоматически
ждут накопления истории, поэтому отдельная проверка не нужна.
Предыдущие значения ATR и гистограммы сохраняются в полях, что позволяет повторить сравнение Delta и Delta1, не
нарушая запрета на прямой доступ к индикаторам по индексу.
Рекомендации по использованию
Проверьте корректность Security.PriceStep, Security.MinVolume и Security.VolumeStep, чтобы переводы пунктов и объёма
соответствовали биржевым требованиям.
Увеличивайте ThresholdMultiplier или AtrPeriod, если стратегия слишком активно торгует во флэте; уменьшайте их, чтобы
быстрее реагировать на расширение волатильности.
Для высокорискованных инструментов уменьшайте TradeVolume, поскольку оригинальный советник был рассчитан на крупные
форекс-лоты.
Используйте свойство Direction совместно с фильтрами старшего таймфрейма, если нужно разрешать только длинные или
только короткие сделки в определённых рыночных условиях.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// MACD Signal ATR: EMA crossover with ATR stops.
/// </summary>
public class MacdSignalAtrStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public MacdSignalAtrStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 12)
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 26)
.SetDisplay("Slow EMA", "Slow EMA 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 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 atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fastEma, slowEma, 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 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) { _entryPrice = close; BuyMarket(); }
else if (fastVal < slowVal && _prevFast >= _prevSlow) { _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.Indicators import ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class macd_signal_atr_strategy(Strategy):
"""
MACD Signal ATR: EMA crossover with ATR-based stops.
"""
def __init__(self):
super(macd_signal_atr_strategy, self).__init__()
self._fast_ema_length = self.Param("FastEmaLength", 12).SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 26).SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._atr_length = self.Param("AtrLength", 14).SetDisplay("ATR Length", "ATR period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candles", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(macd_signal_atr_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def OnStarted2(self, time):
super(macd_signal_atr_strategy, self).OnStarted2(time)
fast = ExponentialMovingAverage()
fast.Length = self._fast_ema_length.Value
slow = ExponentialMovingAverage()
slow.Length = self._slow_ema_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_val, slow_val, atr_val):
if candle.State != CandleStates.Finished:
return
fast = float(fast_val)
slow = float(slow_val)
atr = float(atr_val)
if self._prev_fast == 0.0 or self._prev_slow == 0.0 or atr <= 0:
self._prev_fast = fast
self._prev_slow = slow
return
close = float(candle.ClosePrice)
if self.Position > 0:
if (fast < slow and self._prev_fast >= self._prev_slow) or close <= self._entry_price - atr * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if (fast > slow and self._prev_fast <= self._prev_slow) or close >= self._entry_price + atr * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if fast > slow and self._prev_fast <= self._prev_slow:
self._entry_price = close
self.BuyMarket()
elif fast < slow and self._prev_fast >= self._prev_slow:
self._entry_price = close
self.SellMarket()
self._prev_fast = fast
self._prev_slow = slow
def CreateClone(self):
return macd_signal_atr_strategy()