La estrategia Candle Trailing Stop es un puerto de StockSharp del experto asesor MetaTrader con el mismo nombre. El robot original combinaba filtros de tendencia multitemporal, confirmación de momentum y un motor de trailing stop agresivo que seguía los mínimos y máximos de las velas recientes. La versión C# mantiene el mismo flujo de trabajo pero se apoya en componentes de alto nivel de StockSharp y expone todas las configuraciones críticas como parámetros de estrategia.
Lógica principal
Suscripciones de datos
El marco temporal de trading impulsa las entradas y las actualizaciones del trailing stop.
Un marco temporal superior proporciona confirmación usando medias móviles linealmente ponderadas (LWMA) y un indicador de momentum.
Una tercera suscripción calcula una línea MACD en un marco temporal lento (mensual por defecto) para filtrar las operaciones.
Alineación de tendencia
Las operaciones solo se permiten cuando las secuencias de LWMA rápida, media y lenta están alineadas en ambos marcos temporales (secuencia alcista para largos, bajista para cortos).
Puerta de momentum
El indicador de momentum debe estar cerca del valor neutral de 100 en al menos una de las últimas tres barras del marco temporal superior.
Confirmación de MACD
Los largos requieren que la línea MACD esté por encima de la línea de señal; los cortos requieren la relación inversa.
Disparador de entrada
Una ruptura a través de la LWMA rápida en el marco temporal actual (vela cerrando por encima/debajo de la media tras tocarla en la barra anterior) inicia nuevas operaciones respetando un límite de posición configurable.
Gestión de riesgo y salida
Las distancias iniciales de stop-loss y take-profit se definen en pips y se convierten automáticamente a pasos de precio.
Los stops pueden migrar al punto de equilibrio, seguir el extremo de las velas recientes, o retroceder a un trailing clásico de distancia fija.
Las funciones opcionales basadas en capital replican el EA original: take profit monetario, take profit porcentual, trailing de capital y protección contra drawdown.
Parámetros
Grupo
Nombre
Descripción
Por defecto
Negociación
Volume
Tamaño de la orden en lotes/contratos.
1
MaxTrades
Exposición máxima agregada expresada como Volume * MaxTrades.
10
Indicadores
FastCurrentLength
LWMA rápida en el marco temporal de trading.
9
MiddleCurrentLength
LWMA media en el marco temporal de trading.
20
SlowCurrentLength
LWMA lenta en el marco temporal de trading.
52
FastHigherLength
LWMA rápida en el marco temporal superior.
9
MiddleHigherLength
LWMA media en el marco temporal superior.
20
SlowHigherLength
LWMA lenta en el marco temporal superior.
52
MomentumPeriod
Período de momentum en el marco temporal superior.
14
MomentumBuyThreshold
Desviación máxima desde 100 permitida para operaciones largas.
0.3
MomentumSellThreshold
Desviación máxima desde 100 permitida para operaciones cortas.
0.3
MacdFastLength
Longitud de la EMA rápida para confirmación MACD.
12
MacdSlowLength
Longitud de la EMA lenta para confirmación MACD.
26
MacdSignalLength
Longitud de la EMA de señal para confirmación MACD.
9
Riesgo
StopLossPips
Distancia del stop-loss en pips.
20
TakeProfitPips
Distancia del take-profit en pips.
50
UseMoveToBreakEven
Activa la lógica de punto de equilibrio.
true
BreakEvenTriggerPips
Beneficio en pips requerido antes de mover el stop.
30
BreakEvenOffsetPips
Offset añadido al desplazar el stop al punto de equilibrio.
30
UseCandleTrail
Elegir entre trailing basado en velas (true) o trailing clásico (false).
true
CandleTrailLength
Número de velas cerradas usadas para calcular los extremos de trailing.
3
PadAmountPips
Buffer extra añadido debajo/encima del extremo de trailing.
10
TrailTriggerPips
Beneficio requerido antes de que el trailing clásico se active.
40
TrailAmountPips
Distancia mantenida por el trailing clásico.
40
Reglas de capital
UseMoneyTakeProfit
Cerrar todas las posiciones cuando el beneficio flotante supere el objetivo monetario.
false
MoneyTakeProfit
Objetivo de beneficio monetario.
40
UsePercentTakeProfit
Cerrar todas las posiciones cuando el beneficio flotante supere el objetivo porcentual.
false
PercentTakeProfit
Porcentaje del capital inicial usado como objetivo de beneficio.
10
EnableMoneyTrailing
Activa el trailing del beneficio flotante tras un umbral.
true
MoneyTrailTarget
Nivel de beneficio que activa la lógica de trailing monetario.
40
MoneyTrailStop
Retroceso máximo permitido una vez alcanzado el objetivo.
10
UseEquityStop
Activa la protección contra drawdown de capital.
true
EquityRiskPercent
Drawdown máximo desde el pico de capital antes de forzar posición plana.
1
Datos
CurrentCandleType
Marco temporal de trading.
5m
HigherCandleType
Marco temporal superior usado para filtros.
30m
MacdCandleType
Marco temporal para confirmación MACD (mensual por defecto).
30d
Notas y suposiciones
Los pips se convierten a pasos de precio usando el tamaño de tick del instrumento. En símbolos donde un pip difiere de un tick puede ser necesario ajustar las distancias de pip predeterminadas.
Las funciones monetarias se basan en el beneficio no realizado aproximado como (cierre - precioPromedio) * posición. Los ajustes de swap y comisión no están simulados.
La estrategia usa órdenes de mercado para entradas y salidas. Las órdenes iniciales de take-profit se registran una vez que se abre una operación, mientras que la gestión del stop-loss se maneja internamente y se cierra mediante órdenes de mercado cuando se cruza el nivel calculado.
Todos los comentarios en el código están escritos en inglés según las directrices del proyecto.
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 CandleTrailingStopStrategy : 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 CandleTrailingStopStrategy()
{
_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 candle_trailing_stop_strategy(Strategy):
def __init__(self):
super(candle_trailing_stop_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(candle_trailing_stop_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(candle_trailing_stop_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 candle_trailing_stop_strategy()