GitHub で見る

朝夕のStochastic戦略

この戦略は、MetaTrader 5 専門アドバイザー Expert_AMS_ES_Stoch (Stochastic 確認付きの明けの明星/宵の明星) を StockSharp に移植します。この実装では、高レベルのローソク足サブスクリプション API を使用しながら、元のローソク足パターン認識と確率的確認ルールが維持されるため、すべての決定は完成したバーで行われます。

戦略ロジック

  • 指標
    • 構成可能な %K%D、および減速期間を備えた標準の Stochastic オシレーター。
    • MQL バージョンと同様に、ローソク足を「長い」または「小さい」に分類するためのローソク足本体サイズ (絶対的な open-close) の単純移動平均。
  • Long Entry
    • 最後に完成した 3 本のローソク全体のモーニング スター パターン:
      1. 2 バー前: 体の平均を超えるサイズの長い弱気体。
      2. 前のバー: 前のローソク足の下で開閉する小型のローソク足。
      3. 現在の足: 最初のローソク足の中点を上回る強気終値。
    • Stochastic シグナル ライン (%D) は売られすぎのしきい値 (デフォルトは 30) を下回っています。
    • 既存のショートエクスポージャは、ロングポジションをオープンする前にフラット化されます。
  • ショートエントリー
    • 上記のルールを反映したイブニングスターパターン。
    • Stochastic %D は買われすぎのしきい値 (デフォルトは 70) を超えています。
    • 既存のロングエクスポージャーはショートトレードを開始する前にクローズされます。
  • ポジション出口
    • %D が高速回復レベル (20) または極値レベル (80) を超えるとショートはクローズされます。
    • %D80 または 20 を下回るとロングはクローズされます。
    • これらの交差は、MQL 信号モジュールからの「クローズ条件」を再現します。

パラメーター

名前 説明
CandleType パターン検出とすべてのインジケーターに使用されるタイムフレーム (または他の DataType)。
StochasticKPeriod, StochasticDPeriod, StochasticSlowing %K%D、および確率発振器の減速期間。
StochasticOverbought, StochasticOversold イブニングスター/モーニングスターのエントリーを確認するために使用される信号線のしきい値。
PatternAveragePeriod 体のサイズを平均するために使用される完成したキャンドルの数 (|開閉|)。
ShortExitLevel, LongExitLevel %D レベルは、反対方向に通過するとショート/ロング終了を強制します。

実装メモ

  • キャンドルは SubscribeCandles().BindEx(...) を通じて処理されます。このコードは完成したローソクでのみ機能し、インジケーターで GetValue() を呼び出すことはありません。
  • 本体サイズの平均化は、絶対キャンドル本体が供給された SimpleMovingAverage に依存して、MQL ライブラリからの AvgBody() ヘルパーを再現します。
  • パターン チェックは専用のヘルパー メソッドを使用して実装され、決定ロジックを読み取り可能に保ち、元の CCandlePattern ルールを反映します。
  • 逆方向にエントリーする前に、この戦略は、一度に 1 つのネット ポジションを操作するエキスパートアドバイザーの動作に一致するように、既存のエクスポージャーをクローズします。

MQL5 エキスパートとの違い

  • MetaTrader フレームワークの資金管理、トレーリング ストップ、固定ロットの設定は再現されません。 StockSharp の注文量は、戦略 Volume プロパティによって制御されます。
  • Stochastic オシレーターは StockSharp のインジケーター実装を使用します。しきい値は引き続き構成可能なため、元のブローカー フィードがわずかに異なる値を生成した場合でも動作を微調整できます。
  • ロギングでは、デバッグとバックテストを支援するために、すべての入口と出口について詳細な説明 (英語) が提供されます。
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Morning/Evening Star pattern strategy with Stochastic confirmation.
/// Buys on morning star + oversold stochastic, sells on evening star + overbought stochastic.
/// </summary>
public class MorningEveningStochasticStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _stochPeriod;
	private readonly StrategyParam<decimal> _oversold;
	private readonly StrategyParam<decimal> _overbought;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private readonly List<ICandleMessage> _candles = new();
	private decimal _prevK;
	private bool _hasPrevK;
	private int _candlesSinceTrade;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int StochPeriod { get => _stochPeriod.Value; set => _stochPeriod.Value = value; }
	public decimal Oversold { get => _oversold.Value; set => _oversold.Value = value; }
	public decimal Overbought { get => _overbought.Value; set => _overbought.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public MorningEveningStochasticStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_stochPeriod = Param(nameof(StochPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Stoch Period", "Stochastic K period", "Indicators");
		_oversold = Param(nameof(Oversold), 30m)
			.SetDisplay("Oversold", "Stochastic oversold level", "Signals");
		_overbought = Param(nameof(Overbought), 70m)
			.SetDisplay("Overbought", "Stochastic overbought level", "Signals");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 6)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_candles.Clear();
		_prevK = 0m;
		_hasPrevK = false;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_candles.Clear();
		_hasPrevK = false;
		_candlesSinceTrade = SignalCooldownCandles;
		var stoch = new StochasticOscillator { K = { Length = StochPeriod }, D = { Length = 3 } };
		var subscription = SubscribeCandles(CandleType);
		subscription.BindEx(stoch, ProcessCandle).Start();
	}

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

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		var stochTyped = stochValue as StochasticOscillatorValue;
		if (stochTyped?.K is not decimal kValue) return;

		_candles.Add(candle);
		if (_candles.Count > 5)
			_candles.RemoveAt(0);

		if (_candles.Count >= 3)
		{
			var c3 = _candles[^1]; // current
			var c2 = _candles[^2]; // middle (star)
			var c1 = _candles[^3]; // first

			var body1 = Math.Abs(c1.ClosePrice - c1.OpenPrice);
			var body2 = Math.Abs(c2.ClosePrice - c2.OpenPrice);
			var body3 = Math.Abs(c3.ClosePrice - c3.OpenPrice);

			// Morning Star: bearish + small body + bullish, close above midpoint of first
			var isMorningStar = c1.OpenPrice > c1.ClosePrice  // first bearish
				&& body2 < body1 * 0.5m                       // small middle body
				&& c3.ClosePrice > c3.OpenPrice                // third bullish
				&& c3.ClosePrice > (c1.OpenPrice + c1.ClosePrice) / 2m;

			// Evening Star: bullish + small body + bearish, close below midpoint of first
			var isEveningStar = c1.ClosePrice > c1.OpenPrice   // first bullish
				&& body2 < body1 * 0.5m                        // small middle body
				&& c3.OpenPrice > c3.ClosePrice                // third bearish
				&& c3.ClosePrice < (c1.OpenPrice + c1.ClosePrice) / 2m;

			if (isMorningStar && kValue < Oversold && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (isEveningStar && kValue > Overbought && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		// Exit on stochastic cross
		if (_hasPrevK)
		{
			if (Position > 0 && _prevK >= Overbought && kValue < Overbought && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
			else if (Position < 0 && _prevK <= Oversold && kValue > Oversold && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevK = kValue;
		_hasPrevK = true;
	}
}