GitHub で見る

OCO 保留注文戦略

概要

OCO 保留注文戦略は、StockSharpのハイレベルAPI内でMetaTrader4エキスパートアドバイザーOCO_EA.mq4の動作を再現します。このアルゴリズムにより、トレーダーは最大4つの独立した価格トリガー(buy limit、buy stop、sell limit、sell stop)を設定できます。ライブの最良の買値または売値が設定した価格レベルに触れると、戦略は即座にマーケット注文を送信し、古典的な「一つが成立したら残りをキャンセル」(OCO)方式でオプションで他のすべての保留トリガーをキャンセルします。

戦略は純粋にレベル1市場データに依存しており、履歴インジケーターは必要ありません。トレーダーが手動で価格レベルを定義し、レベルに達したら即座にプラットフォームが執行し、保護的な退出注文も添付することを望む裁量的または半自動化された取引ワークフロー向けです。

取引ロジック

  1. トレーダーは4つのトリガー価格の任意の組み合わせを設定し、Armedパラメーターをtrueに切り替えます。
  2. 戦略はレベル1の更新を購読し、最新の最良買値と売値をメモリに保持します。
  3. 各更新で保存された価格を設定されたしきい値と比較します:
    • 最良の売値がBuy limit価格以下の場合、設定されたボリュームでマーケット買い注文が送信されます。
    • 最良の売値がBuy stop価格以上の場合、マーケット買い注文が送信されます。
    • 最良の買値がSell limit価格以上の場合、マーケット売り注文が送信されます。
    • 最良の買値がSell stop価格以下の場合、マーケット売り注文が送信されます。
  4. 各トリガー実行後、対応するレベルはクリアされます(ゼロに戻されます)。Use OCO linkが有効の場合、他のすべてのレベルが即座にクリアされ、元のMT4の動作を反映します。OCOリンクが無効の場合、他のレベルはトリガーするか手動でクリアされるまでアクティブのままです。
  5. すべてのトリガー価格がゼロの場合、戦略はArmedfalseに戻して自動的に解除します。

すべての実行はStockSharp環境で設定された取引所ルーティングを尊重する即座の注文フィルを確保するためにBuyMarketSellMarket呼び出しで行われます。監視を簡単にするために各トリガーの情報ログエントリが作成されます。

パラメーター

  • Order volume – 各マーケット注文で送信されるボリューム。値は正でなければなりません。
  • Buy limit price – 制限スタイルのロングエントリーを起動する売値しきい値。0に設定すると無効。
  • Buy stop price – ストップスタイルのロングエントリーを起動する売値しきい値。0に設定すると無効。
  • Sell limit price – 制限スタイルのショートエントリーを起動する買値しきい値。0に設定すると無効。
  • Sell stop price – ストップスタイルのショートエントリーを起動する買値しきい値。0に設定すると無効。
  • Stop loss (pips) – 保護ストップのインストゥルメントポイント単位の距離。Security.PriceStepを掛けて価格に変換されます(インストゥルメントがティックサイズを報告しない場合はフォールバック1)。
  • Take profit (pips) – 利益目標のインストゥルメントポイント単位の距離。ストップロスと同じ変換ロジックが使用されます。
  • Use OCO linktrueの場合、最初に約定した注文が残りの価格レベルをクリアして戦略を解除します。falseの場合、残りのレベルは個別にトリガーされるまでアクティブのままです。
  • Armed – 取引ロジックを有効/無効にする安全スイッチ。アクティブなトリガーレベルが残っていない場合、戦略は自動的にfalseにリセットします。

リスク管理

StartProtectionOnStarted中に有効化され、各オープンポジションに絶対価格ストップロスとテイクプロフィットオフセットを添付します。オフセットはインストゥルメントのティックサイズを使用して**Stop loss (pips)Take profit (pips)**パラメーターから導出されます。保護注文は、基礎となるインストゥルメントが流動性が低い場合でも退出執行を保証するために常にマーケット注文として送信されます。

戦略は純粋にイベント駆動型であるため、取引所に保留リミット注文を維持しません。市場データに反応してマーケット注文を送信します。これは即座の注文を発行してからストップロスとテイクプロフィット距離を適用するために修正した元のMQLバージョンと同様です。

使用上のヒント

  1. StockSharp内で通常通り証券、ポートフォリオ、接続を設定します。
  2. 希望するロットサイズに合わせてOrder volumeを設定します。
  3. トリガー価格のサブセットを入力し、Armedtrueに切り替えます。0のままの値は無視されます。
  4. オプションで最初の約定後に残りのトリガーをアクティブに保つためにUse OCO linkを無効にします。
  5. 各トリガーと自動リセット状態を確認するメッセージのログを監視します。

戦略はブローカーが提供する価格ステップを使用することに注意してください。取引インストゥルメントが分数ピップで表示されているか、非従来的なティックサイズを使用している場合は、戦略を設定する前にピップベースの距離を適切に調整してください。

元のMQLスクリプトとの違い

  • 戦略はストップロスとテイクプロフィットレベルを適用するために注文を手動で修正する代わりにStockSharpのStartProtectionヘルパーを使用します。
  • レベル1データの購読はBidAskHighLow値の手動ポーリングではなくハイレベルバインディングを通じて処理されます。
  • パラメーターはStrategyParam<T>を通じて公開されているため、StockSharp UIで直接調整・最適化できます。
  • ロギングはMT4のCommentPlaySound通知を置き換え、StockSharpログ内で実行の透明性を提供します。
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// OCO Pending Orders strategy. Uses Highest/Lowest channel breakout.
/// </summary>
public class OcoPendingOrdersStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _period;
	private decimal? _prevHigh;
	private decimal? _prevLow;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int Period { get => _period.Value; set => _period.Value = value; }

	public OcoPendingOrdersStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
		_period = Param(nameof(Period), 20).SetGreaterThanZero().SetDisplay("Channel Period", "Highest/Lowest lookback", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevHigh = null;
		_prevLow = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevHigh = null; _prevLow = null;
		var highest = new Highest { Length = Period };
		var lowest = new Lowest { Length = Period };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(highest, lowest, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal high, decimal low)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!IsFormedAndOnlineAndAllowTrading()) { _prevHigh = high; _prevLow = low; return; }
		if (_prevHigh == null || _prevLow == null) { _prevHigh = high; _prevLow = low; return; }
		var close = candle.ClosePrice;
		if (close > _prevHigh.Value && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
		else if (close < _prevLow.Value && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
		_prevHigh = high; _prevLow = low;
	}
}