Boring EA2 Alert recrea la lógica de notificaciones del asesor experto MetaTrader 4 boring-ea2. La estrategia escucha velas terminadas, calcula tres medias móviles simples (SMA 3, SMA 20, SMA 150) y emite logs informativos cuando ocurre un cruce entre medias. La implementación evita intencionalmente colocar órdenes: el objetivo es dar alertas oportunas que los traders puedan combinar con ejecución discrecional u otras estrategias automatizadas.
Lógica de estrategia
Seguimiento de medias móviles
Sesgo de corto plazo: una SMA de 3 periodos reacciona a cambios inmediatos de precio.
Tendencia media: una SMA de 20 periodos suaviza el precio en el horizonte de swing de corto plazo.
Tendencia larga: una SMA de 150 periodos representa el trasfondo dominante.
Detección de cruces
SMA3 vs SMA20: informa "crossed up" cuando SMA3 sube por encima de SMA20 y "crossed down" cuando cae por debajo. Banderas internas garantizan que cada transición se informe una vez.
SMA3 vs SMA150: replica la misma lógica frente a la media de largo plazo para detectar impulsos de momentum o reversiones contra la tendencia dominante.
SMA20 vs SMA150: añade una capa de confirmación de medio/largo plazo para que los cambios de estructura superior generen sus propias alertas.
Guarda de inicialización: la primera vela terminada solo siembra el estado inicial. Las alertas empiezan con la segunda vela terminada cuando se observa un cambio real de relación.
Formato de notificación
Las alertas replican el mensaje del EA original: Alert!!! - SYMBOL - TF - description.
El código de marco temporal se deriva del tipo de vela configurado. Se usan etiquetas estándar estilo MetaTrader (M1, M5, H1, etc.) cuando existen; otros marcos usan notación compacta (por ejemplo, M45 o D2).
Los mensajes se escriben con AddInfoLog, permitiendo enrutar a visores de log, scripts o dashboards GUI.
Parámetros
Short SMA Length: número de periodos de la media móvil rápida (predeterminado 3).
Medium SMA Length: número de periodos de la media móvil intermedia (predeterminado 20).
Long SMA Length: número de periodos de la media móvil lenta (predeterminado 150).
Candle Type: marco temporal usado para calcular las medias móviles. El valor predeterminado son velas de 1 minuto, igualando las comprobaciones por tick del EA con alta reactividad.
Notas adicionales
La estrategia no envía, modifica ni cancela órdenes. Es puramente informativa.
Como Bind alimenta valores finalizados, cada cruce se evalúa sobre velas completadas. Esto evita los giros ruidosos intrabar que el EA original mitigaba contando ticks.
Las notificaciones basadas en logging pueden integrarse con handlers personalizados suscribiéndose a eventos de log de la estrategia dentro de una aplicación anfitriona.
No se proporciona traducción Python por ahora; solo la versión C# se incluye en el paquete API.
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()