Ver en GitHub

Estrategia de Órdenes Pendientes en Noticias

Esta estrategia coloca un par de órdenes stop pendientes alrededor del precio actual y las gestiona a medida que el mercado evoluciona. Está destinada a operar durante publicaciones de noticias donde se esperan movimientos bruscos.

Cómo funciona

  • Cuando está sin posición, la estrategia coloca:
    • Una orden de buy stop en Ask + Step.
    • Una orden de sell stop en Bid - Step.
  • Las órdenes pendientes se reprician cada TimeModify segundos si el mercado se ha movido al menos StepTrail.
  • Cuando se ejecuta una orden, la orden pendiente opuesta se cancela.
  • Se crean un stop loss protector y un take profit opcional basados en el precio de entrada.
  • El stop loss puede moverse al punto de equilibrio tras una ganancia definida y luego seguir el precio a medida que avanza.

La estrategia opera con datos de Nivel1 y no depende de ningún indicador.

Parámetros

Parámetro Predeterminado Descripción
Step 10 Distancia en ticks para colocar las órdenes stop pendientes.
StopLoss 10 Stop loss inicial en ticks.
TakeProfit 50 Take profit en ticks (0 lo desactiva).
TrailingStop 10 Distancia del trailing stop en ticks.
TrailingStart 0 Ganancia en ticks antes de activar el trailing.
StepTrail 2 Cambio mínimo en el precio del stop (en ticks) para enviar una nueva orden stop.
BreakEven false Mover el stop a la entrada al alcanzar MinProfitBreakEven.
MinProfitBreakEven 0 Ganancia en ticks requerida para mover el stop al punto de equilibrio.
TimeModify 30 Segundos entre intentos de repricio de órdenes pendientes.

Notas

  • Las órdenes se gestionan mediante la API de alto nivel de StockSharp.
  • La estrategia cancela las órdenes protectoras cuando la posición se cierra.
  • Solo se proporciona la versión en C#; no se incluye implementación en Python.
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;

/// <summary>
/// News-style volatility breakout strategy.
/// Enters on ATR expansion with momentum confirmation via EMA.
/// </summary>
public class NewsPendingOrdersStrategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _atrMult;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevAtr;
	private decimal _entryPrice;

	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public decimal AtrMult { get => _atrMult.Value; set => _atrMult.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public NewsPendingOrdersStrategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA trend period", "Indicators");
		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR period", "Indicators");
		_atrMult = Param(nameof(AtrMult), 1.5m)
			.SetDisplay("ATR Mult", "ATR expansion multiplier", "Indicators");
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();
		_prevAtr = 0;
		_entryPrice = 0;
	}

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

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var atr = new StandardDeviation { Length = AtrPeriod };

		SubscribeCandles(CandleType).Bind(ema, atr, ProcessCandle).Start();
	}

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

		if (_prevAtr <= 0) { _prevAtr = atr; return; }

		var close = candle.ClosePrice;
		var bodySize = Math.Abs(candle.ClosePrice - candle.OpenPrice);

		// Volatility expansion: big body candle relative to stddev
		var expansion = bodySize > atr * 0.5m;

		if (expansion && close > ema && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_entryPrice = close;
		}
		else if (expansion && close < ema && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_entryPrice = close;
		}
		// Exit long
		else if (Position > 0)
		{
			if (close < ema || (_entryPrice > 0 && close <= _entryPrice - atr * 2))
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		// Exit short
		else if (Position < 0)
		{
			if (close > ema || (_entryPrice > 0 && close >= _entryPrice + atr * 2))
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		_prevAtr = atr;
	}
}