GitHub で見る

Bitex One Market Maker Strategy

Overview

The Bitex One Market Maker Strategy reproduces the asynchronous quoting robot from the original BITEX.ONE MarketMaker.mq5 source. The algorithm continuously places pairs of limit orders around a reference price and maintains an equal number of levels on the bid and ask sides. The strategy was rewritten for StockSharp using the high-level API: quote management is driven by order book and level 1 subscriptions, while risk and volume normalisation rely on instrument metadata (PriceStep, VolumeStep, and MinVolume).

Trading Logic

  1. Determine the lead price from the selected PriceSource. By default the strategy expects mark prices, but it can use the main order book or an auxiliary instrument (index or mark symbol) via the LeadSecurity parameter.
  2. Compute the distance between price levels as ShiftCoefficient * lead price and create a symmetric ladder of quotes above and below the reference.
  3. Clamp the total exposure on each side to MaxVolumePerLevel * LevelCount. Executed trades immediately reduce available volume so the grid always reflects the current position.
  4. Normalise prices and volumes using the security tick size and volume step. The algorithm cancels outdated orders and registers new ones whenever price or volume drift beyond the tolerance inherited from the original MQL code (0.05% price threshold and half-step volume threshold).
  5. All active orders are cancelled during stop/reset events to keep the book clean.

Parameters

  • MaxVolumePerLevel – maximum volume quoted at any single price level. Affects both sides of the book and acts as a cap when the current position grows.
  • ShiftCoefficient – relative offset from the lead price applied for each incremental level (leadPrice ± shift * levelIndex).
  • LevelCount – number of quoting levels per side. Each level creates one buy and one sell limit order.
  • PriceSource – enumerated value (OrderBook, MarkPrice, IndexPrice) defining where the reference price originates.
  • LeadSecurity – optional security used when external mark or index prices are required. If omitted, the main strategy security provides the reference.

Conversion Notes

  • The asynchronous order management from MetaTrader (SendAsync/ModifyAsync/RemoveOrderAsync) is mapped to StockSharp’s BuyLimit/SellLimit helpers combined with explicit cancellation when tolerances are exceeded.
  • The position balancing logic (max_pos * level_count ± position) is preserved to keep the ladder centred and risk aware.
  • The lead price selection mimics the suffix logic of the original robot (symbol, symbolm, symboli) by allowing a custom LeadSecurity combined with a PriceSource hint.
  • Timer-driven checks in MQL are replaced with reactive updates triggered by order book/level 1 messages and portfolio events.

Usage Notes

  • Ensure that the connected adapter provides market depth or level 1 data for both the trading symbol and the optional LeadSecurity.
  • When using mark or index feeds, subscribe to the corresponding instruments before starting the strategy so that the lead price becomes available immediately.
  • Consider enabling portfolio protection or additional risk management in the hosting environment if the exchange requires stringent quote-to-trade ratios.
  • The strategy does not start quoting until a positive lead price is received; verify connectivity if no orders appear after start-up.
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;
		}
	}
}