GitHub で見る

Auto Stop-Loss と Take-Profit 戦略

このユーティリティ戦略は、設定されたインストゥルメントのすべてのオープンポジションに保護的なストップロスとテイクプロフィット注文を自動的に添付します。アクティブポジションリストを監視し、保護注文を登録する前にブローカーの距離制限を適用することで、元のMetaTraderエキスパート「AutoSet SL TP」の動作を反映します。

この戦略は単独ではトレードを開きません。代わりに、手動または他の戦略によって作成されたポジションのボリューム、方向、実行価格を監視します。ロングまたはショートポジションが現れるとすぐに、アルゴリズムはMetaTraderスタイルのピップで表された希望するストップロスとテイクプロフィットレベルを計算し、取引所が公表するフリーズおよびストップ制約に準拠するようにレベルを調整し、適切なマーケット保護注文を送信します。ポジションが完全にクローズされると、保護注文は自動的にキャンセルされます。

動作の仕組み

  1. 最良ビッド/アスク価格をブローカーが提供するオプションのStopLevelおよびFreezeLevelフィールドと共に受信するためにレベル1データを購読します。
  2. 設定されたピップ距離をシンボルのメタデータ(価格ステップと小数精度)を使用して絶対価格に変換します。5桁および3桁の相場はMetaTraderのピップ意味論に合わせて自動的に10倍にスケーリングされます。
  3. 各相場更新または個人トレード通知時:
    • オープンポジションがないか、方向が設定されたフィルター(買いのみ、売りのみ、または両方)と一致しない場合はシグナルを無視します。
    • 市場価格と保護注文の間の最小許容距離を計算します。ブローカーがフリーズ/ストップレベルを公表しない場合、アルゴリズムは禁止ゾーンの外に安全に留まるために3スプレッドに1.1を掛けたフォールバックを使用します。
    • 現在のアスク(ロング用)またはビッド(ショート用)に対するストップロスとテイクプロフィット価格を決定し、結果をインストゥルメントの価格ステップに正規化します。
    • 正確なポジションボリュームでストップまたはリミット保護注文を配置または再登録します。注文はターゲット価格またはボリュームが変更された場合のみ置き換えられ、取引所の変更を最小限に抑えます。
  4. ポジションボリュームがゼロになった場合、すべての未決の保護注文がキャンセルされます。また、トレード方向がフィルターによって許可されなくなった場合も既存の注文をキャンセルします。

アルゴリズムは外部フィルのみに依存するため、エントリーを管理する裁量取引、パネル、または他の自動化システムと組み合わせることができ、この戦略が一貫した保護エンベロープを保証します。

パラメーター

  • StopLossPips – MetaTraderピップでの現在の市場価格からストップロスまでの距離。0の値はストップ注文を無効にします。デフォルト:50
  • TakeProfitPips – MetaTraderピップでの現在の市場価格からテイクプロフィットまでの距離。0の値はテイクプロフィット注文を無効にします。デフォルト:140
  • DirectionFilter – どのポジション方向を管理するかを指定します:
    • Buy – ロングエクスポージャーのみ保護します。
    • Sell – ショートエクスポージャーのみ保護します。
    • BuySell – 両サイドを保護します(元のスクリプトのデフォルト動作)。

実践的な注記

  • 保護注文は常に絶対的なポジションボリュームで作成されます。ブローカーが最小または最大ロットサイズを強制する場合、戦略は注文を配置する前にボリュームを最も近い許容値に丸めます。
  • アルゴリズムはアクティブな保護注文を調整するためにReRegisterOrderを使用します。これにより可能な限り同じ取引所注文識別子が維持され、不必要なキャンセルが回避されます。
  • フォールバック距離(スプレッド × 3 × 1.1)は、明示的なフリーズ/ストップレベルが提供されない場合に、ストップまたはテイクプロフィットが隠れた取引所制限に違反することを防ぎます。
  • 戦略はエントリーを管理しないため、ポジションが開かれる前でも後でも開始できます。起動時に既に存在する適格なポジションは、最初の相場更新後に即座に保護されます。
  • MetaTraderの「ピップス」は、3桁または5桁の小数を持つシンボルの取引所価格ステップとは異なります。戦略は元のExpert Advisorを反映し、それに応じてポイント値を乗算して、設定された数値がMT5の設定に正確にマッピングされるようにします。

MetaTraderエキスパートとの相違点

  • ポジション内のストップおよびテイクプロフィット属性を変更する代わりに、StockSharpは明示的な保護ストップおよびリミット注文を管理します。このアプローチにより、ロジックはStockSharpの注文帳内で完全に透明に保たれます。
  • StockSharpバージョンはレベル1市場データを使用してブローカーの制限レベルを再構築します。プロバイダーがフリーズまたはストップ距離に異なるフィールド名を公開する場合、戦略はLevel1Fields列挙型のリフレクションを通じてそれらを自動的に発見します。
  • すべてのコードコメントとログメッセージはコーディングガイドラインに準拠するために英語で記述されており、ドキュメントはエンドユーザー向けにロシア語と中国語にローカライズされています。
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;

public class AutoSetStopLossTakeProfitStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public AutoSetStopLossTakeProfitStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "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");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}