Ver en GitHub

Estrategia Expert NEWS

Descripción general

La Estrategia Expert NEWS es una conversión directa del robot MQL5 "Expert_NEWS". La estrategia coloca continuamente órdenes stop simétricas por encima y por debajo del precio de mercado actual, gestionando las posiciones resultantes con protección de punto de equilibrio, stops de seguimiento y actualizaciones programadas de órdenes pendientes. La implementación se basa en cotizaciones Level1 y mantiene el volumen de negociación predeterminado en 0.1 lotes.

Lógica de negociación

  1. Suscripción a cotizaciones – la estrategia escucha las actualizaciones de mejor bid/ask y calcula los precios de las órdenes a partir de los últimos valores.
  2. Órdenes stop iniciales – cuando no existe posición larga ni buy stop activo, se coloca un nuevo buy stop en ask + EntryOffsetTicks * PriceStep. Cuando no existe posición corta ni sell stop activo, se coloca un sell stop en bid - EntryOffsetTicks * PriceStep.
  3. Actualización de órdenes – cada OrderRefreshSeconds, la estrategia cancela y recrea un stop pendiente si el precio requerido se desvía más de TrailingStepTicks ticks.
  4. Protección de posición – tras una ejecución, la estrategia abre órdenes stop de protección y take-profit si las distancias solicitadas cumplen la restricción MinimumStopTicks.
  5. Control de punto de equilibrio – cuando UseBreakEven está activado, el stop se mueve a entrada ± BreakEvenProfitTicks una vez que el mercado se desplaza suficientemente y el nuevo stop respeta la distancia mínima de la cotización actual.
  6. Stop de seguimiento – una vez que el precio avanza TrailingStartTicks, el stop sigue usando TrailingStopTicks como distancia y TrailingStepTicks como paso de mejora mínima.
  7. Limpieza – cerrar la posición cancela todas las órdenes de protección restantes.

Parámetros

Parámetro Descripción
StopLossTicks Distancia inicial del stop de protección (ticks). Establezca en cero para desactivar la orden stop inicial.
TakeProfitTicks Distancia inicial del take-profit (ticks). Establezca en cero para desactivar la orden objetivo.
TrailingStopTicks Distancia del stop de seguimiento (ticks).
TrailingStartTicks Beneficio en ticks requerido antes de que se active la lógica de seguimiento.
TrailingStepTicks Mejora mínima al actualizar el stop de seguimiento o las órdenes de entrada pendientes.
UseBreakEven Activa el desplazamiento del stop al punto de equilibrio una vez que hay suficiente beneficio.
BreakEvenProfitTicks Margen de beneficio adicional al mover el stop al punto de equilibrio.
EntryOffsetTicks Distancia entre la cotización actual y cada nueva orden stop de entrada.
OrderRefreshSeconds Intervalo de tiempo entre intentos automáticos de actualización de órdenes stop pendientes.
MinimumStopTicks Respaldo manual para el requisito de nivel de stop del bróker. Los stops más cercanos que esta distancia no se envían.

Gestión de posición

  • Las órdenes de protección siempre coinciden con el volumen de la posición neta. Las ejecuciones parciales redimensionan automáticamente las órdenes stop y take-profit.
  • La lógica de punto de equilibrio y de seguimiento funciona incluso cuando el stop inicial está desactivado; el stop se creará dinámicamente una vez que se cumplan las reglas.
  • La estrategia mantiene el precio del stop más reciente en memoria para que las actualizaciones de seguimiento preserven un comportamiento monótono.

Notas de uso

  • Asegúrese de que Security.PriceStep esté configurado; cada parámetro de distancia en ticks se multiplica por este valor.
  • El volumen predeterminado es 0.1 para reflejar el robot original. Ajuste la propiedad Volume si se requiere otro tamaño.
  • MinimumStopTicks debe establecerse al requisito de nivel de stop de la plataforma de negociación si ésta lo exige. Déjelo en cero para permitir los stops más ajustados posibles.
  • El algoritmo no depende de barras históricas y puede operar solo con cotizaciones en tiempo real.
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>
/// Breakout strategy converted from the MQL Expert NEWS robot.
/// Detects breakouts above/below a reference price range and enters positions.
/// Uses stop loss and take profit for position management.
/// </summary>
public class ExpertNewsStrategy : Strategy
{
	private readonly StrategyParam<decimal> _entryOffset;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<int> _lookbackPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _highs = new();
	private readonly List<decimal> _lows = new();
	private decimal _entryPrice;
	private int _lastSignal;

	/// <summary>
	/// Entry offset from the high/low range.
	/// </summary>
	public decimal EntryOffset
	{
		get => _entryOffset.Value;
		set => _entryOffset.Value = value;
	}

	/// <summary>
	/// Stop loss distance in price units.
	/// </summary>
	public decimal StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Take profit distance in price units.
	/// </summary>
	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Number of bars to determine high/low range.
	/// </summary>
	public int LookbackPeriod
	{
		get => _lookbackPeriod.Value;
		set => _lookbackPeriod.Value = value;
	}

	/// <summary>
	/// Candle type for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Constructor.
	/// </summary>
	public ExpertNewsStrategy()
	{
		_entryOffset = Param(nameof(EntryOffset), 200m)
			.SetGreaterThanZero()
			.SetDisplay("Entry Offset", "Offset from range high/low for entry", "Parameters");

		_stopLoss = Param(nameof(StopLoss), 1000m)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss", "Stop loss in price units", "Risk");

		_takeProfit = Param(nameof(TakeProfit), 2000m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit", "Take profit in price units", "Risk");

		_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Lookback Period", "Bars for range calculation", "Parameters");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_highs.Clear();
		_lows.Clear();
		_entryPrice = 0m;
		_lastSignal = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

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

		StartProtection(
			new Unit(TakeProfit, UnitTypes.Absolute),
			new Unit(StopLoss, UnitTypes.Absolute));
	}

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

		_highs.Add(candle.HighPrice);
		_lows.Add(candle.LowPrice);

		if (_highs.Count > LookbackPeriod + 1)
			_highs.RemoveAt(0);
		if (_lows.Count > LookbackPeriod + 1)
			_lows.RemoveAt(0);

		if (_highs.Count <= LookbackPeriod)
			return;

		// Compute range from prior bars (excluding current)
		var rangeHigh = decimal.MinValue;
		var rangeLow = decimal.MaxValue;
		for (int i = 0; i < _highs.Count - 1; i++)
		{
			if (_highs[i] > rangeHigh) rangeHigh = _highs[i];
			if (_lows[i] < rangeLow) rangeLow = _lows[i];
		}

		var close = candle.ClosePrice;
		var breakoutUp = close > rangeHigh + EntryOffset;
		var breakoutDown = close < rangeLow - EntryOffset;

		if (breakoutUp && _lastSignal != 1 && Position <= 0)
		{
			BuyMarket();
			_entryPrice = close;
			_lastSignal = 1;
		}
		else if (breakoutDown && _lastSignal != -1 && Position >= 0)
		{
			SellMarket();
			_entryPrice = close;
			_lastSignal = -1;
		}
		else if (!breakoutUp && !breakoutDown)
			_lastSignal = 0;
	}
}