Ver en GitHub

Estrategia de malos pedidos

Descripción general

La Estrategia BadOrders es una adaptación directa del MetaTrader 4 asesor experto BadOrders.mq4. El guión original fue escrito intencionalmente para demostrar cómo la gestión incorrecta de órdenes conduce a operaciones rechazadas. En cada entrada, marque:

  1. Cierra con fuerza la posición abierta más recientemente al precio de oferta actual.
  2. Coloca un nuevo stop de compra 100 puntos por encima de la oferta.
  3. Modifica inmediatamente esa orden pendiente para ubicarse 100 puntos por debajo de la oferta, violando las reglas de distancia del corredor y provocando un error.

La versión StockSharp reproduce este comportamiento con el nivel alto API. Se suscribe a cotizaciones de Nivel 1 para monitorear la mejor oferta y reproduce el mismo ciclo de cierre, lugar e invalidación cada vez que llega una cotización.

Detalles de implementación

  • Flujo de datos: SubscribeLevel1() se utiliza porque el script MT4 reacciona a cada tick en lugar de completar velas.
  • Gestión de pedidos: Las posiciones abiertas se cierran con el ayudante ClosePosition(). Las paradas pendientes se gestionan a través de BuyStop() y ReRegisterOrder() para que podamos mover inmediatamente la orden de parada a un precio ilegal, imitando el flujo de trabajo interrumpido del código fuente.
  • Normalización de precios: Todos los precios se normalizan mediante Security.ShrinkPrice() y el concepto MetaTrader de Point se emula a través del instrumento PriceStep. Cuando no hay ningún tamaño de tick disponible, la estrategia vuelve a ser 0.0001.
  • Lógica de protección: antes de llamar a ClosePosition(), el código verifica las órdenes de liquidación existentes para evitar acumular solicitudes de salida duplicadas.

Parámetros

Nombre Descripción Predeterminado
DistancePoints Distancia en MetaTrader “puntos” agregados por encima y por debajo de la oferta actual al realizar o volver a registrar la orden de parada. 100

Resumen de comportamiento

  • Siempre que la oferta cambia, la estrategia intenta aplanar cualquier posición abierta.
  • Se envía una parada de compra en bid + DistancePoints * PointValue después de cerrar la posición.
  • La misma orden se vuelve a registrar inmediatamente en bid - DistancePoints * PointValue, lo que viola las reglas de intercambio y se espera que falle, reflejando precisamente los errores intencionales en BadOrders.mq4.

Nota: Este proyecto existe únicamente para mantener la paridad con la muestra MT4 y no está diseñado para operaciones reales.

using System;
using System.Collections.Generic;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Bad Orders strategy - ATR breakout with EMA filter.
/// Buys when price breaks above EMA + ATR threshold.
/// Sells when price breaks below EMA - ATR threshold.
/// </summary>
public class BadOrdersStrategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _atrMultiplier;
	private readonly StrategyParam<DataType> _candleType;

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

	public BadOrdersStrategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 20)
			.SetDisplay("EMA Period", "EMA lookback", "Indicators");

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetDisplay("ATR Period", "ATR lookback", "Indicators");

		_atrMultiplier = Param(nameof(AtrMultiplier), 1.5m)
			.SetDisplay("ATR Multiplier", "ATR breakout multiplier", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
	protected override void OnReseted() { base.OnReseted(); }

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

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

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

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

		var close = candle.ClosePrice;
		var upper = ema + atr * AtrMultiplier;
		var lower = ema - atr * AtrMultiplier;

		if (close > upper && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		else if (close < lower && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}
	}
}