GitHub で見る

Constituents EA 戦略

この戦略は MQL/22595Constituents EA をStockSharpのハイレベルAPIにポーティングしたものです。特定の時間に最近の レンジの周囲に2つの指値注文を配置する元のロジックを再現しながら、StockSharpの注文処理とリスク保護ヘルパーと互換性の あるワークフローを維持します。

戦略の動作方法

  1. スケジュールされた有効化 – 各ローソク足の終わりに、戦略は次のバーが StartHour に開始するかどうかを確認します。 その時だけ新しい指値注文が考慮されます。これは、設定された時間と一致する開始時刻を持つバーの誕生に反応したMetaTrader コードを反映します。
  2. レンジ検出 – 前の SearchDepth 本の完了ローソク足の中の最高値と最低値が Highest/Lowest インジケーターで 追跡されます。この2つの価格が注文配置に使用されるブレイクアウト/平均回帰レベルを定義します。
  3. 価格距離フィルター – 現在の最良のbid/askクォートが注文板フィードからストリーミングされます。クォートと候補価格との 距離が MinOrderDistancePips 以上(PointValue を使って絶対価格に変換)の場合にのみ注文が配置されます。これは元の フリーズレベル検証を再実装し、無効な指値注文を防ぎます。
  4. 注文スタイルの選択PendingOrderMode は指値注文(最低値でのbuy limit、最高値でのsell limit)またはストップ注文 (最高値上のbuy stop、最低値下のsell stop)の間で選択します。MetaTraderスクリプトと同様に、両方の注文が同時に送信 されます。
  5. リスク保護 – 組み込みの StartProtection ヘルパーは絶対価格ステップで表現されたストップロスとテイクプロフィット レベルを添付します(StopLossPips/TakeProfitPips)。MinStopDistancePips に対する最小距離チェックは、保護注文が シンボルのストップレベルを尊重しなければならないというMT5の要件を複製します。
  6. 注文管理 – 指値注文の一方が約定すると、反対の注文は即座にキャンセルされます。バーの間隔中、アクティブな注文が 存在する限り戦略は追加注文を配置しないため、ソースEAの動作と一致します。

パラメーター

パラメーター 説明
StartHour 新しい指値注文のペアが作成される時間(0-23)。
SearchDepth 最高値/最低値レンジの計算に使用する前の完了ローソク足の数。
PendingOrderMode Limit は平均回帰バリアントを複製、Stop はブレイクアウト注文を配置します。
StopLossPips pips単位のストップロス距離(PointValue で変換)。無効にするには0に設定。
TakeProfitPips pips単位のテイクプロフィット距離。無効にするには0に設定。
PointValue 価格単位でのpip値。Security.PriceStep/MinStep から自動検出するには0に設定。
MinOrderDistancePips 現在のbid/askと指値価格の間の最小許容距離。フリーズレベルチェックをモデル化します。
MinStopDistancePips ストップ/テイクの最小許容距離。StopsLevel チェックを反映します。
CandleType レンジ計算とスケジューリングロジックに使用する時間軸。

Strategy.Volume が注文サイズを制御します;BuyLimitSellLimitBuyStopSellStop が注文を送信できるよう正に 維持してください。

使用方法

  1. 戦略を銘柄に添付し、取引したい時間軸に CandleType を設定します。
  2. MT5の入力と同じように StartHourSearchDepth を設定します。ブローカーが注文と市場価格の間の最小距離を強制する 場合は Min*Pips の閾値を調整します。
  3. セキュリティのメタデータからの自動検出が不可能な場合(例えば合成商品の場合)は PointValue を調整します。
  4. StopLossPipsTakeProfitPips を元のEAと一致するように設定します。注文が約定したら保護モジュールが自動的に ストップとターゲットを添付します。
  5. 正の Volume を設定して戦略を開始します。ローソク足と注文板データをサブスクライブし、スケジュールされたバーで両方の 指値注文を配置し、一方のトレードが実行されたら反対の注文をキャンセルします。

元のEAとの違い

  • MetaTraderの MoneyFixedMargin リスクモード(パーセントベースのサイジング)はポートされていません。StockSharpユーザーは Strategy.Volume を直接設定するか、外部のポジションサイジングモジュールで戦略をラップする必要があります。
  • フリーズレベルとストップレベルのチェックは設定可能な MinOrderDistancePipsMinStopDistancePips パラメーターを 通じて表現されます。これは同等の取引所メタデータが常にStockSharpを通じて利用できるわけではないためです。
  • 注文配置は前のローソク足が閉じて次のバーが StartHour に開始するときに発生します。これは新しいバーの誕生でトリガー されたMT5実装と機能的に同一です。
  • ソースコード内のすべてのコメントは英語に翻訳されており、外部ドキュメントは利便性のために複数の言語で利用可能です。

取引する予定の商品に合わせて距離と取引時間を調整します。スプレッドの広い市場では、ブローカーによる即時拒否を避けるために より大きな MinOrderDistancePips またはpip値が必要になる場合があります。

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>
/// Constituents breakout strategy converted from the original MetaTrader expert advisor.
/// Detects the recent high/low range from N candles and enters with market orders
/// when price breaks above the high (buy) or below the low (sell).
/// Uses stop-loss, take-profit, and trailing stop for risk management.
/// </summary>
public class ConstituentsEAStrategy : Strategy
{
	private readonly StrategyParam<int> _searchDepth;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<DataType> _candleType;

	private Highest _highest = null!;
	private Lowest _lowest = null!;

	private decimal _pipSize;
	private decimal _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takePrice;
	private decimal _prevHigh;
	private decimal _prevLow;
	private bool _exitRequested;

	/// <summary>
	/// Number of completed candles used to determine the recent range.
	/// </summary>
	public int SearchDepth
	{
		get => _searchDepth.Value;
		set => _searchDepth.Value = value;
	}

	/// <summary>
	/// Stop loss distance expressed in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take profit distance expressed in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Working candle type.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="ConstituentsEaStrategy"/> class.
	/// </summary>
	public ConstituentsEAStrategy()
	{
		_searchDepth = Param(nameof(SearchDepth), 3)
			.SetGreaterThanZero()
			.SetDisplay("Search Depth", "Number of completed candles used to find extremes", "Setup");

		_stopLossPips = Param(nameof(StopLossPips), 50m)
			.SetNotNegative()
			.SetDisplay("Stop Loss (pips)", "Stop loss distance expressed in pips", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 100m)
			.SetNotNegative()
			.SetDisplay("Take Profit (pips)", "Take profit distance expressed in pips", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Working timeframe used to evaluate highs/lows", "General");
	}

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

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

		_highest = null!;
		_lowest = null!;
		_pipSize = 0m;
		_entryPrice = 0m;
		_stopPrice = null;
		_takePrice = null;
		_prevHigh = 0m;
		_prevLow = 0m;
		_exitRequested = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_pipSize = CalculatePipSize();

		_highest = new Highest { Length = SearchDepth };
		_lowest = new Lowest { Length = SearchDepth };

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

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

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

		// Process indicators
		var highValue = _highest.Process(new DecimalIndicatorValue(_highest, candle.HighPrice, candle.OpenTime) { IsFinal = true });
		var lowValue = _lowest.Process(new DecimalIndicatorValue(_lowest, candle.LowPrice, candle.OpenTime) { IsFinal = true });

		if (!_highest.IsFormed || !_lowest.IsFormed)
			return;

		var currentHigh = highValue.ToDecimal();
		var currentLow = lowValue.ToDecimal();

		// Manage existing position
		if (Position != 0)
		{
			ManagePosition(candle);

			// Update range for next trade
			_prevHigh = currentHigh;
			_prevLow = currentLow;
			return;
		}

		// Check for breakout signals using previous range
		if (_prevHigh > 0m && _prevLow > 0m)
		{
			// Breakout above the recent high -> buy
			if (candle.ClosePrice > _prevHigh)
			{
				_entryPrice = candle.ClosePrice;
				_exitRequested = false;

				if (StopLossPips > 0m)
					_stopPrice = _entryPrice - StopLossPips * _pipSize;
				else
					_stopPrice = null;

				if (TakeProfitPips > 0m)
					_takePrice = _entryPrice + TakeProfitPips * _pipSize;
				else
					_takePrice = null;

				BuyMarket();
			}
			// Breakout below the recent low -> sell
			else if (candle.ClosePrice < _prevLow)
			{
				_entryPrice = candle.ClosePrice;
				_exitRequested = false;

				if (StopLossPips > 0m)
					_stopPrice = _entryPrice + StopLossPips * _pipSize;
				else
					_stopPrice = null;

				if (TakeProfitPips > 0m)
					_takePrice = _entryPrice - TakeProfitPips * _pipSize;
				else
					_takePrice = null;

				SellMarket();
			}
		}

		// Update range for next candle
		_prevHigh = currentHigh;
		_prevLow = currentLow;
	}

	private void ManagePosition(ICandleMessage candle)
	{
		if (_exitRequested)
			return;

		if (Position > 0)
		{
			// Check take profit
			if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
			{
				_exitRequested = true;
				SellMarket();
				return;
			}

			// Check stop loss
			if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
			{
				_exitRequested = true;
				SellMarket();
				return;
			}
		}
		else if (Position < 0)
		{
			// Check take profit
			if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
			{
				_exitRequested = true;
				BuyMarket();
				return;
			}

			// Check stop loss
			if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
			{
				_exitRequested = true;
				BuyMarket();
				return;
			}
		}
	}

	private decimal CalculatePipSize()
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
			return 0.01m;

		return step;
	}
}