Ver en GitHub

Estrategia E-News Lucky

Descripción general

La Estrategia E-News Lucky es un puerto de StockSharp del asesor experto de MetaTrader e-News-Lucky. El sistema automatiza el clásico enfoque de ruptura por noticias:

  • En un PlacementTime configurable, envía órdenes tanto de compra stop como de venta stop alrededor del precio actual, desplazadas por DistancePips.
  • Cuando se ejecuta cualquier orden pendiente, la orden opuesta se cancela inmediatamente. Los niveles iniciales de protección de stop-loss y take-profit se adjuntan según los desplazamientos en pips configurados.
  • Se puede habilitar un trailing stop mediante TrailingStopPips y TrailingStepPips para asegurar ganancias a medida que la operación se mueve en la dirección favorable.
  • En el CancelTime configurado, todas las órdenes pendientes restantes se eliminan y cualquier posición abierta se cierra para evitar mantener riesgo fuera de la ventana de trading.

La estrategia usa datos de velas (CandleType, 1 minuto por defecto) solo para rastrear los tiempos programados y actualizar el trailing stop. No depende de cálculos de indicadores.

Parámetros

Nombre Descripción
Volume Volumen de orden para cada entrada pendiente. La estrategia envía órdenes simétricas de compra stop y venta stop con este volumen.
StopLossPips Distancia entre el precio de entrada y el stop-loss de protección, expresada en pips. Establecer en cero para deshabilitar el stop.
TakeProfitPips Distancia entre el precio de entrada y el objetivo de beneficio en pips. Establecer en cero para deshabilitar el objetivo.
TrailingStopPips Distancia del trailing stop en pips. El motor de trailing se activa solo cuando este valor es mayor que cero.
TrailingStepPips Ganancia mínima en pips requerida antes de que el trailing stop se mueva nuevamente. Previene actualizaciones excesivas del stop en mercados laterales.
DistancePips Desplazamiento (en pips) del precio actual usado para colocar las órdenes stop.
PlacementTime Hora del día (tiempo del broker/servidor) en que se colocan las órdenes pendientes. Por defecto: 10:30.
CancelTime Hora del día en que se cancelan las órdenes pendientes y se cierran las posiciones abiertas. Por defecto: 22:30.
CandleType Serie de velas usada para programación y trailing. Por defecto: marco temporal de 1 minuto.

Notas de implementación

  • El tamaño de pip sigue la lógica de MetaTrader: si el símbolo tiene 3 o 5 dígitos, la estrategia multiplica el paso de precio por 10 para trabajar en unidades de pip.
  • Todos los precios se normalizan al paso de precio del instrumento antes de enviar órdenes.
  • Los trailing stops comparan el último cierre contra PositionPrice y solo mueven el stop de protección cuando la ganancia supera tanto TrailingStopPips como TrailingStepPips.
  • Las órdenes pendientes se recrean cada día de trading cuando se alcanza el tiempo de colocación. Las verificaciones del tiempo de cancelación garantizan que toda la exposición esté plana al final de la ventana.

Consejos de uso

  1. Adjunte la estrategia a un instrumento líquido con spreads ajustados; las distancias de ruptura asumen un comportamiento de precio similar al de las noticias.
  2. Establezca PlacementTime y CancelTime de acuerdo con el calendario económico de interés.
  3. Ajuste las distancias en pips para coincidir con la volatilidad del instrumento. Los valores más grandes reducen la posibilidad de falsos disparadores, mientras que los valores más pequeños pueden capturar movimientos más tempranos pero aumentan el riesgo de whipsaw.
  4. Deshabilite el trailing manteniendo TrailingStopPips en cero si se prefieren stops fijos.
  5. Monitoree el deslizamiento y el spread durante noticias de alto impacto para asegurar que las órdenes pendientes se ejecuten como se espera.
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>
/// Scheduled breakout strategy that monitors price around a reference level and enters on breakout.
/// Converted from the original pending-order version to use market orders.
/// </summary>
public class ENewsLuckyStrategy : Strategy
{
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<decimal> _trailingStepPips;
	private readonly StrategyParam<decimal> _distancePips;
	private readonly StrategyParam<int> _placementHour;
	private readonly StrategyParam<int> _cancelHour;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _pipSize;
	private decimal? _buyLevel;
	private decimal? _sellLevel;
	private decimal _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takePrice;
	private bool _pendingActive;
	private bool _lastWasPlacementDay;

	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	public decimal TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	public decimal TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

	public decimal DistancePips
	{
		get => _distancePips.Value;
		set => _distancePips.Value = value;
	}

	public int PlacementHour
	{
		get => _placementHour.Value;
		set => _placementHour.Value = value;
	}

	public int CancelHour
	{
		get => _cancelHour.Value;
		set => _cancelHour.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public ENewsLuckyStrategy()
	{
		_stopLossPips = Param(nameof(StopLossPips), 50m)
			.SetDisplay("Stop Loss", "Stop loss in pips", "Trading");

		_takeProfitPips = Param(nameof(TakeProfitPips), 150m)
			.SetDisplay("Take Profit", "Take profit in pips", "Trading");

		_trailingStopPips = Param(nameof(TrailingStopPips), 5m)
			.SetDisplay("Trailing Stop", "Trailing distance in pips", "Risk");

		_trailingStepPips = Param(nameof(TrailingStepPips), 5m)
			.SetDisplay("Trailing Step", "Minimum trailing step in pips", "Risk");

		_distancePips = Param(nameof(DistancePips), 20m)
			.SetGreaterThanZero()
			.SetDisplay("Entry Distance", "Distance from market in pips", "Trading");

		_placementHour = Param(nameof(PlacementHour), 2)
			.SetDisplay("Placement Hour", "Hour to set breakout levels", "General");

		_cancelHour = Param(nameof(CancelHour), 22)
			.SetDisplay("Cancel Hour", "Hour to cancel and close", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Working candle timeframe", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_pipSize = 0m;
		_buyLevel = null;
		_sellLevel = null;
		_entryPrice = 0m;
		_stopPrice = null;
		_takePrice = null;
		_pendingActive = false;
		_lastWasPlacementDay = false;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var step = Security?.PriceStep ?? 0m;
		_pipSize = step > 0 ? step : 1m;

		SubscribeCandles(CandleType)
			.Bind(ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var hour = candle.CloseTime.Hour;
		var price = candle.ClosePrice;

		// Set breakout levels at placement hour
		if (hour == PlacementHour && !_lastWasPlacementDay && Position == 0)
		{
			var distance = DistancePips * _pipSize;
			_buyLevel = price + distance;
			_sellLevel = price - distance;
			_pendingActive = true;
			_lastWasPlacementDay = true;
		}

		if (hour != PlacementHour)
			_lastWasPlacementDay = false;

		// Cancel at cancel hour
		if (hour == CancelHour && _pendingActive)
		{
			_pendingActive = false;
			_buyLevel = null;
			_sellLevel = null;

			if (Position > 0)
				SellMarket(Position);
			else if (Position < 0)
				BuyMarket(-Position);

			_entryPrice = 0m;
			_stopPrice = null;
			_takePrice = null;
			return;
		}

		// Check breakout triggers
		if (_pendingActive && Position == 0)
		{
			if (_buyLevel.HasValue && candle.HighPrice >= _buyLevel.Value)
			{
				var buyLevel = _buyLevel.Value;
				BuyMarket(Volume);
				_entryPrice = buyLevel;
				_stopPrice = StopLossPips > 0 ? _entryPrice - StopLossPips * _pipSize : null;
				_takePrice = TakeProfitPips > 0 ? _entryPrice + TakeProfitPips * _pipSize : null;
				_pendingActive = false;
				_buyLevel = null;
				_sellLevel = null;
			}
			else if (_sellLevel.HasValue && candle.LowPrice <= _sellLevel.Value)
			{
				var sellLevel = _sellLevel.Value;
				SellMarket(Volume);
				_entryPrice = sellLevel;
				_stopPrice = StopLossPips > 0 ? _entryPrice + StopLossPips * _pipSize : null;
				_takePrice = TakeProfitPips > 0 ? _entryPrice - TakeProfitPips * _pipSize : null;
				_pendingActive = false;
				_buyLevel = null;
				_sellLevel = null;
			}
		}

		// Manage open position
		if (Position > 0)
		{
			// Trailing stop
			if (TrailingStopPips > 0 && _entryPrice > 0)
			{
				var trailDist = TrailingStopPips * _pipSize;
				var stepDist = TrailingStepPips * _pipSize;
				if (price - _entryPrice > trailDist + stepDist)
				{
					var newStop = price - trailDist;
					if (!_stopPrice.HasValue || newStop > _stopPrice.Value)
						_stopPrice = newStop;
				}
			}

			if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
			{
				SellMarket(Position);
				ResetPosition();
				return;
			}

			if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
			{
				SellMarket(Position);
				ResetPosition();
			}
		}
		else if (Position < 0)
		{
			// Trailing stop
			if (TrailingStopPips > 0 && _entryPrice > 0)
			{
				var trailDist = TrailingStopPips * _pipSize;
				var stepDist = TrailingStepPips * _pipSize;
				if (_entryPrice - price > trailDist + stepDist)
				{
					var newStop = price + trailDist;
					if (!_stopPrice.HasValue || newStop < _stopPrice.Value)
						_stopPrice = newStop;
				}
			}

			if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
			{
				BuyMarket(-Position);
				ResetPosition();
				return;
			}

			if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
			{
				BuyMarket(-Position);
				ResetPosition();
			}
		}
	}

	private void ResetPosition()
	{
		_entryPrice = 0m;
		_stopPrice = null;
		_takePrice = null;
	}
}