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
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.
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.
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.
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).
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;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class bitex_one_market_maker_strategy(Strategy):
def __init__(self):
super(bitex_one_market_maker_strategy, self).__init__()
self._sma_period = self.Param("SmaPeriod", 100) \
.SetDisplay("SMA Period", "SMA period for mean reversion", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._sma = None
self._entry_price = 0.0
self._cooldown = 0
@property
def sma_period(self):
return self._sma_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(bitex_one_market_maker_strategy, self).OnReseted()
self._sma = None
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(bitex_one_market_maker_strategy, self).OnStarted2(time)
self._sma = SimpleMovingAverage()
self._sma.Length = self.sma_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._sma, self._process_candle)
subscription.Start()
def _process_candle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
sma_val = float(sma_value)
if not self._sma.IsFormed:
return
if self._cooldown > 0:
self._cooldown -= 1
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
# Check SL/TP
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
return
# Mean reversion around SMA
deviation = sma_val * 0.008
if close < sma_val - deviation and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif close > sma_val + deviation and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
def CreateClone(self):
return bitex_one_market_maker_strategy()