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
Subscribe to time-frame candles (default 5 minutes) and build a 60-period EMA over the closing prices.
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.
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.
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.
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();
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage
class beer_god_ema_timing_strategy(Strategy):
"""BeerGod EMA Timing: mean-reversion with EMA trend filter."""
def __init__(self):
super(beer_god_ema_timing_strategy, self).__init__()
self._ema_length = self.Param("EmaLength", 60) \
.SetGreaterThanZero() \
.SetDisplay("EMA Length", "EMA length for the trend filter", "Indicator")
self._trigger_minutes = self.Param("TriggerMinutesFromOpen", 3) \
.SetDisplay("Trigger Minutes", "Minutes after open to check signals", "Timing")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Primary candle type", "General")
self._current_ema = 0.0
self._previous_ema = 0.0
self._current_close = 0.0
self._previous_close = 0.0
@property
def EmaLength(self):
return int(self._ema_length.Value)
@property
def TriggerMinutesFromOpen(self):
return int(self._trigger_minutes.Value)
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(beer_god_ema_timing_strategy, self).OnStarted2(time)
self._current_ema = 0.0
self._previous_ema = 0.0
self._current_close = 0.0
self._previous_close = 0.0
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._ema, self.process_candle).Start()
def process_candle(self, candle, ema_value):
if candle.State != CandleStates.Finished:
return
self._previous_ema = self._current_ema
self._previous_close = self._current_close
self._current_ema = float(ema_value)
self._current_close = float(candle.ClosePrice)
if not self._ema.IsFormed or self._previous_ema == 0:
return
price = float(candle.ClosePrice)
ma_current = self._current_ema
ma_previous = self._previous_ema
prev_close = self._previous_close
new_buy = price < ma_current and ma_current < ma_previous and price < prev_close
new_sell = price > ma_current and ma_current > ma_previous and price > prev_close
if not new_buy and not new_sell:
return
if new_buy and self.Position <= 0:
self.BuyMarket()
elif new_sell and self.Position >= 0:
self.SellMarket()
def OnReseted(self):
super(beer_god_ema_timing_strategy, self).OnReseted()
self._current_ema = 0.0
self._previous_ema = 0.0
self._current_close = 0.0
self._previous_close = 0.0
def CreateClone(self):
return beer_god_ema_timing_strategy()