Boring EA2 Alert recreates the notification logic of the MetaTrader 4 expert advisor boring-ea2. The strategy listens to finished candles, calculates three simple moving averages (SMA 3, SMA 20, SMA 150), and emits informative logs whenever a crossover happens between the moving averages. The implementation intentionally avoids order placement: the goal is to provide traders with timely alerts that they can combine with their discretionary execution or other automated strategies.
Strategy logic
Moving average tracking
Short-term bias – a 3-period SMA reacts to immediate price changes.
Medium trend – a 20-period SMA smooths price over the short-term swing horizon.
Long trend – a 150-period SMA represents the dominant backdrop trend.
Crossover detection
SMA3 vs SMA20 – reports "crossed up" when SMA3 rises above SMA20 and "crossed down" when it falls below. Internal flags guarantee that each transition is reported once.
SMA3 vs SMA150 – mirrors the same logic against the long-term average to detect momentum surges or reversals against the prevailing trend.
SMA20 vs SMA150 – adds a medium/long-term confirmation layer so that shifts in the higher timeframe structure trigger their own alerts.
Initialization guard – the first finished candle only seeds the initial state. Alerts begin with the second finished candle once a true change in relationship is observed.
Notification format
Alerts mirror the original EA message: Alert!!! - SYMBOL - TF - description.
The timeframe code is derived from the configured candle type. Standard MetaTrader-style labels (M1, M5, H1, etc.) are used when available; other timeframes fall back to a compact notation (for example, M45 or D2).
Messages are written with AddInfoLog, enabling routing to log viewers, scripts, or GUI dashboards.
Parameters
Short SMA Length – number of periods for the fast moving average (default 3).
Medium SMA Length – number of periods for the intermediate moving average (default 20).
Long SMA Length – number of periods for the slow moving average (default 150).
Candle Type – timeframe used to calculate the moving averages. The default is 1-minute candles, matching the EA's tick-based checks with high reactivity.
Additional notes
The strategy does not submit, modify, or cancel orders. It is purely informational.
Because Bind feeds finalized values, each crossover is evaluated on completed candles. This avoids the noisy intra-bar flips that the original EA mitigated by counting ticks.
The logging-based notifications can be integrated with custom handlers by subscribing to strategy log events within a hosting application.
No Python translation is provided at this time; only the C# version is included in the API package.
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Boring EA2 strategy: Triple SMA crossover.
/// Buys when fast crosses above medium while medium above slow.
/// Sells when fast crosses below medium while medium below slow.
/// </summary>
public class BoringEa2Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public BoringEa2Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fast = new SimpleMovingAverage { Length = 10 };
var med = new SimpleMovingAverage { Length = 20 };
var slow = new SimpleMovingAverage { Length = 40 };
decimal? prevFast = null;
decimal? prevMed = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, med, slow, (candle, fastVal, medVal, slowVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (prevFast.HasValue && prevMed.HasValue)
{
var fastCrossUp = prevFast.Value <= prevMed.Value && fastVal > medVal;
var fastCrossDown = prevFast.Value >= prevMed.Value && fastVal < medVal;
if (fastCrossUp && medVal > slowVal && Position <= 0)
BuyMarket();
else if (fastCrossDown && medVal < slowVal && Position >= 0)
SellMarket();
}
prevFast = fastVal;
prevMed = medVal;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, med);
DrawIndicator(area, slow);
DrawOwnTrades(area);
}
}
}
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.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class boring_ea2_strategy(Strategy):
def __init__(self):
super(boring_ea2_strategy, self).__init__()
self._fast = None
self._med = None
self._slow = None
self._prev_fast = None
self._prev_med = None
def OnReseted(self):
super(boring_ea2_strategy, self).OnReseted()
self._fast = None
self._med = None
self._slow = None
self._prev_fast = None
self._prev_med = None
def OnStarted2(self, time):
super(boring_ea2_strategy, self).OnStarted2(time)
self._fast = SimpleMovingAverage()
self._fast.Length = 10
self._med = SimpleMovingAverage()
self._med.Length = 20
self._slow = SimpleMovingAverage()
self._slow.Length = 40
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(15)))
subscription.Bind(self._fast, self._med, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, med_value, slow_value):
if candle.State != CandleStates.Finished:
return
if not self._fast.IsFormed or not self._med.IsFormed or not self._slow.IsFormed:
return
fast_val = float(fast_value)
med_val = float(med_value)
slow_val = float(slow_value)
if self._prev_fast is not None and self._prev_med is not None:
fast_cross_up = self._prev_fast <= self._prev_med and fast_val > med_val
fast_cross_down = self._prev_fast >= self._prev_med and fast_val < med_val
if fast_cross_up and med_val > slow_val and self.Position <= 0:
self.BuyMarket()
elif fast_cross_down and med_val < slow_val and self.Position >= 0:
self.SellMarket()
self._prev_fast = fast_val
self._prev_med = med_val
def CreateClone(self):
return boring_ea2_strategy()