Estrategia EMA (Edición barabashkakvn)
Convertida del asesor experto MetaTrader 5 "EMA (barabashkakvn's edition)". El sistema opera el cruce de dos medias móviles exponenciales calculadas sobre el precio mediana y utiliza niveles virtuales de take-profit/stop-loss expresados en pips. Las posiciones solo se abren después de un cruce confirmado y un pequeño retroceso hacia el extremo de la vela anterior.
Idea central
- Seguimiento de EMA de 5 y 10 períodos (precio mediana) en el marco temporal seleccionado.
- Cuando la EMA rápida cruza la EMA lenta, armar una señal pendiente en lugar de operar inmediatamente.
- Esperar a que el precio retroceda
MoveBackPips desde el extremo de la vela anterior mientras el spread de EMA supera 2 * pipSize.
- Entrar en la dirección del cruce una vez que ocurra el retroceso.
- Gestionar la posición abierta con objetivos y stops virtuales medidos en pips desde el precio de entrada.
Este comportamiento refleja la implementación MQL original: el experto esperaba el indicador de cruce (check) y luego requería un spread de EMA más un retroceso de precio relativo a la vela anterior para activar la operación. Las reglas de salida también siguen el enfoque "virtual" cerrando posiciones cuando el bid/ask habría tocado las distancias especificadas.
Indicadores y datos
- EMA de 5 períodos sobre precio mediana (high + low) / 2.
- EMA de 10 períodos sobre precio mediana.
- Máximo/mínimo de la vela terminada anterior para verificaciones de retroceso.
- Todo el procesamiento usa velas terminadas de la suscripción
CandleType configurada.
Parámetros
| Parámetro |
Predeterminado |
Descripción |
OrderVolume |
0.1 |
Volumen de trading en lotes/contratos para cada entrada. |
VirtualProfitPips |
5 |
Distancia (en pips) entre el precio de entrada y el take-profit virtual. |
MoveBackPips |
3 |
Retroceso requerido después del cruce, medido desde el extremo de la vela anterior. |
StopLossPips |
20 |
Distancia (en pips) entre el precio de entrada y el stop-loss virtual. |
PipSize |
0.0001 |
Tamaño del pip expresado en unidades de precio. Sobreescribir cuando se operen instrumentos con una definición de pip diferente. |
FastLength |
5 |
Longitud de la EMA rápida. |
SlowLength |
10 |
Longitud de la EMA lenta. |
CandleType |
TimeFrame(1m) |
Fuente de velas usada para los cálculos. |
Todos los valores basados en pips se convierten a distancias de precio usando pipValue = PipSize. Si el parámetro se deja en cero o un número negativo la estrategia recurre a Security.PriceStep (cuando lo proporciona el board).
Lógica de trading
Condiciones de entrada
- Armado de señal: almacenar una señal pendiente siempre que ocurra un cruce (
FastEMA cruza por encima de SlowEMA o viceversa). Todavía no se coloca ninguna operación.
- Entrada corta: requiere
- Señal pendiente presente.
SlowEMA - FastEMA > 2 * pipSize.
- Máximo de la vela actual ≥ mínimo de la vela anterior +
MoveBackPips * pipSize (el precio retrocedió hacia arriba desde el mínimo anterior).
- Entrada larga: requiere
- Señal pendiente presente.
FastEMA - SlowEMA > 2 * pipSize.
- Mínimo de la vela actual ≤ máximo de la vela anterior -
MoveBackPips * pipSize (el precio retrocedió hacia abajo desde el máximo anterior).
Después de abrir una posición el indicador pendiente se resetea para evitar entradas duplicadas.
Condiciones de salida
Los objetivos virtuales emulan el comportamiento MQL comparando los extremos de la vela con las distancias preestablecidas:
- Posición larga:
- Cerrar si el máximo de la vela ≥ precio de entrada +
VirtualProfitPips * pipSize.
- Cerrar si el mínimo de la vela ≤ precio de entrada -
StopLossPips * pipSize.
- Posición corta:
- Cerrar si el mínimo de la vela ≤ precio de entrada -
VirtualProfitPips * pipSize.
- Cerrar si el máximo de la vela ≥ precio de entrada +
StopLossPips * pipSize.
Después de cualquier salida los niveles virtuales se resetean y la estrategia espera el próximo cruce.
Notas de implementación
- Usa la suscripción de velas de alto nivel (
SubscribeCandles) y dibuja EMAs más operaciones en el área de gráfico opcional.
- El precio mediana se computa directamente desde el high/low de la vela para coincidir con
PRICE_MEDIAN de MetaTrader.
- El indicador de cruce (
_hasCrossSignal) reproduce la variable check original, asegurando que las operaciones solo ocurran después de verificaciones de cruce y retroceso.
StartProtection() se llama en OnStarted para habilitar la monitorización de riesgo integrada aunque la estrategia gestione las salidas manualmente.
- El código mantiene todos los comentarios en inglés, según lo solicitado, y depende únicamente de velas terminadas sin acceder directamente a los buffers de indicadores.
Consejos de uso
- Ajustar
PipSize cuando se opere con instrumentos con definiciones de pip no estándar (p.ej., pares JPY, índices, cotizaciones cripto).
- Dado que las salidas dependen de los extremos de las velas, usar marcos temporales más cortos (1–5 minutos) mantiene el comportamiento más cercano al experto basado en ticks original.
- La optimización puede explorar longitudes de EMA, distancias en pips y valores de retroceso usando los metadatos de parámetros proporcionados.
- La estrategia opera una posición a la vez; cualquier posición externa en el mismo instrumento puede interferir con el seguimiento virtual de salidas.
Riesgos
- La simulación basada en velas puede perder toques intrababarra de los niveles virtuales; considerar datos de mayor resolución si la precisión es crítica.
- Las salidas virtuales no colocan órdenes protectoras reales, por lo que las desconexiones o el slippage pueden llevar a pérdidas mayores de lo esperado en trading en vivo.
- Como con cualquier sistema de cruce, el rendimiento se degrada en mercados laterales; combinar con filtros si es necesario.
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>
/// EMA crossover strategy with virtual take profit and stop loss distances.
/// Converted from the MQL5 expert "EMA (barabashkakvn's edition)".
/// </summary>
public class EmaBarabashkakvnEditionStrategy : Strategy
{
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<int> _virtualProfitPips;
private readonly StrategyParam<int> _moveBackPips;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<decimal> _pipSize;
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _fastEma;
private ExponentialMovingAverage _slowEma;
private bool _hasCrossSignal;
private decimal? _prevFast;
private decimal? _prevSlow;
private decimal? _prevHigh;
private decimal? _prevLow;
private decimal? _entryPrice;
private decimal? _virtualTarget;
private decimal? _virtualStop;
/// <summary>
/// Order volume in lots.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Virtual take profit distance in pips.
/// </summary>
public int VirtualProfitPips
{
get => _virtualProfitPips.Value;
set => _virtualProfitPips.Value = value;
}
/// <summary>
/// Retracement distance after a crossover in pips.
/// </summary>
public int MoveBackPips
{
get => _moveBackPips.Value;
set => _moveBackPips.Value = value;
}
/// <summary>
/// Virtual stop loss distance in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Pip size in price units.
/// </summary>
public decimal PipSize
{
get => _pipSize.Value;
set => _pipSize.Value = value;
}
/// <summary>
/// Fast EMA length applied to median price.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// Slow EMA length applied to median price.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="EmaBarabashkakvnEditionStrategy"/>.
/// </summary>
public EmaBarabashkakvnEditionStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Volume", "Order volume in lots", "Trading")
.SetOptimize(0.05m, 1m, 0.05m);
_virtualProfitPips = Param(nameof(VirtualProfitPips), 5)
.SetGreaterThanZero()
.SetDisplay("Virtual Profit", "Take profit distance in pips", "Risk")
.SetOptimize(2, 20, 1);
_moveBackPips = Param(nameof(MoveBackPips), 3)
.SetGreaterThanZero()
.SetDisplay("Move Back", "Retracement after crossover in pips", "Entries")
.SetOptimize(1, 10, 1);
_stopLossPips = Param(nameof(StopLossPips), 20)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Virtual stop loss distance in pips", "Risk")
.SetOptimize(10, 60, 2);
_pipSize = Param(nameof(PipSize), 0.0001m)
.SetGreaterThanZero()
.SetDisplay("Pip Size", "Instrument pip size in price units", "General");
_fastLength = Param(nameof(FastLength), 5)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA length on median price", "Indicators")
.SetOptimize(3, 15, 1);
_slowLength = Param(nameof(SlowLength), 10)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA length on median price", "Indicators")
.SetOptimize(8, 40, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Source candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastEma = default;
_slowEma = default;
_hasCrossSignal = false;
_prevFast = default;
_prevSlow = default;
_prevHigh = default;
_prevLow = default;
_entryPrice = default;
_virtualTarget = default;
_virtualStop = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastEma = new EMA { Length = FastLength };
_slowEma = new EMA { Length = SlowLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastEma);
DrawIndicator(area, _slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Calculate median price as in the original expert (PRICE_MEDIAN).
var medianPrice = (candle.HighPrice + candle.LowPrice) / 2m;
// Update EMA values using the median price.
var fastValue = _fastEma.Process(new DecimalIndicatorValue(_fastEma, medianPrice, candle.OpenTime) { IsFinal = true });
var slowValue = _slowEma.Process(new DecimalIndicatorValue(_slowEma, medianPrice, candle.OpenTime) { IsFinal = true });
if (!_fastEma.IsFormed || !_slowEma.IsFormed)
{
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
_prevFast = fastValue.ToDecimal();
_prevSlow = slowValue.ToDecimal();
return;
}
var fast = fastValue.ToDecimal();
var slow = slowValue.ToDecimal();
if (_prevFast is decimal prevFast && _prevSlow is decimal prevSlow)
{
var bullishCross = prevFast <= prevSlow && fast > slow;
var bearishCross = prevFast >= prevSlow && fast < slow;
if (bullishCross || bearishCross)
_hasCrossSignal = true;
}
_prevFast = fast;
_prevSlow = slow;
var pipValue = PipSize;
if (pipValue <= 0m)
pipValue = Security?.PriceStep ?? 0.0001m;
var moveBackPrice = MoveBackPips * pipValue;
var profitDistance = VirtualProfitPips * pipValue;
var stopDistance = StopLossPips * pipValue;
if (Position == 0 && _hasCrossSignal && _prevHigh is decimal prevHigh && _prevLow is decimal prevLow)
{
var bearishSpread = slow - fast;
var bullishSpread = fast - slow;
var bearishReady = bearishSpread > 2m * pipValue && candle.HighPrice >= prevLow + moveBackPrice;
var bullishReady = bullishSpread > 2m * pipValue && candle.LowPrice <= prevHigh - moveBackPrice;
if (bearishReady)
{
// Enter short after bearish cross and retracement above the previous low.
_entryPrice = candle.ClosePrice;
_virtualTarget = _entryPrice - profitDistance;
_virtualStop = _entryPrice + stopDistance;
SellMarket();
_hasCrossSignal = false;
}
else if (bullishReady)
{
// Enter long after bullish cross and retracement below the previous high.
_entryPrice = candle.ClosePrice;
_virtualTarget = _entryPrice + profitDistance;
_virtualStop = _entryPrice - stopDistance;
BuyMarket();
_hasCrossSignal = false;
}
}
else if (Position != 0 && _entryPrice is decimal && _virtualTarget is decimal target && _virtualStop is decimal stop)
{
if (Position > 0)
{
// Long position: use high for profit target and low for stop.
var hitTarget = candle.HighPrice >= target;
var hitStop = candle.LowPrice <= stop;
if (hitTarget || hitStop)
{
SellMarket();
_hasCrossSignal = false;
_entryPrice = null;
_virtualTarget = null;
_virtualStop = null;
}
}
else if (Position < 0)
{
// Short position: use low for profit target and high for stop.
var hitTarget = candle.LowPrice <= target;
var hitStop = candle.HighPrice >= stop;
if (hitTarget || hitStop)
{
BuyMarket();
_hasCrossSignal = false;
_entryPrice = null;
_virtualTarget = null;
_virtualStop = null;
}
}
}
if (Position == 0)
{
_entryPrice = null;
_virtualTarget = null;
_virtualStop = null;
}
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class ema_barabashkakvn_edition_strategy(Strategy):
def __init__(self):
super(ema_barabashkakvn_edition_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", 0.1)
self._virtual_profit_pips = self.Param("VirtualProfitPips", 5)
self._move_back_pips = self.Param("MoveBackPips", 3)
self._stop_loss_pips = self.Param("StopLossPips", 20)
self._pip_size = self.Param("PipSize", 0.0001)
self._fast_length = self.Param("FastLength", 5)
self._slow_length = self.Param("SlowLength", 10)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._has_cross_signal = False
self._prev_fast = None
self._prev_slow = None
self._prev_high = None
self._prev_low = None
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
@property
def OrderVolume(self):
return self._order_volume.Value
@OrderVolume.setter
def OrderVolume(self, value):
self._order_volume.Value = value
@property
def VirtualProfitPips(self):
return self._virtual_profit_pips.Value
@VirtualProfitPips.setter
def VirtualProfitPips(self, value):
self._virtual_profit_pips.Value = value
@property
def MoveBackPips(self):
return self._move_back_pips.Value
@MoveBackPips.setter
def MoveBackPips(self, value):
self._move_back_pips.Value = value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@StopLossPips.setter
def StopLossPips(self, value):
self._stop_loss_pips.Value = value
@property
def PipSize(self):
return self._pip_size.Value
@PipSize.setter
def PipSize(self, value):
self._pip_size.Value = value
@property
def FastLength(self):
return self._fast_length.Value
@FastLength.setter
def FastLength(self, value):
self._fast_length.Value = value
@property
def SlowLength(self):
return self._slow_length.Value
@SlowLength.setter
def SlowLength(self, value):
self._slow_length.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(ema_barabashkakvn_edition_strategy, self).OnStarted2(time)
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowLength
self._has_cross_signal = False
self._prev_fast = None
self._prev_slow = None
self._prev_high = None
self._prev_low = None
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
median_price = (high + low) / 2.0
fast_result = process_float(self._fast_ema, median_price, candle.OpenTime, True)
slow_result = process_float(self._slow_ema, median_price, candle.OpenTime, True)
if not self._fast_ema.IsFormed or not self._slow_ema.IsFormed:
self._prev_high = high
self._prev_low = low
self._prev_fast = float(fast_result)
self._prev_slow = float(slow_result)
return
fast = float(fast_result)
slow = float(slow_result)
if self._prev_fast is not None and self._prev_slow is not None:
bullish_cross = self._prev_fast <= self._prev_slow and fast > slow
bearish_cross = self._prev_fast >= self._prev_slow and fast < slow
if bullish_cross or bearish_cross:
self._has_cross_signal = True
self._prev_fast = fast
self._prev_slow = slow
pip_value = float(self.PipSize)
if pip_value <= 0.0:
pip_value = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 0.0001
move_back_price = int(self.MoveBackPips) * pip_value
profit_distance = int(self.VirtualProfitPips) * pip_value
stop_distance = int(self.StopLossPips) * pip_value
if self.Position == 0 and self._has_cross_signal and self._prev_high is not None and self._prev_low is not None:
bearish_spread = slow - fast
bullish_spread = fast - slow
bearish_ready = bearish_spread > 2.0 * pip_value and high >= self._prev_low + move_back_price
bullish_ready = bullish_spread > 2.0 * pip_value and low <= self._prev_high - move_back_price
if bearish_ready:
self._entry_price = close
self._virtual_target = self._entry_price - profit_distance
self._virtual_stop = self._entry_price + stop_distance
self.SellMarket()
self._has_cross_signal = False
elif bullish_ready:
self._entry_price = close
self._virtual_target = self._entry_price + profit_distance
self._virtual_stop = self._entry_price - stop_distance
self.BuyMarket()
self._has_cross_signal = False
elif self.Position != 0 and self._entry_price is not None and self._virtual_target is not None and self._virtual_stop is not None:
if self.Position > 0:
hit_target = high >= self._virtual_target
hit_stop = low <= self._virtual_stop
if hit_target or hit_stop:
self.SellMarket()
self._has_cross_signal = False
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
elif self.Position < 0:
hit_target = low <= self._virtual_target
hit_stop = high >= self._virtual_stop
if hit_target or hit_stop:
self.BuyMarket()
self._has_cross_signal = False
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
if self.Position == 0:
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
self._prev_high = high
self._prev_low = low
def OnReseted(self):
super(ema_barabashkakvn_edition_strategy, self).OnReseted()
self._has_cross_signal = False
self._prev_fast = None
self._prev_slow = None
self._prev_high = None
self._prev_low = None
self._entry_price = None
self._virtual_target = None
self._virtual_stop = None
def CreateClone(self):
return ema_barabashkakvn_edition_strategy()