Esta estrategia reproduce el comportamiento del asesor experto "MACDSimple" de Yury Reshetov de MetaTrader dentro del framework StockSharp. Trabaja con un único instrumento y evalúa señales MACD clásicas que son modificadas por dos parámetros de offset. El algoritmo procesa solo velas completadas, asegurando que todas las decisiones de trading se tomen sobre datos confirmados y evitando el ruido intrabarra.
Indicadores y cálculos
MACD (Moving Average Convergence Divergence) – la línea MACD y la línea de señal se calculan con períodos personalizados:
Período de EMA rápida = SignalPeriod + DF
Período de EMA lenta = SignalPeriod + DS + DF
Período de línea de señal = SignalPeriod
Los offsets DF y DS siguen las entradas originales del experto y permiten al trader estirar o comprimir los componentes MACD manteniendo intacta su relación.
Parámetros
Nombre
Descripción
Por defecto
Volume
Tamaño de orden usado para cada entrada de mercado.
2
DF
Offset añadido a la longitud de la EMA rápida del MACD. Debe ser cero o positivo.
1
DS
Offset adicional aplicado a la longitud de la EMA lenta del MACD. Debe ser cero o positivo.
2
SignalPeriod
Período base del que se derivan las longitudes de EMA rápida y lenta.
10
CandleType
Marco temporal de las velas usadas para análisis y trading.
Marco temporal de 30 minutos
Lógica de trading
Manejo de posición
En cada vela terminada, la estrategia actualiza el indicador MACD e ignora la barra si el indicador aún no está completamente formado.
Si hay una posición larga abierta y la línea MACD cae por debajo de cero, la estrategia cierra toda la posición larga al precio de mercado.
Si hay una posición corta abierta y la línea MACD sube por encima de cero, la estrategia cierra toda la posición corta al precio de mercado.
Después de cerrar una posición en una barra dada, el algoritmo deja de procesar esa barra, reflejando el comportamiento del asesor experto original.
Reglas de entrada
El algoritmo verifica que tanto la línea MACD como la línea de señal compartan el mismo signo (ambas positivas o ambas negativas). Los signos mixtos no producen operaciones.
Cuando ambas líneas son positivas, se abre una posición larga si la línea MACD está por encima de la línea de señal.
Cuando ambas líneas son negativas, se abre una posición corta si la línea MACD está por debajo de la línea de señal.
Las órdenes de mercado tienen el tamaño configurado con el parámetro Volume. Solo puede existir una posición a la vez.
Reglas de salida
Las salidas son impulsadas únicamente por la línea MACD cruzando el nivel cero en contra de la posición abierta, como se describe en la sección de manejo de posición. No se implementan salidas parciales, stop losses ni take profits por defecto.
Notas adicionales
La estrategia opera solo cuando IsFormedAndOnlineAndAllowTrading() se satisface, asegurando que los datos en vivo estén disponibles y el trading esté habilitado antes de entrar en nuevas posiciones.
No hay gestión de riesgo automática incorporada. Los usuarios pueden agregar protecciones personalizadas como StartProtection() o combinar la estrategia con controles de riesgo a nivel de portafolio si lo desean.
Debido a que los parámetros del MACD se derivan de un único período base más offsets, ajustar SignalPeriod, DF o DS afecta a todos los componentes simultáneamente, preservando el espaciado relativo pretendido por el asesor experto original.
Detalles de implementación
El enlace de indicadores usa la API SubscribeCandles().Bind() de alto nivel de StockSharp, manteniendo la implementación concisa y orientada a eventos.
La conversión sigue el conjunto de reglas descrito en AGENTS.md: se usan tabulaciones para la indentación, los valores de indicadores se consumen directamente desde el callback de enlace, y las funciones de trading BuyMarket/SellMarket gestionan entradas y salidas.
La estructura de la estrategia está lista para extensión (por ejemplo, añadiendo filtros o lógica de riesgo) mientras se mantiene fiel a la lógica del experto MetaTrader original.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// MACD-based strategy adapted from Yury Reshetov's MACDSimple expert advisor.
/// </summary>
public class MacdSimpleReshetovStrategy : Strategy
{
private readonly StrategyParam<int> _df;
private readonly StrategyParam<int> _ds;
private readonly StrategyParam<int> _signalPeriod;
private readonly StrategyParam<DataType> _candleType;
private MovingAverageConvergenceDivergenceSignal _macd;
public int Df { get => _df.Value; set => _df.Value = value; }
public int Ds { get => _ds.Value; set => _ds.Value = value; }
public int SignalPeriod { get => _signalPeriod.Value; set => _signalPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public MacdSimpleReshetovStrategy()
{
_df = Param(nameof(Df), 1)
.SetNotNegative()
.SetDisplay("DF", "Offset for the fast EMA", "Indicators");
_ds = Param(nameof(Ds), 2)
.SetNotNegative()
.SetDisplay("DS", "Offset for the slow EMA", "Indicators");
_signalPeriod = Param(nameof(SignalPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Signal Period", "Signal line period", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// The MQL version derives MACD periods from the signal period with DF and DS offsets.
var fastPeriod = SignalPeriod + Df;
var slowPeriod = SignalPeriod + Ds + Df;
_macd = new MovingAverageConvergenceDivergenceSignal
{
Macd = { ShortMa = { Length = fastPeriod }, LongMa = { Length = slowPeriod } },
SignalMa = { Length = SignalPeriod }
};
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var result = _macd.Process(candle);
if (!_macd.IsFormed)
return;
var macdValue = result as MovingAverageConvergenceDivergenceSignalValue;
if (macdValue == null)
return;
var macdLine = macdValue.Macd ?? 0m;
var signalLine = macdValue.Signal ?? 0m;
// Manage existing positions before evaluating new signals.
if (Position > 0)
{
if (macdLine < 0m)
SellMarket(Position);
return;
}
if (Position < 0)
{
if (macdLine > 0m)
BuyMarket(-Position);
return;
}
// Enter only when MACD and signal lines share the same sign.
if (macdLine * signalLine <= 0m)
return;
if (macdLine > 0m && macdLine > signalLine)
{
BuyMarket(Volume);
}
else if (macdLine < 0m && macdLine < signalLine)
{
SellMarket(Volume);
}
}
}
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 MovingAverageConvergenceDivergenceSignal, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class macd_simple_reshetov_strategy(Strategy):
def __init__(self):
super(macd_simple_reshetov_strategy, self).__init__()
self._df = self.Param("Df", 1)
self._ds = self.Param("Ds", 2)
self._signal_period = self.Param("SignalPeriod", 10)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._macd = None
@property
def Df(self):
return self._df.Value
@property
def Ds(self):
return self._ds.Value
@property
def SignalPeriod(self):
return self._signal_period.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(macd_simple_reshetov_strategy, self).OnStarted2(time)
fast_period = self.SignalPeriod + self.Df
slow_period = self.SignalPeriod + self.Ds + self.Df
self._macd = MovingAverageConvergenceDivergenceSignal()
self._macd.Macd.ShortMa.Length = fast_period
self._macd.Macd.LongMa.Length = slow_period
self._macd.SignalMa.Length = self.SignalPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
civ = CandleIndicatorValue(self._macd, candle)
civ.IsFinal = True
result = self._macd.Process(civ)
if not self._macd.IsFormed:
return
try:
macd_line = float(result.Macd) if result.Macd is not None else 0.0
signal_line = float(result.Signal) if result.Signal is not None else 0.0
except:
return
pos = float(self.Position)
if pos > 0:
if macd_line < 0:
self.SellMarket(pos)
return
if pos < 0:
if macd_line > 0:
self.BuyMarket(abs(pos))
return
if macd_line * signal_line <= 0:
return
if macd_line > 0 and macd_line > signal_line:
self.BuyMarket(float(self.Volume))
elif macd_line < 0 and macd_line < signal_line:
self.SellMarket(float(self.Volume))
def OnReseted(self):
super(macd_simple_reshetov_strategy, self).OnReseted()
self._macd = None
def CreateClone(self):
return macd_simple_reshetov_strategy()