Ver en GitHub

Estrategia de Creación de Mercado BitexOne

Descripción General

La Estrategia de Creación de Mercado BitexOne reproduce el robot de cotización asíncrono del fuente original BITEX.ONE MarketMaker.mq5. El algoritmo coloca continuamente pares de órdenes límite alrededor de un precio de referencia y mantiene un número igual de niveles en los lados de oferta y demanda. La estrategia fue reescrita para StockSharp usando la API de alto nivel: la gestión de cotizaciones está impulsada por las suscripciones al libro de órdenes y nivel 1, mientras que la normalización de riesgos y volúmenes se basa en los metadatos del instrumento (PriceStep, VolumeStep y MinVolume).

Lógica de Trading

  1. Determinar el precio líder desde el PriceSource seleccionado. Por defecto la estrategia espera precios mark, pero puede usar el libro de órdenes principal o un instrumento auxiliar (índice o símbolo mark) via el parámetro LeadSecurity.
  2. Calcular la distancia entre niveles de precio como ShiftCoefficient * lead price y crear una escalera simétrica de cotizaciones por encima y por debajo de la referencia.
  3. Limitar la exposición total en cada lado a MaxVolumePerLevel * LevelCount. Las operaciones ejecutadas reducen inmediatamente el volumen disponible para que la cuadrícula siempre refleje la posición actual.
  4. Normalizar precios y volúmenes usando el tamaño de tick del instrumento y el paso de volumen. El algoritmo cancela órdenes desactualizadas y registra nuevas cuando el precio o el volumen derivan más allá de la tolerancia heredada del código MQL original (umbral de precio del 0.05% y umbral de volumen de medio paso).
  5. Todas las órdenes activas se cancelan durante eventos de stop/reset para mantener el libro limpio.

Parámetros

  • MaxVolumePerLevel – volumen máximo cotizado en cualquier nivel de precio único. Afecta ambos lados del libro y actúa como límite cuando crece la posición actual.
  • ShiftCoefficient – desplazamiento relativo desde el precio líder aplicado para cada nivel incremental (leadPrice ± shift * levelIndex).
  • LevelCount – número de niveles de cotización por lado. Cada nivel crea una orden límite de compra y una de venta.
  • PriceSource – valor enumerado (OrderBook, MarkPrice, IndexPrice) que define de dónde se origina el precio de referencia.
  • LeadSecurity – instrumento opcional usado cuando se requieren precios mark o de índice externos. Si se omite, el instrumento de estrategia principal proporciona la referencia.

Notas de Conversión

  • La gestión asíncrona de órdenes de MetaTrader (SendAsync/ModifyAsync/RemoveOrderAsync) se mapea a los helpers BuyLimit/SellLimit de StockSharp combinados con cancelación explícita cuando se exceden las tolerancias.
  • La lógica de equilibrio de posición (max_pos * level_count ± position) se preserva para mantener la escalera centrada y consciente del riesgo.
  • La selección del precio líder imita la lógica de sufijos del robot original (symbol, symbolm, symboli) permitiendo un LeadSecurity personalizado combinado con una pista PriceSource.
  • Las comprobaciones periódicas impulsadas por temporizador en MQL son reemplazadas con actualizaciones reactivas desencadenadas por mensajes del libro de órdenes/nivel 1 y eventos de cartera.

Notas de Uso

  • Asegúrese de que el adaptador conectado proporcione profundidad de mercado o datos de nivel 1 tanto para el símbolo de trading como para el LeadSecurity opcional.
  • Cuando use feeds mark o de índice, suscríbase a los instrumentos correspondientes antes de iniciar la estrategia para que el precio líder esté disponible inmediatamente.
  • Considere habilitar protección de cartera o gestión de riesgo adicional en el entorno de hosting si el intercambio requiere ratios estrictos de cotización a operación.
  • La estrategia no comienza a cotizar hasta que se recibe un precio líder positivo; verifique la conectividad si no aparecen órdenes después del inicio.
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>
/// BitexOne market maker strategy using SMA mean-reversion approach.
/// Buys when price drops below lower band, sells when above upper band.
/// </summary>
public class BitexOneMarketMakerStrategy : Strategy
{
	private readonly StrategyParam<int> _smaPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private SimpleMovingAverage _sma;

	private decimal _entryPrice;
	private int _cooldown;

	/// <summary>
	/// SMA period.
	/// </summary>
	public int SmaPeriod
	{
		get => _smaPeriod.Value;
		set => _smaPeriod.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="BitexOneMarketMakerStrategy"/> class.
	/// </summary>
	public BitexOneMarketMakerStrategy()
	{
		_smaPeriod = Param(nameof(SmaPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("SMA Period", "SMA period for mean reversion", "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");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_sma = null;
		_entryPrice = 0;
		_cooldown = 0;
	}

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

		_sma = new SimpleMovingAverage { Length = SmaPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_sma, ProcessCandle);
		subscription.Start();
	}

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

		if (!_sma.IsFormed)
			return;

		if (_cooldown > 0)
		{
			_cooldown--;
			return;
		}

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

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 100;
				return;
			}

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

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 100;
				return;
			}
		}

		// Mean reversion around SMA
		var deviation = smaValue * 0.008m;

		if (close < smaValue - deviation && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
			_cooldown = 100;
		}
		else if (close > smaValue + deviation && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
			_cooldown = 100;
		}
	}
}