GitHub で見る

BitexOneマーケットメーカー戦略

概要

BitexOneマーケットメーカー戦略は、オリジナルのBITEX.ONE MarketMaker.mq5ソースからの非同期クォーティングロボットを再現します。 アルゴリズムは参照価格の周囲に継続的にリミット注文のペアを配置し、ビッドとアスクの両側に同数のレベルを維持します。戦略は StockSharpの高レベルAPIを使用して書き直されました:クォート管理は注文板とレベル1のサブスクリプションによって駆動され、リスクと ボリュームの正規化は銘柄メタデータ(PriceStepVolumeStepMinVolume)に依存します。

取引ロジック

  1. 選択されたPriceSourceからリード価格を決定します。デフォルトで戦略はマーク価格を期待しますが、LeadSecurityパラメーターを 通じてメイン注文板または補助銘柄(インデックスまたはマークシンボル)を使用できます。
  2. 価格レベル間の距離をShiftCoefficient * lead priceとして計算し、参照の上下に対称的なクォートのラダーを作成します。
  3. 各側の総エクスポージャーをMaxVolumePerLevel * LevelCountに制限します。実行された取引は利用可能なボリュームを即座に削減し、 グリッドが常に現在のポジションを反映するようにします。
  4. 銘柄のティックサイズとボリュームステップを使用して価格とボリュームを正規化します。アルゴリズムは元のMQLコードから継承された 許容範囲(価格閾値0.05%とハーフステップボリューム閾値)を超えて価格またはボリュームが乖離したときに古い注文をキャンセルして 新しい注文を登録します。
  5. ブックをクリーンに保つために、すべてのアクティブな注文はストップ/リセットイベント中にキャンセルされます。

パラメーター

  • MaxVolumePerLevel – 任意の単一価格レベルでクォートされる最大ボリューム。ブックの両側に影響し、現在のポジションが大きくなる 際の上限として機能します。
  • ShiftCoefficient – 各インクリメンタルレベルに適用されるリード価格からの相対オフセット(leadPrice ± shift * levelIndex)。
  • LevelCount – 片側あたりのクォートレベル数。各レベルは1つの買いと1つの売りのリミット注文を作成します。
  • PriceSource – 参照価格がどこから来るかを定義する列挙値(OrderBookMarkPriceIndexPrice)。
  • LeadSecurity – 外部マークまたはインデックス価格が必要な場合に使用するオプションの銘柄。省略した場合、メイン戦略銘柄が 参照を提供します。

変換上の注意

  • MetaTraderの非同期注文管理(SendAsync/ModifyAsync/RemoveOrderAsync)はStockSharpのBuyLimit/SellLimitヘルパーにマップされ、 許容範囲を超えた場合の明示的なキャンセルと組み合わせられます。
  • ポジションバランシングロジック(max_pos * level_count ± position)はラダーを中央に保ちリスクを意識した状態を維持するために 保存されています。
  • リード価格の選択は元のロボットのサフィックスロジック(symbolsymbolmsymboli)を模倣し、PriceSourceヒントと組み合わせた カスタムLeadSecurityを許可します。
  • MQLのタイマー駆動チェックは、注文板/レベル1メッセージとポートフォリオイベントによってトリガーされる反応的な更新に置き換えられます。

使用上の注意

  • 接続されたアダプターが取引シンボルとオプションのLeadSecurityの両方に対して市場の深さまたはレベル1データを提供することを 確認してください。
  • マークまたはインデックスフィードを使用する場合、戦略を開始する前に対応する銘柄をサブスクライブして、リード価格がすぐに 利用可能になるようにしてください。
  • 取引所が厳格なクォート対取引比率を要求する場合は、ホスティング環境でポートフォリオ保護または追加のリスク管理を有効にすることを 検討してください。
  • 戦略は正のリード価格を受け取るまでクォートを開始しません;起動後に注文が表示されない場合は接続を確認してください。
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;
		}
	}
}