Auf GitHub ansehen

Hans123 Trader Strategie

Überblick

Hans123 Trader ist ein Ausbruchssystem, das aus dem originalen MetaTrader 5 Expert Advisor Hans123_Trader konvertiert wurde. Die Strategie scannt einen rollierenden Preisbereich und platziert ausstehende Stop-Orders während eines konfigurierbaren Intraday-Fensters. Schutz-Stops, Gewinnziele und Trailing-Regeln spiegeln die MQL5-Logik wider, sodass der StockSharp-Port sich wie der Quell-Roboter verhält.

Kernkonzepte

  • Bereichsausbruch – verwendet das höchste Hoch und das niedrigste Tief der letzten N Kerzen, um den Ausbruchskanal zu definieren.
  • Zeitfilter – wertet Signale nur zwischen den Start- und Endstunden aus, um Nachtlärm zu vermeiden.
  • Synchrone ausstehende Orders – aktualisiert Buy-Stop- und Sell-Stop-Orders bei jeder abgeschlossenen Kerze innerhalb des Handelsfensters.
  • Risikokontrolle – optionale Stop-Loss-, Take-Profit- und Trailing-Stop-Abstände in Pips ausgedrückt.
  • Dynamisches Trailing – sobald der Preis die Trailing-Stop- plus Trailing-Step-Distanz zurücklegt, wird der Schutz-Stop enger gezogen, um Gewinne zu sichern.

Handelslogik

  1. Die ausgewählte Kerzenserie abonnieren und warten, bis sich das RangeLength-Indikator-Fenster gebildet hat.
  2. Bei jeder abgeschlossenen Kerze:
    • Den 80-Bar (konfigurierbar) Hoch/Tief-Kanal aktualisieren.
    • Verarbeitung überspringen, wenn die aktuelle Zeit außerhalb des Intervalls [StartHour, EndHour) liegt.
    • Bestehende Einstiegsorders stornieren und neue Stop-Orders platzieren:
      • Buy Stop am Bereichshoch für OrderVolume.
      • Sell Stop am Bereichstief für OrderVolume.
  3. Wenn eine Einstiegsorder ausgeführt wird:
    • Die entgegengesetzte ausstehende Order stornieren.
    • Stop-Loss- und Take-Profit-Orders registrieren, wenn die entsprechenden Pip-Abstände größer als null sind.
  4. Während eine Position offen ist:
    • Wenn der Preis mindestens TrailingStopPips + TrailingStepPips vorrückt, den Schutz-Stop um TrailingStopPips in Richtung Markt verschieben.
    • Schutzorders werden automatisch storniert, wenn die Position auf null zurückgeht.

Parameter

Name Beschreibung Standardwert
OrderVolume Ordergröße für Ausbruchseinstiege. 0.1
RangeLength Anzahl der Kerzen im Ausbruchskanal. 80
StopLossPips Stop-Loss-Abstand in Pips (0 deaktiviert den Stop). 50
TakeProfitPips Take-Profit-Abstand in Pips (0 deaktiviert das Ziel). 50
TrailingStopPips Trailing-Stop-Abstand in Pips (0 deaktiviert Trailing). 10
TrailingStepPips Zusätzliche Pips, die benötigt werden, bevor der Trailing-Stop aktualisiert wird. Muss positiv sein, wenn Trailing aktiviert ist. 5
StartHour Inklusiver Stundenwert des Tages (UTC), wenn Ausbruchsorders beginnen. 6
EndHour Exklusiver Stundenwert des Tages (UTC), wenn Ausbruchsorders enden. 10
CandleType Arbeitender Kerzen-Datentyp und Zeitrahmen. 1 Stunde Kerzen

Praktische Hinweise

  • Die Pip-Größe passt sich den Dezimalstellen des Wertpapiers an (3/5-stellige Forex-Symbole erhalten die übliche ×10-Anpassung).
  • Trailing-Stops werden erst erstellt, nachdem eine Position die Aktivierungsdistanz zurückgelegt hat; wenn StopLossPips null ist, wird der anfängliche Stop weggelassen, bis die Trailing-Bedingungen erfüllt sind.
  • Portfolio-Berechtigungen an das ausgewählte OrderVolume und die Kontraktgröße des Instruments anpassen.
  • Die StockSharp-Konvertierung verwendet Diagramm-Hilfsmittel zur Visualisierung von Kerzen, dem Kanal und Trades zum Debuggen.

Unterschiede zur MQL5-Version

  • Stop- und Ziel-Orders werden über StockSharp-High-Level-Helfer registriert, anstatt über MetaTrader-Handelsanfragen.
  • Volumen-Standardwerte bleiben identisch (0.1 Lots), können aber über StrategyParam-Metadaten optimiert werden.
  • Ausstehende Orders werden bei jeder abgeschlossenen Kerze aktualisiert, anstatt auf Tick-Level-Updates zu warten, was dem StockSharp-Ereignismodell entspricht.

Verwendung

  1. Die Strategie an ein Portfolio/Wertpapier-Paar anhängen und sicherstellen, dass das Kerzenabonnement mit dem gewünschten Zeitrahmen übereinstimmt.
  2. Parameter für die Instrumentvolatilität und Sessiongrenzen anpassen.
  3. Die Strategie starten; die Diagrammbereichsüberlagerung überwachen, um Ausbruchsniveaus und ausgeführte Trades zu bestätigen.
  4. Die integrierten Parameter für die Optimierung innerhalb der StockSharp-Testumgebung verwenden, falls gewünscht.
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;
	}
}