GitHub で見る

Straddle トレーリング管理戦略

概要

Straddle トレーリング管理戦略は、MetaTrader 5 の元の「Straddle&Trail」Expert Advisor の動作を再現します。この戦略は、スケジュールされたニュースイベントの前または要求に応じて即座に、現在の価格周辺にストップ注文のペア(ストラドル)を配置します。ポジションが発動されると、アルゴリズムはブレークイーブンの移行、トレーリングストップ、保留注文をキャンセルまたはオープンポジションをクローズするオプションのシャットダウンコマンドを管理します。

この実装は StockSharp の高レベル API の上に構築されています。注文の配置、ポジション管理、リスク管理は低レベルメッセージ処理を使わずに実装されています。

トレードロジック

  1. ストラドルの配置

    • スケジュールされたイベントウィンドウに達したとき、または PlaceStraddleImmediately が有効な場合は即座に、2 つのストップ注文(上方の buy stop と下方の sell stop)が作成されます。
    • 注文価格は現在の bid/ask から DistanceFromPrice(pips 単位)分ずらされます。このずれは銘柄の価格ステップを使って価格単位に変換されます。
    • 注文が調整または明示的にキャンセルされない限り、戦略は同日に何度もストラドルを再作成しません。
  2. イベント前の注文管理

    • AdjustPendingOrders が有効な場合、ストップ注文は毎分キャンセルされ再配置されて、現在価格と常に整合します。
    • 調整はボラティリティが上昇したときに価格を追いかけるのを避けるため、イベントの StopAdjustMinutes 前に停止します。
    • RemoveOppositeOrder が有効な場合、ストラドルの片方が発動してポジションをオープンすると、残りのストップ注文は自動的にキャンセルされます。
  3. リスク管理

    • 初期のストップロスとテイクプロフィットレベルは StopLossPipsTakeProfitPips から計算され、内部で追跡されます。
    • オープン利益が BreakevenTriggerPips に達すると、ストップレベルはエントリー価格プラス BreakevenLockPips(ショートトレードの場合は対称値)に移動します。
    • TrailPips がゼロより大きい場合、トレーリングストップが価格を追跡します。トレーリングは TrailAfterBreakeven に応じて即座に始まるか、ブレークイーブン条件の後にのみ始まります。
    • テイクプロフィットとストップの決済は信頼性のために成行注文で実行されます。
  4. 手動シャットダウン

    • ShutdownNowtrue に設定すると、ShutdownMode オプションに従って即座のクリーンアップが開始されます。可能なアクションはロング/ショートポジションのクローズと保留中のロング/ショート注文のキャンセルを含みます。

パラメーター

パラメーター 説明
ShutdownNow 次のローソク足更新時にシャットダウン手続きを開始します。実行後に自動的に false にリセットされます。
ShutdownMode キャンセルまたはクローズすべきものを定義します(All, LongPositions, ShortPositions, PendingLong, PendingShort)。
DistanceFromPrice 現在の価格と各ストップ注文間の距離(pips 単位)。
StopLossPips 発動したポジションの初期ストップロス距離。0 で無効化。
TakeProfitPips 初期テイクプロフィット距離。0 で無効化。
TrailPips トレーリングストップ距離。0 でトレーリングを無効化。
TrailAfterBreakeven true の場合、ブレークイーブン条件が満たされた後にのみトレーリングが開始されます。
BreakevenLockPips ブレークイーブントリガーが発動したときにロックされる利益。
BreakevenTriggerPips ブレークイーブンロジックを起動する利益閾値。
EventHour / EventMinute スケジュールされたイベント時刻(ブローカー/サーバー時刻)。両方を 0 に設定してイベントスケジューラーを無効化。
PreEventEntryMinutes ストラドルを配置すべきイベントの何分前か。イベントが無効または即時配置が有効な場合は無視。
StopAdjustMinutes 保留注文の自動調整が停止するイベントの何分前か。
RemoveOppositeOrder ストラドルの最初の部分が発動したとき、未約定のストップ注文をキャンセルします。
AdjustPendingOrders イベント待機中に保留注文の自動再センタリングを有効化します。
PlaceStraddleImmediately 戦略開始直後にストラドルを配置し、イベントスケジュールをバイパスします。
CandleType 時間追跡に使用するローソク足サブスクリプション。デフォルトは 1 分足。

ボリューム – StockSharp の Volume プロパティが注文サイズを制御します。デフォルトでは 1 に設定され、戦略開始前に変更できます。

データサブスクリプション

戦略がサブスクライブするもの:

  • スケジューラー、トレーリングロジック、シャットダウンチェックを実行するための設定されたローソク足シリーズ(デフォルト 1 分)。
  • 精確なストップ注文の整合のために最新の bid/ask 価格を追跡する注文板。

注意事項と制限

  • ストップロスとテイクプロフィット管理は、ブローカー側の保護注文を変更するのではなく、成行注文で実行されます。これは元の動作を反映しつつ実装をシンプルに保ちます。
  • 戦略は pip サイズの近似に銘柄の PriceStep を使用します。エキゾチックな銘柄ではパラメーターを適切に調整してください。
  • シャットダウンコマンドは新しいローソク足データが到着した時のみ評価されます。即座の動作には、ローソク足の時間足を短くしてください。
  • Python の実装は要求に応じて意図的に省略されています。

変換メモ

  • ブレークイーブンとトレーリングロジックは MQL バージョンから行ごとにポートされています。StockSharp バージョンは同じ数値関係を維持しますが、小数価格で動作し、成行決済を使用します。
  • 手動トレード処理(MQL のマジックナンバー 0)は、StockSharp 戦略が独自のポジションを管理するため再現されません。すべての保護ロジックは戦略が生成したトレードにのみ適用されます。
  • CalcMagic 関数は StockSharp では不要なため削除されました。戦略の状態はフレームワークによって内部で追跡されます。
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy that simulates a straddle approach: defines upper/lower breakout levels
/// from a consolidation range (ATR-based) and enters on breakouts with trailing stop.
/// </summary>
public class StraddleTrailStrategy : Strategy
{
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _atrMultiplier;
	private readonly StrategyParam<decimal> _stopLossMult;
	private readonly StrategyParam<decimal> _takeProfitMult;
	private readonly StrategyParam<decimal> _trailMult;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _entryPrice;
	private decimal? _stopLevel;
	private decimal? _takeLevel;
	private int _barsSinceEntry;
	private int _cooldownCounter;

	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public decimal AtrMultiplier { get => _atrMultiplier.Value; set => _atrMultiplier.Value = value; }
	public decimal StopLossMult { get => _stopLossMult.Value; set => _stopLossMult.Value = value; }
	public decimal TakeProfitMult { get => _takeProfitMult.Value; set => _takeProfitMult.Value = value; }
	public decimal TrailMult { get => _trailMult.Value; set => _trailMult.Value = value; }
	public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public StraddleTrailStrategy()
	{
		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR calculation length", "ATR");

		_atrMultiplier = Param(nameof(AtrMultiplier), 2.5m)
			.SetGreaterThanZero()
			.SetDisplay("ATR Multiplier", "Breakout distance multiplier", "ATR");

		_stopLossMult = Param(nameof(StopLossMult), 1.0m)
			.SetGreaterThanZero()
			.SetDisplay("SL Multiplier", "Stop loss as ATR multiple", "Risk");

		_takeProfitMult = Param(nameof(TakeProfitMult), 3.0m)
			.SetGreaterThanZero()
			.SetDisplay("TP Multiplier", "Take profit as ATR multiple", "Risk");

		_trailMult = Param(nameof(TrailMult), 1.5m)
			.SetGreaterThanZero()
			.SetDisplay("Trail Multiplier", "Trailing distance as ATR multiple", "Risk");

		_cooldownBars = Param(nameof(CooldownBars), 6)
			.SetGreaterThanZero()
			.SetDisplay("Cooldown", "Bars to wait after exit", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Candle subscription", "General");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();
		_entryPrice = 0;
		_stopLevel = null;
		_takeLevel = null;
		_barsSinceEntry = 0;
		_cooldownCounter = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var atr = new AverageTrueRange { Length = AtrPeriod };
		var sma = new SimpleMovingAverage { Length = 20 };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(atr, sma, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, sma);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal atr, decimal sma)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var close = candle.ClosePrice;

		// Manage existing position
		if (Position != 0)
		{
			_barsSinceEntry++;

			if (Position > 0)
			{
				// Trail stop up
				var newTrail = close - TrailMult * atr;
				if (_stopLevel == null || newTrail > _stopLevel)
					_stopLevel = newTrail;

				// Check stop or take
				if (close <= _stopLevel || (_takeLevel != null && close >= _takeLevel))
				{
					SellMarket(Math.Abs(Position));
					ResetPosition();
					return;
				}
			}
			else
			{
				// Trail stop down
				var newTrail = close + TrailMult * atr;
				if (_stopLevel == null || newTrail < _stopLevel)
					_stopLevel = newTrail;

				// Check stop or take
				if (close >= _stopLevel || (_takeLevel != null && close <= _takeLevel))
				{
					BuyMarket(Math.Abs(Position));
					ResetPosition();
					return;
				}
			}

			return;
		}

		// Cooldown after exit
		if (_cooldownCounter > 0)
		{
			_cooldownCounter--;
			return;
		}

		// Entry: breakout above/below SMA + ATR distance
		var upperLevel = sma + AtrMultiplier * atr;
		var lowerLevel = sma - AtrMultiplier * atr;

		if (close > upperLevel)
		{
			BuyMarket();
			_entryPrice = close;
			_stopLevel = close - StopLossMult * atr;
			_takeLevel = close + TakeProfitMult * atr;
			_barsSinceEntry = 0;
		}
		else if (close < lowerLevel)
		{
			SellMarket();
			_entryPrice = close;
			_stopLevel = close + StopLossMult * atr;
			_takeLevel = close - TakeProfitMult * atr;
			_barsSinceEntry = 0;
		}
	}

	private void ResetPosition()
	{
		_entryPrice = 0;
		_stopLevel = null;
		_takeLevel = null;
		_barsSinceEntry = 0;
		_cooldownCounter = CooldownBars;
	}
}