Открыть на GitHub

Стратегия MACD Sample 1010

Обзор

Данный модуль переносит советник MetaTrader macd_sample_1010.mq4 в высокоуровневый API StockSharp. Изначально скрипт использовал полосы Боллинджера и простые правила управления капиталом: закрытие выше верхней полосы с учётом заданного буфера открывало короткую позицию, закрытие ниже нижней полосы — длинную. Каждая позиция сопровождалась фиксированным профитом и стопом, заданными в пунктах. Перенесённая стратегия подписывается на нужный поток свечей, привязывает индикатор BollingerBands и повторяет всю логику входа и выхода через обработчик завершённых свечей.

Как и оригинальный советник, порт работает только с полностью сформированными свечами, чтобы решения совпадали с обработкой на закрытии бара в MetaTrader. Флаг LotIncrease, отвечающий за автоматическое изменение объёма относительно баланса, также реализован и ведёт себя аналогично МТ4-версии.

Особенности портирования

  • Используется высокоуровневая связка SubscribeCandles + Bind, поэтому индикатор получает готовые свечи без ручного накопления данных.
  • Все входные параметры оформлены через StrategyParam<T>, что делает их видимыми в интерфейсе и пригодными для оптимизации.
  • Расстояния в пунктах переводятся в денежные единицы через Security.PriceStep, поэтому вызовы BuyMarket и SellMarket воспроизводят вычисления MQL4.
  • Баланс-зависимое масштабирование объёма опирается на Portfolio.CurrentValue (при недоступности — на BeginValue) и ограничено 500 лотами, как в исходном коде.
  • Обработка ограничена завершёнными свечами, что исключает «дребезг» сигнала между тиками.
  • В код добавлены подробные комментарии на английском, поясняющие назначение каждого блока.

Параметры

Параметр Тип Значение по умолчанию Описание
ProfitTargetPips decimal 3 Количество пунктов прибыли, после достижения которого позиция закрывается. Ноль отключает правило.
LossLimitPips decimal 20 Допустимая просадка в пунктах. При значении 0 стоп-правило не используется.
BandDistancePips decimal 3 Дополнительный буфер (в пунктах) к верхней и нижней полосам Боллинджера перед подтверждением пробоя.
BollingerPeriod int 4 Длина окна для расчёта полос Боллинджера.
BollingerDeviation decimal 2 Множитель стандартного отклонения в индикаторе.
BaseVolume decimal 1 Базовый торговый объём в лотах и одновременно стартовое значение для масштабирования.
LotIncrease bool true При включении пересчитывает объём на каждой свече в зависимости от отношения текущего баланса к стартовому.
OneOrderOnly bool true Разрешает новые входы только при нулевой нетто-позиции. Даже при отключении StockSharp всё равно ведёт одну совокупную позицию.
CandleType DataType TimeFrame(15m) Тип свечей, применяемый и для индикатора, и для торговых решений.

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

  1. В OnStarted создаётся индикатор Bollinger Bands с заданными параметрами, оформляется подписка на CandleType и связывается обработчик ProcessCandle.
  2. Каждая завершённая свеча вызывает ProcessCandle; при активном LotIncrease предварительно обновляется рабочий объём сделки.
  3. Если закрытие выше верхней полосы плюс буфер, отправляется рыночная заявка на продажу. Если закрытие ниже нижней полосы минус буфер — рыночная заявка на покупку. При OneOrderOnly = true входы возможны только при отсутствии позиции.
  4. После возможного входа стратегия контролирует открытую позицию:
    • Для лонга фиксируется прибыль при достижении ProfitTargetPips и фиксируется убыток при LossLimitPips.
    • Для шорта условия симметричные: закрытие по прибыли или убытку в тех же диапазонах.
  5. Все сравнения выполняются в ценовых единицах, рассчитанных из пункта (PriceStep), что обеспечивает идентичность расчётам MQL4.

Управление объёмом

  • При выключенном LotIncrease объём всегда равен BaseVolume.
  • При включённом LotIncrease первая свеча запоминает «баланс на один лот». Далее объём на каждой свече равен отношению текущего баланса к этому эталону, округлённому до одной десятой (аналог NormalizeDouble(..., 1)), и ограниченному сверху значением 500.
  • Если данные по портфелю недоступны, стратегия автоматически возвращается к фиксированному объёму.

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

  1. Перед запуском убедитесь, что Security.PriceStep соответствует минимальному шагу цены для инструмента.
  2. Подберите подходящий таймфрейм в CandleType. Оригинал чаще запускался на внутридневных свечах (5–15 минут), но допускается любой горизонт.
  3. Настройте параметры полос Боллинджера, величину буфера и значения take-profit/stop-loss под свои требования.
  4. Определите, требуется ли динамическое масштабирование объёма (LotIncrease).
  5. Запустите стратегию и следите за журналом: входы и выходы должны совпадать с закрытиями свечей и рассчитанными уровнями.

Отличия от MetaTrader-версии

  • StockSharp оперирует агрегированной позицией, поэтому даже при отключенном OneOrderOnly фактически будет существовать один нетто-объём, а не отдельные сделки по билетам.
  • Правила выхода реализованы проверками в коде на закрытии свечей, а не установкой отложенных заявок, однако фактическое поведение остаётся тем же.
  • Переключатели детального логирования (logging, logerrs, logtick) не переносились: встроенный логгер StockSharp уже фиксирует все ордера и сделки.
  • Файловая статистика оригинального советника не воспроизводится — в StockSharp доступны более полные отчёты по портфелям и стратегиям.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// MACD Sample 1010: SMA band mean reversion with ATR stops.
/// </summary>
public class MacdSample1010Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _smaLength;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<decimal> _bandMult;

	private decimal _entryPrice;

	public MacdSample1010Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_smaLength = Param(nameof(SmaLength), 20)
			.SetDisplay("SMA Length", "Band center period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");

		_bandMult = Param(nameof(BandMult), 2.0m)
			.SetDisplay("Band Multiplier", "ATR multiplier for bands.", "Indicators");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int SmaLength
	{
		get => _smaLength.Value;
		set => _smaLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	public decimal BandMult
	{
		get => _bandMult.Value;
		set => _bandMult.Value = value;
	}

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

		_entryPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_entryPrice = 0;

		var sma = new SimpleMovingAverage { Length = SmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(sma, atr, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, sma);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal smaVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (atrVal <= 0)
			return;

		var close = candle.ClosePrice;
		var upper = smaVal + atrVal * BandMult;
		var lower = smaVal - atrVal * BandMult;

		if (Position > 0)
		{
			if (close >= smaVal || close <= _entryPrice - atrVal * 2m)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close <= smaVal || close >= _entryPrice + atrVal * 2m)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (close < lower)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (close > upper)
			{
				_entryPrice = close;
				SellMarket();
			}
		}
	}
}