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
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.
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.
Erfordern, dass EMA, SMA und die Kerze geschlossen sind, um innerhalb des Rechtecks zu bleiben. Dies reproduziert den Seitwärtsfilter aus der MQL-Version.
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.
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.
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();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class rectangle_test_strategy(Strategy):
def __init__(self):
super(rectangle_test_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._ema_period = self.Param("EmaPeriod", 20)
self._sma_period = self.Param("SmaPeriod", 50)
self._range_candles = self.Param("RangeCandles", 10)
self._rectangle_size_percent = self.Param("RectangleSizePercent", 10.0)
self._highs = []
self._lows = []
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def EmaPeriod(self):
return self._ema_period.Value
@EmaPeriod.setter
def EmaPeriod(self, value):
self._ema_period.Value = value
@property
def SmaPeriod(self):
return self._sma_period.Value
@SmaPeriod.setter
def SmaPeriod(self, value):
self._sma_period.Value = value
@property
def RangeCandles(self):
return self._range_candles.Value
@RangeCandles.setter
def RangeCandles(self, value):
self._range_candles.Value = value
@property
def RectangleSizePercent(self):
return self._rectangle_size_percent.Value
@RectangleSizePercent.setter
def RectangleSizePercent(self, value):
self._rectangle_size_percent.Value = value
def OnReseted(self):
super(rectangle_test_strategy, self).OnReseted()
self._highs = []
self._lows = []
def OnStarted2(self, time):
super(rectangle_test_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
ema = ExponentialMovingAverage()
ema.Length = self.EmaPeriod
sma = SimpleMovingAverage()
sma.Length = self.SmaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, sma, self._process_candle).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent))
def _process_candle(self, candle, ema_value, sma_value):
if candle.State != CandleStates.Finished:
return
ema_val = float(ema_value)
sma_val = float(sma_value)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
range_candles = self.RangeCandles
if len(self._highs) >= range_candles:
start_idx = len(self._highs) - range_candles
highest = max(self._highs[start_idx:])
lowest = min(self._lows[start_idx:])
if highest > 0 and lowest > 0:
range_pct = (highest - lowest) / highest * 100.0
rect_size = float(self.RectangleSizePercent)
if 0 < range_pct < rect_size:
# Breakout above rectangle with bullish trend
if close > highest and ema_val > sma_val and self.Position == 0:
self.BuyMarket()
# Breakout below rectangle with bearish trend
elif close < lowest and ema_val < sma_val and self.Position == 0:
self.SellMarket()
self._highs.append(high)
self._lows.append(low)
while len(self._highs) > range_candles + 1:
self._highs.pop(0)
self._lows.pop(0)
def CreateClone(self):
return rectangle_test_strategy()