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
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.
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.
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.
Order-Stil-Auswahl – PendingOrderMode 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.
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.
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
Die Strategie an ein Wertpapier anhängen und CandleType auf den gewünschten Zeitrahmen setzen.
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.
PointValue kalibrieren, wenn die Auto-Erkennung aus den Sicherheitsmetadaten nicht möglich ist (zum Beispiel bei
synthetischen Instrumenten).
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.
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;
}
}
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
from StockSharp.Algo.Indicators import Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class constituents_ea_strategy(Strategy):
def __init__(self):
super(constituents_ea_strategy, self).__init__()
self._search_depth = self.Param("SearchDepth", 3) \
.SetDisplay("Search Depth", "Number of completed candles used to find extremes", "Setup")
self._stop_loss_pips = self.Param("StopLossPips", 50.0) \
.SetDisplay("Stop Loss pips", "Stop loss distance expressed in pips", "Risk")
self._take_profit_pips = self.Param("TakeProfitPips", 100.0) \
.SetDisplay("Take Profit pips", "Take profit distance expressed in pips", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Working timeframe used to evaluate highs and lows", "General")
self._highest = None
self._lowest = None
self._pip_size = 0.0
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
self._prev_high = 0.0
self._prev_low = 0.0
self._exit_requested = False
@property
def search_depth(self):
return self._search_depth.Value
@property
def stop_loss_pips(self):
return self._stop_loss_pips.Value
@property
def take_profit_pips(self):
return self._take_profit_pips.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(constituents_ea_strategy, self).OnReseted()
self._highest = None
self._lowest = None
self._pip_size = 0.0
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
self._prev_high = 0.0
self._prev_low = 0.0
self._exit_requested = False
def OnStarted2(self, time):
super(constituents_ea_strategy, self).OnStarted2(time)
step = self.Security.PriceStep if self.Security is not None else None
self._pip_size = float(step) if step is not None and float(step) > 0 else 0.01
self._highest = Highest()
self._highest.Length = self.search_depth
self._lowest = Lowest()
self._lowest.Length = self.search_depth
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._highest, self._lowest, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle, high_value, low_value):
if candle.State != CandleStates.Finished:
return
if not self._highest.IsFormed or not self._lowest.IsFormed:
return
current_high = float(high_value)
current_low = float(low_value)
if self.Position != 0:
self._manage_position(candle)
self._prev_high = current_high
self._prev_low = current_low
return
close = float(candle.ClosePrice)
sl_pips = float(self.stop_loss_pips)
tp_pips = float(self.take_profit_pips)
if self._prev_high > 0 and self._prev_low > 0:
if close > self._prev_high:
self._entry_price = close
self._exit_requested = False
self._stop_price = self._entry_price - sl_pips * self._pip_size if sl_pips > 0 else None
self._take_price = self._entry_price + tp_pips * self._pip_size if tp_pips > 0 else None
self.BuyMarket()
elif close < self._prev_low:
self._entry_price = close
self._exit_requested = False
self._stop_price = self._entry_price + sl_pips * self._pip_size if sl_pips > 0 else None
self._take_price = self._entry_price - tp_pips * self._pip_size if tp_pips > 0 else None
self.SellMarket()
self._prev_high = current_high
self._prev_low = current_low
def _manage_position(self, candle):
if self._exit_requested:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self.Position > 0:
if self._take_price is not None and high >= self._take_price:
self._exit_requested = True
self.SellMarket()
return
if self._stop_price is not None and low <= self._stop_price:
self._exit_requested = True
self.SellMarket()
return
elif self.Position < 0:
if self._take_price is not None and low <= self._take_price:
self._exit_requested = True
self.BuyMarket()
return
if self._stop_price is not None and high >= self._stop_price:
self._exit_requested = True
self.BuyMarket()
return
def CreateClone(self):
return constituents_ea_strategy()