Ver en GitHub

Estrategia de Cuadrícula con Cobertura Tunnel Gen4

Esta estrategia replica la lógica del experto MetaTrader "Tunnel gen4" usando la API de alto nivel de StockSharp. Mantiene una cobertura neutral al mercado abriendo un par inicial de compra/venta, duplica la posición en la dirección de la ruptura una vez que el precio recorre un número configurable de pips, y sale de toda la cesta cuando la misma distancia se cubre nuevamente más allá del segundo ancla.

Lógica de Trading

  • Cobertura inicial: Tan pronto como no existe exposición, la estrategia envía órdenes simultáneas de compra y venta a mercado con volumen StartVolume. La primera ejecución define el precio de referencia para todas las decisiones posteriores.
  • Detección de paso: El StepPips configurado se convierte en un desplazamiento de precio usando el tamaño de tick del instrumento (con ajustes automáticos para citas forex de tres y cinco decimales). Las actualizaciones del mejor bid/ask del flujo Level 1 se comparan contra este desplazamiento.
  • Orden de refuerzo: Cuando el mejor bid sube al menos un paso desde la primera ejecución, se envía una orden de venta con el doble del volumen base. Cuando el mejor ask baja al menos un paso, se emite una orden de compra del mismo tamaño. La primera ejecución de esta orden se convierte en el segundo ancla.
  • Terminación del ciclo: Después de que el segundo ancla está activo, cualquier movimiento adicional del tamaño de un paso en cualquier dirección activa la liquidación completa de todas las posiciones abiertas. Una vez que ambos lados están cerrados, el estado se reinicia y puede comenzar un nuevo ciclo.
  • Validación de volumen: El inicio de la estrategia verifica que tanto los volúmenes inicial como duplicado respeten los requisitos mínimos, máximos e incrementales del instrumento, para que cada orden enviada al conector sea ejecutable.

Condiciones de Entrada

Refuerzo largo

  • Hay al menos una posición abierta del cobertura inicial.
  • El segundo ancla aún no ha sido creado.
  • El precio actual del mejor ask es menor o igual a first_fill_price - StepPips_en_precio.

Refuerzo corto

  • Hay al menos una posición abierta de la cobertura inicial.
  • El segundo ancla aún no ha sido creado.
  • El precio actual del mejor bid es mayor o igual a first_fill_price + StepPips_en_precio.

Gestión de Salida

  • Cierre de cesta: Una vez que el segundo ancla está definido, si el mejor bid sube por encima de second_anchor + StepOffset o el mejor ask cae por debajo de second_anchor - StepOffset, se envían órdenes a mercado para cerrar la exposición larga y corta acumulada. Las órdenes de cierre se rastrean para garantizar que el estado se reinicie solo después de que todas las operaciones sean confirmadas.
  • Reinicio de estado: Después de que ambos lados estén cerrados y no queden órdenes de cierre activas, la estrategia limpia los anclas internos y espera a que se abra una nueva cobertura.

Datos e Indicadores

  • La suscripción Level 1 entrega los mejores precios bid y ask usados para las comparaciones de paso.
  • No se requieren indicadores adicionales; toda la lógica funciona con actualizaciones de cotización en bruto.
  • La conversión del paso de precio imita el ajuste punto-a-pip de MetaTrader, por lo que los símbolos forex con tres o cinco decimales se comportan igual que en el experto origen.

Parámetros

Parámetro Descripción
StartVolume Volumen de las órdenes de compra y venta que forman la cobertura inicial.
StepPips Distancia en pips que activa la orden de refuerzo y la posterior salida de la cesta.

Notas de Implementación

  • StockSharp mantiene una posición neta por instrumento. La estrategia mantiene contadores de exposición internos para emular los tickets largos y cortos separados usados por el experto MetaTrader y emite órdenes a mercado con los volúmenes acumulados al cerrar la cesta.
  • Como la lógica depende de spreads en tiempo real, proporcione datos Level 1 tanto en backtests como en sesiones de trading en vivo. La falta de información bid/ask deshabilita el bucle de trading.
  • Asegúrese de que la cuenta de trading admita órdenes de compra y venta simultáneas para el mismo instrumento, ya que el algoritmo asume que ambos lados de la cobertura pueden coexistir hasta que se cumpla la condición de salida.
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>
/// Tunnel strategy that uses Bollinger Bands to define a price channel.
/// Buys when price crosses above the lower band (reversal from oversold),
/// sells when price crosses below the upper band (reversal from overbought).
/// </summary>
public class TunnelGen4Strategy : Strategy
{
	private readonly StrategyParam<int> _bbLength;
	private readonly StrategyParam<decimal> _bbWidth;
	private readonly StrategyParam<decimal> _stepPips;

	private BollingerBands _bb;

	private decimal _prevClose;
	private decimal _prevUpper;
	private decimal _prevLower;
	private decimal _entryPrice;

	/// <summary>
	/// Bollinger Bands period length.
	/// </summary>
	public int BbLength
	{
		get => _bbLength.Value;
		set => _bbLength.Value = value;
	}

	/// <summary>
	/// Bollinger Bands width (standard deviations).
	/// </summary>
	public decimal BbWidth
	{
		get => _bbWidth.Value;
		set => _bbWidth.Value = value;
	}

	/// <summary>
	/// Step distance expressed in pips for profit target.
	/// </summary>
	public decimal StepPips
	{
		get => _stepPips.Value;
		set => _stepPips.Value = value;
	}

	/// <summary>
	/// Initialize strategy parameters.
	/// </summary>
	public TunnelGen4Strategy()
	{
		_bbLength = Param(nameof(BbLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("BB Length", "Bollinger Bands period", "Indicator");

		_bbWidth = Param(nameof(BbWidth), 2.0m)
			.SetGreaterThanZero()
			.SetDisplay("BB Width", "Bollinger Bands width", "Indicator");

		_stepPips = Param(nameof(StepPips), 50m)
			.SetGreaterThanZero()
			.SetDisplay("Step (pips)", "Distance between tunnel anchors", "Trading");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_bb = null;
		_prevClose = 0;
		_prevUpper = 0;
		_prevLower = 0;
		_entryPrice = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_bb = new BollingerBands
		{
			Length = BbLength,
			Width = BbWidth
		};

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.BindEx(_bb, OnProcess);
		subscription.Start();
	}

	private void OnProcess(ICandleMessage candle, IIndicatorValue value)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var bb = (BollingerBandsValue)value;
		if (bb.UpBand is not decimal upper ||
			bb.LowBand is not decimal lower)
			return;

		if (!_bb.IsFormed)
		{
			_prevClose = candle.ClosePrice;
			_prevUpper = upper;
			_prevLower = lower;
			return;
		}

		var close = candle.ClosePrice;

		// Buy signal: price crosses above lower band from below
		if (_prevClose < _prevLower && close >= lower && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
		}
		// Sell signal: price crosses below upper band from above
		else if (_prevClose > _prevUpper && close <= upper && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
		}

		// Exit on profit target if in position
		if (Position > 0 && _entryPrice > 0)
		{
			var pipValue = Security?.PriceStep ?? 1m;
			var target = _entryPrice + StepPips * pipValue;
			if (close >= target)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			var pipValue = Security?.PriceStep ?? 1m;
			var target = _entryPrice - StepPips * pipValue;
			if (close <= target)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		_prevClose = close;
		_prevUpper = upper;
		_prevLower = lower;
	}
}