Esta estrategia utilitaria adjunta automáticamente órdenes protectoras de stop-loss y take-profit a cada posición abierta en el instrumento configurado. Replica el comportamiento del experto original de MetaTrader "AutoSet SL TP" monitoreando la lista de posiciones activas y aplicando las restricciones de distancia del bróker antes de registrar las órdenes protectoras.
La estrategia no abre trades por sí sola. En su lugar, monitorea el volumen, dirección y precio de ejecución de las posiciones que han sido creadas manualmente o por otras estrategias. En cuanto aparece una posición larga o corta, el algoritmo calcula los niveles deseados de stop-loss y take-profit expresados en pips al estilo MetaTrader, ajusta los niveles para cumplir con las restricciones de congelación y parada publicadas por el lugar de negociación, y luego envía las órdenes protectoras de mercado apropiadas. Cuando la posición se cierra completamente, las órdenes protectoras se cancelan automáticamente.
Cómo funciona
Se suscribe a datos de Nivel1 para recibir los mejores precios bid/ask junto con los campos opcionales StopLevel y FreezeLevel suministrados por el bróker.
Convierte las distancias configuradas en pips a precios absolutos usando los metadatos del símbolo (paso de precio y precisión decimal). Las cotizaciones de cinco y tres dígitos se escalan automáticamente por un factor de diez para coincidir con la semántica de pip de MetaTrader.
En cada actualización de cotización o notificación de trade personal:
Ignora la señal si no hay posición abierta o si la dirección no coincide con el filtro configurado (solo compra, solo venta o ambos).
Calcula la distancia mínima permitida entre el precio de mercado y una orden protectora. Si el bróker no publica niveles de congelación/parada, el algoritmo recurre a tres spreads multiplicados por 1.1 para mantenerse con seguridad fuera de las zonas prohibidas.
Determina el precio de stop-loss y take-profit en relación con el ask actual (para largos) o bid (para cortos) y normaliza el resultado al paso de precio del instrumento.
Coloca o re-registra órdenes protectoras de stop o límite con el volumen exacto de la posición. Las órdenes se reemplazan solo cuando cambia el precio objetivo o el volumen, lo que minimiza las modificaciones en el exchange.
Si el volumen de la posición se vuelve cero, todas las órdenes protectoras pendientes se cancelan. La estrategia también cancela las órdenes existentes cuando la dirección del trade ya no está permitida por el filtro.
Dado que el algoritmo depende únicamente de llenados externos, puede combinarse con trading discrecional, paneles u otros sistemas automatizados que gestionan las entradas, mientras esta estrategia garantiza un envolvente protector consistente.
Parámetros
StopLossPips – distancia desde el precio de mercado actual al stop-loss en pips de MetaTrader. Un valor de 0 deshabilita la orden de stop. Predeterminado: 50.
TakeProfitPips – distancia desde el precio de mercado actual al take-profit en pips de MetaTrader. Un valor de 0 deshabilita la orden de take-profit. Predeterminado: 140.
DirectionFilter – especifica qué dirección de posición se gestiona:
Buy – proteger solo la exposición larga.
Sell – proteger solo la exposición corta.
BuySell – proteger ambos lados (comportamiento predeterminado en el script original).
Notas prácticas
Las órdenes protectoras siempre se crean con el volumen absoluto de la posición. Si el bróker impone tamaños de lote mínimos o máximos, la estrategia redondea el volumen al valor permisible más cercano antes de colocar las órdenes.
El algoritmo usa ReRegisterOrder para ajustar las órdenes protectoras activas. Esto mantiene los mismos identificadores de orden del exchange siempre que sea posible y evita cancelaciones innecesarias.
La distancia de respaldo (spread × 3 × 1.1) evita que el stop o take-profit viole restricciones ocultas del exchange cuando no se proporcionan niveles explícitos de congelación/parada.
Dado que la estrategia no gestiona entradas, puede iniciarse antes o después de que se abran posiciones. Cualquier posición calificada que ya exista al momento del inicio será protegida inmediatamente después de la primera actualización de cotización.
Los "pips" de MetaTrader difieren de los pasos de precio del exchange en símbolos con tres o cinco dígitos decimales. La estrategia replica el Expert Advisor original multiplicando el valor del punto en consecuencia, asegurando que los números configurados se correspondan exactamente con la configuración de MT5.
Diferencias con el experto de MetaTrader
En lugar de modificar los atributos de stop y take-profit en posición, StockSharp gestiona órdenes protectoras de stop y límite explícitas. Este enfoque mantiene la lógica completamente transparente dentro del libro de órdenes de StockSharp.
La versión de StockSharp usa datos de mercado de Nivel1 para reconstruir los niveles de restricción del bróker. Si el proveedor expone diferentes nombres de campo para las distancias de congelación o parada, la estrategia los descubre automáticamente a través de reflexión en el enum Level1Fields.
Cada comentario de código y mensaje de registro está en inglés para mantener la coherencia con las directrices de codificación, mientras la documentación está localizada en ruso y chino para los usuarios finales.
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 AutoSetStopLossTakeProfitStrategy : 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 AutoSetStopLossTakeProfitStrategy()
{
_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 auto_set_stop_loss_take_profit_strategy(Strategy):
def __init__(self):
super(auto_set_stop_loss_take_profit_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(auto_set_stop_loss_take_profit_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(auto_set_stop_loss_take_profit_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 auto_set_stop_loss_take_profit_strategy()