Ver en GitHub

Estrategia Check Execution

Descripción general

La estrategia Check Execution reproduce el comportamiento del experto MQL original que modifica repetidamente una orden del bróker para medir la calidad de ejecución. El algoritmo puede probar un buy stop pendiente o un sell stop de protección que cubre una posición larga abierta con una orden de mercado. Cada modificación registra tanto el spread observado como el tiempo necesario para que el centro de negociación acepte el cambio, lo que facilita evaluar condiciones sensibles a la latencia ofrecidas por un bróker.

Lógica central

  1. Suscribirse a actualizaciones de mejor bid/ask mediante la API de alto nivel SubscribeLevel1.
  2. Colocar la orden inicial de prueba según el modo seleccionado:
    • Pendiente - enviar un buy stop por encima del precio ask actual.
    • Mercado - comprar a mercado y luego enviar un sell stop de protección por debajo del último ask.
  3. En cada actualización de cotización:
    • Actualizar la media móvil del spread bid/ask usando SimpleMovingAverage.
    • Re-registrar la orden rastreada en el nuevo desplazamiento desde el precio ask cuando se requiere un cambio y no hay una solicitud previa esperando confirmación.
    • Medir la latencia de ejecución tan pronto como la orden vuelve al estado Active e introducirla en un segundo SimpleMovingAverage para obtener el retraso promedio en milisegundos.
  4. Repetir el ciclo de modificación hasta alcanzar el número configurado de iteraciones. Después, la estrategia cancela cualquier orden pendiente/stop restante, cierra la posición larga abierta si es necesario e imprime las estadísticas agregadas de spread y latencia.

Parámetros

Nombre Descripción Predeterminado
Volume Volumen de trading usado para cada orden. 0.01
Iterations Número de intentos de modificación para promediar. Limitado a 1-500. 30
Order Mode Selecciona el flujo: Pending o Market. Pending
Pending Offset Distancia en pasos de precio por encima del ask para el buy stop de prueba. 100
Stop Offset Distancia en pasos de precio por debajo del ask para el sell stop de protección. 100

Notas de comportamiento

  • Los valores de volumen se normalizan a las restricciones VolumeStep, MinVolume y MaxVolume del valor para evitar órdenes rechazadas.
  • Los desplazamientos de precio se traducen a precios reales usando el PriceStep del instrumento. Se usa un paso predeterminado de 0.0001 si el valor no proporciona uno.
  • La estrategia solo cuenta una modificación cuando el centro de negociación confirma la solicitud moviendo la orden al estado Active o Done. Cada confirmación actualiza tanto el temporizador de ejecución como el contador de modificaciones.
  • Cuando se alcanza el número objetivo de iteraciones, la estrategia deja automáticamente de modificar órdenes, cancela la protección pendiente, cierra cualquier posición de prueba y registra un mensaje resumen con los promedios medidos.

Diferencias con la versión MQL

  • Los promedios de spread y ejecución se calculan con indicadores SimpleMovingAverage de StockSharp en lugar de arreglos manuales.
  • La gestión de órdenes usa helpers de alto nivel como BuyMarket, BuyStop, SellStop y ReRegisterOrder para mantener coherencia con el framework de estrategias de StockSharp.
  • La retroalimentación de interfaz se proporciona mediante el log de la estrategia en lugar de comentarios en el gráfico y objetos gráficos.
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;

public class CheckExecutionStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public CheckExecutionStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}