Estrategia Universal de Stop Móvil
Esta estrategia replica la idea central del script MQL4 original cm_universal_trailing_stop.mq4. No genera señales de entrada; en cambio, gestiona una posición existente moviendo el stop-loss en la dirección de la ganancia.
El algoritmo mantiene un desplazamiento desde el precio actual y desplaza el stop cada vez que el mercado se mueve un paso configurable. Una vez que se alcanza el umbral mínimo de ganancia, el stop móvil se activa y sigue el precio automáticamente tanto para posiciones largas como cortas.
Detalles
- Criterios de entrada: ninguno. La posición debe abrirse manualmente o por otra estrategia.
- Largo/Corto: ambos.
- Criterios de salida: orden de stop activada cuando el precio retrocede el desplazamiento configurado.
- Stops: stop móvil basado en puntos.
- Parámetros:
Delta– distancia desde el precio al stop en puntos.Step– movimiento mínimo del precio en puntos para desplazar el stop.StartProfit– ganancia en puntos requerida para activar el trailing.CandleType– marco temporal utilizado para los cálculos de trailing.
- Filtros:
- Categoría: Gestión de riesgos
- Dirección: Ambos
- Indicadores: Ninguno
- Stops: Trailing
- Complejidad: Simple
- Marco temporal: Cualquiera
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that enters on EMA crossover and manages position with a trailing stop.
/// Uses two EMAs for entries and a price-based trailing stop for exits.
/// </summary>
public class UniversalTrailingStopStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<decimal> _trailPercent;
private decimal _prevFast;
private decimal _prevSlow;
private bool _isInitialized;
private decimal _entryPrice;
private decimal _bestPrice;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
public decimal TrailPercent
{
get => _trailPercent.Value;
set => _trailPercent.Value = value;
}
public UniversalTrailingStopStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 10)
.SetDisplay("Fast Period", "Fast EMA period", "Entry");
_slowPeriod = Param(nameof(SlowPeriod), 30)
.SetDisplay("Slow Period", "Slow EMA period", "Entry");
_trailPercent = Param(nameof(TrailPercent), 1.5m)
.SetDisplay("Trail %", "Trailing stop distance in percent", "Trailing");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0;
_prevSlow = 0;
_isInitialized = false;
_entryPrice = 0;
_bestPrice = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0;
_prevSlow = 0;
_isInitialized = false;
_entryPrice = 0;
_bestPrice = 0;
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, slow, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, slow);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_isInitialized)
{
_prevFast = fastValue;
_prevSlow = slowValue;
_isInitialized = true;
return;
}
var price = candle.ClosePrice;
// Trailing stop check
if (Position > 0)
{
if (price > _bestPrice)
_bestPrice = price;
var stopLevel = _bestPrice * (1 - TrailPercent / 100m);
if (price <= stopLevel)
{
SellMarket();
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
else if (Position < 0)
{
if (price < _bestPrice)
_bestPrice = price;
var stopLevel = _bestPrice * (1 + TrailPercent / 100m);
if (price >= stopLevel)
{
BuyMarket();
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
// Entry signals: EMA crossover
var prevCrossUp = _prevFast <= _prevSlow;
var currCrossUp = fastValue > slowValue;
var prevCrossDown = _prevFast >= _prevSlow;
var currCrossDown = fastValue < slowValue;
if (prevCrossUp && currCrossUp && !(_prevFast > _prevSlow))
{
if (Position < 0)
BuyMarket();
if (Position <= 0)
{
BuyMarket();
_entryPrice = price;
_bestPrice = price;
}
}
else if (prevCrossDown && currCrossDown && !(_prevFast < _prevSlow))
{
if (Position > 0)
SellMarket();
if (Position >= 0)
{
SellMarket();
_entryPrice = price;
_bestPrice = price;
}
}
_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 universal_trailing_stop_strategy(Strategy):
def __init__(self):
super(universal_trailing_stop_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._fast_period = self.Param("FastPeriod", 10) \
.SetDisplay("Fast Period", "Fast EMA period", "Entry")
self._slow_period = self.Param("SlowPeriod", 30) \
.SetDisplay("Slow Period", "Slow EMA period", "Entry")
self._trail_percent = self.Param("TrailPercent", 1.5) \
.SetDisplay("Trail %", "Trailing stop distance in percent", "Trailing")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._is_initialized = False
self._entry_price = 0.0
self._best_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def trail_percent(self):
return self._trail_percent.Value
def OnReseted(self):
super(universal_trailing_stop_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._is_initialized = False
self._entry_price = 0.0
self._best_price = 0.0
def OnStarted2(self, time):
super(universal_trailing_stop_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._is_initialized = False
self._entry_price = 0.0
self._best_price = 0.0
fast = ExponentialMovingAverage()
fast.Length = self.fast_period
slow = ExponentialMovingAverage()
slow.Length = self.slow_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_value = float(fast_value)
slow_value = float(slow_value)
if not self._is_initialized:
self._prev_fast = fast_value
self._prev_slow = slow_value
self._is_initialized = True
return
price = float(candle.ClosePrice)
trail_pct = float(self.trail_percent)
# Trailing stop check
if self.Position > 0:
if price > self._best_price:
self._best_price = price
stop_level = self._best_price * (1.0 - trail_pct / 100.0)
if price <= stop_level:
self.SellMarket()
self._prev_fast = fast_value
self._prev_slow = slow_value
return
elif self.Position < 0:
if price < self._best_price:
self._best_price = price
stop_level = self._best_price * (1.0 + trail_pct / 100.0)
if price >= stop_level:
self.BuyMarket()
self._prev_fast = fast_value
self._prev_slow = slow_value
return
# Entry signals: EMA crossover
prev_cross_up = self._prev_fast <= self._prev_slow
curr_cross_up = fast_value > slow_value
prev_cross_down = self._prev_fast >= self._prev_slow
curr_cross_down = fast_value < slow_value
if prev_cross_up and curr_cross_up and not (self._prev_fast > self._prev_slow):
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
self._entry_price = price
self._best_price = price
elif prev_cross_down and curr_cross_down and not (self._prev_fast < self._prev_slow):
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
self._entry_price = price
self._best_price = price
self._prev_fast = fast_value
self._prev_slow = slow_value
def CreateClone(self):
return universal_trailing_stop_strategy()