Gap DM es una estrategia de trading de gaps contrarian que rastrea la distancia entre el cierre de la sesión anterior y la apertura de la siguiente sesión. Cuando el mercado abre con un gap visible, la estrategia opera inmediatamente en la dirección opuesta, esperando que el precio revierta y llene el gap. La implementación sigue el algoritmo original de MetaTrader 5 "Gap DM" de cmillion, adaptado a la API de alto nivel de StockSharp. Todas las decisiones de trading se derivan de velas completadas del marco temporal seleccionado, asegurando un comportamiento determinista en backtests y ejecución en vivo.
Lógica de señal
Suscribirse a la serie de velas especificada por CandleType.
Esperar que cada vela termine (CandleStates.Finished).
Comparar el precio de cierre de la vela anterior con el precio de apertura de la vela actual.
Calcular el tamaño del gap en pips usando el paso de precio del instrumento. Un multiplicador de 10 se aplica automáticamente para cotizaciones de 3 y 5 dígitos, reproduciendo la conversión punto-a-pip de MT5.
Si la apertura actual está por debajo del cierre anterior por al menos Minimum Gap (pips), tratar como un gap bajista y entrar en largo.
Si la apertura actual está por encima del cierre anterior por al menos Minimum Gap (pips), tratar como un gap alcista y entrar en corto.
Omitir entradas cuando el trading no está permitido (por ejemplo, la estrategia está desconectada o todavía en calentamiento).
Dimensionamiento de posición y límites
Order Volume especifica el tamaño de lote para cada nueva operación. La estrategia también usa el valor para cerrar o revertir exposición existente, manteniendo la posición neta consistente con el modelo de contabilidad neta de StockSharp.
Max Positions define el volumen máximo agregado (en lotes) que se puede mantener en una dirección. Cuando se alcanza el límite, las nuevas entradas en la misma dirección se ignoran.
Al revertir de corto a largo (o viceversa), la estrategia agrega automáticamente el volumen necesario para cerrar la exposición opuesta antes de abrir la nueva posición.
Gestión de riesgo
Stop Loss (pips) coloca un stop protector relativo al precio de entrada. El stop se evalúa en cada vela completada. Si el rango de la vela toca el nivel de stop, la posición se cierra inmediatamente con una orden de mercado.
Take Profit (pips) funciona simétricamente al stop-loss. Establecer el parámetro en cero para deshabilitar el objetivo.
No se aplica trailing stop por defecto; la lógica de salida coincide con el Expert Advisor fuente.
Parámetros
Parámetro
Descripción
Predeterminado
Order Volume
Volumen de trading usado para cada entrada en lotes.
1
Stop Loss (pips)
Distancia del stop protector. Establecer en 0 para deshabilitar.
0
Take Profit (pips)
Distancia del objetivo de beneficio. Establecer en 0 para deshabilitar.
0
Minimum Gap (pips)
Diferencia mínima entre el cierre anterior y la apertura actual requerida para generar una señal.
1
Max Positions
Exposición acumulada máxima permitida en una sola dirección (en lotes).
15
Candle Type
Marco temporal usado para medir gaps de sesión.
1 Hora
Flujo de ejecución
Restablecer el estado cacheado en cada reinicio (umbrales de gap, niveles de stop, cierre anterior).
Iniciar la suscripción de velas y dibujar elementos del gráfico (velas y operaciones) cuando hay un área de gráfico disponible.
En cada vela terminada:
Actualizar o restablecer el stop activo y el objetivo dependiendo de la posición actual.
Evaluar las condiciones de gap y colocar órdenes de mercado cuando aparece una señal válida.
Volver a verificar las órdenes protectoras para que los eventos de stop-loss o take-profit dentro de la misma vela se manejen sin demora.
Almacenar el último cierre para la siguiente evaluación.
Notas y diferencias vs. la versión MT5 original
Las estrategias de StockSharp operan con posiciones netas. El algoritmo emula múltiples entradas escalando la exposición neta en lugar de crear tickets separados. Esto mantiene el comportamiento cercano al Expert Advisor de MT5 mientras respeta el modelo de contabilidad de StockSharp.
Todos los comentarios en el código fuente están en inglés, en concordancia con las pautas del proyecto.
La gestión monetaria mediante porcentaje de riesgo (modo risk en el script MT5) no se reproduce; en cambio, se proporciona un parámetro de volumen fijo. Establecer Order Volume al tamaño de lote que desea operar.
Requisitos
Compatible con cualquier instrumento que exponga un PriceStep válido.
Funciona con velas basadas en tiempo, volumen o rango soportadas por StockSharp, siempre que el concepto de gap sea significativo.
Requiere un entorno StockSharp capaz de ejecutar órdenes de mercado y monitorear operaciones propias.
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;
public class GapDMStrategy : 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 GapDMStrategy()
{
_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 gap_dm_strategy(Strategy):
def __init__(self):
super(gap_dm_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(gap_dm_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(gap_dm_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 gap_dm_strategy()