Esta estrategia es la conversión de StockSharp del MetaTrader 4 asesor experto ubicado en MQL/8606/EMA_CROSS_2.mq4. Preserva la idea original de rastrear la relación entre una media móvil exponencial lenta y rápida y abrir una posición de mercado única cuando se produce un cruce. Las salidas protectoras (takeprofit, stop loss y trailing stop) se manejan a través del asistente de alto nivel StartProtection, por lo que el comportamiento refleja la implementación de MetaTrader mientras se utilizan las mejores prácticas de StockSharp.
Lógica comercial
Construya velas con el CandleType configurable (barras de 15 minutos por defecto) y alimente dos indicadores EMA: el EMA lenta usa SlowEmaLength y el EMA rápida usa FastEmaLength.
Mantenga la última dirección del EMA lento en relación con el EMA rápido. La primera vela completa después de que se forman ambos indicadores se usa solo para inicializar esta dirección, al igual que la guardia first_time en el asesor original.
Cuando el EMA lento se mueve por encima del EMA rápido (la nueva dirección se convierte en 1) y la estrategia es plana, envíe una orden de compra de mercado. Cuando el EMA lento se mueve por debajo del EMA rápido (la nueva dirección se convierte en 2) y la estrategia es plana, envíe una orden de venta de mercado. Esto reproduce exactamente el mapeo arriba/abajo de la función MQL Crossed(LEma, SEma).
Sólo puede haber una posición activa a la vez. Mientras una operación está abierta (o la orden de entrada aún está pendiente), se ignoran los cruces adicionales.
Gestión comercial y de riesgos
StartProtection configura las distancias de toma de ganancias, stop loss y trailing stop en unidades de precio calculadas a partir del instrumento PriceStep. Las paradas dinámicas son opcionales: establezca TrailingStopPips en cero para desactivarlas.
Las órdenes se realizan con BuyMarket/SellMarket y se cierran por mercado cuando se activa cualquier nivel de protección, exactamente igual que OrderSend y la lógica de seguimiento del asesor original.
El tamaño del lote base está controlado por OrderVolume. Antes de cada entrada se alinea con el paso de volumen del instrumento, mínimo y máximo para evitar rechazo.
Parámetros
Parámetro
Descripción
TakeProfitPips
Distancia en pips (pasos de precio) utilizada para la toma de ganancias protectora. Predeterminado: 20.
StopLossPips
Distancia en pips utilizada para el stop loss de protección. Predeterminado: 30.
TrailingStopPips
Distancia de seguimiento en pips. Establezca en 0 para deshabilitar el seguimiento. Predeterminado: 50.
OrderVolume
Tamaño de lote de las entradas al mercado antes de la alineación. Predeterminado: 2.
FastEmaLength
Periodo de la EMA rápida aplicado a los precios de cierre. Predeterminado: 5.
SlowEmaLength
Período de la EMA lenta aplicada a los precios de cierre. Predeterminado: 60.
CandleType
Plazo para la construcción de velas. Predeterminado: 15 minutos.
Notas
La estrategia espera hasta que ambos EMA estén completamente formados antes de reaccionar ante un cruce, eliminando la verificación Bars < 100 del script MQL y logrando la misma estabilidad.
Debido a que solo se utilizan órdenes de mercado, no hay llamadas OrderModify individuales. El módulo de protección incorporado reposiciona automáticamente el trailing stop de la misma manera que el bucle MetaTrader actualizó OrderStopLoss.
No se proporciona ningún puerto Python (por solicitud); solo se incluye la implementación de C#.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// EMA crossover strategy with trailing stop.
/// Buys when fast EMA crosses above slow EMA, sells on the opposite crossover.
/// </summary>
public class EmaCrossTrailingStrategy : Strategy
{
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<DataType> _candleType;
private int _currentDirection;
private bool _hasInitialDirection;
public EmaCrossTrailingStrategy()
{
_fastEmaLength = Param(nameof(FastEmaLength), 5)
.SetDisplay("Fast EMA", "Length of the fast exponential moving average.", "Indicator");
_slowEmaLength = Param(nameof(SlowEmaLength), 60)
.SetDisplay("Slow EMA", "Length of the slow exponential moving average.", "Indicator");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Time frame used to build candles and EMAs.", "General");
}
public int FastEmaLength
{
get => _fastEmaLength.Value;
set => _fastEmaLength.Value = value;
}
public int SlowEmaLength
{
get => _slowEmaLength.Value;
set => _slowEmaLength.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_currentDirection = 0;
_hasInitialDirection = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
// Determine direction: 1 = fast above slow (bullish), -1 = fast below slow (bearish)
var newDirection = fastValue > slowValue ? 1 : fastValue < slowValue ? -1 : 0;
if (newDirection == 0)
return;
if (!_hasInitialDirection)
{
_currentDirection = newDirection;
_hasInitialDirection = true;
return;
}
if (newDirection == _currentDirection)
return;
var prevDirection = _currentDirection;
_currentDirection = newDirection;
// Crossover detected
if (newDirection == 1 && prevDirection == -1)
{
// Bullish crossover
if (Position < 0)
BuyMarket(); // Close short
if (Position <= 0)
BuyMarket(); // Open long
}
else if (newDirection == -1 && prevDirection == 1)
{
// Bearish crossover
if (Position > 0)
SellMarket(); // Close long
if (Position >= 0)
SellMarket(); // Open short
}
}
}
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 ema_cross_trailing_strategy(Strategy):
"""
EMA crossover strategy with trailing stop.
Buys when fast EMA crosses above slow EMA, sells on opposite crossover.
"""
def __init__(self):
super(ema_cross_trailing_strategy, self).__init__()
self._fast_ema_length = self.Param("FastEmaLength", 5) \
.SetDisplay("Fast EMA", "Length of the fast EMA", "Indicator")
self._slow_ema_length = self.Param("SlowEmaLength", 60) \
.SetDisplay("Slow EMA", "Length of the slow EMA", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Time frame for candles and EMAs", "General")
self._current_direction = 0
self._has_initial_direction = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ema_cross_trailing_strategy, self).OnReseted()
self._current_direction = 0
self._has_initial_direction = False
def OnStarted2(self, time):
super(ema_cross_trailing_strategy, self).OnStarted2(time)
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self._fast_ema_length.Value
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self._slow_ema_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast_ema, slow_ema, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, slow_ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_val)
slow_val = float(slow_val)
if fast_val > slow_val:
new_direction = 1
elif fast_val < slow_val:
new_direction = -1
else:
return
if not self._has_initial_direction:
self._current_direction = new_direction
self._has_initial_direction = True
return
if new_direction == self._current_direction:
return
prev_direction = self._current_direction
self._current_direction = new_direction
if new_direction == 1 and prev_direction == -1:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
elif new_direction == -1 and prev_direction == 1:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
def CreateClone(self):
return ema_cross_trailing_strategy()