Открыть на GitHub

Стратегия BeerGod EMA Timing

Эта стратегия переносит эксперта BeerGodEA из MetaTrader в экосистему StockSharp. Торговля ведётся на одном инструменте по сигналам возврата к среднему. Для фильтра направления используется экспоненциальная скользящая средняя (EMA) с периодом 60, а сравнение ведётся с ценой закрытия предыдущей свечи. Проверка условий выполняется один раз для каждой свечи через заданное количество минут после её открытия, что повторяет поведение оригинального советника.

Когда цена отклоняется от EMA, а сама средняя движется навстречу текущему импульсу, стратегия открывает рыночную позицию в расчёте на возврат цены. Если в данный момент открыта позиция противоположного направления, объём новой заявки увеличивается, и короткая позиция полностью закрывается перед открытием новой длинной (и наоборот).

Как работает стратегия

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

Параметры

  • Volume – объём заявки в лотах (по умолчанию 1). При развороте позиции объём заявки увеличивается на модуль текущей позиции, что позволяет закрыть старую и открыть новую позицию одной сделкой.
  • EMA Length – период EMA для фильтра направления (по умолчанию 60).
  • Trigger Minutes – количество минут после открытия свечи, через которое выполняется проверка условий (по умолчанию 3). Если момент пропущен, стратегия ждёт следующую свечу.
  • Candle Type – тип свечей, используемых для расчётов (по умолчанию пятиминутные свечи).

Особенности торговли

  • Стратегия подходит для любых инструментов, где доступны свечные данные и поток Level1. При необходимости измените длительность свечи, чтобы адаптироваться к часовым поясам или торговым сессиям инструмента.
  • Одновременно поддерживается только одна позиция. При смене направления размер рыночной заявки автоматически корректируется для полного закрытия текущей позиции и открытия новой.
  • Оригинальный эксперт не использует стоп-лосс и тейк-профит. При необходимости добавьте внешнее управление рисками.
  • Включена защитная функция StartProtection, позволяющая StockSharp контролировать аварийное закрытие позиций при сбоях или ручном вмешательстве.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Mean-reversion strategy that triggers trades a few minutes after the bar opens using an EMA trend filter.
/// </summary>
public class BeerGodEmaTimingStrategy : Strategy
{
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _triggerMinutes;
	private readonly StrategyParam<DataType> _candleType;

	private EMA _ema = null!;
	private DateTimeOffset _currentCandleOpenTime = DateTimeOffset.MinValue;
	private decimal _currentEma;
	private decimal _previousEma;
	private decimal _currentClose;
	private decimal _previousClose;


	/// <summary>
	/// EMA length used as the directional filter.
	/// </summary>
	public int EmaLength
	{
		get => _emaLength.Value;
		set => _emaLength.Value = value;
	}

	/// <summary>
	/// Minutes from the candle open when the entry check is performed.
	/// </summary>
	public int TriggerMinutesFromOpen
	{
		get => _triggerMinutes.Value;
		set => _triggerMinutes.Value = value;
	}

	/// <summary>
	/// Candle type used for signal generation.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="BeerGodEmaTimingStrategy"/>.
	/// </summary>
	public BeerGodEmaTimingStrategy()
	{

		_emaLength = Param(nameof(EmaLength), 60)
			.SetGreaterThanZero()
			.SetDisplay("EMA Length", "EMA length for the trend filter", "Indicator")
			
			.SetOptimize(20, 120, 10);

		_triggerMinutes = Param(nameof(TriggerMinutesFromOpen), 3)
			.SetNotNegative()
			.SetDisplay("Trigger Minutes", "Minutes after open to check signals", "Timing")
			
			.SetOptimize(1, 10, 1);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Primary candle type", "General");
	}

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

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

		_currentCandleOpenTime = DateTimeOffset.MinValue;
		_currentEma = 0m;
		_previousEma = 0m;
		_currentClose = 0m;
		_previousClose = 0m;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_ema = new EMA
		{
			Length = EmaLength
		};

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

		// no fixed protection needed
	}

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

		_previousEma = _currentEma;
		_previousClose = _currentClose;

		_currentEma = emaValue;
		_currentClose = candle.ClosePrice;

		if (!_ema.IsFormed || _previousEma == 0m)
			return;

		var price = candle.ClosePrice;
		var maCurrent = _currentEma;
		var maPrevious = _previousEma;
		var prevClose = _previousClose;

		var newBuy = price < maCurrent && maCurrent < maPrevious && price < prevClose;
		var newSell = price > maCurrent && maCurrent > maPrevious && price > prevClose;

		if (!newBuy && !newSell)
			return;

		if (newBuy && Position <= 0)
		{
			BuyMarket();
		}
		else if (newSell && Position >= 0)
		{
			SellMarket();
		}
	}
}