Ver en GitHub

Estrategia de proyección Gandalf PRO

Descripción general

La estrategia Gandalf PRO es una versión StockSharp del MetaTrader 4 asesor experto Gandalf_PRO. El robot original construye un filtro de suavizado adaptativo a partir de una media móvil ponderada y un componente de tendencia recursivo. Cuando el precio proyectado se mueve a al menos 15 pips más allá del precio de mercado actual, el EA entra en esa dirección con un stop-loss distante y una toma de ganancias en el nivel proyectado. La conversión StockSharp reproduce el mismo filtro y lógica de decisión mientras se basa en la vela de alto nivel. API para que cada cálculo se realice en barras terminadas.

Lógica comercial

  1. Suscríbase al período de tiempo seleccionado por CandleType (predeterminado: velas de 1 hora) y procese solo velas completadas.
  2. Mantenga un historial continuo de precios de cierre lo suficientemente grande como para cubrir el máximo de CountBuy y CountSell más una barra adicional.
  3. Recree la función MetaTrader Out(): calcule promedios móviles ponderados lineales y simples (usando un desplazamiento de una barra), obtenga el componentes recursivos s y t con el precio configurado y los factores de tendencia, y obtener el precio proyectado s[1] + t[1].
  4. Para configuraciones largas (EnableBuy):
    • Compruebe que el precio proyectado esté al menos 15 pips por encima del último cierre (Bid + 15*x*Point en MT4).
    • Si no hay ninguna posición larga abierta, compre el volumen configurado (consulte BaseVolume y BuyRiskMultiplier).
    • Almacene el precio proyectado como toma de ganancias y calcule el límite de pérdidas restando BuyStopLossPips convertido en pasos de precio.
  5. Para configuraciones breves (EnableSell):
    • Exija que el precio proyectado se sitúe al menos 15 pips por debajo del último cierre.
    • Si no hay ninguna posición corta abierta, venda el volumen configurado (revirtiendo una posición larga existente si es necesario).
    • Guarde el precio proyectado como toma de ganancias y establezca el límite de pérdidas SellStopLossPips pips por encima del mercado.
  6. Mientras exista una posición, monitoree cada vela terminada:
    • Salir de posiciones largas si el mínimo de la vela cruza el stop almacenado o el máximo alcanza la toma de ganancias.
    • Salga de los cortos si el máximo de la vela cruza el stop o el mínimo alcanza el objetivo.
    • Las salidas utilizan ClosePosition(), lo que aplana la exposición neta en StockSharp.

Parámetros

Nombre Tipo Predeterminado Descripción
EnableBuy bool true Permita que la estrategia abra posiciones largas.
CountBuy int 24 Longitud del filtro suavizante utilizado para proyecciones largas.
BuyPriceFactor decimal 0.18 Peso del cierre actual en el filtro recursivo largo.
BuyTrendFactor decimal 0.18 Ponderación aplicada al término de tendencia al construir la proyección larga.
BuyStopLossPips int 62 Distancia de stop-loss para posiciones largas, medida en pips.
BuyRiskMultiplier decimal 0 Multiplicador aplicado a BaseVolume antes de enviar una orden larga (0 mantiene el volumen base).
EnableSell bool true Permita que la estrategia abra posiciones cortas.
CountSell int 24 Longitud del filtro suavizante utilizado para proyecciones cortas.
SellPriceFactor decimal 0.18 Peso del cierre actual en el filtro recursivo corto.
SellTrendFactor decimal 0.18 Ponderación aplicada al término de tendencia al construir la proyección corta.
SellStopLossPips int 62 Distancia de stop-loss para posiciones cortas, medida en pips.
SellRiskMultiplier decimal 0 Multiplicador aplicado a BaseVolume antes de enviar una orden corta (0 mantiene el volumen base).
BaseVolume decimal 1 Tamaño de orden base utilizado cuando ambos multiplicadores de riesgo son cero.
CandleType DataType plazo de 1 hora Serie de velas procesadas por la estrategia.

Diferencias con el original MetaTrader EA

  • MetaTrader puede mantener entradas de compra y venta independientes simultáneamente. StockSharp utiliza posiciones netas, por lo que el puerto cierra o invierte una posición existente antes de abrir el lado opuesto.
  • La función de lote MT4 utilizaba margen libre de cuenta. La conversión expone BaseVolume y dos multiplicadores de riesgo; cuando son cero el volumen base se usa tal cual; de lo contrario, el volumen simplemente se escala (BaseVolume * RiskMultiplier).
  • Los niveles de stop-loss y take-profit se ejecutan monitoreando las velas completadas. Por lo tanto, los rellenos intrabarra pueden diferir de MetaTrader donde las órdenes de protección son gestionadas por el corredor.
  • El ajuste de cinco dígitos Digits/Point se emula inspeccionando Security.Decimals y Security.PriceStep para convertir pip. distancias a precios absolutos.
  • Todos los cálculos de los indicadores se realizan en código administrado sin llamar a iMA; el filtro recursivo se recrea en CalculateTarget usando los mismos coeficientes que la función MQL.

Notas de uso

  • Asigne el instrumento deseado a Strategy.Security antes de comenzar. La estrategia genera una excepción si no se adjunta ningún valor.
  • Configure BaseVolume para que coincida con el tamaño del contrato esperado por su lugar; ajuste los multiplicadores de riesgo solo si desea escalar la exposición relativa al volumen base.
  • El historial de velas debe contener al menos max(CountBuy, CountSell) + 1 barras antes de que se pueda generar cualquier operación. Proporcionar suficiente datos de calentamiento o iniciar la estrategia con velas históricas cargadas.
  • El búfer de entrada de 15 pips es fijo (al igual que en EA). Aumente CountBuy/CountSell para suavizar la proyección o modificar la factores de precio/tendencia para que coincidan con el comportamiento observado en MetaTrader.
  • Debido a que las salidas dependen de los extremos de las velas, habilite un período de tiempo que se adapte a su latencia de ejecución. Los plazos más cortos reaccionarán antes pero requieren más datos históricos y pueden generar más señales.

Detalles de implementación

  • Utiliza SubscribeCandles() con Bind(ProcessCandle) para que cada decisión se base en velas finalizadas.
  • Mantiene una lista compacta de cierres recientes y reconstruye el filtro recursivo s/t a pedido, imitando la rutina Out().
  • Convierte compensaciones basadas en pips a través del tamaño de tick del instrumento y la precisión decimal para replicar la escala MetaTrader x * Point.
  • ClosePosition() se invoca cuando se superan los niveles de protección, lo que garantiza que la posición neta se nivele antes de que se realice otra entrada. considerado.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Gandalf PRO trend-following strategy using adaptive smoothing filter.
/// Opens trades when projected price exceeds a buffer threshold.
/// </summary>
public class GandalfProProjectionStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _filterLength;
	private readonly StrategyParam<decimal> _priceFactor;
	private readonly StrategyParam<decimal> _trendFactor;
	private readonly StrategyParam<int> _atrLength;

	private readonly List<decimal> _closeBuffer = new();
	private decimal _entryPrice;

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

		_filterLength = Param(nameof(FilterLength), 24)
			.SetDisplay("Filter Length", "Smoothing filter length.", "Filter");

		_priceFactor = Param(nameof(PriceFactor), 0.18m)
			.SetDisplay("Price Factor", "Close price weight in filter.", "Filter");

		_trendFactor = Param(nameof(TrendFactor), 0.18m)
			.SetDisplay("Trend Factor", "Trend term weight in filter.", "Filter");

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

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

	public int FilterLength
	{
		get => _filterLength.Value;
		set => _filterLength.Value = value;
	}

	public decimal PriceFactor
	{
		get => _priceFactor.Value;
		set => _priceFactor.Value = value;
	}

	public decimal TrendFactor
	{
		get => _trendFactor.Value;
		set => _trendFactor.Value = value;
	}

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

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

		_closeBuffer.Clear();
		_entryPrice = 0;
	}

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

		var atr = new AverageTrueRange { Length = AtrLength };

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

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

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

		_closeBuffer.Add(candle.ClosePrice);
		var maxDepth = FilterLength + 2;
		while (_closeBuffer.Count > maxDepth)
			_closeBuffer.RemoveAt(0);

		if (_closeBuffer.Count <= FilterLength || atrVal <= 0)
			return;

		var close = candle.ClosePrice;
		var target = CalculateTarget();
		if (target == null)
			return;

		var targetPrice = target.Value;
		var buffer = atrVal * 0.3m;

		// Manage position
		if (Position > 0)
		{
			// Exit if projection flips below close or on stop
			if (targetPrice < close - buffer)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (targetPrice > close + buffer)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		// Entry
		if (Position == 0)
		{
			if (targetPrice > close + buffer)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (targetPrice < close - buffer)
			{
				_entryPrice = close;
				SellMarket();
			}
		}
	}

	private decimal? CalculateTarget()
	{
		var n = FilterLength;
		if (n < 2 || _closeBuffer.Count < n + 1)
			return null;

		var sum = 0m;
		for (var i = 1; i <= n; i++)
			sum += GetClose(i);

		var sm = sum / n;

		var weightedSum = 0m;
		for (var i = 0; i < n; i++)
		{
			var price = GetClose(i + 1);
			var weight = n - i;
			weightedSum += price * weight;
		}

		var denominator = (decimal)n * (n + 1) / 2m;
		if (denominator <= 0m)
			return null;

		var lm = weightedSum / denominator;
		var divisor = n - 1;
		if (divisor <= 0)
			return null;

		var s = new decimal[n + 2];
		var t = new decimal[n + 2];

		var tn = (6m * lm - 6m * sm) / divisor;
		var sn = 4m * sm - 3m * lm - tn;
		s[n] = sn;
		t[n] = tn;

		for (var k = n - 1; k > 0; k--)
		{
			var close = GetClose(k);
			s[k] = PriceFactor * close + (1m - PriceFactor) * (s[k + 1] + t[k + 1]);
			t[k] = TrendFactor * (s[k] - s[k + 1]) + (1m - TrendFactor) * t[k + 1];
		}

		return s[1] + t[1];
	}

	private decimal GetClose(int index)
	{
		var idx = _closeBuffer.Count - 1 - index;
		if (idx < 0) idx = 0;
		if (idx >= _closeBuffer.Count) idx = _closeBuffer.Count - 1;
		return _closeBuffer[idx];
	}
}