在 GitHub 上查看

Bitex One 做市策略

概述

Bitex One 做市策略 复刻自原始的 BITEX.ONE MarketMaker.mq5 程序。策略会围绕参考价格持续挂出买卖两侧的限价单,并保持每侧相同的级数。该版本基于 StockSharp 高层 API 实现:行情数据通过盘口与 Level1 订阅驱动,价格和数量的归一化依赖于品种参数(PriceStepVolumeStepMinVolume)。

交易流程

  1. 根据 PriceSource 参数确定参考价格。默认使用 mark price,也可以通过 LeadSecurity 指定主盘口或外部指数/mark 合约。
  2. ShiftCoefficient * leadPrice 计算相邻价差,在参考价上下生成对称的报价阶梯。
  3. 将每一侧的总头寸限制在 MaxVolumePerLevel * LevelCount。成交后立即减少可用数量,使得网格始终反映当前仓位。
  4. 使用最小价格步长和最小手数对价格与数量进行规范化。当已有订单的价格偏离超过 0.05%,或数量偏离超过半个最小手数时,策略会撤单并重新报价。
  5. 当策略停止或重置时,会撤销所有活动订单,保持账户干净。

参数说明

  • MaxVolumePerLevel:单个价位允许的最大委托量,同时限制整体风险暴露。
  • ShiftCoefficient:参考价的相对偏移量,用于计算每一级的买卖价格(leadPrice ± shift * levelIndex)。
  • LevelCount:每一侧的阶梯数量。每级包含一张买单与一张卖单。
  • PriceSource:参考价格的来源,可选 OrderBookMarkPriceIndexPrice
  • LeadSecurity:当需要使用外部 mark/指数价格时指定的辅助品种,未设置时默认使用主交易品种。

转换要点

  • MetaTrader 中的异步下单、改价与撤单(SendAsyncModifyAsyncRemoveOrderAsync)在 StockSharp 中通过 BuyLimit/SellLimit 及显式撤单实现。
  • 维持网格中心的仓位平衡逻辑(max_pos * level_count ± position)完整保留,确保风险控制与原版一致。
  • 通过 PriceSourceLeadSecurity 组合,模拟原策略使用不同后缀(symbolsymbolmsymboli)选取参考价格的方式。
  • 定时器触发的轮询被事件驱动的盘口、Level1 与持仓变化替代,使得响应更加及时。

使用建议

  • 确认数据源能够提供主品种以及 LeadSecurity(如有)的盘口或 Level1 数据,否则策略无法计算参考价格。
  • 如果使用外部 mark/指数价格,请在启动策略前完成相关品种订阅,以便立即获得初始参考价。
  • 交易所若对报价频率有严格要求,可结合账户保护或自定义风控措施限制最大挂单量。
  • 启动后若未见到网格报价,请检查连接状态以及参考价格是否为正值。
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;
		}
	}
}