Auf GitHub ansehen

Constituents EA Strategie

Diese Strategie portiert den Constituents EA aus MQL/22595 in die StockSharp High-Level-API. Sie recreiert die ursprüngliche Logik, zwei ausstehende Orders um den jüngsten Bereich zu einer bestimmten Stunde zu platzieren, während der Workflow mit dem Order-Handling und den Risikoschutzhelfern von StockSharp kompatibel bleibt.

Wie die Strategie funktioniert

  1. Geplante Aktivierung – am Ende jeder Kerze prüft die Strategie, ob der nächste Balken bei StartHour beginnt. Nur in diesem Moment werden neue ausstehende Orders in Betracht gezogen, was den MetaTrader-Code widerspiegelt, der auf die Geburt des Balkens reagierte, dessen Öffnungszeit mit der konfigurierten Stunde übereinstimmt.
  2. Bereichserkennung – das höchste Hoch und das niedrigste Tief unter den vorherigen SearchDepth abgeschlossenen Kerzen werden mit Highest/Lowest-Indikatoren verfolgt. Diese beiden Preise definieren die Ausbruchs-/Mean-Reversion-Niveaus für die Order-Platzierung.
  3. Preisabstandsfilter – aktuelle beste Bid/Ask-Kurse werden aus dem Order-Book-Feed gestreamt. Orders werden nur platziert, wenn der Abstand zwischen dem Kurs und dem Kandidatenpreis größer oder gleich MinOrderDistancePips ist (in absoluten Preis umgerechnet mit PointValue). Dies reimplementiert die ursprüngliche Freeze-Level-Validierung und verhindert ungültige ausstehende Orders.
  4. Order-Stil-AuswahlPendingOrderMode wählt zwischen Limit-Orders (Buy-Limit am Tief, Sell-Limit am Hoch) oder Stop-Orders (Buy-Stop über dem Hoch, Sell-Stop unter dem Tief). Beide Orders werden gleichzeitig eingereicht, genau wie im MetaTrader-Skript.
  5. Risikoabsicherung – der eingebaute StartProtection-Helfer hängt Stop-Loss- und Take-Profit-Niveaus an, ausgedrückt in absoluten Preisschritten (StopLossPips/TakeProfitPips). Mindestabstandsprüfungen gegen MinStopDistancePips replizieren die MT5-Anforderung, dass Schutzorders den Symbol-Stop-Level respektieren müssen.
  6. Order-Verwaltung – wenn eine ausstehende Order ausgeführt wird, wird die entgegengesetzte Order sofort storniert. Während des Balken-Intervalls platziert die Strategie keine zusätzlichen Orders, solange aktive existieren, was dem Quell-EA-Verhalten entspricht.

Parameter

Parameter Beschreibung
StartHour Stunde (0-23), wenn das neue Paar ausstehender Orders erstellt wird.
SearchDepth Anzahl vorheriger abgeschlossener Kerzen zur Berechnung des Hoch/Tief-Bereichs.
PendingOrderMode Limit repliziert die Mean-Reversion-Variante, Stop platziert Ausbruchs-Orders.
StopLossPips Stop-Loss-Abstand in Pips (umgerechnet mit PointValue). Auf 0 setzen zum Deaktivieren.
TakeProfitPips Take-Profit-Abstand in Pips. Auf 0 setzen zum Deaktivieren.
PointValue Pip-Wert in Preiseinheiten. Auf 0 setzen zur Auto-Erkennung aus Security.PriceStep/MinStep.
MinOrderDistancePips Mindestabstand zwischen aktuellem Bid/Ask und dem ausstehenden Preis, modelliert Freeze-Level-Prüfungen.
MinStopDistancePips Mindestabstand für Stop/Take, spiegelt StopsLevel-Prüfungen wider.
CandleType Zeitrahmen für Bereichsberechnung und Planungslogik.

Strategy.Volume steuert die Ordergröße; positiv halten, damit BuyLimit, SellLimit, BuyStop und SellStop Orders einreichen können.

Verwendung

  1. Die Strategie an ein Wertpapier anhängen und CandleType auf den gewünschten Zeitrahmen setzen.
  2. StartHour und SearchDepth genauso wie in den MT5-Eingaben konfigurieren. Die Min*Pips-Schwellenwerte anpassen, wenn der Broker Mindestabstände zwischen Orders und dem Marktpreis erzwingt.
  3. PointValue kalibrieren, wenn die Auto-Erkennung aus den Sicherheitsmetadaten nicht möglich ist (zum Beispiel bei synthetischen Instrumenten).
  4. StopLossPips und TakeProfitPips so setzen, dass sie dem ursprünglichen EA entsprechen. Das Schutzmodul hängt automatisch Stops und Ziele an, sobald eine Order ausgeführt wird.
  5. Ein positives Volume angeben und die Strategie starten. Sie abonniert Kerzen- und Order-Book-Daten, platziert beide ausstehenden Orders beim geplanten Balken und storniert die entgegengesetzte Order, wenn ein Trade ausgeführt wird.

Unterschiede gegenüber dem ursprünglichen EA

  • Der MetaTrader-MoneyFixedMargin-Risiko-Modus (prozentbasierte Dimensionierung) ist nicht portiert. StockSharp-Benutzer sollten Strategy.Volume direkt konfigurieren oder die Strategie mit einem externen Positionsgrößen-Modul umhüllen.
  • Freeze-Level- und Stop-Level-Prüfungen werden durch die konfigurierbaren Parameter MinOrderDistancePips und MinStopDistancePips ausgedrückt, da die entsprechenden Exchange-Metadaten nicht immer über StockSharp verfügbar sind.
  • Die Order-Platzierung erfolgt, wenn die vorherige Kerze schließt und der kommende Balken bei StartHour beginnt. Dies ist funktional identisch mit der MT5-Implementierung, die auf die Geburt des neuen Balkens reagierte.
  • Alle Kommentare innerhalb des Quellcodes wurden ins Englische übersetzt, während die externe Dokumentation aus Bequemlichkeit in mehreren Sprachen verfügbar ist.

Die Abstände und die Handelszeit an das geplante Instrument anpassen. Auf Märkten mit breiten Spreads sind möglicherweise größere MinOrderDistancePips- oder Pip-Werte erforderlich, um sofortige Ablehnung durch den Broker zu vermeiden.

using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Constituents breakout strategy converted from the original MetaTrader expert advisor.
/// Detects the recent high/low range from N candles and enters with market orders
/// when price breaks above the high (buy) or below the low (sell).
/// Uses stop-loss, take-profit, and trailing stop for risk management.
/// </summary>
public class ConstituentsEAStrategy : Strategy
{
	private readonly StrategyParam<int> _searchDepth;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<DataType> _candleType;

	private Highest _highest = null!;
	private Lowest _lowest = null!;

	private decimal _pipSize;
	private decimal _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takePrice;
	private decimal _prevHigh;
	private decimal _prevLow;
	private bool _exitRequested;

	/// <summary>
	/// Number of completed candles used to determine the recent range.
	/// </summary>
	public int SearchDepth
	{
		get => _searchDepth.Value;
		set => _searchDepth.Value = value;
	}

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

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

	/// <summary>
	/// Working candle type.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="ConstituentsEaStrategy"/> class.
	/// </summary>
	public ConstituentsEAStrategy()
	{
		_searchDepth = Param(nameof(SearchDepth), 3)
			.SetGreaterThanZero()
			.SetDisplay("Search Depth", "Number of completed candles used to find extremes", "Setup");

		_stopLossPips = Param(nameof(StopLossPips), 50m)
			.SetNotNegative()
			.SetDisplay("Stop Loss (pips)", "Stop loss distance expressed in pips", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 100m)
			.SetNotNegative()
			.SetDisplay("Take Profit (pips)", "Take profit distance expressed in pips", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Working timeframe used to evaluate highs/lows", "General");
	}

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

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

		_highest = null!;
		_lowest = null!;
		_pipSize = 0m;
		_entryPrice = 0m;
		_stopPrice = null;
		_takePrice = null;
		_prevHigh = 0m;
		_prevLow = 0m;
		_exitRequested = false;
	}

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

		_pipSize = CalculatePipSize();

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

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

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

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

		// Process indicators
		var highValue = _highest.Process(new DecimalIndicatorValue(_highest, candle.HighPrice, candle.OpenTime) { IsFinal = true });
		var lowValue = _lowest.Process(new DecimalIndicatorValue(_lowest, candle.LowPrice, candle.OpenTime) { IsFinal = true });

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

		var currentHigh = highValue.ToDecimal();
		var currentLow = lowValue.ToDecimal();

		// Manage existing position
		if (Position != 0)
		{
			ManagePosition(candle);

			// Update range for next trade
			_prevHigh = currentHigh;
			_prevLow = currentLow;
			return;
		}

		// Check for breakout signals using previous range
		if (_prevHigh > 0m && _prevLow > 0m)
		{
			// Breakout above the recent high -> buy
			if (candle.ClosePrice > _prevHigh)
			{
				_entryPrice = candle.ClosePrice;
				_exitRequested = false;

				if (StopLossPips > 0m)
					_stopPrice = _entryPrice - StopLossPips * _pipSize;
				else
					_stopPrice = null;

				if (TakeProfitPips > 0m)
					_takePrice = _entryPrice + TakeProfitPips * _pipSize;
				else
					_takePrice = null;

				BuyMarket();
			}
			// Breakout below the recent low -> sell
			else if (candle.ClosePrice < _prevLow)
			{
				_entryPrice = candle.ClosePrice;
				_exitRequested = false;

				if (StopLossPips > 0m)
					_stopPrice = _entryPrice + StopLossPips * _pipSize;
				else
					_stopPrice = null;

				if (TakeProfitPips > 0m)
					_takePrice = _entryPrice - TakeProfitPips * _pipSize;
				else
					_takePrice = null;

				SellMarket();
			}
		}

		// Update range for next candle
		_prevHigh = currentHigh;
		_prevLow = currentLow;
	}

	private void ManagePosition(ICandleMessage candle)
	{
		if (_exitRequested)
			return;

		if (Position > 0)
		{
			// Check take profit
			if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
			{
				_exitRequested = true;
				SellMarket();
				return;
			}

			// Check stop loss
			if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
			{
				_exitRequested = true;
				SellMarket();
				return;
			}
		}
		else if (Position < 0)
		{
			// Check take profit
			if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
			{
				_exitRequested = true;
				BuyMarket();
				return;
			}

			// Check stop loss
			if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
			{
				_exitRequested = true;
				BuyMarket();
				return;
			}
		}
	}

	private decimal CalculatePipSize()
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
			return 0.01m;

		return step;
	}
}