GitHub で見る

ARD 注文管理コマンド戦略

戦略の概要

ARD Order Management 戦略は、MetaTrader 4 エキスパート アドバイザー ARD_ORDER_MANAGEMENT_.mq4 を StockSharp の高レベル戦略フレームワークに移植します。元のスクリプトは、外部スクリプトまたは UI ボタン​​からトリガーできる一連の手動コマンド (購入、販売、クローズ、変更) を公開していました。すべてのコマンドは、利用可能な自由証拠金、オープンまたは反転した市場ポジション、および固定点距離で付加された保護的なストップロスおよびテイクプロフィットレベルから取引高を再計算しました。

StockSharp バージョンでは、同じインタラクション モデルが維持されます。 Command パラメータを通じて動作を制御します。 None 以外の値が設定されると、ストラテジーは次のレベル 1 更新時に要求されたアクションを実行し、コマンドを自動的に None にリセットします。ストップロスとテイクプロフィットが常に現在のパラメーター値を反映するように、新しいエントリーまたは変更リクエストごとにプロテクション注文が再作成されます。

コマンドのライフサイクル

  1. コマンド ディスパッチCommandBuy または Sell に設定されている場合、ストラテジーはリクエストを保存し、すぐに ClosePosition() を呼び出して、オープンなエクスポージャをフラット化します。アクティブなプロテクト注文は、新しい取引が検討される前にキャンセルされ、OrderClose を介してすべてのチケットをクローズした MQL ループを反映しています。
  2. ボリューム計算 – ボリュームはコマンドごとに再計算されます。 MetaTrader で AccountFreeMargin()/lotsize/1000 が使用されたのとまったく同じように、Portfolio.CurrentValue (Portfolio.BeginValue へのフォールバック) を LotSizeDivisor で割って、1/1000 でスケーリングして使用します。結果は LotDecimals に四捨五入され、MinimumVolume によって制限されます。
  3. フラット ポジションを待機中 – コマンドが到着したときにポジションがオープンだった場合、新しいエントリーは Position がゼロになるまで延期されます。この戦略は、非同期実行パイプラインの競合を避けるために、レベル 1 ティックごとにこの条件をチェックします。
  4. 市場執行 – フラットになると、戦略は BuyMarket または SellMarket を送信します。現実的な約定価格から保護注文が導出されるように、最新の既知の最良買値/売値が保存されます。
  5. 保護の設定 – ストップロスとテイクプロフィットのレベルは、個別のストップ注文と指値注文として実現されます。ロングトレードの場合、ストップは bid − StopLossPoints * PriceStep、ターゲットは ask + TakeProfitPoints * PriceStep に設定されます。ショートトレードではこれらの計算が逆転します。変更コマンドは同じルーチンを再利用しますが、ModifyStopLossPointsModifyTakeProfitPoints が使用されます。
  6. Close コマンドCommandClose に設定すると、すべての保護命令がキャンセルされ、ClosePosition() が呼び出されます。戦略がすでにフラットである場合、コマンドは事実をログに記録するだけで、他には何も行いません。

お金の管理

  • マージンドリブンボリューム – コードは現在のポートフォリオ値を検査し、利用可能な資本に応じてボリュームが縮小または増加します。除数パラメータが誤ってゼロになった場合、アルゴリズムは設定された MinimumVolume に戻り、警告を発します。
  • 四捨五入LotDecimals は、四捨五入後に小数点以下の桁数を保持するかを定義します。この実装では、Math.RoundMidpointRounding.AwayFromZero を使用して、正と負の調整が MetaTrader の NormalizeDouble のように動作するようにします。
  • 最小ロット – 四捨五入後、サイズは MinimumVolume で固定され、lotmax 未満の値が 0.1 に昇格する元の動作を再現します。

ストップロスとテイクプロフィットの処理

  • 保護命令は常に最初から再作成されます。既存のストップ注文またはテイク注文は、新しい注文が送信される前にキャンセルされます。
  • この戦略は、絶対価格を計算する前に Security.PriceStep をチェックします。ステップが欠落しているか、正でない場合、保護命令はスキップされ、警告が記録されます。
  • 変更コマンド (Command = Modify) は、現在の位置サイズを変更せずに、専用の変更距離を使用して保護を再構築します。

データと実行の要件

  • SubscribeLevel1() 経由でレベル 1 データをサブスクライブし、MetaTrader の見積もり更新 (Bid/Ask) をミラーリングします。
  • キャンドルやインジケーターは必要ありません。すべてのロジックはティック/相場の更新時に実行されます。
  • 高レベルのヘルパー (BuyMarketSellMarketBuyStopSellStopBuyLimitSellLimitCancelOrder) を使用して、StockSharp が推奨する API レイヤー内に留まります。

パラメーター

名前 種類 デフォルト 説明
SlippageSteps 整数 4 価格ステップで表される許容スリッページ。互換性のために保存されています。 StockSharp 成行注文はすぐに実行され、この値は消費されません。
LotDecimals 整数 1 計算された体積を四捨五入した後に保持される小数点以下の桁数。
StopLossPoints 10進数 50 エントリーから最初のストップロスまでの距離(価格ポイント)。
TakeProfitPoints 10進数 100 エントリーから最初のテイクプロフィットまでの距離(価格ポイント)。
LotSizeDivisor 10進数 5 ロットにスケーリングする前にポートフォリオ値を除算します (freeMargin / divisor / 1000)。
ModifyStopLossPoints 10進数 20 Command = Modify の場合に適用されるストップロス距離。
ModifyTakeProfitPoints 10進数 100 Command = Modify の場合に適用されるテイクプロフィットディスタンス。
MinimumVolume 10進数 0.1 四捨五入後の最終ボリュームの下限。
OrderComment 文字列 "Placing Order" 監査を容易にするために、すべての注文にコメントが挿入されます。
Command ArdOrderCommand None 実行する手動コマンド。処理が完了すると、自動的に None にリセットされます。

使用上の注意

  • UI またはプログラムで Command を設定します。コマンドは変更ごとに 1 回だけ処理されます。アクションを繰り返すには、値を None に戻してから、再度希望の値に戻します。
  • ストップロスとテイクプロフィットは独立した注文として発注されるため、ブローカー/取引所は同じ証券に対してネイティブのストップ/リミット注文をサポートする必要があります。そうでない場合は、コード内の合成出口に置き換えることを検討してください。
  • スリッページは、MT4 バージョンとのドキュメント同等性のパラメータとして保持されます。 StockSharp のハイレベル マーケット ヘルパーは明示的なスリッページ パラメータを公開しないため、値は情報提供のみです。
  • この戦略は、任意の実行中の監査証跡を支援するために、すべての重要なアクション (LogInfo/LogWarn) をログに記録します。

元の MQL エキスパート アドバイザーとの違い

  • MetaTrader はストップとターゲットをマーケット チケットに直接接続しました。 StockSharp は代わりに、個別の逆指値注文と指値注文を発行します。
  • ポートは、StockSharp の非同期イベント モデルを使用します。ポジションを反転する場合、エントリーは前のポジションがクローズされたと報告されるまで待機し、注文の重複を防ぎます。
  • ポートフォリオ情報は AccountFreeMargin に置き換わります。戦略を開始する前に、ポートフォリオ アダプターが CurrentValue を設定するか、BeginValue を構成していることを確認してください。
  • 注文送信の例外はフレームワーク自体によって表面化されるため、エラー処理は、OrderSend の再試行を繰り返すのではなく、StockSharp のロギングに依存します。

テストのヒント

  • レベル 1 データを使用してシミュレーションで戦略を実行し、予想される距離に保護命令が表示されることを確認します。
  • 実際の環境で戦略を使用する前に、ブローカーの契約仕様に一致するように、さまざまな LotSizeDivisorLotDecimals の値を試してください。
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// ARD Order Management Command: EMA trend following with ATR stops.
/// </summary>
public class ArdOrderManagementCommandStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevClose;
	private decimal _entryPrice;

	public ArdOrderManagementCommandStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");
		_emaLength = Param(nameof(EmaLength), 20)
			.SetDisplay("EMA Length", "Trend filter.", "Indicators");
		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
	public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }

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

		_prevClose = 0; _entryPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevClose = 0; _entryPrice = 0;
		var ema = new ExponentialMovingAverage { Length = EmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, atr, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, ema); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal emaVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished) return;
		var close = candle.ClosePrice;
		if (_prevClose == 0 || atrVal <= 0) { _prevClose = close; return; }

		if (Position > 0)
		{
			if (close >= _entryPrice + atrVal * 2.5m || close <= _entryPrice - atrVal * 1.5m || close < emaVal) { SellMarket(); _entryPrice = 0; }
		}
		else if (Position < 0)
		{
			if (close <= _entryPrice - atrVal * 2.5m || close >= _entryPrice + atrVal * 1.5m || close > emaVal) { BuyMarket(); _entryPrice = 0; }
		}

		if (Position == 0)
		{
			if (close > emaVal && _prevClose <= emaVal) { _entryPrice = close; BuyMarket(); }
			else if (close < emaVal && _prevClose >= emaVal) { _entryPrice = close; SellMarket(); }
		}
		_prevClose = close;
	}
}