Auf GitHub ansehen

Rechteckteststrategie

Überblick

Die Rechtecktest-Strategie reproduziert den MetaTrader-Experten „RectangleTest“ unter Verwendung des übergeordneten API von StockSharp. Es erkennt Seitwärtsbereiche in einem Intraday-Zeitrahmen, prüft, ob zwei gleitende Durchschnitte und der aktuelle Preis innerhalb des erkannten Bereichs bleiben, und handelt dann Ausbrüche vom Rechteck weg in Richtung des schnelleren EMA. Die gesamte Logik wird auf abgeschlossene Kerzen ausgeführt, die von einer konfigurierbaren Kerzenquelle empfangen werden.

Handelslogik

  1. Abonnieren Sie den primären Kerzenstream (Standard: 1-Stunden-Zeitrahmen) und speisen Sie ihn in die folgenden Indikatoren ein:
    • ExponentialMovingAverage (EMA) mit konfigurierbarer Länge EmaPeriod.
    • SimpleMovingAverage (SMA) mit konfigurierbarer Länge SmaPeriod.
    • Höchste und Tiefste-Indikatoren mit der Länge RangeCandles, konfiguriert zum Lesen von Kerzenhochs und -tiefs. Sie stellen die Rechteckgrenzen bereit, die die MetaTrader-Array-basierten Berechnungen emulieren.
  2. Sobald alle Indikatoren gebildet sind, berechnen Sie die Höhe des Rechtecks in Prozent relativ zur oberen Grenze. Nur Kerzen, deren Höhe kleiner als RectangleSizePercent ist, gelten als gültige Konsolidierungen.
  3. Erfordern, dass EMA, SMA und die Kerze geschlossen sind, um innerhalb des Rechtecks zu bleiben. Dies reproduziert den Seitwärtsfilter aus der MQL-Version.
  4. Kurze Einrichtung:
    • EMA liegt über SMA.
    • Der Schlusskurs liegt über EMA (entspricht der Bedingung „Ask > EMA“ von MetaTrader bei abgeschlossenen Kerzen).
    • Die optionale Liquidation einer bestehenden Long-Position erfolgt zunächst, danach wird eine Short-Market-Order gesendet.
  5. Lange Einrichtung:
    • EMA liegt unter SMA.
    • Der Schlusskurs liegt unter EMA (entspricht der Regel „Gebot < EMA“).
    • Bestehende Short-Positionen werden vor der Eröffnung der Long-Position liquidiert.
  6. Bei jedem Eintrag werden der erwartete Einstiegspreis und das erwartete Einstiegsvolumen erfasst. Wenn die Position Null erreicht, vergleicht die Strategie den Ausstiegspreis mit dem gespeicherten Einstiegspreis. Verlierende Trades erhöhen den täglichen Verlustzähler und erzwingen den MaxLosingTradesPerDay-Filter genau wie den MQL-Helfer Loss().

Geld- und Risikomanagement

  • Die Strategie kann in zwei Modi funktionieren:
    • Risikobasierter Modus (UseRiskMoneyManagement = true): Das Positionsvolumen wird anhand des Kontowerts, des RiskPercent und des konfigurierten StopLossPoints ermittelt. Die Berechnung verwendet Security.PriceStep, Security.StepPrice und Security.VolumeStep, um die Routine zur Losgrößenbestimmung von MetaTrader widerzuspiegeln.
    • Fester Volumenmodus (UseRiskMoneyManagement = false): Trades verwenden den Parameter FixedVolume.
  • Nachdem sich die Nettoposition von flach auf ungleich Null geändert hat, registrieren SetStopLoss und SetTakeProfit Schutzaufträge mit StopLossPoints und TakeProfitPoints (ausgedrückt in Preisschritten), die den SL/TP-Distanzen entsprechen, die im ursprünglichen Experten an m_trade.Sell/Buy übergeben wurden.
  • MaxLosingTradesPerDay stoppt neue Signale für den Rest des Tages, sobald die angegebene Anzahl verlorener Trades erkannt wurde.

Zeitmanagement

  • Der Handel ist nur zwischen TradeStartTime und TradeEndTime zulässig. Der Helfer verarbeitet Intervalle, die sich über Mitternacht erstrecken, sowie Sitzungen tagsüber.
  • Wenn EnableTimeClose wahr ist, werden alle offenen Positionen nach TimeClose liquidiert, wodurch die Eingaben MetaTrader „TimeCloseTrue“ und TimeClose repliziert werden.

Unterschiede zur MetaTrader-Version

  • Der ursprüngliche Indikator erzeugte grafische Rechtecke im Diagramm. StockSharp erstellt keine Zeichnungsobjekte; Stattdessen wird derselbe Bereich intern über Höchst-/Tiefstindikatoren berechnet.
  • Verlierergeschäfte werden anhand der Schlusskurse der Signalkerze gezählt. Dies entspricht der Absicht von Loss() (Zählung verlorener Bestellungen pro Tag) und bleibt dabei innerhalb der StockSharp-Abstraktionen auf hoher Ebene.
  • Auftragsabwicklungsmerkmale wie ORDER_FILLING_FOK/IOC werden von der Umgebung von StockSharp verarbeitet, sodass keine explizite Konfiguration des Abfüllmodus erforderlich ist.

Parameter

Name Standard Beschreibung
EmaPeriod 45 Periode des schnellen EMA.
SmaPeriod 200 Zeitraum der langsamen SMA.
RangeCandles 10 Anzahl der Kerzen, die das Rechteck bilden.
RectangleSizePercent 0,5 Maximal zulässige Rechteckhöhe für den Handel.
StopLossPoints 250 Stop-Loss-Distanz in Preisschritten.
TakeProfitPoints 750 Take-Profit-Distanz in Preisschritten.
UseRiskMoneyManagement wahr Wechseln Sie zwischen risikobasiertem und festem Volumen.
RiskPercent 1 Prozentsatz des Kontokapitals, das pro Trade riskiert wird.
FixedVolume 1 Festes Volumen, wenn die risikobasierte Größenanpassung deaktiviert ist.
MaxLosingTradesPerDay 1 Tägliche Obergrenze für den Verlust von Trades.
TradeStartTime 03:00 Tageszeit, zu der Zutritte erlaubt sind.
TradeEndTime 22:50 Uhrzeit, nach der keine neuen Einträge mehr generiert werden.
EnableTimeClose falsch Ermöglicht die Liquidation am Tagesende.
TimeClose 23:00 Tageszeit zum Schließen aller Positionen.
CandleType 1-Stunden-Kerzen Primäre Kerzendatenquelle.

Diagramme

Wenn ein Diagrammbereich verfügbar ist, zeichnet die Strategie die Preiskerzen, schnelle EMA, langsame SMA und eigene Trades, um Bereichsausbrüche und Trade-Timing zu visualisieren.

namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

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

/// <summary>
/// Rectangle breakout strategy: detects tight consolidation ranges and trades breakouts
/// using EMA/SMA trend direction as a filter.
/// </summary>
public class RectangleTestStrategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _smaPeriod;
	private readonly StrategyParam<int> _rangeCandles;
	private readonly StrategyParam<decimal> _rectangleSizePercent;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _highs = new();
	private readonly List<decimal> _lows = new();

	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	public int SmaPeriod
	{
		get => _smaPeriod.Value;
		set => _smaPeriod.Value = value;
	}

	public int RangeCandles
	{
		get => _rangeCandles.Value;
		set => _rangeCandles.Value = value;
	}

	public decimal RectangleSizePercent
	{
		get => _rectangleSizePercent.Value;
		set => _rectangleSizePercent.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public RectangleTestStrategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 20)
			.SetDisplay("Fast EMA Period", "Length of the fast EMA", "Indicators");

		_smaPeriod = Param(nameof(SmaPeriod), 50)
			.SetDisplay("Slow SMA Period", "Length of the slow SMA", "Indicators");

		_rangeCandles = Param(nameof(RangeCandles), 10)
			.SetDisplay("Rectangle Candles", "Number of candles for range detection", "Logic");

		_rectangleSizePercent = Param(nameof(RectangleSizePercent), 10m)
			.SetDisplay("Rectangle Size (%)", "Maximum range height in percent", "Logic");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Primary candle source", "General");
	}

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

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

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var sma = new SimpleMovingAverage { Length = SmaPeriod };

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

		StartProtection(
			takeProfit: new Unit(2, UnitTypes.Percent),
			stopLoss: new Unit(1, UnitTypes.Percent));

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

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

		if (_highs.Count >= RangeCandles)
		{
			// Use PREVIOUS window (excluding current candle) for rectangle detection
			var highestValue = decimal.MinValue;
			var lowestValue = decimal.MaxValue;
			var startIdx = _highs.Count - RangeCandles;
			for (var i = startIdx; i < _highs.Count; i++)
			{
				if (_highs[i] > highestValue) highestValue = _highs[i];
				if (_lows[i] < lowestValue) lowestValue = _lows[i];
			}

			if (highestValue > 0m && lowestValue > 0m)
			{
				var rangePercent = (highestValue - lowestValue) / highestValue * 100m;
				if (rangePercent > 0 && rangePercent < RectangleSizePercent)
				{
					var close = candle.ClosePrice;

					// Breakout above rectangle with bullish trend (EMA > SMA)
					if (close > highestValue && emaValue > smaValue && Position == 0)
					{
						BuyMarket();
					}
					// Breakout below rectangle with bearish trend (EMA < SMA)
					else if (close < lowestValue && emaValue < smaValue && Position == 0)
					{
						SellMarket();
					}
				}
			}
		}

		_highs.Add(candle.HighPrice);
		_lows.Add(candle.LowPrice);

		if (_highs.Count > RangeCandles + 1)
		{
			_highs.RemoveAt(0);
			_lows.RemoveAt(0);
		}
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_highs.Clear();
		_lows.Clear();

		base.OnReseted();
	}
}