Ver en GitHub

Estrategia Gold Dust

Descripción

La estrategia Gold Dust reproduce el asesor experto de MetaTrader 5 "Gold Dust" dentro del framework StockSharp. Evalúa hasta dos perceptrones construidos a partir de una media móvil ponderada linealmente (LWMA) aplicada al precio ponderado de la vela. Cada perceptrón observa cómo el precio se desvía de la media móvil en cuatro puntos de retroceso diferentes separados por el período de la MA. Cuando la salida del perceptrón es positiva, el experto original abre una posición de venta, y cuando es negativa abre una compra. El port de StockSharp mantiene el mismo comportamiento basándose en la API de suscripción de velas de alto nivel.

Generación de señales

  1. Suscribirse al CandleType configurado y calcular un WeightedMovingAverage con el período tomado de MaPeriod.
  2. En cada vela terminada, almacenar los precios de apertura y cierre de la vela junto con el valor de LWMA. La estrategia siempre mantiene tres períodos completos de MA de historia para replicar las llamadas CopyRates/CopyBuffer de la versión MQL.
  3. Calcular los desplazamientos precio/MA:
    • a1 – cierre actual menos LWMA actual
    • a2 – precio de apertura hace un período de MA menos LWMA en la misma vela
    • a3 – precio de apertura hace dos períodos de MA menos LWMA en la misma vela
    • a4 – precio de apertura hace tres períodos de MA menos LWMA en la misma vela
  4. Construir la salida del perceptrón result = Σ (wi × ai) donde cada peso es el parámetro bruto (por ejemplo X11) menos 100, replicando la transformación original w = x - 100.
  5. Interpretar las salidas del perceptrón dependiendo de PassMode:
    • 1 – usar solo el primer perceptrón.
    • 2 – usar solo el segundo perceptrón.
    • 3 – requerir que ambos perceptrones produzcan el mismo signo distinto de cero.
  6. Una señal negativa abre o mantiene una posición larga, una señal positiva abre o mantiene una posición corta, y una señal de cero activa la toma de beneficios en posiciones existentes.

Gestión de posiciones

  • Entradas – la estrategia opera con un TradeVolume fijo. Entrar en largo cierra cualquier exposición corta pendiente y viceversa, de modo que solo permanece una posición direccional, replicando el comportamiento de m_need_open_buy/m_need_open_sell en el código original.
  • Stop-lossStopLossPips se convierte en distancia de precio absoluta usando Security.PriceStep. Para instrumentos cotizados con tres o cinco decimales, la distancia se multiplica por diez para imitar la lógica de "punto ajustado" en la versión MQL. El stop se evalúa en cada vela completada: si el mínimo de la vela (para largos) o el máximo (para cortos) cruza el nivel de stop, la posición se cierra con una orden de mercado.
  • Trailing stop – cuando TrailingStopPips es mayor que cero, la lógica de trailing se activa. Después de que el precio se mueve TrailingStopPips + TrailingStepPips a favor de la operación, el stop se posiciona en cierre ± TrailingStopPips (según la dirección). El trailing es basado en velas y crea un stop incluso si el stop-loss inicial estaba desactivado, igual que PositionModify en el EA.
  • Gestión de beneficios – cuando ningún perceptrón concuerda en una dirección (signal == 0), la estrategia cierra la posición solo si el beneficio flotante es positivo. Esto reproduce CloseProfitPositions donde swaps, comisiones y beneficio deben ser mayores que cero.

Parámetros

Parámetro Predeterminado Descripción
TradeVolume 1 Volumen base para cada nueva entrada. Las posiciones opuestas se aplanan antes de tomar un nuevo lado.
StopLossPips 150 Distancia inicial de stop-loss en pips ajustados (tiene en cuenta el multiplicador de 3/5 dígitos). Establecer en cero para deshabilitar el stop inicial.
TrailingStopPips 25 Distancia de trailing stop en pips ajustados. Establecer en cero para deshabilitar el trailing.
TrailingStepPips 5 Movimiento favorable adicional (en pips) requerido antes de que el trailing stop avance.
MaPeriod 20 Longitud del período de la media móvil ponderada que alimenta los perceptrones.
CandleType H1 Serie de velas usada para evaluación de señales. Se puede seleccionar cualquier otro marco temporal compatible con el proveedor de datos.
PassMode 1 Controla qué perceptrón(es) se evalúan: 1 – primero, 2 – segundo, 3 – consenso de ambos.
X11, X21, X31, X41 100 Pesos brutos para el perceptrón #1. La estrategia resta 100 de cada valor antes de usarlo.
X12, X22, X32, X42 100 Pesos brutos para el perceptrón #2, manejados de la misma manera que el primer conjunto.

Notas sobre la conversión

  • El EA original dependía de actualizaciones tick a tick para gestionar stops; el port de StockSharp evalúa stops y trailing al cierre de vela. Esto mantiene la implementación dentro de la API de alto nivel mientras permanece fiel a la lógica general.
  • La gestión monetaria vía CMoneyFixedMargin fue reemplazada con un parámetro fijo TradeVolume. Los usuarios pueden integrar su propia lógica de dimensionamiento de posición si es necesario.
  • Los cálculos del perceptrón evitan buffers directos de indicadores (CopyBuffer) almacenando en caché los valores necesarios de velas y MA en listas acotadas.
  • Todas las distancias de pips respetan la convención de "punto ajustado" de MetaTrader: si el instrumento opera con 3 o 5 decimales, la distancia se multiplica por diez antes de aplicarse a los niveles de precio.

Consejos de uso

  1. Crear o seleccionar un símbolo, luego establecer CandleType al marco temporal que corresponde al gráfico histórico usado en la versión MQL.
  2. Revisar los pesos del perceptrón (X**) y PassMode para que coincidan con la configuración optimizada de MetaTrader. Cada peso puede optimizarse independientemente dentro de StockSharp.
  3. Ajustar TradeVolume para que cumpla con el tamaño mínimo y el paso del broker conectado. La estrategia agrega automáticamente la exposición opuesta absoluta al cambiar de dirección.
  4. Monitorear el log: cada vez que el trailing stop avanza o se activa un stop-loss, se registra un mensaje, lo que ayuda a verificar que el port se comporta como el EA original.
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 GoldDustStrategy : 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 GoldDustStrategy()
	{
		_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;
	}
}