GitHub で見る

Hans123 トレーダー戦略

概要

Hans123 トレーダーは、MetaTrader 5 のオリジナルエキスパートアドバイザー Hans123_Trader から変換されたブレイクアウトシステムです。この戦略は、動的な価格レンジをスキャンし、設定可能なイントラデイウィンドウ内に保留中のストップ注文を出します。保護ストップ、利益目標、トレーリングルールはMQL5ロジックを反映しており、StockSharpポートがソースロボットと同様に動作します。

主要コンセプト

  • レンジブレイクアウト – 直近 N 本のロウソク足の最高値と最安値を使用してブレイクアウトチャネルを定義します。
  • 時間フィルター – 夜間のノイズを避けるため、開始時間と終了時間の間のみシグナルを評価します。
  • 同期保留注文 – 取引ウィンドウ内のすべての完成したロウソク足でbuyストップとsellストップ注文を更新します。
  • リスク管理 – オプションのストップロス、テイクプロフィット、トレーリングストップ距離(pips単位)。
  • 動的トレーリング – 価格がトレーリングストップ距離とトレーリングステップ距離の合計を超えると、利益を確保するために保護ストップが引き締められます。

取引ロジック

  1. 選択したロウソク足シリーズをサブスクライブし、RangeLength インジケーターウィンドウが形成されるのを待ちます。
  2. 各完成ロウソク足で:
    • 80バー(設定可能)の高値/安値チャネルを更新します。
    • 現在時刻が [StartHour, EndHour) の範囲外の場合、処理をスキップします。
    • 既存のエントリー注文をキャンセルし、新しいストップ注文を出します:
      • Buy ストップ:レンジ高値に OrderVolume で。
      • Sell ストップ:レンジ安値に OrderVolume で。
  3. エントリー注文が約定したとき:
    • 反対側の保留注文をキャンセルします。
    • 対応するpips距離がゼロより大きい場合、ストップロスとテイクプロフィット注文を登録します。
  4. ポジションが開いている間:
    • 価格が少なくとも TrailingStopPips + TrailingStepPips 進んだ場合、保護ストップを TrailingStopPips だけ市場方向に移動します。
    • ポジションがフラットに戻ると保護注文は自動的にキャンセルされます。

パラメーター

名前 説明 デフォルト値
OrderVolume ブレイクアウトエントリーの注文サイズ。 0.1
RangeLength ブレイクアウトチャネルのロウソク足本数。 80
StopLossPips ストップロス距離(pips)(0でストップ無効)。 50
TakeProfitPips テイクプロフィット距離(pips)(0でターゲット無効)。 50
TrailingStopPips トレーリングストップ距離(pips)(0でトレーリング無効)。 10
TrailingStepPips トレーリングストップが更新される前に必要な追加pips。トレーリングが有効の場合、正の値である必要があります。 5
StartHour ブレイクアウト注文が開始する1日の時間(UTC、含む)。 6
EndHour ブレイクアウト注文が停止する1日の時間(UTC、含まない)。 10
CandleType 作業ロウソク足データタイプと時間軸。 1時間ロウソク足

実践的なメモ

  • pip サイズは証券の小数点以下桁数に合わせて調整されます(3/5桁の外国為替シンボルは通常の ×10 調整を受けます)。
  • トレーリングストップは、ポジションが活性化距離を超えた後にのみ作成されます。StopLossPips がゼロの場合、トレーリング条件が満たされるまで初期ストップは省略されます。
  • ポートフォリオの権限を選択した OrderVolume と銘柄のコントラクトサイズに合わせてください。
  • StockSharpの変換は、デバッグのためにロウソク足、チャネル、取引を視覚化するチャートヘルパーを使用します。

MQL5バージョンとの違い

  • ストップと目標注文はMetaTraderの取引リクエストの代わりにStockSharpの高レベルヘルパーを通じて登録されます。
  • ボリュームのデフォルト値は同一(0.1ロット)ですが、StrategyParam メタデータを通じて最適化できます。
  • 保留注文は、StockSharpのイベントモデルに合わせて、ティックレベルの更新を待つ代わりに各完成ロウソク足で更新されます。

使用方法

  1. ストラテジーをポートフォリオ/銘柄ペアに接続し、ロウソク足サブスクリプションが希望の時間軸と一致していることを確認します。
  2. 銘柄のボラティリティとセッション境界に合わせてパラメーターを調整します。
  3. ストラテジーを開始し、チャートエリアのオーバーレイを監視してブレイクアウトレベルと実行された取引を確認します。
  4. 必要に応じて、StockSharpテスト環境内での最適化に組み込みパラメーターを使用します。
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>
/// Hans123 breakout strategy converted from MQL5.
/// Collects an intraday range and trades pending stop orders within a trading window.
/// Applies configurable stop-loss, take-profit, and trailing protection.
/// </summary>
public class Hans123TraderStrategy : Strategy
{
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<int> _rangeLength;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _trailingStopPips;
	private readonly StrategyParam<int> _trailingStepPips;
	private readonly StrategyParam<int> _startHour;
	private readonly StrategyParam<int> _endHour;
	private readonly StrategyParam<DataType> _candleType;

	private Highest _highest = null!;
	private Lowest _lowest = null!;
	private decimal _entryPrice;
	private decimal _pipSize;
	private decimal _highestSinceEntry;
	private decimal _lowestSinceEntry;

	/// <summary>
	/// Volume used for breakout orders.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Number of candles that form the breakout range.
	/// </summary>
	public int RangeLength
	{
		get => _rangeLength.Value;
		set => _rangeLength.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in pips.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in pips.
	/// </summary>
	public int TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	/// <summary>
	/// Extra move (in pips) before trailing activates again.
	/// </summary>
	public int TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

	/// <summary>
	/// Start hour (inclusive) of the trading window.
	/// </summary>
	public int StartHour
	{
		get => _startHour.Value;
		set => _startHour.Value = value;
	}

	/// <summary>
	/// End hour (exclusive) of the trading window.
	/// </summary>
	public int EndHour
	{
		get => _endHour.Value;
		set => _endHour.Value = value;
	}

	/// <summary>
	/// Candle type used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="Hans123TraderStrategy"/> class.
	/// </summary>
	public Hans123TraderStrategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 0.1m)
			.SetDisplay("Order Volume", "Breakout order volume", "General")
			
			.SetOptimize(0.1m, 2m, 0.1m);

		_rangeLength = Param(nameof(RangeLength), 40)
			.SetGreaterThanZero()
			.SetDisplay("Range Length", "Candles in breakout range", "General")
			
			.SetOptimize(40, 120, 10);

		_stopLossPips = Param(nameof(StopLossPips), 50)
			.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips", "Risk Management")
			
			.SetOptimize(0, 150, 10);

		_takeProfitPips = Param(nameof(TakeProfitPips), 50)
			.SetDisplay("Take Profit (pips)", "Take-profit distance in pips", "Risk Management")
			
			.SetOptimize(0, 200, 10);

		_trailingStopPips = Param(nameof(TrailingStopPips), 10)
			.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk Management")
			
			.SetOptimize(0, 100, 5);

		_trailingStepPips = Param(nameof(TrailingStepPips), 5)
			.SetDisplay("Trailing Step (pips)", "Extra pips before trailing updates", "Risk Management")
			
			.SetOptimize(0, 50, 5);

		_startHour = Param(nameof(StartHour), 0)
			.SetDisplay("Start Hour", "Hour (UTC) when orders can be placed", "Schedule")
			
			.SetOptimize(0, 23, 1);

		_endHour = Param(nameof(EndHour), 24)
			.SetDisplay("End Hour", "Hour (UTC) when orders stop", "Schedule")
			
			.SetOptimize(1, 24, 1);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(3).TimeFrame())
			.SetDisplay("Candle Type", "Working candle timeframe", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

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

		_highest = null;
		_lowest = null;
		_entryPrice = 0m;
		_pipSize = 0m;
		_highestSinceEntry = 0m;
		_lowestSinceEntry = 0m;
	}

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


		_pipSize = CalculatePipSize();

		_highest = new Highest { Length = RangeLength };
		_lowest = new Lowest { Length = RangeLength };

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(_highest, _lowest, ProcessCandle).Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _highest);
			DrawIndicator(area, _lowest);
			DrawOwnTrades(area);
		}
	}

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

		// Check protective levels
		CheckProtection(candle);

		if (!_highest.IsFormed || !_lowest.IsFormed)
			return;

		if (!IsWithinTradingWindow(candle.OpenTime))
			return;

		if (OrderVolume <= 0m || highest <= lowest)
			return;

		// Track extremes for trailing
		if (Position > 0 && candle.HighPrice > _highestSinceEntry)
			_highestSinceEntry = candle.HighPrice;
		if (Position < 0 && (_lowestSinceEntry == 0 || candle.LowPrice < _lowestSinceEntry))
			_lowestSinceEntry = candle.LowPrice;

		// Breakout entry logic
		if (Position == 0)
		{
			if (candle.HighPrice >= highest)
			{
				BuyMarket(OrderVolume);
			}
			else if (candle.LowPrice <= lowest)
			{
				SellMarket(OrderVolume);
			}
		}
	}

	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);
		if (trade?.Trade == null) return;
		if (Position != 0m && _entryPrice == 0m)
		{
			_entryPrice = trade.Trade.Price;
			_highestSinceEntry = trade.Trade.Price;
			_lowestSinceEntry = trade.Trade.Price;
		}
		if (Position == 0m)
		{
			_entryPrice = 0m;
			_highestSinceEntry = 0m;
			_lowestSinceEntry = 0m;
		}
	}

	private void CheckProtection(ICandleMessage candle)
	{
		if (Position == 0 || _entryPrice == 0m)
			return;

		var stopDist = StopLossPips > 0 ? StopLossPips * _pipSize : 0m;
		var takeDist = TakeProfitPips > 0 ? TakeProfitPips * _pipSize : 0m;
		var trailDist = TrailingStopPips > 0 ? TrailingStopPips * _pipSize : 0m;
		var activation = (TrailingStopPips + TrailingStepPips) * _pipSize;

		if (Position > 0)
		{
			// Stop loss
			if (stopDist > 0m && candle.LowPrice <= _entryPrice - stopDist)
			{
				SellMarket(Math.Abs(Position));
				return;
			}
			// Take profit
			if (takeDist > 0m && candle.HighPrice >= _entryPrice + takeDist)
			{
				SellMarket(Math.Abs(Position));
				return;
			}
			// Trailing stop
			if (trailDist > 0m && _highestSinceEntry - _entryPrice > activation)
			{
				var trailStop = _highestSinceEntry - trailDist;
				if (candle.LowPrice <= trailStop)
				{
					SellMarket(Math.Abs(Position));
					return;
				}
			}
		}
		else if (Position < 0)
		{
			if (stopDist > 0m && candle.HighPrice >= _entryPrice + stopDist)
			{
				BuyMarket(Math.Abs(Position));
				return;
			}
			if (takeDist > 0m && candle.LowPrice <= _entryPrice - takeDist)
			{
				BuyMarket(Math.Abs(Position));
				return;
			}
			if (trailDist > 0m && _lowestSinceEntry > 0m && _entryPrice - _lowestSinceEntry > activation)
			{
				var trailStop = _lowestSinceEntry + trailDist;
				if (candle.HighPrice >= trailStop)
				{
					BuyMarket(Math.Abs(Position));
					return;
				}
			}
		}
	}

	private decimal CalculatePipSize()
	{
		var step = Security?.PriceStep ?? 1m;
		return step;
	}

	private bool IsWithinTradingWindow(DateTimeOffset time)
	{
		return time.Hour >= StartHour && time.Hour < EndHour;
	}
}