GitHub で見る

早期オープントレンド戦略

概要

  • MQL/9826にあるMetaTrader 4エキスパートアドバイザーearlyOpenTrend.mq4の港。
  • ウィックベースの確認後、現在の価格と毎日の始値を比較することで、1 日あたり 1 方向につき 1 回取引します。
  • ブローカー セッションを 1 時間または 2 時間ずらす夏時間オフセットなど、元のタイム ウィンドウ ロジックを模倣します。
  • ローソク足サブスクリプション、自動ポジション保護、組み込みのセッション処理で、StockSharp の高レベルの API を使用します。

マーケットロジック

  1. 日中のローソク足シリーズ (デフォルトは 15 分) を構築し、当日の始値、高値、安値を再構築します。
  2. アクティブな DST オフセットを決定します。SummerTimeStartDayWinterTimeStartDay の間では、設定されたセッション時間から 2 時間を減算します。それ以外の場合は 1 時間が差し引かれます。これにより、元の ZD 変数が再現されます。
  3. DST 修正後のローソク足の開始時間が [StartHour, EndHour) 以内で、戦略がフラットである場合にのみシグナルを評価します。
  4. 長いセットアップ:
    • 最新のローソク足はその日の始値を上回って終了した。
    • 日次始値と当日の安値の間の距離が RangeFilterPips を超えています (商品のピップ サイズを使用して絶対価格に変換)。
    • 同じ取引日中にこれより早く開始されたロング取引はありません。
  5. 簡単なセットアップ:
    • 最新のローソク足はその日の始値を下回って終了した。
    • 今日の高値と日次始値の間の距離が RangeFilterPips を超えています。
    • 同じ取引日の早い時間に空売り取引が開始されたことはありません。
  6. シグナルがトリガーされると、戦略はボリューム OrderVolume の成行注文を発行します。取引タイムスタンプは、保有時間のエグジットをサポートするために保存されます。

セッションと終了のルール

  • EndHour は、指定された時間 (DST オフセットによって調整) が経過すると、新しいエントリが禁止されます。
  • ClosingHour は、修正されたサーバー時間が設定値に達すると、ポジションを強制的に閉じます。
  • HoldingHours は追加の最大保持期間を課します。一度超過すると、セッション時間に関係なくポジションはクローズされます。
  • 各取引方向は暦日に最大 1 回実行できます。戦略が新しいセッションの開始を検出すると、毎日のフラグがリセットされます。

リスク管理

  • StopLossPipsTakeProfitPips は、Security.PriceStep から導出されたピップ サイズを使用して絶対価格オフセットに変換されます (5 桁の記号は自動的にステップを 10 倍します)。
  • いずれかのパラメータがゼロより大きい場合、戦略は市場執行で StartProtection を有効にし、元のエントリー後の OrderModify ロジックを複製します。
  • 上記の強制終了以外では、追加の末尾ロジックは適用されません。

パラメーター

名前 デフォルト 説明
OrderVolume 0.1 各成行注文のサイズ。
OrderType 0 方向フィルタ: 0 = 両方、1 = ロングのみ、2 = ショートのみ。
RangeFilterPips 1 日足の始値と、エントリー前の対極との間の最小芯距離。
TakeProfitPips 100 利益確定距離 (ピップ単位) (0 を無効にします)。
StopLossPips 1000 ストップロス距離 (ピップ単位) (0 は無効)。
StartHour 7 DST 減算前のセッション開始時間。
EndHour 18 DST が差し引かれる前のセッション終了時間。
ClosingHour 20 オープントレードをフラット化するために使用される時間。
HoldingHours 0 最大保持時間 (時間単位) (0 無効)。
SummerTimeStartDay 87 2 時間の DST オフセットを有効にする年の最初の日。
WinterTimeStartDay 297 オフセットが 1 時間に戻る日。
CandleType 15分の時間枠 計算に使用されるローソク足シリーズ。

使用上の注意

  1. ストラテジーを証券に添付し、ローソク足のタイプが取引したいデータフィードの粒度と一致していることを確認してください。
  2. セッション時間をブローカーのサーバー クロックと一致するように調整します。地域の夏時間制度がヨーロッパのデフォルトのスケジュールと異なる場合は、DST パラメータを調整できます。
  3. 楽器のティックサイズに応じてピップベースのストップとターゲットを設定します。この戦略は、検出された pip 値を使用して自動的に pip を変換します。
  4. 戦略を開始します。終了した各ローソク足の日のプロファイルを更新し、セッションウィンドウ内のエントリー基準を評価し、方向ごとに単一取引の制約を適用します。

元の MQL エキスパートとの違い

  • ティックレベルの Bid/Ask チェックの代わりに完了したローソク足を使用します。これにより、エントリはわずかに遅れますが、StockSharp 内のロジックは確定的になります。
  • 保護命令は、手動の OrderModify 呼び出しではなく、StartProtection を介して実施されます。
  • MetaTrader チャートのグラフィック オブジェクトとステータス コメント (四角形、ラベル、スプレッド表示) は省略されています。
  • セッションクローズ時に強制終了すると、水中にあるときに損益分岐点ターゲットに切り替えるのではなく、ポジションを即座にクローズします。

テストの推奨事項

  • 毎日の高値/安値が実際の環境と一致するように、取引セッション全体をカバーする日中データを使用してバックテストを行います。
  • 夏期と冬期の両方の日付をシミュレートして、DST 構成を検証します。
  • ブローカーのボラティリティプロファイルに合わせて動作を調整するために、さまざまなウィックしきい値とセッション時間を試してください。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Early Open Trend: EMA crossover with RSI filter and ATR stops.
/// </summary>
public class EarlyOpenTrendStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;

	public EarlyOpenTrendStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");
		_fastEmaLength = Param(nameof(FastEmaLength), 12)
			.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators");
		_slowEmaLength = Param(nameof(SlowEmaLength), 26)
			.SetDisplay("Slow EMA Length", "Slow EMA period.", "Indicators");
		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "RSI period.", "Indicators");
		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int FastEmaLength { get => _fastEmaLength.Value; set => _fastEmaLength.Value = value; }
	public int SlowEmaLength { get => _slowEmaLength.Value; set => _slowEmaLength.Value = value; }
	public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
	public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }

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

		_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
		var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var atr = new AverageTrueRange { Length = AtrLength };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fastEma, slowEma, rsi, atr, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fastEma); DrawIndicator(area, slowEma); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal rsiVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished) return;
		if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0) { _prevFast = fastVal; _prevSlow = slowVal; return; }
		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if ((fastVal < slowVal && _prevFast >= _prevSlow) || close <= _entryPrice - atrVal * 2m) { SellMarket(); _entryPrice = 0; }
		}
		else if (Position < 0)
		{
			if ((fastVal > slowVal && _prevFast <= _prevSlow) || close >= _entryPrice + atrVal * 2m) { BuyMarket(); _entryPrice = 0; }
		}

		if (Position == 0)
		{
			if (fastVal > slowVal && _prevFast <= _prevSlow && rsiVal > 50) { _entryPrice = close; BuyMarket(); }
			else if (fastVal < slowVal && _prevFast >= _prevSlow && rsiVal < 50) { _entryPrice = close; SellMarket(); }
		}
		_prevFast = fastVal; _prevSlow = slowVal;
	}
}