Estrategia de Breakeven con Trailing Stop
Estrategia que demuestra cómo mover el stop loss a breakeven y luego seguirlo conforme el precio avanza. La estrategia entra en una posición larga y la gestiona en dos fases:
- Después de que el precio gana
BreakevenPluspuntos, el stop se mueve aBreakevenSteppuntos por encima de la entrada. - Cuando el precio continúa con
TrailingPluspuntos de ganancia por encima del stop, el stop sigue al precio aTrailingSteppuntos de distancia.
La lógica es simétrica para posiciones cortas si se abre una manualmente.
Detalles
- Criterios de entrada: Abre una posición larga en la primera vela completada.
- Largo/Corto: Ambos (el ejemplo usa largo).
- Criterios de salida: El precio cruza el trailing stop.
- Stops: Breakeven y trailing stop.
- Valores predeterminados:
BreakevenPlus= 5BreakevenStep= 2TrailingPlus= 3TrailingStep= 1CandleType= TimeSpan.FromMinutes(1).TimeFrame()
- Filtros:
- Categoría: Gestión de stops
- Dirección: Ambos
- Indicadores: Ninguno
- Stops: Breakeven, trailing
- Complejidad: Básico
- Marco temporal: Intradía
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
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>
/// Strategy that moves stop-loss to breakeven and then trails it as price advances.
/// Uses EMA crossover for entry signals with breakeven + trailing stop management.
/// </summary>
public class BreakevenTrailingStopStrategy : Strategy
{
private readonly StrategyParam<decimal> _breakevenPercent;
private readonly StrategyParam<decimal> _breakevenOffset;
private readonly StrategyParam<decimal> _trailingActivation;
private readonly StrategyParam<decimal> _trailingDistance;
private readonly StrategyParam<int> _fastEmaPeriod;
private readonly StrategyParam<int> _slowEmaPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private decimal _stopPrice;
private bool _breakevenReached;
/// <summary>
/// Profit percent before moving stop to breakeven.
/// </summary>
public decimal BreakevenPercent
{
get => _breakevenPercent.Value;
set => _breakevenPercent.Value = value;
}
/// <summary>
/// Stop offset percent above entry after breakeven activation.
/// </summary>
public decimal BreakevenOffset
{
get => _breakevenOffset.Value;
set => _breakevenOffset.Value = value;
}
/// <summary>
/// Profit percent above breakeven stop required before trailing starts.
/// </summary>
public decimal TrailingActivation
{
get => _trailingActivation.Value;
set => _trailingActivation.Value = value;
}
/// <summary>
/// Distance percent between current price and trailing stop.
/// </summary>
public decimal TrailingDistance
{
get => _trailingDistance.Value;
set => _trailingDistance.Value = value;
}
/// <summary>
/// Fast EMA period for entry signals.
/// </summary>
public int FastEmaPeriod
{
get => _fastEmaPeriod.Value;
set => _fastEmaPeriod.Value = value;
}
/// <summary>
/// Slow EMA period for entry signals.
/// </summary>
public int SlowEmaPeriod
{
get => _slowEmaPeriod.Value;
set => _slowEmaPeriod.Value = value;
}
/// <summary>
/// Candle type used for price updates.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public BreakevenTrailingStopStrategy()
{
_breakevenPercent = Param(nameof(BreakevenPercent), 0.5m)
.SetGreaterThanZero()
.SetDisplay("Breakeven %", "Profit percent before breakeven", "Trading");
_breakevenOffset = Param(nameof(BreakevenOffset), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Breakeven Offset", "Stop offset percent after breakeven", "Trading");
_trailingActivation = Param(nameof(TrailingActivation), 0.3m)
.SetGreaterThanZero()
.SetDisplay("Trailing Activation", "Profit percent above stop before trailing", "Trading");
_trailingDistance = Param(nameof(TrailingDistance), 0.3m)
.SetGreaterThanZero()
.SetDisplay("Trailing Distance", "Percent from price to trailing stop", "Trading");
_fastEmaPeriod = Param(nameof(FastEmaPeriod), 8)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for updates", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fastEma = new ExponentialMovingAverage { Length = FastEmaPeriod };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastEma, decimal slowEma)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
if (Position == 0)
{
_breakevenReached = false;
if (fastEma > slowEma)
{
_entryPrice = close;
_stopPrice = close * (1m - 1m / 100m);
BuyMarket();
}
else if (fastEma < slowEma)
{
_entryPrice = close;
_stopPrice = close * (1m + 1m / 100m);
SellMarket();
}
return;
}
if (Position > 0)
{
if (!_breakevenReached)
{
if (close >= _entryPrice * (1m + BreakevenPercent / 100m))
{
_stopPrice = _entryPrice * (1m + BreakevenOffset / 100m);
_breakevenReached = true;
}
}
else
{
var trailingStop = close * (1m - TrailingDistance / 100m);
if (close >= _stopPrice * (1m + TrailingActivation / 100m) && trailingStop > _stopPrice)
{
_stopPrice = trailingStop;
}
}
if (candle.LowPrice <= _stopPrice)
{
SellMarket();
}
else if (fastEma < slowEma)
{
SellMarket();
}
}
else if (Position < 0)
{
if (!_breakevenReached)
{
if (close <= _entryPrice * (1m - BreakevenPercent / 100m))
{
_stopPrice = _entryPrice * (1m - BreakevenOffset / 100m);
_breakevenReached = true;
}
}
else
{
var trailingStop = close * (1m + TrailingDistance / 100m);
if (close <= _stopPrice * (1m - TrailingActivation / 100m) && trailingStop < _stopPrice)
{
_stopPrice = trailingStop;
}
}
if (candle.HighPrice >= _stopPrice)
{
BuyMarket();
}
else if (fastEma > slowEma)
{
BuyMarket();
}
}
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0m;
_stopPrice = 0m;
_breakevenReached = false;
}
}
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 breakeven_trailing_stop_strategy(Strategy):
def __init__(self):
super(breakeven_trailing_stop_strategy, self).__init__()
self._breakeven_percent = self.Param("BreakevenPercent", 0.5) \
.SetDisplay("Breakeven %", "Profit percent before breakeven", "Trading")
self._breakeven_offset = self.Param("BreakevenOffset", 0.1) \
.SetDisplay("Breakeven Offset", "Stop offset percent after breakeven", "Trading")
self._trailing_activation = self.Param("TrailingActivation", 0.3) \
.SetDisplay("Trailing Activation", "Profit percent above stop before trailing", "Trading")
self._trailing_distance = self.Param("TrailingDistance", 0.3) \
.SetDisplay("Trailing Distance", "Percent from price to trailing stop", "Trading")
self._fast_ema_period = self.Param("FastEmaPeriod", 8) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_ema_period = self.Param("SlowEmaPeriod", 21) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for updates", "General")
self._entry_price = 0.0
self._stop_price = 0.0
self._breakeven_reached = False
@property
def breakeven_percent(self):
return self._breakeven_percent.Value
@property
def breakeven_offset(self):
return self._breakeven_offset.Value
@property
def trailing_activation(self):
return self._trailing_activation.Value
@property
def trailing_distance(self):
return self._trailing_distance.Value
@property
def fast_ema_period(self):
return self._fast_ema_period.Value
@property
def slow_ema_period(self):
return self._slow_ema_period.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(breakeven_trailing_stop_strategy, self).OnReseted()
self._entry_price = 0.0
self._stop_price = 0.0
self._breakeven_reached = False
def OnStarted2(self, time):
super(breakeven_trailing_stop_strategy, self).OnStarted2(time)
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.fast_ema_period
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.slow_ema_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast_ema, slow_ema, self.OnProcess).Start()
def OnProcess(self, candle, fast_ema, slow_ema):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
fast_val = float(fast_ema)
slow_val = float(slow_ema)
if self.Position == 0:
self._breakeven_reached = False
if fast_val > slow_val:
self._entry_price = close
self._stop_price = close * (1.0 - 1.0 / 100.0)
self.BuyMarket()
elif fast_val < slow_val:
self._entry_price = close
self._stop_price = close * (1.0 + 1.0 / 100.0)
self.SellMarket()
return
if self.Position > 0:
if not self._breakeven_reached:
if close >= self._entry_price * (1.0 + float(self.breakeven_percent) / 100.0):
self._stop_price = self._entry_price * (1.0 + float(self.breakeven_offset) / 100.0)
self._breakeven_reached = True
else:
trailing_stop = close * (1.0 - float(self.trailing_distance) / 100.0)
if close >= self._stop_price * (1.0 + float(self.trailing_activation) / 100.0) and trailing_stop > self._stop_price:
self._stop_price = trailing_stop
if float(candle.LowPrice) <= self._stop_price:
self.SellMarket()
elif fast_val < slow_val:
self.SellMarket()
elif self.Position < 0:
if not self._breakeven_reached:
if close <= self._entry_price * (1.0 - float(self.breakeven_percent) / 100.0):
self._stop_price = self._entry_price * (1.0 - float(self.breakeven_offset) / 100.0)
self._breakeven_reached = True
else:
trailing_stop = close * (1.0 + float(self.trailing_distance) / 100.0)
if close <= self._stop_price * (1.0 - float(self.trailing_activation) / 100.0) and trailing_stop < self._stop_price:
self._stop_price = trailing_stop
if float(candle.HighPrice) >= self._stop_price:
self.BuyMarket()
elif fast_val > slow_val:
self.BuyMarket()
def CreateClone(self):
return breakeven_trailing_stop_strategy()