Открыть на GitHub

MA MACD Position Averaging

Стратегия является точной конверсией советника MetaTrader «MA MACD Position averaging». Она объединяет фильтр по взвешенной скользящей средней с проверкой отношения линий MACD и включает модуль усреднения с наращиванием позиции при неблагоприятном движении цены на заданное число пунктов. Все рисковые параметры задаются в пунктах и автоматически переводятся в ценовые отступы с учётом параметров инструмента из StockSharp.

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

  1. Подготовка индикаторов
    • Настраиваемая скользящая средняя (MaPeriod, MaMethod, MaAppliedPrice) рассчитывается на закрытых свечах. Параметры SignalBar и MaShift имитируют возможность MetaTrader обращаться к данным предыдущих баров и выводить индикатор со сдвигом по горизонтали.
    • Индикатор MACD (MacdFastPeriod, MacdSlowPeriod, MacdSignalPeriod, MacdAppliedPrice) рассчитывается по тем же свечам. Основная и сигнальная линии складываются в небольшой буфер, что позволяет получать исторические значения без прямых вызовов индикатора.
  2. Условия входа
    • Покупка: обе линии MACD ниже нуля, отношение MACDmain / MACDsignal не меньше MacdRatio, закрытие свечи выше выбранной скользящей средней, а расстояние между ценой и средней не меньше IndentPips пунктов.
    • Продажа: обе линии MACD выше нуля, отношение превышает MacdRatio, закрытие ниже скользящей средней, а разрыв не меньше IndentPips пунктов.
    • Новые входы разрешены только при отсутствии открытой позиции. Если цикл усреднения уже запущен, сигнальная часть пропускается и работают только правила усреднения.
  3. Модуль усреднения
    • При наличии длинной позиции и снижении цены минимум на StepLossingPips пунктов относительно лучшей (самой дешёвой) покупки отправляется дополнительный ордер BUY объёмом, равным объёму последней ноги, умноженному на LotCoefficient (с округлением по шагу объёма инструмента).
    • При наличии короткой позиции и росте цены минимум на StepLossingPips пунктов относительно лучшей (самой высокой) продажи добавляется новая нога SELL с тем же коэффициентом LotCoefficient.
    • Если одновременно обнаружены длинные и короткие ноги (в норме этого не происходит), стратегия немедленно закрывает все позиции для восстановления консистентности.
  4. Защитные выходы
    • Для каждой ноги сохраняются индивидуальные уровни стоп-лосса и тейк-профита (StopLossPips, TakeProfitPips). На каждой завершённой свече проверяется, пересёк ли ценовой диапазон свечи один из уровней; в случае пересечения нога закрывается рыночным ордером.
    • Трейлинг-стоп (TrailingStopPips, TrailingStepPips) включается по желанию. После движения цены в прибыль на TrailingStopPips + TrailingStepPips пунктов стоп переносится на расстояние TrailingStopPips пунктов от текущего закрытия и двигается дальше только при дополнительном прогрессе не менее TrailingStepPips пунктов.
  5. Служебная логика
    • Все объёмы приводятся к шагу объёма и ограничиваются допустимым диапазоном. Обработка выполняется только для полностью сформированных свечей (CandleStates.Finished), что исключает двойные срабатывания.

Параметры

Параметр Тип Значение по умолчанию Описание
CandleType DataType TimeSpan.FromHours(1).TimeFrame() Таймфрейм для расчёта индикаторов.
OrderVolume decimal 0.1 Базовый объём первой сделки.
StopLossPips int 50 Дистанция стоп-лосса в пунктах (0 отключает стоп).
TakeProfitPips int 50 Дистанция тейк-профита в пунктах (0 отключает цель).
TrailingStopPips int 5 Отступ трейлинг-стопа в пунктах. Должен быть > 0 для активации.
TrailingStepPips int 5 Дополнительное движение в пунктах перед очередным переносом трейлинг-стопа.
StepLossingPips int 30 Просадка в пунктах, при которой открывается новая нога усреднения.
LotCoefficient decimal 2.0 Коэффициент умножения объёма для каждой новой ноги.
SignalBar int 0 Количество полностью завершённых баров, смещающих выборку индикаторов.
MaPeriod int 15 Длина скользящей средней в барах.
MaShift int 0 Горизонтальный сдвиг (в барах) значений скользящей средней.
MaMethod MovingAverageMethod Weighted Тип сглаживания (simple, exponential, smoothed, weighted).
MaAppliedPrice AppliedPriceType Weighted Источник цены для скользящей средней.
IndentPips int 4 Минимальный разрыв между ценой и средней для входа.
MacdFastPeriod int 12 Период быстрой EMA в MACD.
MacdSlowPeriod int 26 Период медленной EMA в MACD.
MacdSignalPeriod int 9 Период сигнальной линии MACD.
MacdAppliedPrice AppliedPriceType Weighted Тип цены, подаваемый на MACD.
MacdRatio decimal 0.9 Минимальное отношение основной и сигнальной линий MACD для торговли.

Преобразование пунктов

Все параметры в пунктах (StopLossPips, TakeProfitPips, TrailingStopPips, TrailingStepPips, StepLossingPips, IndentPips) умножаются на PriceStep инструмента. Если у инструмента 3 или 5 знаков после запятой, значение дополнительно умножается на 10 для соответствия определению пункта в MetaTrader. При отсутствии данных о шаге цены используется значение 0.0001.

Особенности реализации

  • Стратегия хранит собственный список ног позиции, поскольку в StockSharp применяется режим неттинга. Каждая нога содержит цену входа, стоп и тейк, что позволяет воспроизвести логику усреднения исходного советника.
  • Защитные ордера реализуются программно: при достижении уровня стопа или тейка на свечке нога закрывается рыночным ордером на этой же свече.
  • Усреднение автоматически отключается, если StepLossingPips = 0. В остальных случаях объём новой ноги равен объёму предыдущей, умноженному на LotCoefficient, и округляется вниз до шага объёма.
  • Перенос трейлинг-стопа использует цену закрытия свечи в качестве текущей цены. Стоп никогда не отодвигается назад и активируется только после прохождения TrailingStopPips + TrailingStepPips пунктов.
  • Буферы индикаторов учитывают сдвиги SignalBar и MaShift, поэтому логика принимает те же значения, что и в MetaTrader при чтении буферов iMA и iMACD.
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>
/// MA MACD strategy using WMA crossover with SL/TP management.
/// Buys when fast WMA crosses above slow WMA, sells on reverse cross.
/// </summary>
public class MaMacdPositionAveragingStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private WeightedMovingAverage _fast;
	private WeightedMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	/// <summary>
	/// Fast WMA period.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow WMA period.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

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

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

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public MaMacdPositionAveragingStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 15)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast WMA period", "Indicator");

		_slowPeriod = Param(nameof(SlowPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow WMA period", "Indicator");

		_stopLossPoints = Param(nameof(StopLossPoints), 200)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

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

		_fast = null;
		_slow = null;
		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
		_cooldown = 0;
	}

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

		_fast = new WeightedMovingAverage { Length = FastPeriod };
		_slow = new WeightedMovingAverage { 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;

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}

		// WMA crossover
		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
			_cooldown = 80;
		}
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
			_cooldown = 80;
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}