Стратегия переносит советник MetaTrader 5 Simple EA MA plus MACD на высокоуровневый API StockSharp. Алгоритм ищет пробой "сигнальной свечи", для которой выполняются два условия: смещённая скользящая средняя располагается ниже/выше максимумов свечей, а гистограмма MACD пересекает нулевую линию. Когда следующая свеча закрывается за пределами экстремума сигнальной свечи, происходит вход в направлении пробоя.
Последовательность полностью повторяет логику оригинального советника:
Поиск сигнала. На каждой закрытой свече анализируется предыдущий бар. Настраиваемая скользящая средняя (по умолчанию LWMA) на выбранной цене должна быть ниже максимумов двух последних свечей для покупок (выше – для продаж). Одновременно основная линия MACD должна пересечь нуль между двумя предыдущими барами.
Подтверждение сигнала. После фиксации сигнальной свечи стратегия ждёт закрытия следующего бара. Закрытие выше сохранённого максимума активирует покупку; закрытие ниже минимума – продажу. Если цена вернулась внутрь диапазона сигнальной свечи, сигнал аннулируется.
Сопровождение позиции. Для новых сделок применяются уровни стоп-лосса, тейк-профита и трейлинг-стопа в пунктах. Значения конвертируются в абсолютные цены через PriceStep. Для инструментов с 3 или 5 знаками применяется форекс-коррекция (шаг × 10), что совпадает с расчётами MetaTrader.
Управление рисками
Стоп-лосс / тейк-профит. Настраиваемые расстояния в пунктах проверяются на каждом закрытии свечи. При достижении уровня позиция закрывается рыночным ордером.
Трейлинг-стоп. Как только прибыль превышает TrailingStopPips + TrailingStepPips, трейлинг переносится за достигнутый экстремум. Возврат цены к уровню трейлинга приводит к закрытию сделки. Шаг трейлинга равный нулю обновляет защиту при каждом новом экстремуме.
Переворот. При появлении пробоя в противоположную сторону открывается единый рыночный ордер, который одновременно закрывает текущую позицию и открывает новую в противоположном направлении.
Особенности реализации
Скользящая средняя поддерживает все режимы сглаживания и типы цен MetaTrader (Simple, Exponential, Smoothed, LinearWeighted и Close/Open/High/Low/Median/Typical/Weighted).
Параметр MaShift воспроизводит горизонтальный сдвиг индикатора, считывая значения скользящей средней с предыдущих баров перед проверкой условий.
MACD реализован через встроенный MovingAverageConvergenceDivergence. Для стратегии используется только гистограмма (разница быстрых и медленных EMA); период сигнальной линии сохраняется для совместимости с исходными настройками.
Подписка на свечи и расчёт индикаторов выполнены исключительно средствами высокоуровневого API StockSharp без ручной обработки тиков и буферов.
Параметры
Параметр
Значение по умолчанию
Описание
Volume
1
Объём ордера при каждом входе по пробою.
TakeProfitPips
50
Расстояние до тейк-профита в пунктах (пересчёт в абсолютную цену через PriceStep). 0 отключает цель.
StopLossPips
50
Расстояние до стоп-лосса в пунктах. 0 отключает защиту.
TrailingStopPips
5
Базовая величина трейлинг-стопа в пунктах.
TrailingStepPips
5
Минимальный дополнительный прогресс (в пунктах) перед очередным переносом трейлинг-стопа.
MaPeriod
100
Период скользящей средней, контролирующей сигнальный бар.
MaShift
0
Горизонтальный сдвиг скользящей средней, аналог ma_shift из MetaTrader.
MaMethod
LinearWeighted
Метод расчёта скользящей средней (Simple, Exponential, Smoothed, LinearWeighted).
MaAppliedPrice
Weighted
Тип цены для расчёта скользящей средней (Close, Open, High, Low, Median, Typical, Weighted).
MacdFastPeriod
12
Период быстрой EMA в MACD.
MacdSlowPeriod
26
Период медленной EMA в MACD.
MacdSignalPeriod
9
Период сигнальной линии MACD, сохраняемый для совместимости.
MacdAppliedPrice
Weighted
Тип цены, подаваемой на вход MACD.
CandleType
Таймфрейм 1 час
Основной поток свечей для сигналов и сопровождения.
Рекомендации
Перед запуском проверьте корректность PriceStep и других параметров инструмента в коннекторе: от них зависит пересчёт пунктов в абсолютные цены.
Для волатильных рынков можно увеличить TrailingStepPips, чтобы уменьшить число ранних закрытий, либо уменьшить шаг для более агрессивного сопровождения.
Сделки открываются только на закрытых свечах, поэтому пробой должен удержаться до конца бара. Переход на меньший таймфрейм увеличивает частоту сигналов, но повышает уровень шума.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simple EA MA plus MACD strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class SimpleEaMaPlusMacdStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public SimpleEaMaPlusMacdStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 12).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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
from datatype_extensions import *
from indicator_extensions import *
class simple_ea_ma_plus_macd_strategy(Strategy):
def __init__(self):
super(simple_ea_ma_plus_macd_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 12).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator")
self._sl_points = self.Param("StopLossPoints", 200).SetNotNegative().SetDisplay("Stop Loss", "SL in price steps", "Risk")
self._tp_points = self.Param("TakeProfitPoints", 400).SetNotNegative().SetDisplay("Take Profit", "TP in price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candle type", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(simple_ea_ma_plus_macd_strategy, self).OnReseted()
self._prev_fast = 0
self._prev_slow = 0
self._entry_price = 0
self._cooldown = 0
def OnStarted2(self, time):
super(simple_ea_ma_plus_macd_strategy, self).OnStarted2(time)
self._prev_fast = 0
self._prev_slow = 0
self._entry_price = 0
self._cooldown = 0
self._step = 1.0
if self.Security is not None and self.Security.PriceStep is not None and self.Security.PriceStep > 0:
self._step = float(self.Security.PriceStep)
fast = ExponentialMovingAverage()
fast.Length = self._fast_period.Value
slow = ExponentialMovingAverage()
slow.Length = self._slow_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(fast, slow, self.OnProcess).Start()
def OnProcess(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
if self._prev_fast == 0 or self._prev_slow == 0:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = self._step
# Manage SL/TP
if self.Position > 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close <= self._entry_price - self._sl_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._tp_points.Value > 0 and close >= self._entry_price + self._tp_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close >= self._entry_price + self._sl_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._tp_points.Value > 0 and close <= self._entry_price - self._tp_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
# Crossover signals
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return simple_ea_ma_plus_macd_strategy()