Стратегия представляет собой порт MetaTrader-советника EA_MACD_FixedPSAR на C#. Торговая идея основана на пересечениях MACD с фильтрацией по EMA, что позволяет искать развороты тренда. Механизмы управления рисками повторяют оригинал и включают фиксированный трейлинг-стоп и вариант с наращиваемым Parabolic SAR. Все расстояния задаются в пунктах и автоматически переводятся в денежные значения с учётом шага цены инструмента.
Индикаторы
MovingAverageConvergenceDivergenceSignal с периодами 12/26/9 для расчёта линий MACD и сигнальной линии.
ExponentialMovingAverage (по умолчанию 26) выступает в роли трендового фильтра.
Логика торговли
Входы
Покупка: MACD пересекает сигнальную линию снизу вверх, оставаясь ниже нуля; абсолютное значение MACD превышает порог MACD Open Level; EMA растёт относительно предыдущей свечи.
Продажа: MACD пересекает сигнальную линию сверху вниз, оставаясь выше нуля; абсолютное значение MACD превышает порог MACD Open Level; EMA снижается относительно предыдущей свечи.
Выходы
Обратное пересечение MACD с превышением порога MACD Close Level.
Срабатывание заданных уровней тейк-профита и стоп-лосса.
Дополнительный трейлинг-стоп:
Fixed — удерживает постоянную дистанцию от последнего закрытия.
Fixed PSAR — повторяет ступенчатое обновление Parabolic SAR из MQL-версии.
Параметры
Имя
Описание
Volume
Объём сделок для рыночных ордеров.
TakeProfitPips
Размер тейк-профита в пунктах.
StopLossPips
Размер стоп-лосса в пунктах.
TrailMode
Режим трейлинг-стопа (None, Fixed, FixedPsar).
TrailingStopPips
Дистанция для фиксированного трейлинга.
PsarStep
Начальный коэффициент ускорения для PSAR.
PsarMaximum
Максимальный коэффициент ускорения для PSAR.
MacdOpenLevelPips
Минимальная величина MACD (в пунктах) для открытия позиции.
MacdCloseLevelPips
Минимальная величина MACD (в пунктах) для закрытия позиции.
TrendPeriod
Период EMA, применяемой как трендовый фильтр.
CandleType
Тип свечей, используемый при расчётах.
Дополнительно
Перевод пунктов в цену учитывает шаг цены инструмента и специальную поправку для трёх- и пятизначных символов, как в MetaTrader.
Обновление трейлинг-стопов происходит только по завершённым свечам, что снижает влияние рыночного шума.
При наличии области графика стратегия отображает свечи, оба индикатора и метки сделок.
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>
/// Simplified from "EA_MACD_FixedPSAR" MetaTrader expert.
/// Combines MACD histogram crossover with EMA trend filter.
/// Buys when MACD histogram goes positive and price above EMA.
/// Sells when MACD histogram goes negative and price below EMA.
/// </summary>
public class MacdFixedPsarStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _signalPeriod;
private readonly StrategyParam<int> _trendPeriod;
private MovingAverageConvergenceDivergence _macd;
private ExponentialMovingAverage _trendEma;
private readonly Queue<decimal> _macdHistory = new();
private decimal? _prevHistogram;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
public int SignalPeriod
{
get => _signalPeriod.Value;
set => _signalPeriod.Value = value;
}
public int TrendPeriod
{
get => _trendPeriod.Value;
set => _trendPeriod.Value = value;
}
public MacdFixedPsarStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for MACD calculations", "General");
_fastPeriod = Param(nameof(FastPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period for MACD", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period for MACD", "Indicators");
_signalPeriod = Param(nameof(SignalPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("Signal Period", "Signal line smoothing period", "Indicators");
_trendPeriod = Param(nameof(TrendPeriod), 60)
.SetGreaterThanZero()
.SetDisplay("Trend EMA", "Trend filter EMA period (computed as SMA)", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHistogram = null;
_macdHistory.Clear();
_macd = new MovingAverageConvergenceDivergence
{
ShortMa = { Length = FastPeriod },
LongMa = { Length = SlowPeriod },
};
_trendEma = new ExponentialMovingAverage { Length = TrendPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_macd, _trendEma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _macd);
DrawIndicator(area, _trendEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal macdValue, decimal trendValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_macd.IsFormed || !_trendEma.IsFormed)
return;
var close = candle.ClosePrice;
// Compute signal line manually
_macdHistory.Enqueue(macdValue);
if (_macdHistory.Count > SignalPeriod)
_macdHistory.Dequeue();
if (_macdHistory.Count < SignalPeriod)
return;
decimal signalSum = 0;
var history = _macdHistory.ToArray();
foreach (var v in history)
signalSum += v;
var signal = signalSum / history.Length;
var histogram = macdValue - signal;
if (_prevHistogram is null)
{
_prevHistogram = histogram;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
var crossUp = _prevHistogram.Value <= 0 && histogram > 0;
var crossDown = _prevHistogram.Value >= 0 && histogram < 0;
if (crossUp && close > trendValue)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
else if (crossDown && close < trendValue)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
_prevHistogram = histogram;
}
/// <inheritdoc />
protected override void OnReseted()
{
_macd = null;
_trendEma = null;
_prevHistogram = null;
_macdHistory.Clear();
base.OnReseted();
}
}
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 MovingAverageConvergenceDivergence, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class macd_fixed_psar_strategy(Strategy):
def __init__(self):
super(macd_fixed_psar_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._fast_period = self.Param("FastPeriod", 20)
self._slow_period = self.Param("SlowPeriod", 50)
self._signal_period = self.Param("SignalPeriod", 12)
self._trend_period = self.Param("TrendPeriod", 60)
self._macd_history = []
self._prev_histogram = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def FastPeriod(self):
return self._fast_period.Value
@FastPeriod.setter
def FastPeriod(self, value):
self._fast_period.Value = value
@property
def SlowPeriod(self):
return self._slow_period.Value
@SlowPeriod.setter
def SlowPeriod(self, value):
self._slow_period.Value = value
@property
def SignalPeriod(self):
return self._signal_period.Value
@SignalPeriod.setter
def SignalPeriod(self, value):
self._signal_period.Value = value
@property
def TrendPeriod(self):
return self._trend_period.Value
@TrendPeriod.setter
def TrendPeriod(self, value):
self._trend_period.Value = value
def OnReseted(self):
super(macd_fixed_psar_strategy, self).OnReseted()
self._macd_history = []
self._prev_histogram = None
def OnStarted2(self, time):
super(macd_fixed_psar_strategy, self).OnStarted2(time)
self._macd_history = []
self._prev_histogram = None
macd = MovingAverageConvergenceDivergence()
macd.ShortMa.Length = self.FastPeriod
macd.LongMa.Length = self.SlowPeriod
trend_ema = ExponentialMovingAverage()
trend_ema.Length = self.TrendPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(macd, trend_ema, self._process_candle).Start()
def _process_candle(self, candle, macd_value, trend_value):
if candle.State != CandleStates.Finished:
return
macd_val = float(macd_value)
trend_val = float(trend_value)
close = float(candle.ClosePrice)
signal_period = self.SignalPeriod
self._macd_history.append(macd_val)
while len(self._macd_history) > signal_period:
self._macd_history.pop(0)
if len(self._macd_history) < signal_period:
return
signal = sum(self._macd_history) / signal_period
histogram = macd_val - signal
if self._prev_histogram is None:
self._prev_histogram = histogram
return
cross_up = self._prev_histogram <= 0 and histogram > 0
cross_down = self._prev_histogram >= 0 and histogram < 0
if cross_up and close > trend_val:
if self.Position <= 0:
self.BuyMarket()
elif cross_down and close < trend_val:
if self.Position >= 0:
self.SellMarket()
self._prev_histogram = histogram
def CreateClone(self):
return macd_fixed_psar_strategy()