Ver en GitHub

Estrategia de cuadrícula de bandas elásticas

Descripción general

  • Conversión del asesor experto MetaTrader 4 RUBBERBANDS_2.mq4.
  • Ejecuta una cuadrícula simétrica alrededor del precio actual utilizando las mejores cotizaciones de oferta/demanda en lugar de velas.
  • Mantiene libros de contabilidad separados para exposición larga y corta para que el comportamiento coincida con la implementación de MT4 con cobertura.
  • Implementa controles de pérdidas y ganancias a nivel de sesión y un modo de reposo/parada manual idéntico a las entradas originales.

Lógica de trading

  1. La estrategia se suscribe a SubscribeLevel1() y reacciona a cada cambio de la mejor oferta y la mejor demanda.
  2. Dos extremos flotantes (_upperExtreme / _lowerExtreme) capturan el precio de venta más alto y más bajo alcanzado desde el último reinicio. Se inicializan a partir de parámetros cuando UseInitialValues es verdadero; de lo contrario, se utiliza el primer precio de venta recibido.
  3. Cuando no hay operaciones abiertas y el tiempo del servidor llega al primer tic de un minuto (el segundo es igual a cero), la estrategia solicita tanto una compra de mercado como una venta de mercado. Esto refleja el comportamiento de MT4, donde las banderas de compra/venta se activan cada minuto mientras el libro está vacío.
  4. Cada vez que el precio de venta avanza GridStepPoints puntos por encima del máximo almacenado, se emite una nueva orden de venta. Cada caída de la misma distancia por debajo del mínimo almacenado desencadena una nueva orden de compra. Los extremos se actualizan a la demanda actual después de cada activación, de modo que la escalera se "estire" con el precio.
  5. El número total de operaciones abiertas simultáneamente (suma de tramos largos y cortos) está limitado por MaxTrades.
  6. El beneficio flotante se calcula a partir de la oferta/demanda actual: los beneficios a largo plazo utilizan la oferta menos el precio medio a largo plazo, los beneficios a corto plazo utilizan el precio medio a corto menos el precio de venta. El asistente PriceToMoney convierte las diferencias de precios en la moneda de la cuenta usando PriceStep/StepPrice cuando esté disponible.
  7. Cuando el beneficio flotante alcanza SessionTakeProfitPerLot * OrderVolume y UseSessionTakeProfit está habilitado, toda la exposición se estabiliza. Del mismo modo, la pérdida flotante por debajo de -SessionStopLossPerLot * OrderVolume desencadena una salida completa cuando UseSessionStopLoss está habilitado.
  8. Los indicadores manuales reproducen las opciones originales de EA: CloseNow impone un inicio plano, QuiesceMode mantiene la estrategia inactiva mientras está plana y StopNow detiene nuevas entradas sin interferir con las posiciones existentes.

Parámetros

Parámetro Descripción
OrderVolume Volumen para cada orden de mercado (MT4 Lots).
MaxTrades Recuento máximo de operaciones abiertas simultáneamente (MT4 maxcount).
GridStepPoints Distancia en puntos de precio entre capas de la cuadrícula (MT4 pipstep).
QuiesceMode Si está habilitada, la estrategia espera una vez, idéntica a quiescenow.
TriggerImmediateEntries Abre una compra y venta inicial tan pronto como la estrategia esté lista (donow).
StopNow Detiene las entradas automáticas mientras mantiene vivas las posiciones actuales (stopnow).
CloseNow Solicita un aplanamiento inmediato al inicio (closenow).
UseSessionTakeProfit & SessionTakeProfitPerLot Objetivo de beneficio flotante a nivel de sesión por lote.
UseSessionStopLoss & SessionStopLossPerLot Umbral de pérdida flotante a nivel de sesión por lote.
UseInitialValues, InitialMax, InitialMin Soporte de reinicio opcional que reutiliza los extremos anteriores (useinvalues, inmax, inmin).

Notas de implementación

  • Todo el estado interno tiene tabulaciones y se almacena en campos en lugar de colecciones para seguir las pautas del proyecto.
  • Las órdenes de mercado se limitan mediante el seguimiento de _activeBuyOrder y _activeSellOrder, por lo que no se envían solicitudes duplicadas mientras la anterior está pendiente.
  • La contabilidad de cobertura se realiza en OnOwnTradeReceived, donde los precios/volúmenes promedio a corto y largo plazo se actualizan de forma independiente y se convierten en ganancias flotantes para la lógica de parada.
  • TryCloseAll() refleja la rutina MT4 close1by1() al enviar órdenes de mercado opuestas hasta que ambos libros contables estén planos y luego restablecer los extremos a la última solicitud.
  • La estrategia se basa exclusivamente en llamadas API de alto nivel (SubscribeLevel1(), BuyMarket, SellMarket) y evita el acceso directo a los indicadores como lo exigen las reglas del repositorio.
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Rubber Bands Grid: Mean reversion grid using SMA+ATR bands.
/// </summary>
public class RubberBandsGridStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _smaLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _entryPrice;
	private int _gridCount;

	public RubberBandsGridStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_smaLength = Param(nameof(SmaLength), 20)
			.SetDisplay("SMA Length", "SMA period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int SmaLength
	{
		get => _smaLength.Value;
		set => _smaLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

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

		_entryPrice = 0;
		_gridCount = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_entryPrice = 0;
		_gridCount = 0;

		var sma = new SimpleMovingAverage { Length = SmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(sma, atr, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, sma);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal smaVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (atrVal <= 0)
			return;

		var close = candle.ClosePrice;
		var lower = smaVal - atrVal * 2m;
		var upper = smaVal + atrVal * 2m;

		if (Position > 0)
		{
			if (close >= smaVal)
			{
				SellMarket();
				_entryPrice = 0;
				_gridCount = 0;
			}
			else if (close <= _entryPrice - atrVal * 4m)
			{
				SellMarket();
				_entryPrice = 0;
				_gridCount = 0;
			}
			else if (_gridCount < 3 && close <= _entryPrice - atrVal)
			{
				_entryPrice = (_entryPrice + close) / 2m;
				_gridCount++;
				BuyMarket();
			}
		}
		else if (Position < 0)
		{
			if (close <= smaVal)
			{
				BuyMarket();
				_entryPrice = 0;
				_gridCount = 0;
			}
			else if (close >= _entryPrice + atrVal * 4m)
			{
				BuyMarket();
				_entryPrice = 0;
				_gridCount = 0;
			}
			else if (_gridCount < 3 && close >= _entryPrice + atrVal)
			{
				_entryPrice = (_entryPrice + close) / 2m;
				_gridCount++;
				SellMarket();
			}
		}

		if (Position == 0)
		{
			if (close <= lower)
			{
				_entryPrice = close;
				_gridCount = 0;
				BuyMarket();
			}
			else if (close >= upper)
			{
				_entryPrice = close;
				_gridCount = 0;
				SellMarket();
			}
		}
	}
}