Auf GitHub ansehen

BeerGod EMA Timing Strategy

This strategy replicates the BeerGodEA MetaTrader expert advisor inside StockSharp. It trades mean-reversion setups on a single symbol by monitoring a 60-period exponential moving average (EMA) and comparing the current price action with the previous bar. Signals are evaluated only once per bar at a configurable minute offset after the candle opens, imitating the original EA that waits a few minutes before acting.

When price temporarily breaks away from the EMA while the average is trending in the opposite direction, the strategy opens a market position expecting the move to revert. Existing positions in the opposite direction are flipped immediately by adjusting the order size so that shorts are covered before establishing a new long position (and vice versa).

How It Works

  1. Subscribe to time-frame candles (default 5 minutes) and build a 60-period EMA over the closing prices.
  2. Track the current candle in real time. On the first tick of each new bar, store the previous EMA value and the prior bar close so the strategy can compare them later.
  3. Once the configured number of minutes from the open elapses (default 3 minutes), evaluate the following conditions using the current price and EMA slope:
    • Buy setup: current price < current EMA, EMA is below its previous value (falling), and current price < previous bar close.
    • Sell setup: current price > current EMA, EMA is above its previous value (rising), and current price > previous bar close.
  4. If a buy setup occurs while not already long, send a market buy order sized to close any open short and establish the desired long volume. The same logic applies symmetrically for sell setups.
  5. After a trade is triggered, the signal for that candle is considered processed to prevent duplicate entries.

Parameters

  • Volume – order size in lots (default 1). The strategy automatically adds the absolute value of the current position when it needs to flip directions so that the new order closes the old exposure and opens the fresh trade in a single transaction.
  • EMA Length – lookback period for the exponential moving average (default 60).
  • Trigger Minutes – number of minutes after the bar opens when the entry conditions are checked (default 3). If the window is missed, the strategy waits for the next candle.
  • Candle Type – candle data type used for calculations (default 5-minute time frame).

Trading Notes

  • The logic works on any symbol as long as candle data and level1 prices are available. Adjust the candle duration if the instrument trades on different sessions than the original MetaTrader setup.
  • Only one position (long or short) is maintained at any moment. Flipping directions is done by sizing the new market order to cover the outstanding position and open the new trade in one step.
  • No explicit stop-loss or take-profit levels are defined in the original EA. Risk management should be added externally if required.
  • Start protection is enabled so that StockSharp automatically handles emergency position exits when manual intervention or connection issues occur.
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();
		}
	}
}