Exp Highs Lows Signal es un port directo del asesor experto MetaTrader 5 Exp_HighsLowsSignal. La estrategia depende de un detector de patrones que busca un número configurable de velas consecutivas que imprimen máximos más altos y mínimos más altos (secuencia alcista) o máximos más bajos y mínimos más bajos (secuencia bajista). Una vez encontrada una secuencia, la estrategia retrasa la reacción por el número configurado de barras cerradas, cierra cualquier exposición opuesta y opcionalmente abre una posición en la dirección detectada. Los stops protectores se expresan en pasos de precio para reflejar la gestión de dinero basada en puntos del robot original.
Lógica de la estrategia
Detector de secuencia de Highs/Lows
El detector evalúa cada vela finalizada en el marco temporal seleccionado.
Una señal alcista requiere SequenceLength comparaciones consecutivas donde tanto el máximo actual como el mínimo actual son estrictamente mayores que la barra anterior.
Una señal bajista requiere SequenceLength comparaciones consecutivas donde tanto el máximo actual como el mínimo actual son estrictamente menores que la barra anterior.
Las señales se encolan y se liberan después de SignalBarDelay velas cerradas, coincidiendo con el ajuste SignalBar de la implementación MQL.
Reglas de entrada
Entradas largas
Activadas cuando una secuencia alcista se vuelve activa y AllowLongEntry está habilitado.
Primero se cierra cualquier posición corta existente (si AllowShortExit es verdadero), luego se envía una orden de compra de mercado con volumen OrderVolume + |Position| para cubrir cortos y establecer el tamaño largo deseado.
Entradas cortas
Activadas cuando una secuencia bajista se vuelve activa y AllowShortEntry está habilitado.
Primero se cierra cualquier posición larga existente (si AllowLongExit es verdadero), luego se envía una orden de venta de mercado con volumen OrderVolume + |Position| para cubrir largos y establecer el tamaño corto deseado.
Reglas de salida
Una secuencia alcista siempre solicita AllowShortExit para cerrar cortos abiertos.
Una secuencia bajista siempre solicita AllowLongExit para cerrar largos abiertos.
Cuando la bandera relevante está deshabilitada, la exposición opuesta permanece intacta, permitiendo al usuario operar solo en una dirección o ejecutar el detector en modo "solo alertas".
Gestión de riesgo
StopLossTicks y TakeProfitTicks representan distancias en pasos de precio (puntos). Un valor de 0 deshabilita la orden protectora correspondiente, reproduciendo el comportamiento del EA original.
StartProtection convierte esas distancias en desplazamientos de precio absolutos para que todas las entradas de mercado reciban automáticamente órdenes de stop-loss y take-profit correspondientes.
Parámetros
OrderVolume – volumen base de orden usado cuando se abre una nueva operación.
AllowLongEntry / AllowShortEntry – interruptores que habilitan entradas largas o cortas en sus respectivas señales.
AllowLongExit / AllowShortExit – interruptores que permiten a la estrategia aplanar posiciones opuestas cuando aparece la señal contra-tendencia.
StopLossTicks / TakeProfitTicks – distancias protectoras en pasos de precio; establezca en 0 para deshabilitar.
SequenceLength – número de comparaciones consecutivas requeridas para calificar una secuencia alcista o bajista (equivalente a HowManyCandles en MT5).
SignalBarDelay – número de velas cerradas a esperar antes de actuar en una señal (equivalente al input SignalBar).
CandleType – marco temporal usado para construir el detector de Highs/Lows (por defecto: velas de 4 horas).
Notas adicionales
La estrategia almacena solo la cantidad mínima de historial de velas requerida para el detector, manteniendo el comportamiento idéntico al indicador personalizado de MT5.
Debido a que toda la gestión de órdenes ocurre a través de StartProtection, los backtests y el trading en vivo reciben automáticamente órdenes de stop y take-profit correspondientes sin código adicional.
Deshabilite las banderas Allow correspondientes para convertir la estrategia en un filtro direccional o una herramienta de señalización pura.
No se proporciona traducción a Python; solo la versión C# está disponible en este paquete.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Highs Lows Signal strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class ExpHighsLowsSignalStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public ExpHighsLowsSignalStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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 exp_highs_lows_signal_strategy(Strategy):
def __init__(self):
super(exp_highs_lows_signal_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(exp_highs_lows_signal_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(exp_highs_lows_signal_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return exp_highs_lows_signal_strategy()