Открыть на GitHub

Стратегия Expert610 Breakout

Обзор

Expert610 Breakout — это порт на C# советника MetaTrader 4 Expert610.mq4. Исходный робот отслеживает широкую свечу и выставляет симметричные отложенные заявки Buy Stop и Sell Stop вокруг максимумов и минимумов предыдущего бара. Размер позиции рассчитывается как доля свободного капитала, который трейдер готов рискнуть, а параметры стоп-лосса и тейк-профита задаются в пунктах. Реализация на StockSharp повторяет эту логику с использованием высокоуровневого API и предоставляет все настройки через параметры стратегии.

Торговая логика

  1. Сбор данных
    • Стратегия подписывается на выбранный тип свечей и сохраняет последнюю закрытую свечу.
    • Из подписки на стакан рассчитывается текущий спрэд. Если актуальных котировок нет, спрэд считается равным нулю — как и в оригинальном советнике при отсутствии онлайн-спрэда.
  2. Фильтр волатильности
    • Разница между максимумом предыдущей свечи и текущей ценой закрытия, а также между текущим закрытием и минимумом предыдущей свечи должна быть не меньше ThresholdPips (переведённого в цену).
    • Открытие текущей свечи должно находиться ниже предыдущего максимума для лонга и выше предыдущего минимума для шорта. При выполнении обеих условий стратегия размещает обе заявки.
  3. Выставление ордеров
    • Buy Stop устанавливается по формуле предыдущий максимум + BreakoutOffset + спрэд, что соответствует MT4, где применяется цена Ask.
    • Sell Stop размещается по формуле предыдущий минимум - BreakoutOffset — без учёта спрэда, как и в оригинальном коде.
    • Одновременно может существовать только одна пара отложенных ордеров. Если активный ордер ещё висит, новый сигнал игнорируется.
  4. Управление риском
    • Объём сделки = (свободный капитал Portfolio.CurrentValue - Portfolio.BlockedValue) × RiskPercent / 100, округлённый до RoundingDigits и переведённый в лоты по формуле MT4 lot = risk / stopPips * 0.1. Формула предполагает, что 0.1 лота даёт стоимость одного пункта, равную одной единице валюты счёта.
    • Объём дополнительно нормируется по VolumeStep, ограничивается MinVolume/MaxVolume инструмента и параметром MinimumVolume, чтобы заказ всегда соответствовал биржевым требованиям.
    • StartProtection автоматически привязывает стоп и цель (в ценовых единицах) к каждой открытой позиции, моментально включая заданные StopLossPips и TakeProfitPips.

Параметры

Имя Описание Значение по умолчанию Примечания
RoundingDigits Количество знаков после запятой при расчёте риска и объёма. 2 Значение ≥ 0.
RiskPercent Процент свободного капитала на одну сделку. 1 При 0 стратегия использует MinimumVolume.
MinimumVolume Минимально допустимый объём отложенного ордера. 0.1 Также учитываются MinVolume и VolumeStep инструмента.
ThresholdPips Минимальная дистанция от цены закрытия до экстремумов предыдущей свечи. 5 Указывается в пунктах.
BreakoutOffsetPips Дополнительное смещение выше/ниже экстремумов при постановке ордера. 2 Применяется для обеих сторон.
StopLossPips Дистанция стоп-лосса. 5 Передаётся в StartProtection.
TakeProfitPips Дистанция тейк-профита. 10 Можно установить 0, чтобы отключить.
CandleType Тип свечей для расчёта уровней. Таймфрейм 1 час Поддерживаются любые DataType StockSharp.

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

  • Размер пункта вычисляется на основе PriceStep и Decimals инструмента (для 3- и 5-знаковых валютных пар используется множитель 10) — так же, как в MQL4.
  • Нормализация объёма учитывает VolumeStep, ограничивает значения в пределах MinVolume/MaxVolume, а затем применяет порог MinimumVolume, чтобы исключить невыполнимые заявки.
  • Компенсация спрэда строится на лучших котировках из стакана. Если данные недоступны, входные цены совпадают с MT4-версией без учёта спрэда.
  • После смены состояния ордера на исполненный/отменённый/ошибочный он удаляется из внутреннего состояния, что позволяет сразу подготовить новую пару ордеров на следующую подходящую свечу.

Отличия от версии MQL

  • Помимо округления по Digits2Round, реализация на StockSharp дополнительно подгоняет объём под шаг объёма биржи.
  • Вместо передачи стопов и тейков в момент создания ордера используется StartProtection, автоматически выставляющий защитные заявки после фактического открытия позиции.
  • В качестве источника свободного капитала используются поля Portfolio.CurrentValue и Portfolio.BlockedValue. При отсутствии данных стратегия возвращается к фиксированному минимальному объёму.
  • Все вычисления выполняются только по закрытым свечам, что исключает внутридневные перерисовки и соответствует логике вызова start() в конце бара.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Expert610 Breakout: Previous candle high/low breakout with ATR stops.
/// </summary>
public class Expert610BreakoutStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevHigh;
	private decimal _prevLow;
	private decimal _entryPrice;

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

		_emaLength = Param(nameof(EmaLength), 50)
			.SetDisplay("EMA Length", "Trend filter.", "Indicators");

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

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

	public int EmaLength
	{
		get => _emaLength.Value;
		set => _emaLength.Value = value;
	}

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

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

		_prevHigh = 0;
		_prevLow = 0;
		_entryPrice = 0;
	}

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

		_prevHigh = 0;
		_prevLow = 0;
		_entryPrice = 0;

		var ema = new ExponentialMovingAverage { Length = EmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

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

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

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

		var close = candle.ClosePrice;

		if (_prevHigh == 0 || _prevLow == 0 || atrVal <= 0)
		{
			_prevHigh = candle.HighPrice;
			_prevLow = candle.LowPrice;
			return;
		}

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

		if (Position == 0)
		{
			if (close > _prevHigh && close > emaVal)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (close < _prevLow && close < emaVal)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
	}
}