Auf GitHub ansehen

Hans123 Trader v2 Strategie

Hans123 Trader v2 ist eine Ausbruch-Strategie, die ausstehende Stop-Orders rund um die jüngste Handelsspanne platziert. Sie spiegelt die MetaTrader-Implementierung von Vladimir Karputov wider und ist an die StockSharp High-Level-API angepasst. Das System konzentriert sich darauf, Momentum zu erfassen, wenn der Preis die jüngste 80-Bar-Range verlässt, während es Schutzausstiege und einen Trailing Stop verwaltet.

Grundidee

  • Eine konfigurierbare Kerzenreihe überwachen (standardmäßig 1-Stunden-Bars).
  • Während des aktiven Sitzungsfensters das höchste Hoch und niedrigste Tief über die letzten N Kerzen berechnen (Standard 80).
  • Eine Buy-Stop-Order am höchsten Hoch und eine Sell-Stop-Order am niedrigsten Tief platzieren, wenn der Markt weit genug vom aktuellen Bid/Ask entfernt ist.
  • Die Gesamtzahl der aktiven ausstehenden Orders begrenzen, um Überexposure zu vermeiden.
  • Sobald eine Position eröffnet wird, die verbleibenden ausstehenden Orders stornieren, Stop-Loss- und Take-Profit-Abstände (in Pips gemessen) anwenden und einen Trailing Stop aktivieren.

Trade-Management

  • Einstiege: Stop-Orders werden nur platziert, während die Zeit der verarbeiteten Kerze zwischen den konfigurierten Start- und Endstunden liegt. Orders werden außerhalb dieses Fensters ignoriert.
  • Positionsschutz: Wenn eine neue Position erstellt wird, registriert die Strategie sofort Schutz-Stop-Loss- und Take-Profit-Orders mit den konfigurierten Pip-Abständen.
  • Trailing Stop: Falls aktiviert, wird die Stop-Loss-Order näher am Preis neu ausgestellt, sobald sie sich um mehr als den Trailing-Schwellenwert plus Schritt in Positionsrichtung bewegt.
  • Order-Bereinigung: Das Verlassen einer Position storniert die Schutzorders, und jeder neue Einstieg storniert die entgegengesetzten ausstehenden Orders, was dem Verhalten der ursprünglichen MQL-Logik entspricht.

Parameter

Parameter Beschreibung
Volume Ordergröße beim Senden von Ausbruch- und Schutzorders.
StopLossPips Abstand in Pips zwischen dem Einstiegspreis und dem Schutz-Stop-Loss. Auf 0 setzen zum Deaktivieren.
TakeProfitPips Abstand in Pips zwischen dem Einstiegspreis und der Take-Profit-Order. Auf 0 setzen zum Deaktivieren.
TrailingStopPips Anfänglicher Trailing-Stop-Abstand in Pips. 0 deaktiviert Trailing.
TrailingStepPips Minimaler zusätzlicher Gewinn in Pips, der erforderlich ist, bevor der Trailing Stop erneut bewegt wird. Muss ungleich null sein, wenn Trailing aktiviert ist.
StartHour Sitzungseröffnungsstunde (inklusiv) für das Platzieren neuer ausstehender Orders.
EndHour Sitzungsschlussstunde (exklusiv) für das Platzieren neuer ausstehender Orders. Muss größer als StartHour sein.
MaxPendingOrders Maximale Anzahl gleichzeitiger Ausbruchsorders (Kauf + Verkauf) erlaubt.
BreakoutPeriod Rückblicklänge (in Kerzen) für die Berechnungen des höchsten Hochs und niedrigsten Tiefs.
CandleType Von der Strategie verarbeitete Kerzenreihe (Zeitrahmen oder anderer Kerzendatentyp).

Hinweise

  • Die Pip-Größe wird vom Preisschritt des Wertpapiers abgeleitet. Für 3- und 5-stellige Forex-Symbole wird der Punktwert angepasst, um der MQL-Definition eines Pips zu entsprechen.
  • Die Strategie verlässt sich auf Security.BestBid/BestAsk-Snapshots wenn verfügbar. Wenn keine Tiefendaten vorhanden sind, greift sie auf den aktuellen Kerzenschlusskurs zurück, um den Mindestabstand vom Markt zu bewerten.
  • Schutzorders werden bei Bedarf neu erstellt, was die PositionModify-Logik aus dem ursprünglichen Expert Advisor widerspiegelt.
  • Die Implementierung hält die Logik rein in C# ohne Python-Übersetzung, wie angefordert.
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;
					}
				}
			}
		}
	}
}