Ver en GitHub

Mi trailing stop de media móvil TS15

Descripción general

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

  1. Suscríbase a la serie de velas seleccionada y vincule un indicador de promedio móvil usando la API de alto nivel de StockSharp.
  2. 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.
  3. Convierta la configuración basada en puntos a distancias de precios absolutas utilizando el tamaño de tick del instrumento.
  4. 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.
  5. 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.
  6. 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).
  7. 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

  1. Adjunte la estrategia al mismo valor que abre las posiciones.
  2. 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).
  3. Establezca TrailStepPoints en un valor positivo para reducir la rotación de pedidos en instrumentos ilíquidos.
  4. Disable EnforceMaxStopLoss if another risk manager already controls hard stop distances.
  5. 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;
	}
}