GitHub で見る

Hans123 Trader v2 戦略

Hans123 Trader v2 は、最近のトレーディングレンジの周りに未決ストップ注文を配置するブレイクアウト戦略です。Vladimir Karputov による MetaTrader 実装を反映し、StockSharp の高レベル API に適応されています。このシステムは、保護的なエグジットとトレーリングストップを管理しながら、価格が最近の 80 バーレンジを脱出するときのモメンタムを捉えることに焦点を当てています。

中心的なアイデア

  • 設定可能なローソク足シリーズを監視します(デフォルトは 1 時間バー)。
  • アクティブなセッションウィンドウ中に、最後の N ローソク足(デフォルト 80)にわたって最高値の高値と最低値の安値を計算します。
  • 市場が現在の買値/売値から十分に離れている場合、最高値の高値にバイストップ注文を、最低値の安値にセルストップ注文を配置します。
  • 過剰エクスポージャーを避けるために、アクティブな未決注文の合計数を制限します。
  • ポジションが開かれると、残りの未決注文をキャンセルし、ストップロスとテイクプロフィットのオフセット(ピップで測定)を適用し、トレーリングストップを有効にします。

トレード管理

  • エントリー: ストップ注文は処理されたローソク足の時間が設定された開始時間と終了時間の間にある場合にのみ配置されます。その窓の外では注文は無視されます。
  • ポジション保護: 新しいポジションが作成されると、戦略は設定されたピップ距離を使用して保護的なストップロスとテイクプロフィット注文をすぐに登録します。
  • トレーリングストップ: 有効な場合、ポジションに有利な方向にトレーリング閾値プラスステップ以上動いたら、ストップロス注文が価格により近い位置に再発行されます。
  • 注文クリーンアップ: ポジションを終了すると保護注文がキャンセルされ、新しいエントリーは反対の未決注文をキャンセルして、元の MQL ロジックの動作と一致します。

パラメーター

パラメーター 説明
Volume ブレイクアウトおよび保護注文を送信する際に使用される注文サイズ。
StopLossPips エントリー価格と保護ストップロスの間のピップでの距離。無効にするには 0 に設定。
TakeProfitPips エントリー価格とテイクプロフィット注文の間のピップでの距離。無効にするには 0 に設定。
TrailingStopPips ピップでの初期トレーリングストップ距離。0 はトレーリングを無効にします。
TrailingStepPips トレーリングストップを再び動かす前に必要な最小追加利益(ピップ)。トレーリングが有効な場合は非ゼロでなければなりません。
StartHour 新しい未決注文を配置するためのセッション開始時間(含む)。
EndHour 新しい未決注文を配置するためのセッション終了時間(含まない)。StartHour より大きくなければなりません。
MaxPendingOrders 許可される同時ブレイクアウト注文(買い + 売り)の最大数。
BreakoutPeriod 最高値の高値と最低値の安値計算のためのルックバック長(ローソク足数)。
CandleType 戦略によって処理されるローソク足シリーズ(タイムフレームまたはその他のローソク足データタイプ)。

注意事項

  • ピップサイズは銘柄の価格ステップから導出されます。3 桁および 5 桁の外国為替シンボルの場合、ポイント値は MQL のピップ定義と一致するように調整されます。
  • 戦略は利用可能な場合に Security.BestBid/BestAsk スナップショットに依存します。深度データが存在しない場合は、現在のローソク足終値にフォールバックして市場からの最小距離を評価します。
  • 保護注文は移動が必要な場合に再作成され、元のエキスパートアドバイザーの PositionModify ロジックを反映します。
  • 実装はリクエストに応じて Python 翻訳なしに純粋に C# でロジックを保持します。
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 Trader v2 breakout strategy converted from the original MQL expert.
/// Enters on breakout of recent range extremes and manages trailing protection.
/// </summary>
public class Hans123TraderV2Strategy : Strategy
{
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<decimal> _trailingStepPips;
	private readonly StrategyParam<int> _startHour;
	private readonly StrategyParam<int> _endHour;
	private readonly StrategyParam<int> _breakoutPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private Highest _highest;
	private Lowest _lowest;

	private decimal _entryPrice;
	private decimal _pipSize;
	private decimal _stopLossDistance;
	private decimal _takeProfitDistance;
	private decimal _trailingStopDistance;
	private decimal _trailingStepDistance;
	private decimal _highestStopPrice;
	private decimal? _prevBreakoutHigh;
	private decimal? _prevBreakoutLow;

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

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

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

	/// <summary>
	/// Trailing step in pips.
	/// </summary>
	public decimal TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

	/// <summary>
	/// Session start hour.
	/// </summary>
	public int StartHour
	{
		get => _startHour.Value;
		set => _startHour.Value = value;
	}

	/// <summary>
	/// Session end hour.
	/// </summary>
	public int EndHour
	{
		get => _endHour.Value;
		set => _endHour.Value = value;
	}

	/// <summary>
	/// Lookback length for calculating highs and lows.
	/// </summary>
	public int BreakoutPeriod
	{
		get => _breakoutPeriod.Value;
		set => _breakoutPeriod.Value = value;
	}

	/// <summary>
	/// Candle type processed by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes <see cref="Hans123TraderV2Strategy"/>.
	/// </summary>
	public Hans123TraderV2Strategy()
	{
		_stopLossPips = Param(nameof(StopLossPips), 50m)
			.SetNotNegative()
			.SetDisplay("Stop Loss (pips)", "Stop distance", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 50m)
			.SetNotNegative()
			.SetDisplay("Take Profit (pips)", "Target distance", "Risk");

		_trailingStopPips = Param(nameof(TrailingStopPips), 10m)
			.SetNotNegative()
			.SetDisplay("Trailing Stop (pips)", "Trailing distance", "Risk");

		_trailingStepPips = Param(nameof(TrailingStepPips), 5m)
			.SetNotNegative()
			.SetDisplay("Trailing Step (pips)", "Extra profit before trailing", "Risk");

		_startHour = Param(nameof(StartHour), 0)
			.SetDisplay("Start Hour", "Session start hour", "Session");

		_endHour = Param(nameof(EndHour), 23)
			.SetDisplay("End Hour", "Session end hour", "Session");

		_breakoutPeriod = Param(nameof(BreakoutPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Breakout Period", "High/low lookback", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Processed candles", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_pipSize = 0m;
		_stopLossDistance = 0m;
		_takeProfitDistance = 0m;
		_trailingStopDistance = 0m;
		_trailingStepDistance = 0m;
		_highest = null;
		_lowest = null;
		_entryPrice = 0m;
		_highestStopPrice = 0m;
		_prevBreakoutHigh = null;
		_prevBreakoutLow = null;
	}

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

		_pipSize = Security?.PriceStep ?? 1m;
		UpdateDistanceCache();

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

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

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

	private void UpdateDistanceCache()
	{
		_stopLossDistance = StopLossPips * _pipSize;
		_takeProfitDistance = TakeProfitPips * _pipSize;
		_trailingStopDistance = TrailingStopPips * _pipSize;
		_trailingStepDistance = TrailingStepPips * _pipSize;
	}

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

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

		// Manage existing position: trailing stop and SL/TP
		if (Position != 0)
		{
			ManagePosition(candle);
			return;
		}

		// Session filter
		var hour = candle.OpenTime.TimeOfDay.Hours;
		if (hour < StartHour || hour >= EndHour)
		{
			_prevBreakoutHigh = breakoutHigh;
			_prevBreakoutLow = breakoutLow;
			return;
		}

		// Breakout entry: buy when price breaks above previous bar's high, sell when below previous bar's low
		if (_prevBreakoutHigh.HasValue && _prevBreakoutLow.HasValue)
		{
			if (candle.HighPrice > _prevBreakoutHigh.Value)
			{
				BuyMarket();
				_entryPrice = candle.ClosePrice;
				_highestStopPrice = 0m;
			}
			else if (candle.LowPrice < _prevBreakoutLow.Value)
			{
				SellMarket();
				_entryPrice = candle.ClosePrice;
				_highestStopPrice = 0m;
			}
		}

		_prevBreakoutHigh = breakoutHigh;
		_prevBreakoutLow = breakoutLow;
	}

	private void ManagePosition(ICandleMessage candle)
	{
		var price = candle.ClosePrice;

		if (Position > 0)
		{
			// Check stop loss
			if (_stopLossDistance > 0m && candle.LowPrice <= _entryPrice - _stopLossDistance)
			{
				SellMarket(Position);
				return;
			}

			// Check take profit
			if (_takeProfitDistance > 0m && candle.HighPrice >= _entryPrice + _takeProfitDistance)
			{
				SellMarket(Position);
				return;
			}

			// Trailing stop
			if (_trailingStopDistance > 0m)
			{
				var moveFromEntry = price - _entryPrice;
				if (moveFromEntry > _trailingStopDistance + _trailingStepDistance)
				{
					var newStop = price - _trailingStopDistance;
					if (newStop > _highestStopPrice + _trailingStepDistance)
						_highestStopPrice = newStop;

					if (_highestStopPrice > 0m && candle.LowPrice <= _highestStopPrice)
					{
						SellMarket(Position);
						return;
					}
				}
			}
		}
		else if (Position < 0)
		{
			var vol = Math.Abs(Position);

			// Check stop loss
			if (_stopLossDistance > 0m && candle.HighPrice >= _entryPrice + _stopLossDistance)
			{
				BuyMarket(vol);
				return;
			}

			// Check take profit
			if (_takeProfitDistance > 0m && candle.LowPrice <= _entryPrice - _takeProfitDistance)
			{
				BuyMarket(vol);
				return;
			}

			// Trailing stop
			if (_trailingStopDistance > 0m)
			{
				var moveFromEntry = _entryPrice - price;
				if (moveFromEntry > _trailingStopDistance + _trailingStepDistance)
				{
					var newStop = price + _trailingStopDistance;
					if (_highestStopPrice == 0m || newStop < _highestStopPrice - _trailingStepDistance)
						_highestStopPrice = newStop;

					if (_highestStopPrice > 0m && candle.HighPrice >= _highestStopPrice)
					{
						BuyMarket(vol);
						return;
					}
				}
			}
		}
	}
}