Открыть на GitHub

Стратегия Separate Trade

Обзор

Стратегия Separate Trade представляет собой порт MetaTrader 5 эксперта «Separate trade» на платформу StockSharp. В версии для StockSharp сохранены все исходные фильтры, но они реализованы через высокоуровневый API, что облегчает визуальную настройку и оптимизацию. Основная идея — искать точки разворота, когда рынок демонстрирует низкую волатильность и минимальное расхождение между скользящими средними. В рамках StockSharp используется только одна суммарная позиция, что соответствует ограничению на количество позиций в оригинальном советнике.

Индикаторы и данные

  • Две скользящие средние с общим методом сглаживания (SMA, EMA, SMMA или линейно-взвешенная) и единым источником цен.
  • Два индикатора ATR с независимыми периодами и порогами для длинных и коротких сделок.
  • Два индикатора стандартного отклонения, рассчитывающие волатильность по выбранной цене и контролирующие отсутствие расширения диапазона.
  • Источник данных задаётся параметром CandleType, что позволяет подключать стратегию к любому таймфрейму или типу свечей, поддерживаемому StockSharp.

Параметры

Параметр Назначение Значение по умолчанию
TradeVolume Объём заявки в лотах. 1
SlowMaPeriod / FastMaPeriod Периоды медленной и быстрой скользящих средних. 65 / 14
MaMethod Метод сглаживания для обеих средних (Simple, Exponential, Smoothed, LinearWeighted). Exponential
PriceType Тип цены для расчёта скользящих и стандартного отклонения. Close
StopLossBuyPips / StopLossSellPips Расстояние стоп-лосса для длинных и коротких позиций в пунктах (0 отключает стоп). 50
TakeProfitBuyPips / TakeProfitSellPips Расстояние тейк-профита в пунктах (0 отключает тейк). 50
TrailingStopPips Базовая дистанция трейлинг-стопа. 5
TrailingStepPips Минимальное движение в прибыль перед переносом трейлинг-стопа (обязательно > 0 при активном трейлинге). 5
MaxPositions Максимальное количество одновременных нетто-позиций. В реализации StockSharp всегда ведётся одна суммарная позиция. 1
DeltaBuyPips / DeltaSellPips Максимально допустимая дистанция между быстрой и медленной средними. Значение 0 отключает фильтр. 2
AtrPeriodBuy / AtrPeriodSell Периоды ATR для длинных и коротких сделок. 26
AtrLevelBuy / AtrLevelSell Пороги ATR, которые не должны быть превышены перед входом. 0.0016
StdDevPeriodBuy / StdDevPeriodSell Периоды стандартного отклонения. 54
StdDevLevelBuy / StdDevLevelSell Допустимый максимум стандартного отклонения. 0.0051
CandleType Тип свечей, используемый стратегией. TimeSpan.FromMinutes(15)

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

  1. Работа только с завершёнными свечами. Все расчёты выполняются после получения свечи в состоянии Finished, что повторяет защиту по новому бару из кода MT5.
  2. Фильтры для длинных сделок. Медленная MA должна находиться ниже быстрой, ATR — меньше AtrLevelBuy, стандартное отклонение — ниже StdDevLevelBuy, а расстояние между средними — меньше DeltaBuyPips (если параметр больше нуля).
  3. Фильтры для коротких сделок. Аналогичные требования, но с использованием параметров для продаж и обратного неравенства между средними.
  4. Контроль повторных входов. После открытия сделки сохраняется время свечи. Новый вход в ту же сторону возможен только на следующей свече, что предотвращает множественные заявки внутри одного бара.
  5. Исполнение ордеров. Используются рыночные заявки. При появлении сигнала в противоположную сторону стратегия сначала закрывает текущую позицию за счёт добавочного объёма Volume + Math.Abs(Position).

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

  • Пересчёт пунктов. Стопы и цели переводятся в цены через Security.PriceStep. Если инструмент котируется с точностью 3 или 5 знаков после запятой, размер пункта равен PriceStep * 10, как и в MQL-версии.
  • Стоп-лосс и тейк-профит. Стратегия отслеживает уровни и закрывает позицию, когда минимум или максимум свечи пересекает соответствующий уровень. Установка параметра в 0 отключает механизм.
  • Трейлинг-стоп. Когда прибыль превышает сумму TrailingStopPips + TrailingStepPips, стоп переносится, сохраняя фиксированную дистанцию до цены. Это повторяет логику функции Trailing() из оригинального кода.

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

  • StockSharp оперирует суммарной позицией, поэтому MaxPositions служит больше для совместимости и предотвращает открытие новых сделок, если значение меньше либо равно текущей величине позиции.
  • Все параметры созданы через StrategyParam, что делает стратегию полностью совместимой с Designer и оптимизатором.
  • При включении трейлинг-стопа проверяется положительность шага. В случае нарушения требование стратегия записывает ошибку и останавливается, аналогично проверке Trailing Step в MQL.
  • Комментарии и сообщения оставлены на английском языке в соответствии с правилами репозитория.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Separate trade strategy using dual EMA crossover with ATR volatility filter.
/// Trades when fast EMA crosses slow EMA and ATR confirms adequate volatility.
/// </summary>
public class SeparateTradeStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _atrPeriod;

	private decimal? _prevFast;
	private decimal? _prevSlow;

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

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	public SeparateTradeStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_fastPeriod = Param(nameof(FastPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast EMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 65)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow EMA period", "Indicators");

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR period for volatility filter", "Indicators");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = null;
		_prevSlow = null;
	}

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

		_prevFast = null;
		_prevSlow = null;

		var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
		var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
		var atr = new AverageTrueRange { Length = AtrPeriod };

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

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

	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow, decimal atr)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		if (_prevFast == null || _prevSlow == null)
		{
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		var prevAbove = _prevFast.Value > _prevSlow.Value;
		var currAbove = fast > slow;

		_prevFast = fast;
		_prevSlow = slow;

		// Require minimum ATR relative to price
		if (atr < candle.ClosePrice * 0.0005m)
			return;

		// Golden cross
		if (!prevAbove && currAbove)
		{
			if (Position < 0)
				BuyMarket();
			if (Position <= 0)
				BuyMarket();
		}
		// Death cross
		else if (prevAbove && !currAbove)
		{
			if (Position > 0)
				SellMarket();
			if (Position >= 0)
				SellMarket();
		}
	}
}