Esta estrategia reproduce el comportamiento del asesor experto original my_ts15.mq5 al gestionar órdenes de trailing stop en torno a una posición neta existente. Un promedio móvil ponderado lineal (LWMA) impulsa la colocación de las paradas y puede ser reemplazado por otros métodos de suavizado. La lógica continuamente:
Lee el valor promedio móvil de un número configurable de velas completadas.
Compara el progreso de los precios con la trayectoria del promedio móvil y las compensaciones basadas en precios.
Mueve la orden de parada de protección solo cuando el nuevo nivel mejora el anterior al menos en el paso especificado.
Opcionalmente, impone una distancia máxima de pérdida fijando el tope o liquidando inmediatamente la posición cuando se rompe el límite.
The strategy does not produce entry signals. It is meant to run together with other components (manual or automated) that open positions on the same security.
Lógica de trading
Suscríbase a la serie de velas seleccionada y vincule un indicador de promedio móvil usando la API de alto nivel de StockSharp.
Tan pronto como finalice una vela, almacene el resultado del indicador y obtenga el valor que está MaBarsTrail + MaShift barras detrás de la barra actual.
Convierta la configuración basada en puntos a distancias de precios absolutas utilizando el tamaño de tick del instrumento.
Para posiciones largas, elija la más baja de:
The moving average minus its offset.
El precio actual menos la compensación "en ganancias".
Luego fije el sendero a la distancia "en pérdida" y, opcionalmente, a la pérdida máxima permitida.
Para posiciones cortas, elija la más alta de:
The moving average plus its offset.
El precio actual más la compensación "en ganancias".
Luego fije el sendero a la distancia "en pérdida" y, opcionalmente, a la pérdida máxima permitida.
Actualice la orden de detención solo cuando la mejora exceda TrailStepPoints (a menos que sea cero, en cuyo caso se aceptan todas las mejoras).
If the price breaches the maximum loss distance and EnforceMaxStopLoss is enabled, the strategy closes the position immediately.
Todas las entradas de precios utilizan el precio de vela especificado en MaPrice, que coincide con la configuración original de MQL donde el indicador se alimenta con la serie PRICE_WEIGHTED.
Parámetros
Nombre
Predeterminado
Descripción
MaPeriod
50
Longitud de la media móvil utilizada como columna vertebral final.
MaShift
0
Desplazamiento adicional (en barras) aplicado al muestrear el valor de la media móvil.
MaMethod
LinearWeighted
Método de suavizado de la media móvil (simple, exponencial, suavizado, ponderado lineal).
MaPrice
Weighted
Precio de vela alimentado a la media móvil.
MaBarsTrail
1
Número de barras completadas entre la vela actual y la muestra de media móvil.
TrailBehindMaPoints
5
Distancia en puntos mantenida entre el stop y la media móvil.
TrailBehindPricePoints
30
Distancia en puntos que se mantiene detrás del precio cuando la posición es rentable.
TrailBehindNegativePoints
60
Distancia en puntos que se mantiene detrás del precio cuando la posición está perdiendo.
TrailStepPoints
0
Mejora mínima (en puntos) requerida antes de mover la parada. Zero replica el comportamiento de “actualizar siempre”.
EnforceMaxStopLoss
false
Si está habilitado, limite el stop a la pérdida máxima permitida y liquide la posición cuando el precio supere ese límite.
MaxStopLossPoints
100
Maximum allowed loss distance in points.
ShowIndicator
true
Dibuje la media móvil y los marcadores comerciales en el gráfico cuando la interfaz de usuario esté disponible.
CandleType
M1
Tipo de datos de vela que impulsa los cálculos.
Todas las entradas basadas en puntos se convierten a distancias de precios mediante el tamaño del pip del instrumento calculado a partir de Security.PriceStep.
Notas de conversión
El experto MQL actualizó el identificador MA manualmente. La implementación StockSharp utiliza BindEx para procesar el indicador sin acceder a los buffers internos ni llamar a GetValue.
Bid/Ask prices are not directly available from finished candles, therefore the trailing calculations use the candle price selected by MaPrice. This keeps the behaviour consistent because the original script fed the indicator with the same weighted price and compared it with Bid/Ask ticks.
PositionModify se reemplaza cancelando y recreando órdenes de suspensión de protección (SellStop para largo, BuyStop para corto). La estrategia almacena el último nivel de parada para imitar los umbrales finales MetaTrader.
El cierre forzado opcional (pre_init) sigue la lógica original: una vez que el mercado supera MaxStopLossPoints, la posición se cierra inmediatamente.
No se ha agregado ninguna lógica de entrada; users should combine this trailing module with their own signal provider.
Consejos de uso
Adjunte la estrategia al mismo valor que abre las posiciones.
Ajuste las distancias de los puntos al tamaño del tick del instrumento (los símbolos Forex generalmente usan valores de "pip", los CFD pueden requerir diferentes multiplicadores).
Establezca TrailStepPoints en un valor positivo para reducir la rotación de pedidos en instrumentos ilíquidos.
Disable EnforceMaxStopLoss if another risk manager already controls hard stop distances.
Mantenga ShowIndicator habilitado mientras ajusta los parámetros para visualizar la media móvil y el comportamiento final.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// My TS15 strategy: WMA trend following with trailing stop management.
/// Enters on price crossing WMA, exits with trailing stop logic.
/// </summary>
public class MyTs15Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _trailMultiplier;
private readonly StrategyParam<int> _signalCooldownCandles;
private decimal _entryPrice;
private decimal _bestPrice;
private bool _wasBullish;
private bool _hasPrevSignal;
private int _candlesSinceTrade;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int MaPeriod { get => _maPeriod.Value; set => _maPeriod.Value = value; }
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
public decimal TrailMultiplier { get => _trailMultiplier.Value; set => _trailMultiplier.Value = value; }
public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }
public MyTs15Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(120).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_maPeriod = Param(nameof(MaPeriod), 100)
.SetGreaterThanZero()
.SetDisplay("MA Period", "WMA period", "Indicators");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period for trailing", "Indicators");
_trailMultiplier = Param(nameof(TrailMultiplier), 3m)
.SetDisplay("Trail Multiplier", "ATR multiplier for trailing stop", "Risk");
_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 12)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0m;
_bestPrice = 0m;
_wasBullish = false;
_hasPrevSignal = false;
_candlesSinceTrade = SignalCooldownCandles;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
_bestPrice = 0;
_hasPrevSignal = false;
_candlesSinceTrade = SignalCooldownCandles;
var wma = new WeightedMovingAverage { Length = MaPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(wma, atr, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal wmaValue, decimal atrValue)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
var trailDist = atrValue * TrailMultiplier;
var isBullish = close > wmaValue;
if (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
// Trailing stop check
if (Position > 0)
{
if (close > _bestPrice) _bestPrice = close;
if (_bestPrice - close > trailDist)
{
SellMarket();
_entryPrice = 0;
_bestPrice = 0;
_candlesSinceTrade = 0;
return;
}
}
else if (Position < 0)
{
if (close < _bestPrice) _bestPrice = close;
if (close - _bestPrice > trailDist)
{
BuyMarket();
_entryPrice = 0;
_bestPrice = 0;
_candlesSinceTrade = 0;
return;
}
}
// Entry signals
if (_hasPrevSignal && isBullish != _wasBullish && _candlesSinceTrade >= SignalCooldownCandles)
{
if (isBullish && Position <= 0)
{
BuyMarket();
_entryPrice = close;
_bestPrice = close;
_candlesSinceTrade = 0;
}
else if (!isBullish && Position >= 0)
{
SellMarket();
_entryPrice = close;
_bestPrice = close;
_candlesSinceTrade = 0;
}
}
_wasBullish = isBullish;
_hasPrevSignal = true;
}
}
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 WeightedMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class my_ts15_strategy(Strategy):
def __init__(self):
super(my_ts15_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 100) \
.SetDisplay("MA Period", "WMA period", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "ATR period for trailing", "Indicators")
self._trail_multiplier = self.Param("TrailMultiplier", 3.0) \
.SetDisplay("Trail Multiplier", "ATR multiplier for trailing stop", "Risk")
self._signal_cooldown = self.Param("SignalCooldownCandles", 12) \
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading")
self._wma = None
self._atr = None
self._entry_price = 0.0
self._best_price = 0.0
self._was_bullish = False
self._has_prev_signal = False
self._candles_since_trade = 0
@property
def ma_period(self):
return self._ma_period.Value
@property
def atr_period(self):
return self._atr_period.Value
@property
def trail_multiplier(self):
return self._trail_multiplier.Value
@property
def signal_cooldown(self):
return self._signal_cooldown.Value
def OnReseted(self):
super(my_ts15_strategy, self).OnReseted()
self._wma = None
self._atr = None
self._entry_price = 0.0
self._best_price = 0.0
self._was_bullish = False
self._has_prev_signal = False
self._candles_since_trade = self.signal_cooldown
def OnStarted2(self, time):
super(my_ts15_strategy, self).OnStarted2(time)
self._wma = WeightedMovingAverage()
self._wma.Length = self.ma_period
self._atr = AverageTrueRange()
self._atr.Length = self.atr_period
self._entry_price = 0.0
self._best_price = 0.0
self._has_prev_signal = False
self._candles_since_trade = self.signal_cooldown
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(120)))
subscription.Bind(self._wma, self._atr, self._process_candle)
subscription.Start()
def _process_candle(self, candle, wma_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not self._wma.IsFormed or not self._atr.IsFormed:
return
close = float(candle.ClosePrice)
wma_val = float(wma_value)
atr_val = float(atr_value)
trail_dist = atr_val * self.trail_multiplier
is_bullish = close > wma_val
if self._candles_since_trade < self.signal_cooldown:
self._candles_since_trade += 1
if self.Position > 0:
if close > self._best_price:
self._best_price = close
if self._best_price - close > trail_dist:
self.SellMarket()
self._entry_price = 0.0
self._best_price = 0.0
self._candles_since_trade = 0
return
elif self.Position < 0:
if close < self._best_price:
self._best_price = close
if close - self._best_price > trail_dist:
self.BuyMarket()
self._entry_price = 0.0
self._best_price = 0.0
self._candles_since_trade = 0
return
if self._has_prev_signal and is_bullish != self._was_bullish and self._candles_since_trade >= self.signal_cooldown:
if is_bullish and self.Position <= 0:
self.BuyMarket()
self._entry_price = close
self._best_price = close
self._candles_since_trade = 0
elif not is_bullish and self.Position >= 0:
self.SellMarket()
self._entry_price = close
self._best_price = close
self._candles_since_trade = 0
self._was_bullish = is_bullish
self._has_prev_signal = True
def CreateClone(self):
return my_ts15_strategy()