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
Suscribirse al CandleType configurado y calcular un WeightedMovingAverage con el período tomado de MaPeriod.
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.
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
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.
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.
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-loss – StopLossPips 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
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.
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.
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.
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class gold_dust_strategy(Strategy):
def __init__(self):
super(gold_dust_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(gold_dust_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(gold_dust_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return gold_dust_strategy()