GitHub で見る

強気&弱気ハラミ Stochastic 戦略

強気&弱気ハラミ Stochastic 戦略 は、フォルダー MQL/310 の MetaTrader エキスパートアドバイザー expert_abh_bh_stoch.mq5 の StockSharp 移植です。オリジナルのエキスパートは、強気のハラミと弱気のハラミのセットアップにローソク足パターン認識を使用しており、確率的オシレーターの確認を必要としています。 C# バージョンは、高レベルの StockSharp API を使用して同じロジックを維持し、監視を容易にするために詳細なログとグラフ出力を追加します。

コアアイデア

  • 前の 2 つの完成したローソク足を使用して、強気ハラミと弱気ハラミのローソク足パターンを検出します。
  • 売られ過ぎ閾値を下回る確率的 %D ラインで強気のセットアップ、買われ過ぎ閾値を上回る %D で弱気のセットアップを確認します。
  • 確率的 %D ラインが下限または上限の出口しきい値を上回ったときにショート ポジションを閉じ、%D がこれらのしきい値を下回ったときにロング ポジションを閉じます。

パラメーター

パラメータ 説明 デフォルト
CandleType パターン認識に使用されるローソク足シリーズの時間枠。 1 Hour
StochasticKPeriod 確率的 %K 計算のルックバック期間。 47
StochasticDPeriod %D ラインの平滑化期間。 9
StochasticSlowing %K に追加のスムージングが適用されました (MT5 の「減速」)。 13
MovingAveragePeriod パターン検証のために体のサイズを平均するために使用されるキャンドルの数。 5
OversoldLevel Stochastic 強気シグナルを確認するための %D しきい値。 30
OverboughtLevel Stochastic 弱気シグナルを確認するための %D しきい値。 70
ExitLowerLevel 出口を引き起こすより低い確率的レベル。 20
ExitUpperLevel 出口をトリガーする上位確率レベル。 80

取引ルール

ロングエントリー

  1. 強気のハラミ パターンは、直近に完了した 2 本のローソク足で検出されます (下降トレンドにある長い弱気のローソク足に飲み込まれた小さな強気のローソク足)。
  2. 確認ローソク足の確率的 %D ラインは OversoldLevel 以下です。
  3. 現在オープンしているロングポジションはありません (Position <= 0)。
  4. この戦略は、設定された Volume を市場で購入し、必要に応じてポジションを反転するためのショートエクスポージャーを追加します。

ショートエントリー

  1. 弱気のハラミ パターンが検出されました (上昇トレンド中の長い強気のローソク足の中に小さな弱気のローソク足)。
  2. 確率的 %D 値は OverboughtLevel 以上です。
  3. 短時間露出は存在しません (Position >= 0)。
  4. この戦略は市場で販売され、必要に応じてロングポジションを最初にカバーします。

出口

  • カバーショート: 確率的 %D が ExitLowerLevel または ExitUpperLevel のいずれかを上方にクロスすると、アルゴリズムはショートポジション全体をカバーします。
  • ロングを閉じる: 確率的 %D が ExitUpperLevel または ExitLowerLevel を通過して下向きにクロスすると、ロング ポジションは閉じられます。

ファイル

  • CS/BullishBearishHaramiStochasticStrategy.cs — 戦略の高レベルの StockSharp 実装。
  • README.md — 英語のドキュメント (このファイル)。
  • README_ru.md — ロシア語のドキュメント。
  • README_zh.md — 中国語のドキュメント。

注: 変換手順に従って、Python バージョンは含まれていません。

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>
/// Harami + Stochastic strategy: Bullish/bearish harami patterns with stochastic confirmation.
/// </summary>
public class BullishBearishHaramiStochasticStrategy : 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 BullishBearishHaramiStochasticStrategy()
	{
		_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 >= 2)
		{
			var curr = _candles[^1];
			var prev = _candles[^2];

			// Bullish harami: prev bearish, curr bullish, curr body inside prev body
			var bullishHarami = prev.OpenPrice > prev.ClosePrice
				&& curr.ClosePrice > curr.OpenPrice
				&& curr.OpenPrice > prev.ClosePrice
				&& curr.ClosePrice < prev.OpenPrice;

			// Bearish harami: prev bullish, curr bearish, curr body inside prev body
			var bearishHarami = prev.ClosePrice > prev.OpenPrice
				&& curr.OpenPrice > curr.ClosePrice
				&& curr.ClosePrice > prev.OpenPrice
				&& curr.OpenPrice < prev.ClosePrice;

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

		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;
	}
}