Ver en GitHub

Estrategia Gap DM

Descripción

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

  1. Suscribirse a la serie de velas especificada por CandleType.
  2. Esperar que cada vela termine (CandleStates.Finished).
  3. Comparar el precio de cierre de la vela anterior con el precio de apertura de la vela actual.
  4. 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.
  5. 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.
  6. 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.
  7. 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

  1. Restablecer el estado cacheado en cada reinicio (umbrales de gap, niveles de stop, cierre anterior).
  2. Iniciar la suscripción de velas y dibujar elementos del gráfico (velas y operaciones) cuando hay un área de gráfico disponible.
  3. 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.
  4. 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;
	}
}