Открыть на GitHub

Стратегия MACD Parabolic SAR Wizard

Общее описание

Стратегия реализует в StockSharp советник «MACD + Parabolic SAR», созданный мастером MQL5. Логика полностью повторяет систему взвешенных оценок: каждому индикатору присваивается нормированная оценка от 0 до 100, после чего оценки объединяются по заданным весам для формирования торгового решения.

Логика работы

  • Индикаторы
    • MACD (12, 24, 9): знак гистограммы определяет преобладающее направление импульса. Положительный histogram → бычий импульс, отрицательный → медвежий.
    • Parabolic SAR (0.02, 0.2): закрытие выше точки SAR указывает на восходящий тренд, ниже — на нисходящий.
  • Формирование оценок
    • MACD выдаёт 100 баллов в сторону текущего импульса и 0 баллов в противоположную. Для коротких позиций оценки инвертируются.
    • Parabolic SAR работает аналогично, давая 100 баллов, когда цена подтверждает соответствующее направление тренда.
    • Финальные оценки считаются по формуле: bullScore = macdBull * MacdWeight + sarBull * SarWeight, bearScore = macdBear * MacdWeight + sarBear * SarWeight. При дефолтных весах (0.9 и 0.1) доминирует MACD, как и в шаблоне мастера.
  • Условия входа
    • Открыть или перевернуться в лонг, когда bullScore >= OpenThreshold (по умолчанию 20).
    • Открыть или перевернуться в шорт, когда bearScore >= OpenThreshold.
  • Условия выхода
    • Закрыть длинную позицию, если bearScore >= CloseThreshold (по умолчанию 100).
    • Закрыть короткую позицию, если bullScore >= CloseThreshold.
    • Сначала проверяются выходы, затем входы, что воспроизводит приоритет завершения конфликтующих сделок в оригинальном советнике.

Управление рисками

  • StopLossPoints и TakeProfitPoints соответствуют пунктовым стоп- и тейк-уровням из MQL5. Значения умножаются на PriceStep инструмента и передаются в StartProtection.
  • Значение 0 отключает соответствующую защиту.

Параметры

Параметр Назначение Значение по умолчанию
MacdFastPeriod Быстрый период EMA для MACD 12
MacdSlowPeriod Медленный период EMA для MACD 24
MacdSignalPeriod Период сглаживания сигнальной линии 9
MacdWeight Вес оценки MACD (0..1) 0.9
SarWeight Вес оценки Parabolic SAR (0..1) 0.1
OpenThreshold Порог открытия/реверса позиции 20
CloseThreshold Порог закрытия позиции в противоположную сторону 100
SarStep Шаг ускорения Parabolic SAR 0.02
SarMax Максимальное ускорение Parabolic SAR 0.2
StopLossPoints Дистанция стоп-лосса в пунктах 50
TakeProfitPoints Дистанция тейк-профита в пунктах 115
CandleType Тип свечей для расчёта индикаторов 15 минут

Рекомендации по использованию

  • Все параметры совпадают с исходным файлом .mq5, поэтому конвертированная стратегия ведёт себя аналогично мастеру MQL5.
  • Изменяйте веса и пороги, чтобы адаптировать чувствительность. Повышение OpenThreshold снижает количество сделок, тогда как уменьшение CloseThreshold ускорит выход из позиции.
  • Поля _lastBullScore и _lastBearScore обновляются на каждой свече — их можно использовать для журналирования или дополнительной визуализации качества сигналов.
  • Стратегия работает только по закрытым свечам. Убедитесь, что источник данных передаёт финальные значения для выбранного CandleType.
  • Защитные приказы задаются в пунктах: проверьте, что шаг цены инструмента соответствует ожиданиям, чтобы стопы и тейки размещались корректно.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy that replicates the MetaTrader "MACD + Parabolic SAR" expert built with the MQL5 Wizard.
/// Combines the trend direction from Parabolic SAR with MACD momentum scores and uses weighted thresholds for decisions.
/// </summary>
public class MacdParabolicSarWizardStrategy : Strategy
{
	private readonly StrategyParam<int> _macdFastPeriod;
	private readonly StrategyParam<int> _macdSlowPeriod;
	private readonly StrategyParam<int> _macdSignalPeriod;
	private readonly StrategyParam<decimal> _macdWeight;
	private readonly StrategyParam<decimal> _sarWeight;
	private readonly StrategyParam<decimal> _openThreshold;
	private readonly StrategyParam<decimal> _closeThreshold;
	private readonly StrategyParam<decimal> _sarStep;
	private readonly StrategyParam<decimal> _sarMax;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private MovingAverageConvergenceDivergenceSignal _macd = null!;
	private ParabolicSar _parabolicSar = null!;
	private decimal _lastBullScore;
	private decimal _lastBearScore;

	/// <summary>
	/// Fast EMA period for MACD.
	/// </summary>
	public int MacdFastPeriod
	{
		get => _macdFastPeriod.Value;
		set => _macdFastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period for MACD.
	/// </summary>
	public int MacdSlowPeriod
	{
		get => _macdSlowPeriod.Value;
		set => _macdSlowPeriod.Value = value;
	}

	/// <summary>
	/// Signal line period for MACD.
	/// </summary>
	public int MacdSignalPeriod
	{
		get => _macdSignalPeriod.Value;
		set => _macdSignalPeriod.Value = value;
	}

	/// <summary>
	/// Weight of the MACD signal in the combined score (0..1).
	/// </summary>
	public decimal MacdWeight
	{
		get => _macdWeight.Value;
		set => _macdWeight.Value = value;
	}

	/// <summary>
	/// Weight of the Parabolic SAR signal in the combined score (0..1).
	/// </summary>
	public decimal SarWeight
	{
		get => _sarWeight.Value;
		set => _sarWeight.Value = value;
	}

	/// <summary>
	/// Minimum combined bullish or bearish score required to open a position.
	/// </summary>
	public decimal OpenThreshold
	{
		get => _openThreshold.Value;
		set => _openThreshold.Value = value;
	}

	/// <summary>
	/// Minimum opposite score required to exit the current position.
	/// </summary>
	public decimal CloseThreshold
	{
		get => _closeThreshold.Value;
		set => _closeThreshold.Value = value;
	}

	/// <summary>
	/// Acceleration step for Parabolic SAR.
	/// </summary>
	public decimal SarStep
	{
		get => _sarStep.Value;
		set => _sarStep.Value = value;
	}

	/// <summary>
	/// Maximum acceleration factor for Parabolic SAR.
	/// </summary>
	public decimal SarMax
	{
		get => _sarMax.Value;
		set => _sarMax.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in price points.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in price points.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Type of candles used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initialize <see cref="MacdParabolicSarWizardStrategy"/>.
	/// </summary>
	public MacdParabolicSarWizardStrategy()
	{
		_macdFastPeriod = Param(nameof(MacdFastPeriod), 12)
		.SetDisplay("MACD Fast", "Fast EMA period for MACD", "MACD")
		;

		_macdSlowPeriod = Param(nameof(MacdSlowPeriod), 24)
		.SetDisplay("MACD Slow", "Slow EMA period for MACD", "MACD")
		;

		_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 9)
		.SetDisplay("MACD Signal", "Signal SMA period for MACD", "MACD")
		;

		_macdWeight = Param(nameof(MacdWeight), 0.9m)
		.SetDisplay("MACD Weight", "Relative weight of MACD in scoring", "Scoring")
		;

		_sarWeight = Param(nameof(SarWeight), 0.1m)
		.SetDisplay("SAR Weight", "Relative weight of SAR in scoring", "Scoring")
		;

		_openThreshold = Param(nameof(OpenThreshold), 90m)
		.SetDisplay("Open Threshold", "Score required to open trades", "Scoring")
		;

		_closeThreshold = Param(nameof(CloseThreshold), 90m)
		.SetDisplay("Close Threshold", "Score required to exit trades", "Scoring")
		;

		_sarStep = Param(nameof(SarStep), 0.02m)
		.SetDisplay("SAR Step", "Acceleration factor for Parabolic SAR", "Parabolic SAR")
		;

		_sarMax = Param(nameof(SarMax), 0.2m)
		.SetDisplay("SAR Max", "Maximum acceleration for Parabolic SAR", "Parabolic SAR")
		;

		_stopLossPoints = Param(nameof(StopLossPoints), 50m)
		.SetDisplay("Stop Loss (pts)", "Stop-loss distance in points", "Risk")
		;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 115m)
		.SetDisplay("Take Profit (pts)", "Take-profit distance in points", "Risk")
		;

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Primary candle source", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_lastBullScore = 0m;
		_lastBearScore = 0m;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		// Configure MACD indicator replicating the wizard defaults.
		_macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = MacdFastPeriod },
				LongMa = { Length = MacdSlowPeriod },
			},
			SignalMa = { Length = MacdSignalPeriod }
		};

		// Configure Parabolic SAR indicator.
		_parabolicSar = new ParabolicSar
		{
			AccelerationStep = SarStep,
			AccelerationMax = SarMax,
		};

		// Subscribe to candles and bind indicators.
		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(_macd, _parabolicSar, ProcessCandle)
			.Start();

		// Configure risk management using point-based distances.
		var step = Security?.PriceStep ?? 1m;
		var takeProfit = TakeProfitPoints > 0m ? new Unit(TakeProfitPoints * step, UnitTypes.Absolute) : new Unit();
		var stopLoss = StopLossPoints > 0m ? new Unit(StopLossPoints * step, UnitTypes.Absolute) : new Unit();

		StartProtection(takeProfit, stopLoss, useMarketOrders: true);

		// Prepare chart visualization if the environment supports it.
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _parabolicSar);
			DrawIndicator(area, _macd);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue, IIndicatorValue sarValue)
	{
		// Process only finished candles to avoid premature trades.
		if (candle.State != CandleStates.Finished)
			return;

		if (!macdValue.IsFinal || !sarValue.IsFinal)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var macdTyped = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
		if (macdTyped.Macd is not decimal macdLine || macdTyped.Signal is not decimal signalLine)
			return;

		var sar = sarValue.ToDecimal();

		// Translate indicator states into normalized scores (0..100).
		var macdBull = macdLine > signalLine ? 100m : 0m;
		var macdBear = macdLine < signalLine ? 100m : 0m;
		var sarBull = candle.ClosePrice > sar ? 100m : 0m;
		var sarBear = candle.ClosePrice < sar ? 100m : 0m;

		var bullScore = macdBull * MacdWeight + sarBull * SarWeight;
		var bearScore = macdBear * MacdWeight + sarBear * SarWeight;

		_lastBullScore = bullScore;
		_lastBearScore = bearScore;

		// Exit conditions take priority to mirror the wizard behaviour.
		if (Position > 0 && bearScore >= CloseThreshold)
		{
			SellMarket();
			return;
		}

		if (Position < 0 && bullScore >= CloseThreshold)
		{
			BuyMarket();
			return;
		}

		// Entry rules: open when the weighted score exceeds the open threshold.
		if (Position <= 0 && bullScore >= OpenThreshold)
		{
			BuyMarket();
			return;
		}

		if (Position >= 0 && bearScore >= OpenThreshold)
		{
			SellMarket();
		}
	}
}