GitHub で見る

為替戦略の規則性

この StockSharp 戦略は、MetaTrader 4 Expert Advisor Strategy_of_Regularities_of_Exchange_Rates.mq4 を忠実に C# 変換したものです。このシステムは毎日のブレイクアウト ストラドルとして設計されています。特定の時間が到来すると市場を逆指値注文で囲み、それらの注文を夜の終了時間までアクティブに保ちます。取引が定義された取引セッションを超えて長引くことのないように、約定されたポジションはブローカー側のストップロスと日中のテイクプロフィットウォッチドッグの両方によって監視されます。

インジケーター駆動のシステムとは異なり、ロジックは時間と距離のみに焦点を当てます。スケジュールで市場の準備が整っている必要があると示されると、この戦略は現在の買値と売値からブローカー ポイント (pips) で固定オフセットを測定し、対称的な逆指値注文を 1 組発注します。このコードは、元の MQL バージョンの動作と一致するように、3 桁または 5 桁の引用符を含むシンボルにポイント計算を自動的に適応させます。

取引ロジック

  1. 開始時間 – 終了したローソク足が OpeningHour を報告すると、戦略は残っている未決注文をキャンセルし、現在の売り値より上の 買いストップ と現在の入札より下の 売りストップ を送信します。距離は EntryOffsetPoints * point です。ここで、point の値は商品 PriceStep から導出され、小数引用符に合わせて調整されます。
  2. 保護命令 – 戦略の開始直後に、構成された StopLossPointsStartProtection が有効になります。したがって、実行された取引は、元の EA と同じブローカー側のストップロスを受け取ります。
  3. テイクプロフィットの監視 – 完了したローソク足ごとに、アルゴリズムは現在の利益が TakeProfitPoints * point を超えているかどうかをチェックします。そうであれば、市場でポジションをクローズします。これは、利益がしきい値に達したときに終了した元の OrderClose ループを反映しています。
  4. 終了時間 – 時計が ClosingHour に達すると、ストラテジーはオープン ポジションを強制的に閉じ、逆指値注文をキャンセルし、次のセッションまで帳簿がフラットになるようにします。
  5. 毎日のリセット – 未決注文の新しいバッチは取引日ごとに 1 回だけ送信され、セッションごとに 1 つのセットアップという本来の意図を尊重しながら重複を防ぎます。

パラメーター

パラメータ デフォルト 説明
OpeningHour 9 ペアの逆指値注文が発注される時間 (0 ~ 23)。
ClosingHour 2 未決注文が削除され、オープンな取引がフラット化される時間 (0 ~ 23)。
EntryOffsetPoints 20 現在のビッド/アスクからストップオーダーまでのブローカーポイントの距離。
TakeProfitPoints 20 市場撤退を引き起こすブローカーポイントの利益目標。手動による利益確定を無効にするには、0 に設定します。
StopLossPoints 500 StartProtection 経由で接続された保護ストップのブローカー ポイントの距離。
OrderVolume 0.1 各ストップ注文のボリューム。
CandleType 30 minute time frame スケジュールの評価に使用されるキャンドルシリーズ。 1 時間以内のどの時間枠でも、MQL スクリプトとの動作の一貫性が保たれます。

変換メモ

  • 元のエキスパート アドバイザーはティック イベントに取り組み、Hour() を直接参照しました。 StockSharp では、戦略は完成したキャンドルをリッスンし、その開始時間を使用します。これにより、キャンドルの状態に関するリポジトリのガイドラインに準拠しながら、1 時間に 1 回のロジックが維持されます。
  • 未決注文は、生成された価格が常に商品ティックサイズと一致するように、Security.ShrinkPrice で正規化されます。
  • ストップ管理は StartProtection に委任され、MetaTrader が OrderSend 中に付加したプラットフォーム生成のストップロスを再作成します。
  • このコードは、元の EA の 1 時間未満の時間枠で発生する可能性のある、同じ日に同じブラケットを複数回再送信することを回避するために、最後の取引日を追跡します。
  • 広範なインライン コメントにより、将来のメンテナンスや実験のためにワークフローの各ステップが明確になります。
using System;
using System.Linq;
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>
/// Time-based breakout strategy converted from the "Strategy of Regularities of Exchange Rates" MQL expert advisor.
/// At a scheduled hour captures reference price, then enters on breakout above/below offset levels.
/// Exits at a closing hour or on take-profit/stop-loss hit.
/// </summary>
public class RegularitiesOfExchangeRatesStrategy : Strategy
{
	private readonly StrategyParam<int> _openingHour;
	private readonly StrategyParam<int> _closingHour;
	private readonly StrategyParam<decimal> _entryOffsetPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<DataType> _candleType;

	private SimpleMovingAverage _dummySma;
	private decimal _pointSize;
	private DateTime? _lastEntryDate;
	private decimal _referencePrice;
	private decimal _entryPrice;
	private bool _waitingForBreakout;

	public int OpeningHour
	{
		get => _openingHour.Value;
		set => _openingHour.Value = value;
	}

	public int ClosingHour
	{
		get => _closingHour.Value;
		set => _closingHour.Value = value;
	}

	public decimal EntryOffsetPoints
	{
		get => _entryOffsetPoints.Value;
		set => _entryOffsetPoints.Value = value;
	}

	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

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

	public RegularitiesOfExchangeRatesStrategy()
	{
		_openingHour = Param(nameof(OpeningHour), 9)
			.SetDisplay("Opening Hour", "Hour (0-23) when breakout levels are set", "Schedule")
			.SetRange(0, 23);

		_closingHour = Param(nameof(ClosingHour), 2)
			.SetDisplay("Closing Hour", "Hour (0-23) when the strategy exits", "Schedule")
			.SetRange(0, 23);

		_entryOffsetPoints = Param(nameof(EntryOffsetPoints), 20m)
			.SetDisplay("Entry Offset (points)", "Distance from reference price for breakout", "Orders")
			.SetGreaterThanZero();

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 20m)
			.SetDisplay("Take Profit (points)", "Profit target distance in points", "Risk")
			.SetNotNegative();

		_stopLossPoints = Param(nameof(StopLossPoints), 500m)
			.SetDisplay("Stop Loss (points)", "Stop-loss distance in points", "Risk")
			.SetGreaterThanZero();

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used to evaluate trading hours", "General");
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_pointSize = 0m;
		_lastEntryDate = null;
		_referencePrice = 0m;
		_entryPrice = 0m;
		_waitingForBreakout = false;
	}

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

		_pointSize = Security?.PriceStep ?? 0.01m;
		if (_pointSize <= 0m)
			_pointSize = 0.01m;

		_dummySma = new SimpleMovingAverage { Length = 2 };

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

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

		var hour = candle.OpenTime.Hour;
		var close = candle.ClosePrice;
		var high = candle.HighPrice;
		var low = candle.LowPrice;

		// At closing hour: flatten position and cancel breakout watch
		if (hour == ClosingHour)
		{
			if (Position > 0)
				SellMarket(Position);
			else if (Position < 0)
				BuyMarket(-Position);

			_waitingForBreakout = false;
			_entryPrice = 0m;
		}

		// Manage take-profit and stop-loss for existing position
		if (Position != 0 && _entryPrice > 0m)
		{
			var tp = TakeProfitPoints * _pointSize;
			var sl = StopLossPoints * _pointSize;

			if (Position > 0)
			{
				if ((tp > 0m && close - _entryPrice >= tp) || (sl > 0m && _entryPrice - close >= sl))
				{
					SellMarket(Position);
					_entryPrice = 0m;
					_waitingForBreakout = false;
				}
			}
			else if (Position < 0)
			{
				if ((tp > 0m && _entryPrice - close >= tp) || (sl > 0m && close - _entryPrice >= sl))
				{
					BuyMarket(-Position);
					_entryPrice = 0m;
					_waitingForBreakout = false;
				}
			}
		}

		// At opening hour: set reference price for breakout
		if (hour == OpeningHour && Position == 0)
		{
			var date = candle.OpenTime.Date;
			if (!_lastEntryDate.HasValue || _lastEntryDate.Value != date)
			{
				_referencePrice = close;
				_waitingForBreakout = true;
				_lastEntryDate = date;
			}
		}

		// Check for breakout entry
		if (_waitingForBreakout && Position == 0 && _referencePrice > 0m)
		{
			var offset = EntryOffsetPoints * _pointSize;
			var buyLevel = _referencePrice + offset;
			var sellLevel = _referencePrice - offset;

			if (high >= buyLevel)
			{
				BuyMarket();
				_entryPrice = close;
				_waitingForBreakout = false;
			}
			else if (low <= sellLevel)
			{
				SellMarket();
				_entryPrice = close;
				_waitingForBreakout = false;
			}
		}
	}
}