GitHub で見る

レイモンド曇りの日戦略

概要

Raymond Cloudy Day は、元の 「Raymond Cloudy Day for EA」 MQL5 エキスパート アドバイザーの取引ロジックを再構築したブレイクアウト フォロー戦略です。このアルゴリズムは、より高いタイムフレームのローソク足から一連の基準レベルを導出し、それらを使用して実行タイムフレームでの勢いの再開を検出します。 StockSharp ポートは、元の取引ルールを維持しながら、各コンポーネントを構成可能な戦略パラメーターとして公開します。

市場データ

  • シグナルキャンドル – 取引が実行される時間枠。この戦略は、エントリーシグナルとポジション管理のためにこのシリーズをサブスクライブします。
  • ピボット キャンドル – レイモンド レベルの計算に使用されるより高い時間枠。デフォルトでは、これは毎日のローソク足で、MQL5 入力 RayMondTimeframe を再現します。

どちらのサブスクリプションも GetWorkingSecurities を通じて自動的に登録されるため、戦略は開始するとすぐに必要なデータ ストリームをリクエストします。

レイモンドレベルの計算

完成したピボット キャンドルごとに、戦略は元の EA で定義された 4 つのコア レベルを保存します。

[ \begin{整列} TradeSS &= \frac{高値 + 安値 + 始値 + 終値}{4} \ ピボット範囲 &= 高 - 低 \ ETB &= TradeSS + 0.382 \time PivotRange \ ETS &= TradeSS - 0.382 \time PivotRange \ TPB1 &= TradeSS + 0.618 \time PivotRange \ TPS1 &= TradeSS - 0.618 \time PivotRange \ TPB2 &= TradeSS + ピボット範囲 \ TPS2 &= TradeSS - PivotRange \end{整列} ]

StockSharp 実装は、これらの値の最新のスナップショットを維持し、すべての更新をログに記録するため、ユーザーは時間の経過とともにレベルがどのように変化するかを監視できます。

エントリーロジック

レイモンド レベルが利用可能になると、戦略は完成した各シグナル キャンドルを評価します。

  1. ロングセットアップ – ローソク足の安値が TPS1 を下回り、終値がそのレベルを上回った場合、戦略はロングポジションに入ります。これは、EA 条件 Low[1] < TPS1 && Close[1] > TPS1 を反映し、レベルの強気の拒否をキャプチャします。
  2. ショートセットアップ – ローソク足が TPS1 を完全に上回ったままであるが、その下で閉じた場合、戦略はショート ポジションをオープンします (非対称ではあるものの元のルールと一致します)。

新しい注文を出す前に、アルゴリズムは未処理の注文をすべてキャンセルし、必要に応じて反対のポジションを閉じて、一方向の取引のみがアクティブなままになるようにします。

リスク管理

Raymond Cloudy Day は、ティック単位で測定される対称保護オフセットを使用します。

  • ストップロス – ロングエントリーの下(またはショートエントリーの上)に位置するProtectiveOffsetTicks
  • 利益確定 – ロングエントリーの上(またはショートエントリーの下)に ProtectiveOffsetTicks を配置します。

オフセットは商品の PriceStep で乗算され、ティックを絶対価格距離に変換します。完了した各シグナルキャンドルは、いずれかの保護レベルに達したときにポジションを閉じるチェックをトリガーします。戦略がフラットの場合、古いレベルを避けるために内部保護状態がリセットされます。

パラメーター

名前 説明 デフォルト 注意事項
TradeVolume 各エントリーに使用される注文量。 1 起動時に Volume プロパティと同期されます。
ProtectiveOffsetTicks ストップロスとテイクプロフィットの両方のティック単位の距離。 500 絶対価格を得るには、PriceStep を掛けます。
SignalCandleType トレードシグナルを生成するローソク足タイプ。 1 hour 時間枠 キャンドルを表す任意の DataType に設定できます。
PivotCandleType レイモンド レベルの計算の時間枠が長くなります。 1 day 時間枠 MQL EA からの RayMondTimeframe 入力と一致します。

すべてのパラメータは、StockSharp デザイナーの最適化範囲と説明的なメタデータをサポートしています。

追加メモ

  • この戦略では、接続されたセキュリティによって PriceStep が定義される必要があります。これが欠落している場合、取引エントリはスキップされ、警告が記録されます。
  • チャートの視覚化により、実行された取引とともに実行ローソク足が追加されます。必要に応じて、追加のカスタム図面を追加できます。
  • この実装では、インジケーター値の直接ポーリングを回避し、AGENTS.md のプロジェクト ガイドラインに従って、完了したローソク足のみを処理します。

元の EA の詳細を保持

  • レイモンド レベルの公式と乗数 (0.3820.6181.0)。
  • 最初の売り確定利益 (TPS1) に基づくエントリー ロジック。
  • StockSharp 環境のティックに変換された対称の 500 ポイントのストップロスとテイクプロフィットのオフセット。

これらのコンポーネントを使用すると、StockSharp 戦略はソース EA と同じように動作し、さらなる研究と自動化に適した豊富な構成とログを提供します。

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>
/// Raymond Cloudy Day strategy.
/// Computes Raymond levels from a higher timeframe and trades pullbacks around the first sell take-profit level.
/// </summary>
public class RaymondCloudyDayStrategy : Strategy
{
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<int> _protectiveOffsetTicks;
	private readonly StrategyParam<DataType> _signalCandleType;
	private readonly StrategyParam<DataType> _pivotCandleType;

	private decimal? _tradeSessionLevel;
	private decimal? _extendedBuyLevel;
	private decimal? _extendedSellLevel;
	private decimal? _takeProfitBuyLevel;
	private decimal? _takeProfitSellLevel;
	private decimal? _takeProfitBuyLevel2;
	private decimal? _takeProfitSellLevel2;

	private decimal? _entryPrice;
	private decimal? _takePrice;
	private decimal? _stopPrice;

	/// <summary>
	/// Trade volume used for new positions.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Distance in ticks used to build stop-loss and take-profit levels around the entry price.
	/// </summary>
	public int ProtectiveOffsetTicks
	{
		get => _protectiveOffsetTicks.Value;
		set => _protectiveOffsetTicks.Value = value;
	}

	/// <summary>
	/// Candle type that triggers trade signals.
	/// </summary>
	public DataType SignalCandleType
	{
		get => _signalCandleType.Value;
		set => _signalCandleType.Value = value;
	}

	/// <summary>
	/// Higher timeframe candle type used to compute Raymond levels.
	/// </summary>
	public DataType PivotCandleType
	{
		get => _pivotCandleType.Value;
		set => _pivotCandleType.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public RaymondCloudyDayStrategy()
	{
		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Order volume used for entries", "Trading")
			
			.SetOptimize(0.1m, 5m, 0.1m);

		_protectiveOffsetTicks = Param(nameof(ProtectiveOffsetTicks), 500)
			.SetGreaterThanZero()
			.SetDisplay("Protective Offset (ticks)", "Distance in ticks for stop-loss and take-profit", "Risk Management")
			
			.SetOptimize(50, 1000, 50);

		_signalCandleType = Param(nameof(SignalCandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Signal Candle Type", "Candle type used for trade signals", "Data");

		_pivotCandleType = Param(nameof(PivotCandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Pivot Candle Type", "Higher timeframe used to compute Raymond levels", "Data");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, SignalCandleType);
	}

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

		_tradeSessionLevel = null;
		_extendedBuyLevel = null;
		_extendedSellLevel = null;
		_takeProfitBuyLevel = null;
		_takeProfitSellLevel = null;
		_takeProfitBuyLevel2 = null;
		_takeProfitSellLevel2 = null;

		ResetProtection();
	}

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

		Volume = TradeVolume;

		var signalSubscription = SubscribeCandles(SignalCandleType);
		signalSubscription
			.Bind(ProcessBothCandle)
			.Start();

		var priceArea = CreateChartArea();
		if (priceArea != null)
		{
			DrawCandles(priceArea, signalSubscription);
			DrawOwnTrades(priceArea);
		}
	}

	private void ProcessBothCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;
		ProcessPivotCandle(candle);
		ProcessSignalCandle(candle);
	}

	private void ProcessPivotCandle(ICandleMessage candle)
	{
		// Skip unfinished candles to keep the level calculation consistent.
		if (candle.State != CandleStates.Finished)
			return;

		var high = candle.HighPrice;
		var low = candle.LowPrice;
		var open = candle.OpenPrice;
		var close = candle.ClosePrice;

		var tradeSession = (high + low + open + close) / 4m;
		var pivotRange = high - low;

		_tradeSessionLevel = tradeSession;
		_extendedBuyLevel = tradeSession + 0.382m * pivotRange;
		_extendedSellLevel = tradeSession - 0.382m * pivotRange;
		_takeProfitBuyLevel = tradeSession + 0.618m * pivotRange;
		_takeProfitSellLevel = tradeSession - 0.618m * pivotRange;
		_takeProfitBuyLevel2 = tradeSession + pivotRange;
		_takeProfitSellLevel2 = tradeSession - pivotRange;

		LogInfo($"Updated Raymond levels from {candle.OpenTime:u}. TradeSS={tradeSession}, ETB={_extendedBuyLevel}, ETS={_extendedSellLevel}, TPB1={_takeProfitBuyLevel}, TPS1={_takeProfitSellLevel}.");
	}

	private void ProcessSignalCandle(ICandleMessage candle)
	{
		// Manage exits first so protective logic reacts even when trading is disabled.
		if (candle.State != CandleStates.Finished)
			return;

		ManageOpenPosition(candle);

		//if (!IsFormedAndOnlineAndAllowTrading())
		//	return;

		if (_takeProfitSellLevel is not decimal triggerLevel)
			return;

		var low = candle.LowPrice;
		var close = candle.ClosePrice;

		// Replicate the original EA condition around the TPS1 level.
		if (Position <= 0 && low < triggerLevel && close > triggerLevel)
		{
			EnterLong(close);
		}
		else if (Position >= 0 && low > triggerLevel && close < triggerLevel)
		{
			EnterShort(close);
		}
	}

	private void EnterLong(decimal closePrice)
	{
		var priceStep = Security?.PriceStep ?? 1m;
		if (priceStep <= 0m)
			priceStep = 1m;

		CancelActiveOrders();

		var volume = TradeVolume + Math.Max(0m, -Position);
		BuyMarket(volume);

		var offset = priceStep * ProtectiveOffsetTicks;
		_entryPrice = closePrice;
		_takePrice = closePrice + offset;
		_stopPrice = closePrice - offset;

		LogInfo($"Opened long position at {closePrice}. TP={_takePrice}, SL={_stopPrice}.");
	}

	private void EnterShort(decimal closePrice)
	{
		var priceStep = Security?.PriceStep ?? 1m;
		if (priceStep <= 0m)
			priceStep = 1m;

		CancelActiveOrders();

		var volume = TradeVolume + Math.Max(0m, Position);
		SellMarket(volume);

		var offset = priceStep * ProtectiveOffsetTicks;
		_entryPrice = closePrice;
		_takePrice = closePrice - offset;
		_stopPrice = closePrice + offset;

		LogInfo($"Opened short position at {closePrice}. TP={_takePrice}, SL={_stopPrice}.");
	}

	private void ManageOpenPosition(ICandleMessage candle)
	{
		if (Position == 0)
		{
			ResetProtection();
			return;
		}

		if (_entryPrice is not decimal entry || _takePrice is not decimal take || _stopPrice is not decimal stop)
			return;

		if (Position > 0)
		{
			// Close the long position if price breaches the protective levels.
			if (candle.LowPrice <= stop)
			{
				SellMarket(Position);
				ResetProtection();
				LogInfo($"Long stop-loss triggered at {stop}.");
				return;
			}

			if (candle.HighPrice >= take)
			{
				SellMarket(Position);
				ResetProtection();
				LogInfo($"Long take-profit triggered at {take}.");
				return;
			}
		}
		else
		{
			var volume = Math.Abs(Position);

			// Close the short position when stop or take-profit is hit.
			if (candle.HighPrice >= stop)
			{
				BuyMarket(volume);
				ResetProtection();
				LogInfo($"Short stop-loss triggered at {stop}.");
				return;
			}

			if (candle.LowPrice <= take)
			{
				BuyMarket(volume);
				ResetProtection();
				LogInfo($"Short take-profit triggered at {take}.");
			}
		}
	}

	private void ResetProtection()
	{
		_entryPrice = null;
		_takePrice = null;
		_stopPrice = null;
	}
}