La estrategia Trend Reversal es un sistema direccional que intenta capturar rupturas después de un retroceso de corto plazo dentro de una tendencia existente. Fue adaptada desde el asesor experto de MetaTrader "Trend Reversal" y reescrita para usar la API de alto nivel de StockSharp. La conversión conserva el bloque central de confirmación (medias móviles, momentum y MACD), sustituyendo los filtros gráficos de líneas originales por comprobaciones de solapamiento de precios más fáciles de reproducir programáticamente.
Conjunto de indicadores
Medias móviles ponderadas lineales (LWMA) sobre precio típico, con longitudes rápida y lenta personalizables. La línea rápida sigue el swing más reciente, mientras que la lenta identifica la tendencia dominante.
Oscilador Momentum calculado en el mismo marco temporal. La estrategia registra la distancia absoluta desde el nivel neutral 100 para las tres últimas velas cerradas, emulando la lógica de MetaTrader.
Par de líneas de señal MACD configurado con longitudes rápida, lenta y de señal independientes. La dirección del histograma se usa como confirmación de marco temporal superior para operaciones largas y cortas.
Lógica de trading
Esperar una vela finalizada en el marco temporal configurado. La estrategia ignora barras parcialmente formadas.
Asegurar que ambas LWMAs y el indicador de momentum estén completamente formados. Sin historial suficiente, el sistema permanece plano.
Mantener una cola móvil de las tres desviaciones de momentum más recientes respecto a 100. Una configuración solo es válida si al menos uno de esos valores supera el umbral de compra o venta correspondiente.
Exigir que la vela de hace dos barras tenga un mínimo más bajo que el máximo de la vela anterior. Esto recrea la estructura "solapada" usada en el EA original para detectar una consolidación estrecha antes de la ruptura.
Evaluar filtros direccionales:
Largo: LWMA rápida por encima de LWMA lenta y valor principal MACD por encima de la línea de señal.
Corto: LWMA rápida por debajo de LWMA lenta y valor principal MACD por debajo de la línea de señal.
Respetar el límite de posición neta. La estrategia entra o añade a una posición solo cuando la exposición absoluta (posición actual dividida por volumen de operación) está por debajo del valor configurado MaxPositions.
Las órdenes se envían con BuyMarket() o SellMarket(), lo que permite giros parciales o completos según la exposición actual.
Gestión de riesgos
Las distancias opcionales de take profit y stop loss (expresadas en unidades de precio) pueden adjuntarse mediante el bloque de protección integrado de StockSharp. Ambos niveles se desactivan cuando un parámetro se establece en cero.
Esta adaptación no incluye trailing stop automático ni ajuste de break-even. Estas funciones pueden implementarse con manejadores de eventos adicionales si se necesitan.
Parámetros
Nombre
Descripción
Predeterminado
CandleType
Marco temporal principal usado para construir velas.
Marco temporal de 15 minutos
FastLength
Período de la LWMA rápida.
6
SlowLength
Período de la LWMA lenta.
85
MomentumLength
Período del oscilador momentum.
14
MomentumBuyThreshold
Desviación mínima absoluta de momentum (desde 100) que valida una configuración larga.
0.3
MomentumSellThreshold
Desviación mínima absoluta de momentum (desde 100) que valida una configuración corta.
0.3
MacdFastLength
Período de EMA rápida usado dentro del filtro MACD.
12
MacdSlowLength
Período de EMA lenta usado dentro del filtro MACD.
26
MacdSignalLength
Período de EMA de señal usado dentro del filtro MACD.
9
TakeProfit
Distancia de take profit en unidades de precio. Establecer en 0 para desactivar.
50
StopLoss
Distancia de stop loss en unidades de precio. Establecer en 0 para desactivar.
20
TradeVolume
Volumen de orden expresado en lotes.
1
MaxPositions
Número máximo de unidades de volumen de operación permitidas en la posición neta.
1
Notas de uso
Adjunte la estrategia a un valor con información válida de paso y precio para que las órdenes de protección funcionen correctamente.
Para trading multidireccional (piramidación o escalado), aumente MaxPositions. La estrategia seguirá añadiendo posiciones mientras los filtros sigan siendo válidos y la exposición permanezca dentro del límite.
El backtesting debe realizarse con el mismo marco temporal de velas especificado por el parámetro CandleType. StockSharp solicitará automáticamente los datos adecuados cuando la estrategia inicie.
Como la versión MetaTrader dependía de líneas de tendencia dibujadas a mano, esta reescritura sustituye esas comprobaciones por una condición determinista de solapamiento de velas. Esto mantiene el comportamiento consistente entre backtests y ejecución en vivo.
Diferencias frente al EA original
No se implementan trailing stop, movimientos a break-even ni salidas de emergencia basadas en equity para mantener el ejemplo centrado en la generación central de señales.
Las funciones de gestión monetaria como multiplicación de lotes y filtrado por Magic Number no son necesarias en StockSharp y, por tanto, se eliminaron.
La confirmación MACD usa el mismo marco temporal que las velas de trading en lugar de la agregación mensual original. Si se desea, se puede emular la configuración multitemporal suscribiéndose a un tipo de vela más lento y vinculando el filtro MACD a esa suscripción.
Consejos de optimización
Optimice primero las longitudes de las medias móviles para ajustarlas al ciclo dominante del mercado y después refine los umbrales de momentum.
Experimente con distancias más amplias de stop-loss y take-profit al operar instrumentos volátiles. Dado que la lógica sigue tendencia, buffers de salida más grandes suelen mejorar la rentabilidad.
Monitorice las estadísticas de drawdown durante las ejecuciones de optimización. Aumentar MaxPositions puede mejorar la respuesta, pero también amplifica el riesgo.
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 TrendReversalStrategy : 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 TrendReversalStrategy()
{
_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 trend_reversal_strategy(Strategy):
def __init__(self):
super(trend_reversal_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(trend_reversal_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(trend_reversal_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 trend_reversal_strategy()