Открыть на GitHub

Стратегия MA MACD Position Averaging v2

Обзор

MA MACD Position Averaging v2 — порт MetaTrader-советника Владимира Карпутова. Стратегия сочетает фильтр на основе взвешенной скользящей средней, подтверждение по MACD и модуль усреднения, наращивающий позицию при неблагоприятном движении рынка. Реализация для StockSharp сохраняет исходную последовательность сигналов, рассчитывает индикаторы на закрытых свечах и воспроизводит брокерскую логику (стопы, тейки, трейлинг) в коде.

Логика торговли

  1. Подготовка индикаторов
    • Гибкая скользящая средняя строится по выбранному типу свечей и ценовому источнику. Параметр MaShift эмулирует MetaTrader-смещение, считывая значения прошлых баров, а BarOffset позволяет анализировать текущий или предыдущие бары.
    • Индикатор MACD рассчитывает основную и сигнальную линии по настраиваемым периодам быстрых/медленных EMA и заданному типу цены — полностью повторяя оригинальный советник.
  2. Проверка сигнала
    • Для покупки обе линии MACD должны быть отрицательными, цена — выше смещённой скользящей средней, а расстояние до неё — не меньше MaIndentPips (переведённых в абсолютную цену через размер пункта).
    • Для продажи условия зеркальные: обе линии MACD положительные, цена ниже средней, расстояние до неё превышает MaIndentPips.
    • Фильтр MacdRatio требует выполнения неравенства MACD_main / MACD_signal >= MacdRatio (деление в десятичном формате).
    • При ReverseSignals = true направление ордера инвертируется после прохождения всех фильтров.
  3. Жизненный цикл позиции
    • При нулевой позиции стратегия отправляет рыночный ордер объёмом OrderVolume (с учётом шага объёма инструмента) и сразу фиксирует уровни стоп-лосса и тейк-профита согласно StopLossPips и TakeProfitPips.
    • При открытой позиции противоположные сделки не формируются. Возможны два варианта:
      • аварийное закрытие, если одновременно обнаружены лонги и шорты (контроль как в оригинале),
      • запуск модуля усреднения по направлению текущей позиции.
  4. Модуль усреднения
    • Для лонгов выбирается открытая нога с минимальной ценой входа, чья плавающая просадка превышает StepLossPips. Для шортов — максимальная цена входа среди убыточных ног.
    • Если кандидат найден, отправляется рыночный ордер объёмом Кандидат × LotCoefficient (после округления по шагу, минимуму и максимуму объёма), что воспроизводит геометрическую прогрессию из MQL.
    • Новые ноги получают те же уровни стопа и тейка и попадают под действие трейлинг-алгоритма.
  5. Защита позиции
    • Трейлинг-стоп активируется только если TrailingStopPips > 0 и TrailingStepPips > 0. Для лонгов стоп переносится на Close - TrailingStopPips, когда прибыль превышает TrailingStopPips + TrailingStepPips; для шортов логика зеркальная.
    • На каждом закрытом баре проверяются условия срабатывания стоп-лосса/тейк-профита. При выполнении условий соответствующая нога закрывается рыночным ордером и удаляется из списка усреднения.

Параметры

Параметр Описание
OrderVolume Базовый объём первой сделки в серии.
StopLossPips Расстояние до стоп-лосса в пунктах (0 — без стопа).
TakeProfitPips Расстояние до тейк-профита в пунктах (0 — без тейка).
TrailingStopPips Дистанция трейлинг-стопа, используется совместно с TrailingStepPips.
TrailingStepPips Дополнительный ценовой ход, необходимый для переноса трейлинг-стопа.
StepLossPips Минимальная просадка (в пунктах), при которой добавляется новая нога.
LotCoefficient Множитель объёма выбранной убыточной ноги при усреднении.
BarOffset Сдвиг назад при чтении индикаторов (0 — текущий завершённый бар).
ReverseSignals Инвертирует направления сделок, сохраняя фильтры.
MaPeriod Период скользящей средней.
MaShift Смещение средней вперёд (как в MetaTrader).
MaMethod Метод сглаживания (Simple, Exponential, Smoothed, Weighted).
MaPrice Тип цены свечи для расчёта средней.
MaIndentPips Минимальное расстояние цены от средней перед входом.
MacdFastPeriod Быстрый период EMA для MACD.
MacdSlowPeriod Медленный период EMA для MACD.
MacdSignalPeriod Период сигнальной EMA для MACD.
MacdPrice Применяемая цена в расчёте MACD.
MacdRatio Минимальное отношение основной и сигнальной линий MACD.
CandleType Тип свечей, используемый стратегией.

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

  • Размер пункта вычисляется из шага цены инструмента с поправкой на 3/5 знаков, как в оригинальном советнике, поэтому все дистанции в пунктах совпадают.
  • Для хранения смещённых значений используются очереди, что позволяет имитировать индексы ma_shift и BarOffset без запрещённых исторических вызовов.
  • Корректировка объёма учитывает Security.VolumeStep, Security.MinVolume и Security.MaxVolume, предотвращая ошибки при умножении на LotCoefficient.
  • Стопы, тейки и трейлинг реализованы на стороне стратегии, без обращения к брокерским методам модификации позиций.
  • Класс размещён в пространстве имён StockSharp.Samples.Strategies, код отформатирован табуляцией и содержит комментарии только на английском, согласно требованиям репозитория.
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 Position Averaging v2 strategy using WMA crossover with EMA trend filter.
/// Buys when fast WMA crosses above slow WMA and price above EMA.
/// Sells on reverse conditions.
/// </summary>
public class MaMacdPositionAveragingV2Strategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private WeightedMovingAverage _fast;
	private WeightedMovingAverage _slow;
	private ExponentialMovingAverage _ema;

	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>
	/// EMA trend filter period.
	/// </summary>
	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.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 MaMacdPositionAveragingV2Strategy()
	{
		_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");

		_emaPeriod = Param(nameof(EmaPeriod), 200)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA trend filter 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");
	}

	/// <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;
		_ema = 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 };
		_ema = new ExponentialMovingAverage { Length = EmaPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, _ema, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue, decimal emaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_fast.IsFormed || !_slow.IsFormed || !_ema.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 with EMA trend filter
		if (_prevFast <= _prevSlow && fastValue > slowValue && close > emaValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

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

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

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}