La estrategia replica el asesor experto de MetaTrader "Crossover_2EMA" operando la relación entre una media móvil exponencial (EMA) rápida y una lenta calculadas sobre precios de cierre. Cuando la EMA rápida sube por encima de la lenta, el algoritmo entra largo. Cuando vuelve a caer por debajo, revierte a una posición corta. El enfoque mantiene siempre la posición alineada con el estado actual de tendencia rápida/lenta y, por tanto, funciona como un sistema totalmente reversible.
Lógica de negociación
Suscribirse a la serie de velas configurada y calcular dos EMA con periodos definidos por el usuario.
Seguir el spread entre los valores de EMA rápida y lenta en cada vela terminada.
Detectar un cruce alcista cuando el spread pasa de no positivo a positivo. Cerrar cualquier exposición corta y abrir una posición larga con el volumen configurado.
Detectar un cruce bajista cuando el spread pasa de no negativo a negativo. Cerrar cualquier exposición larga y abrir una posición corta con el volumen configurado.
Las órdenes se emiten a mercado para asegurar reacción inmediata al cruce. El volumen se aumenta automáticamente al revertir para aplanar la posición existente antes de abrir una nueva.
Gestión de riesgo
La estrategia invoca StartProtection() al iniciarse para que puedan configurarse los mecanismos protectores estándar de StockSharp (por ejemplo, protección contra drawdown, límites de horario o circuit breakers).
Las reversiones de posición usan una única orden de mercado combinada, reduciendo la latencia frente a una salida y reentrada secuenciales.
Parámetros
Candle Type: serie de datos usada para los cálculos EMA.
Fast EMA Period: periodo de la EMA rápida. Debe ser menor que el periodo de la EMA lenta.
Slow EMA Period: periodo de la EMA lenta. Debe ser mayor que el periodo de la EMA rápida.
Notas adicionales
Ambas EMA deben estar completamente formadas antes de iniciar operaciones, evitando señales prematuras.
La configuración predeterminada usa EMA de 12/24 periodos sobre velas de un minuto, replicando el experto MQL original.
Los parámetros están marcados como optimizables, permitiendo optimización por lotes en StockSharp.
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Crossover 2 EMA strategy: Fast/slow EMA crossover.
/// Buys when fast EMA crosses above slow EMA.
/// Sells when fast EMA crosses below slow EMA.
/// </summary>
public class Crossover2EmaStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
public Crossover2EmaStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
decimal? prevFast = null;
decimal? prevSlow = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, slow, (candle, fastVal, slowVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (prevFast.HasValue && prevSlow.HasValue)
{
var crossUp = prevFast.Value <= prevSlow.Value && fastVal > slowVal;
var crossDown = prevFast.Value >= prevSlow.Value && fastVal < slowVal;
if (crossUp && Position <= 0)
BuyMarket();
else if (crossDown && Position >= 0)
SellMarket();
}
prevFast = fastVal;
prevSlow = slowVal;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class crossover2_ema_strategy(Strategy):
def __init__(self):
super(crossover2_ema_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._fast_period = self.Param("FastPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetGreaterThanZero() \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._prev_fast = None
self._prev_slow = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(crossover2_ema_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(crossover2_ema_strategy, self).OnStarted2(time)
self._fast_ind = ExponentialMovingAverage()
self._fast_ind.Length = self._fast_period.Value
self._slow_ind = ExponentialMovingAverage()
self._slow_ind.Length = self._slow_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._fast_ind, self._slow_ind, self._process_candle).Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if self._prev_fast is not None and self._prev_slow is not None:
cross_up = self._prev_fast <= self._prev_slow and fast_val > slow_val
cross_down = self._prev_fast >= self._prev_slow and fast_val < slow_val
if cross_up and self.Position <= 0:
self.BuyMarket()
elif cross_down and self.Position >= 0:
self.SellMarket()
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return crossover2_ema_strategy()