Die Price-Extreme-Strategie repliziert den MetaTrader-Expertenberater Price_Extreme_Strategy mithilfe der High-Level-API von StockSharp. Das System überwacht einen gleitenden Kanal, der aus dem höchsten Hoch und dem niedrigsten Tief über eine konfigurierbare Anzahl abgeschlossener Kerzen abgeleitet wird. Ausbruchssignale werden erzeugt, wenn die ausgewählte Referenzkerze oberhalb der oberen Grenze oder unterhalb der unteren Grenze schließt. Die Logik kann optional invertiert werden, um Ausbruchsbedingungen in Gegentrendeinträge umzuwandeln.
Diese Umsetzung hält den Trading-Workflow ereignisgesteuert. Aufträge werden unmittelbar nach dem Schließen jeder abgeschlossenen Kerze eingereicht, was dem Verhalten des ursprünglichen MQL-Algorithmus entspricht, der auf dem Eröffnungstick der nächsten Bar reagierte.
Indikatorlogik
Der Price-Extreme-Kanal wird bei jeder abgeschlossenen Kerze mithilfe der StockSharp-Indikatoren Highest und Lowest neu berechnet:
Highest verfolgt das maximale Hoch über die letzten N Kerzen.
Lowest verfolgt das minimale Tief über die letzten N Kerzen.
Diese Puffer emulieren die benutzerdefinierte Studie Price_Extreme_Indicator, die mit dem ursprünglichen Expertenberater gebündelt ist. Die Indikatorlänge wird über den Parameter Level Length zugänglich gemacht.
Ein separater Parameter Signal Shift legt fest, welche geschlossene Kerze zur Auswertung der Ausbruchsbedingung verwendet wird. Ein Shift von 1 bedeutet „die gerade geschlossene Kerze verwenden" (Standard). Größere Werte ermöglichen das Warten auf zusätzliche Bestätigung durch Referenzierung älterer Bars.
Handelsregeln
Obere und untere Kanalwerte für jede abgeschlossene Kerze neu berechnen.
Die durch Signal Shift angegebene Kerze aus dem internen Verlaufspuffer abrufen.
Richtungsabsichten generieren:
Ausbruch nach oben: der Schlusskurs der Kerze liegt über dem oberen Kanalwert.
Ausbruch nach unten: der Schlusskurs der Kerze liegt unter dem unteren Kanalwert.
Optionale Invertierung mit Reverse Signals anwenden:
Wenn deaktiviert, in Ausbruchsrichtung handeln (kaufen bei Ausbruch nach oben, verkaufen bei Ausbruch nach unten).
Wenn aktiviert, die Reaktionen tauschen (verkaufen bei Ausbruch nach oben, kaufen bei Ausbruch nach unten).
Enable Long- und Enable Short-Berechtigungen vor der Auftragsübermittlung respektieren.
Automatisch jede entgegengesetzte Position schließen, bevor ein neuer Trade eröffnet wird, sodass zu jedem Zeitpunkt nur eine Nettoposition existiert.
Risikomanagement
Die Strategie bietet Stop-Loss- und Take-Profit-Handling, das die punktbasierten Kontrollen der MQL-Version widerspiegelt:
Stop Loss und Take Profit werden in Preisschritten (Security.PriceStep) ausgedrückt.
Zielpreise werden neu berechnet, wenn sich die Nettopositionsgröße ändert.
Wenn eine abgeschlossene Kerze die Schutzlevels überschreitet (Tief unter dem Stop für Longs, Hoch über dem Stop für Shorts usw.), wird die Position per Marktauftrag geschlossen und die Schutzziele werden gelöscht.
StartProtection() wird während OnStarted aktiviert, um die integrierten StockSharp-Sicherheitsmechanismen zu nutzen.
Parameter
Parameter
Beschreibung
Standard
Gruppe
LevelLength
Anzahl der abgeschlossenen Kerzen, die bei der Berechnung des Extremkanals berücksichtigt werden.
5
Indicator
SignalShift
Index der geschlossenen Kerze für die Ausbruchsvalidierung (1 = letzte geschlossene Kerze).
1
Indicator
EnableLong
Erlaubt Käufe wenn true.
true
Trading
EnableShort
Erlaubt Verkäufe wenn true.
true
Trading
ReverseSignals
Invertiert Ausbruchsreaktionen (kaufen bei Kursrückgang, verkaufen bei Ausbruch).
false
Trading
OrderVolume
Volumen, das mit jedem Marktauftrag gesendet wird. Muss größer als null sein.
1
Trading
StopLossPoints
Stop-Loss-Abstand gemessen in Preisschritten. Ein Wert von 0 deaktiviert den Stop.
0
Risk
TakeProfitPoints
Take-Profit-Abstand gemessen in Preisschritten. Ein Wert von 0 deaktiviert das Ziel.
0
Risk
CandleType
Primärer Zeitrahmen für das Datenabonnement.
5-Minuten-Kerzen
Data
Alle Parameter verwenden StrategyParam<T> mit UI-Metadaten, damit sie im Designer optimiert oder geändert werden können.
Verwendungshinweise
Die Strategie einem Instrument zuweisen und den Candle Type passend zum Zeitrahmen der ursprünglichen MetaTrader-Einrichtung setzen.
Level Length anpassen, wenn ein breiterer oder engerer Price-Extreme-Kanal gewünscht ist.
Signal Shift konfigurieren, um zu steuern, wie viele geschlossene Kerzen vor der Ausbruchsauswertung gewartet werden soll.
Gewünschte Handelsrichtungen über Enable Long, Enable Short und Reverse Signals auswählen.
Order Volume, Stop Loss und Take Profit entsprechend den Risikopräferenzen festlegen. Beachten Sie, dass beide Schutzwerte in Preisschritten arbeiten.
Strategie starten. Kerzen, Indikatorbänder und ausgeführte Trades werden automatisch gezeichnet, wenn ein Diagrammbereich verfügbar ist.
Zusätzliche Hinweise
Die Strategie arbeitet absichtlich auf einer einzigen Nettoposition und spiegelt die Hedging-Logik des MQL-Experten wider, indem die Gegenseite vor dem Einstieg in einen neuen Trade abgeflacht wird.
Schutz-Stops und -Ziele werden auf abgeschlossenen Kerzen ausgewertet. Im Live-Trading approximiert dies die serverseitigen Schutzaufträge des Originalskripts.
Kein Python-Port enthalten, wie 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>
/// Breakout strategy based on the Price Extreme indicator.
/// </summary>
public class PriceExtremeStrategy : Strategy
{
private readonly StrategyParam<int> _levelLength;
private readonly StrategyParam<int> _signalShift;
private readonly StrategyParam<bool> _enableLong;
private readonly StrategyParam<bool> _enableShort;
private readonly StrategyParam<bool> _reverseSignals;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private readonly List<ICandleMessage> _history = new();
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private decimal? _stopPrice;
private decimal? _takePrice;
private decimal _prevPosition;
private decimal _entryPrice;
private decimal _prevUpper;
private decimal _prevLower;
/// <summary>
/// Number of candles used to build extreme levels.
/// </summary>
public int LevelLength
{
get => _levelLength.Value;
set => _levelLength.Value = value;
}
/// <summary>
/// Shift in candles used for the breakout signal.
/// </summary>
public int SignalShift
{
get => _signalShift.Value;
set => _signalShift.Value = value;
}
/// <summary>
/// Enable long trades.
/// </summary>
public bool EnableLong
{
get => _enableLong.Value;
set => _enableLong.Value = value;
}
/// <summary>
/// Enable short trades.
/// </summary>
public bool EnableShort
{
get => _enableShort.Value;
set => _enableShort.Value = value;
}
/// <summary>
/// Reverse long and short signals.
/// </summary>
public bool ReverseSignals
{
get => _reverseSignals.Value;
set => _reverseSignals.Value = value;
}
/// <summary>
/// Order volume in lots.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Stop loss distance expressed in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance expressed in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Type of candles used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="PriceExtremeStrategy"/>.
/// </summary>
public PriceExtremeStrategy()
{
_levelLength = Param(nameof(LevelLength), 20)
.SetGreaterThanZero()
.SetDisplay("Level Length", "Number of candles for price extremes", "Indicator")
.SetOptimize(3, 30, 1);
_signalShift = Param(nameof(SignalShift), 1)
.SetGreaterThanZero()
.SetDisplay("Signal Shift", "Closed candles used for breakout", "Indicator");
_enableLong = Param(nameof(EnableLong), true)
.SetDisplay("Enable Long", "Allow buying trades", "Trading");
_enableShort = Param(nameof(EnableShort), true)
.SetDisplay("Enable Short", "Allow selling trades", "Trading");
_reverseSignals = Param(nameof(ReverseSignals), false)
.SetDisplay("Reverse Signals", "Invert breakout direction", "Trading");
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Volume sent with market orders", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 0)
.SetDisplay("Stop Loss", "Protective stop in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 0)
.SetDisplay("Take Profit", "Profit target in price steps", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe", "Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_history.Clear();
_highs.Clear();
_lows.Clear();
_prevUpper = 0m;
_prevLower = 0m;
_entryPrice = 0m;
_prevPosition = 0m;
ResetTargets();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(3, UnitTypes.Percent),
stopLoss: new Unit(2, UnitTypes.Percent));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private bool CanOpenLong => EnableLong && OrderVolume > 0m;
private bool CanOpenShort => EnableShort && OrderVolume > 0m;
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
_history.Add(candle);
var maxHistory = Math.Max(LevelLength + SignalShift + 2, 10);
if (_history.Count > maxHistory)
{
var removeCount = _history.Count - maxHistory;
_history.RemoveRange(0, removeCount);
_highs.RemoveRange(0, removeCount);
_lows.RemoveRange(0, removeCount);
}
if (_highs.Count < LevelLength)
return;
var upper = decimal.MinValue;
var lower = decimal.MaxValue;
for (var i = _highs.Count - LevelLength; i < _highs.Count; i++)
{
if (_highs[i] > upper) upper = _highs[i];
if (_lows[i] < lower) lower = _lows[i];
}
if (_history.Count < SignalShift)
return;
var signalCandle = _history[_history.Count - SignalShift];
var breakoutUp = candle.ClosePrice > _prevUpper && _prevUpper > 0;
var breakoutDown = candle.ClosePrice < _prevLower && _prevLower > 0;
_prevUpper = upper;
_prevLower = lower;
var wantLong = ReverseSignals ? breakoutDown : breakoutUp;
var wantShort = ReverseSignals ? breakoutUp : breakoutDown;
if (wantLong && CanOpenLong && Position == 0)
{
BuyMarket();
}
else if (wantShort && CanOpenShort && Position == 0)
{
SellMarket();
}
}
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (trade?.Trade == null) return;
if (Position != 0 && _entryPrice == 0m)
_entryPrice = trade.Trade.Price;
if (Position == 0)
_entryPrice = 0m;
}
private void UpdateTargets()
{
_stopPrice = null;
_takePrice = null;
var step = Security?.PriceStep ?? 0m;
if (step <= 0m || Position == 0m)
return;
if (Position > 0m)
{
if (StopLossPoints > 0)
_stopPrice = _entryPrice - StopLossPoints * step;
if (TakeProfitPoints > 0)
_takePrice = _entryPrice + TakeProfitPoints * step;
}
else if (Position < 0m)
{
if (StopLossPoints > 0)
_stopPrice = _entryPrice + StopLossPoints * step;
if (TakeProfitPoints > 0)
_takePrice = _entryPrice - TakeProfitPoints * step;
}
}
private void ApplyRiskManagement(ICandleMessage candle)
{
if (Position > 0m)
{
if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(-Position);
ResetTargets();
return;
}
if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(-Position);
ResetTargets();
}
}
else if (Position < 0m)
{
if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(-Position);
ResetTargets();
return;
}
if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(-Position);
ResetTargets();
}
}
}
private void ResetTargets()
{
_stopPrice = null;
_takePrice = null;
_prevPosition = Position;
}
}
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.Strategies import Strategy
class price_extreme_strategy(Strategy):
def __init__(self):
super(price_extreme_strategy, self).__init__()
self._level_length = self.Param("LevelLength", 20)
self._signal_shift = self.Param("SignalShift", 1)
self._enable_long = self.Param("EnableLong", True)
self._enable_short = self.Param("EnableShort", True)
self._reverse_signals = self.Param("ReverseSignals", False)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._highs = []
self._lows = []
self._history = []
self._prev_upper = 0.0
self._prev_lower = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def LevelLength(self):
return self._level_length.Value
@property
def SignalShift(self):
return self._signal_shift.Value
@property
def EnableLong(self):
return self._enable_long.Value
@property
def EnableShort(self):
return self._enable_short.Value
@property
def ReverseSignals(self):
return self._reverse_signals.Value
def OnStarted2(self, time):
super(price_extreme_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
self._history = []
self._prev_upper = 0.0
self._prev_lower = 0.0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
self.StartProtection(
Unit(3, UnitTypes.Percent),
Unit(2, UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
h = float(candle.HighPrice)
l = float(candle.LowPrice)
c = float(candle.ClosePrice)
self._highs.append(h)
self._lows.append(l)
self._history.append(c)
max_hist = max(self.LevelLength + self.SignalShift + 2, 10)
while len(self._history) > max_hist:
self._history.pop(0)
self._highs.pop(0)
self._lows.pop(0)
if len(self._highs) < self.LevelLength:
return
upper = max(self._highs[-self.LevelLength:])
lower = min(self._lows[-self.LevelLength:])
if len(self._history) < self.SignalShift:
self._prev_upper = upper
self._prev_lower = lower
return
breakout_up = c > self._prev_upper and self._prev_upper > 0
breakout_down = c < self._prev_lower and self._prev_lower > 0
self._prev_upper = upper
self._prev_lower = lower
want_long = breakout_down if self.ReverseSignals else breakout_up
want_short = breakout_up if self.ReverseSignals else breakout_down
if want_long and self.EnableLong and self.Position == 0:
self.BuyMarket()
elif want_short and self.EnableShort and self.Position == 0:
self.SellMarket()
def OnReseted(self):
super(price_extreme_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._history = []
self._prev_upper = 0.0
self._prev_lower = 0.0
def CreateClone(self):
return price_extreme_strategy()