Открыть на GitHub

Стратегия Cross (конверсия MQL 27596)

Общее описание

Cross Strategy — это перенос советника MetaTrader Cross.mq4 из каталога MQL/27596. Оригинальный алгоритм сравнивает цену открытия бара с экспоненциальной скользящей средней (EMA), при каждом пересечении разворачивает позицию и сразу задаёт фиксированные значения тейк-профита и стоп-лосса. Версия на StockSharp полностью повторяет логику, но использует высокоуровневый API платформы: подписки на свечи, привязку индикаторов и встроенные средства учёта позиций.

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

  1. Индикатор — одна EMA по ценам закрытия. Период по умолчанию 200, как и в оригинальном MQL-скрипте.
  2. Формирование сигналов:
    • Если бар открылся выше EMA и ранее условие было ложным, фиксируется бычий сигнал (Cross(0, Open[0] > EMA) в MQL).
    • Если бар открылся ниже EMA после периода, когда условие было истинным или равным, появляется медвежий сигнал (Cross(1, Open[0] < EMA)).
  3. Управление позицией — каждый сигнал приводит к развороту:
    • При бычьем сигнале стратегия закрывает короткую позицию (если она есть) и открывает новую длинную с объёмом Volume + |Position|.
    • При медвежьем сигнале выполняется зеркальное действие: закрывается длинная позиция и открывается короткая.
  4. Фиксация результата — в процессе удержания позиции стратегия контролирует экстремумы свечи. Достижение стоп-лосса или тейк-профита, заданных в шагах цены, приводит к немедленному закрытию.

Параметры

Параметр Значение по умолчанию Назначение
EMA Length 200 Период EMA для определения направления. Значение должно быть больше нуля.
Take Profit (steps) 200 Расстояние до тейк-профита в шагах цены. Ноль отключает цель по прибыли.
Stop Loss (steps) 100 Расстояние до стоп-лосса в шагах цены. Ноль отключает защитный стоп.
Candle Type Таймфрейм 1 минута Тип свечей, на которых выполняются расчёты. Можно выбрать другой таймфрейм или пользовательский тип свечей StockSharp.

Объём заявок задаётся свойством Volume. При развороте стратегия всегда отправляет объём Volume + |Position|, чтобы сначала закрыть текущую позицию и только после этого открыть новую.

Последовательность работы

  1. В OnStarted стратегия подписывается на поток свечей с заданным типом и привязывает к нему EMA через метод Bind.
  2. Обработчик пропускает незавершённые свечи и ждёт, пока EMA полностью сформируется. После этого он:
    • Проверяет активную позицию и при необходимости закрывает её по стоп-лоссу или тейк-профиту (сравниваются минимум и максимум свечи).
    • Анализирует новое состояние пересечения цены открытия и EMA.
    • При появлении сигнала отправляет рыночный ордер на разворот.
  3. Метод OnNewMyTrade отслеживает среднюю цену входа и направление текущей позиции, что важно при последовательных доливках.
  4. При наличии графика создаются визуализации: свечи, EMA и сделки.

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

  • Стоп-лосс вычисляется как цена входа ± StopLoss × PriceStep в зависимости от направления позиции. Если минимум (для лонга) или максимум (для шорта) свечи пересекает уровень, позиция закрывается.
  • Тейк-профит рассчитывается аналогично и срабатывает по пересечению соответствующего экстремума свечи.
  • Защита счёта — при запуске вызывается StartProtection(), чтобы использовать глобальные настройки защиты капитала в StockSharp.

Рекомендации по настройке

  • Сокращение периода EMA или выбор более мелкого таймфрейма увеличивает число сделок и чувствительность к шуму. В этом случае разумно увеличивать расстояние стопов.
  • Для мульти-инструментальной торговли создавайте отдельные экземпляры стратегии с собственными инструментами и параметрами свечей.
  • При оптимизации выбирайте диапазоны параметров, соответствующие волатильности и шагу цены торгуемого инструмента.

Особенности конверсии

  • MQL-массив crossed[2] реализован через два булевых поля, которые сохраняют прошлое состояние условий пересечения.
  • Функция OrderSend заменена на BuyMarket/SellMarket, что позволяет одновременно закрывать противоположные позиции и открывать новые.
  • Значения EMA поступают через callback метода Bind, поэтому в коде не используется прямой доступ к GetValue, что соответствует требованиям репозитория.

Данное описание позволит развернуть стратегию в экосистеме StockSharp и добиться поведения, идентичного оригинальному советнику MetaTrader, с дополнительными преимуществами по визуализации, тестированию и интеграции.

namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Cross strategy: opens long when candle open crosses above EMA,
/// short when candle open crosses below EMA.
/// </summary>
public class CrossStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private bool _prevAbove;
	private bool _hasPrev;
	private int _candlesSinceTrade;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public CrossStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_emaLength = Param(nameof(EmaLength), 100)
			.SetGreaterThanZero()
			.SetDisplay("EMA Length", "EMA period for cross detection", "Indicators");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 3)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between entries", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevAbove = false;
		_hasPrev = false;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_hasPrev = false;
		_candlesSinceTrade = SignalCooldownCandles;
		var ema = new ExponentialMovingAverage { Length = EmaLength };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, ProcessCandle).Start();
	}

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

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		var above = candle.OpenPrice > ema && candle.ClosePrice > ema;

		if (_hasPrev && _candlesSinceTrade >= SignalCooldownCandles)
		{
			if (above && !_prevAbove && Position <= 0)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (!above && _prevAbove && Position >= 0)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevAbove = above;
		_hasPrev = true;
	}
}