Esta estrategia porta el asesor experto de MetaTrader 5 TDSGlobal de MQL/23255/TDSGlobal.mq5 a la API de alto nivel de StockSharp. Evalúa el Momentum en velas de cuatro horas a través de la línea MACD, el histograma MACD (OsMA) y el Índice de Fuerza. Cuando la combinación de indicadores señala una posible reversión, la estrategia envía órdenes límite pendientes alrededor de los extremos de la vela anterior y gestiona la posición resultante con lógica opcional de stop-loss, take-profit y trailing-stop.
La implementación reproduce el flujo de trabajo original adaptándolo a construcciones idiomáticas de StockSharp como StrategyParam<T>, suscripciones de velas mediante SubscribeCandles y manejo asíncrono de órdenes a través de los eventos del ciclo de vida de la estrategia.
Lógica de trading
Cálculo de indicadores
MACD(12, 26, 9) proporciona tanto la línea MACD como el histograma (OsMA).
ForceIndex(24) mide la fuerza de la última vela completada.
Cada indicador se actualiza al cierre del tipo de vela seleccionado (por defecto: 4 horas).
Detección de señales
El algoritmo espera hasta que estén disponibles dos valores históricos de MACD y OsMA para determinar su pendiente.
Una configuración de venta requiere que OsMA aumente (osma[1] > osma[2]) mientras que el Índice de Fuerza de la vela anterior sea negativo.
Una configuración de compra requiere que OsMA disminuya (osma[1] < osma[2]) mientras que el Índice de Fuerza anterior sea positivo.
Colocación de órdenes
Las órdenes límite de venta se colocan ligeramente por encima del máximo de la vela anterior; las órdenes límite de compra ligeramente por debajo del mínimo de la vela anterior.
Si el precio no está suficientemente lejos del bid/ask actual, el precio de la orden se ajusta al búfer de compensación configurado (EntryOffsetPips, por defecto 16 pips).
La estrategia verifica que la distancia entre el precio de la orden y el bid/ask actual supere la aproximación del nivel de seguridad del bróker (MinDistancePips o el valor dinámico basado en el spread).
Controles de riesgo
Los niveles opcionales de stop-loss y take-profit se calculan desde el precio de la orden.
Cuando una posición está activa, un trailing stop puede avanzar por el paso configurado una vez que el precio supera la distancia de trailing inicial.
Si el precio alcanza los niveles de protección dentro de una vela, la posición se cierra con una orden de mercado para imitar el comportamiento de MetaTrader.
Mantenimiento de órdenes
Las órdenes pendientes se cancelan cuando la pendiente de OsMA se vuelve contra la configuración original, coincidiendo con la rutina de limpieza del EA fuente.
El llenado de un lado cancela automáticamente la orden pendiente opuesta para evitar exposiciones conflictivas.
Gestión de capital
Dos enfoques de dimensionamiento de posición están disponibles:
Volumen fijo (por defecto OrderVolume = 1) — usa el Strategy.Volume base sin ajustes.
Dimensionamiento basado en riesgo — cuando UseRiskSizing está habilitado, la estrategia estima el patrimonio del portafolio, convierte el porcentaje de riesgo configurado en riesgo en moneda y lo divide por la distancia del stop-loss para derivar el volumen de la orden. Los volúmenes se alinean al paso de volumen del instrumento para evitar tamaños de orden inválidos.
Parámetros
Nombre
Descripción
Por defecto
OrderVolume
Tamaño de orden fijo cuando el dimensionamiento por riesgo está deshabilitado.
1
UseRiskSizing
Habilitar gestión de capital basada en RiskPercent.
true
RiskPercent
Porcentaje del patrimonio del portafolio arriesgado por operación.
3
MacdFastPeriod
Longitud de la EMA rápida para la línea MACD.
12
MacdSlowPeriod
Longitud de la EMA lenta para la línea MACD.
26
MacdSignalPeriod
Longitud de la EMA de señal para el histograma MACD.
9
ForceLength
Longitud de suavizado EMA para el Índice de Fuerza.
24
StopLossPips
Distancia del stop-loss en pips (0 deshabilita).
50
TakeProfitPips
Distancia del take-profit en pips (0 deshabilita).
50
TrailingStopPips
Distancia del trailing stop en pips (0 deshabilita).
5
TrailingStepPips
Paso mínimo para actualizaciones de trailing.
5
EntryOffsetPips
Búfer añadido alrededor de máximos/mínimos anteriores para órdenes pendientes.
16
MinDistancePips
Distancia mínima permitida entre el precio y los niveles de protección.
3
PipSize
Tamaño del pip usado para conversiones pip-a-precio.
0.0001
CandleType
Tipo de vela procesado por la estrategia.
Velas de 4 horas
Notas de uso
Añade el archivo CS/TdsGlobalPendingStrategy.cs a tu proyecto StockSharp o cárgalo dinámicamente a través del entorno de Backtester.
Asigna el instrumento y portafolio deseados antes de iniciar la estrategia. Si UseRiskSizing está habilitado, asegúrate de que el portafolio proporcione valores de patrimonio actuales.
La estrategia requiere al menos dos velas completadas para inicializar las pendientes de MACD/OsMA. Se espera una breve fase de calentamiento.
Monitorea los logs para eventos detallados de órdenes y posiciones. La implementación registra las acciones clave (envío de órdenes, cancelación, actualizaciones de trailing) para facilitar la verificación contra el comportamiento del EA original.
Diferencias con la versión MQL
La API de alto nivel gestiona eventos de órdenes asíncronos, por lo que los llenados de órdenes límite se manejan mediante OnOwnTradeReceived en lugar de resultados síncronos de OrderSend.
Los niveles de "congelamiento" y "stops" del bróker se aproximan usando la distancia mínima configurada y una heurística basada en el spread porque StockSharp no expone límites de trading específicos de MetaTrader.
Las salidas protectoras se ejecutan mediante órdenes de mercado cuando la vela muestra una ruptura. Esto replica la lógica de modificación manual de stop del EA sin depender de las restricciones del servidor de trading MT5.
Estos ajustes mantienen la lógica de trading fiel mientras aseguran que la estrategia se integre sin problemas con el framework de StockSharp.
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 TdsGlobalPendingStrategy : 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 TdsGlobalPendingStrategy()
{
_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 tds_global_pending_strategy(Strategy):
def __init__(self):
super(tds_global_pending_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(tds_global_pending_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(tds_global_pending_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 tds_global_pending_strategy()