Diese Strategie ist eine C#-Konvertierung des MetaTrader Expert Advisors Autotrade (barabashkakvns Edition). Sie pflegt kontinuierlich zwei symmetrische Stop-Einstiegsorders rund um den aktuellen Marktpreis. Wenn der Markt flat bleibt und keine Position offen ist, aktualisiert die Strategie beide ausstehenden Orders. Wenn eine Stop-Order ausgeführt wird, wird die Position aktiv überwacht: Ausstiege werden ausgelöst, sobald sich die Kursbewegung stabilisiert oder ein absoluter Gewinn-/Verlust-Schwellenwert erreicht wird. Die Implementierung verwendet die StockSharp High-Level-API gemäß den Projektrichtlinien.
Zuordnung der Originalparameter
StockSharp-Parameter
MQL5-Parameter
Beschreibung
IndentTicks
InpIndent
Abstand (in Preisschritten) zwischen dem aktuellen Preis und den Stop-Einstiegsorders.
MinProfit
MinProfit
Minimaler schwebender Gewinn (Kontowährung), der für den Ausstieg in einer ruhigen Marktphase benötigt wird.
ExpirationMinutes
ExpirationMinutes
Lebensdauer der ausstehenden Stop-Orders, bevor sie storniert und neu erstellt werden.
AbsoluteFixation
AbsoluteFixation
Absolutes Gewinn- oder Verlustniveau (Währung), das den Positionsschluss erzwingt.
StabilizationTicks
InpStabilization
Maximale Größe des vorherigen Kerzenkörpers, der als Konsolidierungszone behandelt wird.
OrderVolume
Lots
Volumen, das für die Buy-Stop- und Sell-Stop-Order verwendet wird.
CandleType
Period()
Kerzenserie, die die Logik antreibt (standardmäßig 1-Minuten-Zeitrahmen).
Alle numerischen Eingaben, die Preisabstände darstellen, werden von "Punkten" in tatsächliche Preisschritte durch den Security.PriceStep-Wert konvertiert. Gewinnbasierte Schwellenwerte werden mit Security.StepPrice berechnet, was die MQL-Gewinnberechnungen in der Einzahlungswährung widerspiegelt.
Handelslogik
Deployment ausstehender Orders
Die Strategie reagiert nur auf abgeschlossene Kerzen (CandleStates.Finished).
Die erste Kerze wird verwendet, um historische Daten (vorheriges Open/Close) zu speichern und sofort ausstehende Orders zu planen.
Wenn keine Position offen ist, werden inaktive Referenzen gelöscht und:
Eine Buy-Stop wird bei Close + IndentTicks * PriceStep platziert.
Eine Sell-Stop wird bei Close - IndentTicks * PriceStep platziert.
Jede ausstehende Order erhält einen Ablaufzeitstempel von CloseTime + ExpirationMinutes Minuten. Wenn diese Zeit erreicht ist, wird die Order storniert und bei der nächsten Kerze neu erstellt.
Positionsverwaltung
Sobald eine Stop-Order ausgeführt wird, wird die entgegengesetzte ausstehende Order storniert, um unerwünschtes Hedging im netting-basierten StockSharp-Kontomodell zu vermeiden.
Die Strategie speichert den vorherigen Kerzenkörper (|Open - Close|), um ruhige Marktbedingungen zu erkennen.
Bei jeder Kerze mit offener Position:
Der nicht realisierte Gewinn wird in Währung geschätzt, indem die Preisdifferenz gegenüber PositionAvgPrice verwendet und mit Security.PriceStep und Security.StepPrice skaliert wird.
Wenn der Gewinn MinProfit überschreitet und der vorherige Kerzenkörper unter StabilizationTicks * PriceStep liegt, wird die Position zum Marktpreis geschlossen.
Unabhängig von der Stabilisierung wird die Position auch zum Marktpreis geschlossen, wenn der absolute Gewinn oder Verlust AbsoluteFixation überschreitet.
Wenn die Position auf flat zurückgeht, werden alle verbleibenden ausstehenden Orders gelöscht.
Zusätzliche Verhaltensweisen
Es ist jeweils nur eine Position erlaubt; Order-Volumina werden mit OrderVolume verrechnet.
Da StockSharp während Backtests Bid/Ask nicht auf die gleiche Weise wie MetaTrader exponiert, wird der Schlusskurs der abgeschlossenen Kerze als Referenzniveau für neue Stop-Orders verwendet.
Die Strategie aktualisiert automatisch den gecachten Volume-Wert, wenn OrderVolume über Parameter oder Optimierung angepasst wird.
Implementierungshinweise und Unterschiede
Gewinnberechnungen hängen von Security.PriceStep und Security.StepPrice ab. Stellen Sie sicher, dass diese Felder in den Instrumentenmetadaten ausgefüllt sind; andernfalls wird der Wert 1 als Fallback verwendet.
Die ursprüngliche MQL-Version erlaubte temporäres Hedging (mehrere Orders in entgegengesetzten Richtungen). Der StockSharp-Port storniert den nicht verwendeten Stop sofort nach einer Ausführung, um dem Netting-Modell der Plattform zu entsprechen.
Die Ablaufzeit ausstehender Orders verwendet den CloseTime der Kerze. Wenn historische Daten keine Schlusszeitstempel enthalten, passen Sie den Feed an, um diese bereitzustellen, oder erweitern Sie den Code entsprechend.
Die Strategie funktioniert mit jedem Kerzendatentyp durch Anpassung von CandleType. Standard-Kerzen sind zeitrahmenbasiert (TimeSpan.FromMinutes(1).TimeFrame()).
Verwendungsempfehlungen
Konfigurieren Sie die Kerzenserie, die dem in MetaTrader verwendeten Chart-Zeitraum entspricht.
Setzen Sie IndentTicks, StabilizationTicks und Gewinnschwellen in Bezug auf die Tick-Größe und den Tick-Wert des Instruments.
Überprüfen Sie, ob das Portfolio Hedging oder Netting nach Wunsch verwendet. Die Strategie setzt Netting voraus und schließt das Buch, bevor Stop-Orders neu aufgesetzt werden.
Verwenden Sie die bereitgestellten Parameter für die Optimierung in StockSharp Designer oder Backtester, um das Verhalten an verschiedene Märkte anzupassen.
Überwachen Sie die Log-Ausgabe: Der Code hängt von abgeschlossenen Kerzen und Marktverfügbarkeit (IsFormedAndOnlineAndAllowTrading()) ab, bevor neue Orders übermittelt werden.
Risikohinweis
Automatisierter Handel birgt erhebliche Risiken. Testen Sie gründlich, validieren Sie die Parameter auf historischen Daten und bestätigen Sie broker-spezifische Anforderungen (wie Mindestabstände für Stop-Orders), bevor Sie die Strategie auf einem Live-Konto einsetzen.
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>
/// Conversion of the MQL Autotrade strategy that places symmetric stop orders around the market.
/// Pending stop entries are refreshed on every candle while no position is open.
/// Positions are closed when the market calms down or when absolute profit/loss thresholds are reached.
/// </summary>
public class AutotradePendingStopsStrategy : Strategy
{
private readonly StrategyParam<int> _indentTicks;
private readonly StrategyParam<decimal> _minProfit;
private readonly StrategyParam<int> _expirationMinutes;
private readonly StrategyParam<decimal> _absoluteFixation;
private readonly StrategyParam<int> _stabilizationTicks;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevOpen;
private decimal _prevClose;
private bool _hasPrevCandle;
private decimal _tickSize = 1m;
private decimal _tickValue = 1m;
/// <summary>
/// Distance in price steps from the current market to the pending stop entries.
/// </summary>
public int IndentTicks
{
get => _indentTicks.Value;
set => _indentTicks.Value = value;
}
/// <summary>
/// Minimal profit in account currency required to exit when price action stabilizes.
/// </summary>
public decimal MinProfit
{
get => _minProfit.Value;
set => _minProfit.Value = value;
}
/// <summary>
/// Lifetime of pending stop orders in minutes.
/// </summary>
public int ExpirationMinutes
{
get => _expirationMinutes.Value;
set => _expirationMinutes.Value = value;
}
/// <summary>
/// Absolute profit or loss that forces the position to close.
/// </summary>
public decimal AbsoluteFixation
{
get => _absoluteFixation.Value;
set => _absoluteFixation.Value = value;
}
/// <summary>
/// Maximum size of the previous candle body that is treated as consolidation.
/// </summary>
public int StabilizationTicks
{
get => _stabilizationTicks.Value;
set => _stabilizationTicks.Value = value;
}
/// <summary>
/// Order volume used for entries.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set
{
_orderVolume.Value = value;
Volume = value;
}
}
/// <summary>
/// Candle type used to drive the strategy logic.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public AutotradePendingStopsStrategy()
{
_indentTicks = Param(nameof(IndentTicks), 200)
.SetGreaterThanZero()
.SetDisplay("Indent Ticks", "Distance in ticks between price and pending stop orders", "Entries");
_minProfit = Param(nameof(MinProfit), 2m)
.SetGreaterThanZero()
.SetDisplay("Min Profit", "Minimum profit to close during low volatility", "Risk");
_expirationMinutes = Param(nameof(ExpirationMinutes), 41)
.SetGreaterThanZero()
.SetDisplay("Order Expiration", "Lifetime of pending stops in minutes", "Entries");
_absoluteFixation = Param(nameof(AbsoluteFixation), 43m)
.SetGreaterThanZero()
.SetDisplay("Absolute Fixation", "Profit or loss in currency that forces exit", "Risk");
_stabilizationTicks = Param(nameof(StabilizationTicks), 25)
.SetGreaterThanZero()
.SetDisplay("Stabilization Ticks", "Maximum candle body considered as flat market", "Exits");
_orderVolume = Param(nameof(OrderVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Default volume for both stop orders", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame that drives order refresh", "General");
Volume = _orderVolume.Value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
// Reset runtime state when the strategy is reloaded.
_prevOpen = 0m;
_prevClose = 0m;
_hasPrevCandle = false;
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = _orderVolume.Value;
// Cache price step and tick value for fast profit calculations.
_tickSize = Security.PriceStep ?? 1m;
_tickValue = GetSecurityValue<decimal?>(Level1Fields.StepPrice) ?? _tickSize;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
// Only act on completed candles to stay aligned with the original MQL logic.
if (candle.State != CandleStates.Finished)
return;
if (!_hasPrevCandle)
{
// Store the first candle so that stabilization checks have history.
_prevOpen = candle.OpenPrice;
_prevClose = candle.ClosePrice;
_hasPrevCandle = true;
EnsurePendingOrders(candle);
return;
}
UpdatePendingOrdersLifetime(candle);
if (Position == 0)
{
// Refresh pending orders as soon as the market is flat.
EnsurePendingOrders(candle);
}
else
{
// Manage the active position and close it when required.
ManageOpenPosition(candle);
}
// Keep the previous candle body for stabilization checks on the next bar.
_prevOpen = candle.OpenPrice;
_prevClose = candle.ClosePrice;
}
private decimal _entryPrice;
private void EnsurePendingOrders(ICandleMessage candle)
{
if (!IsFormedAndOnlineAndAllowTrading())
return;
var indent = IndentTicks * _tickSize;
var buyPrice = candle.ClosePrice + indent;
var sellPrice = candle.ClosePrice - indent;
// Simulate stop-order breakout: if high breaches buy level, go long
if (candle.HighPrice >= buyPrice && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(OrderVolume);
_entryPrice = buyPrice;
}
// if low breaches sell level, go short
else if (candle.LowPrice <= sellPrice && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(OrderVolume);
_entryPrice = sellPrice;
}
}
private void UpdatePendingOrdersLifetime(ICandleMessage candle)
{
// No pending orders in simplified version - nothing to expire.
}
private void ManageOpenPosition(ICandleMessage candle)
{
var entryPrice = _entryPrice;
if (entryPrice == 0)
return;
var priceDiff = Position > 0 ? candle.ClosePrice - entryPrice : entryPrice - candle.ClosePrice;
var prevBodySize = Math.Abs(_prevClose - _prevOpen);
// Exit if profitable and market consolidating, or if loss exceeds threshold
var exitByProfit = priceDiff > 0 && prevBodySize < candle.ClosePrice * 0.001m;
var exitByLoss = priceDiff < -candle.ClosePrice * 0.005m;
if (Position > 0 && (exitByProfit || exitByLoss))
{
SellMarket();
}
else if (Position < 0 && (exitByProfit || exitByLoss))
{
BuyMarket();
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class autotrade_pending_stops_strategy(Strategy):
def __init__(self):
super(autotrade_pending_stops_strategy, self).__init__()
self._indent_ticks = self.Param("IndentTicks", 200)
self._min_profit = self.Param("MinProfit", 2.0)
self._expiration_minutes = self.Param("ExpirationMinutes", 41)
self._absolute_fixation = self.Param("AbsoluteFixation", 43.0)
self._stabilization_ticks = self.Param("StabilizationTicks", 25)
self._order_volume = self.Param("OrderVolume", 1.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._prev_open = 0.0
self._prev_close = 0.0
self._has_prev_candle = False
self._tick_size = 1.0
self._entry_price = 0.0
@property
def IndentTicks(self):
return self._indent_ticks.Value
@IndentTicks.setter
def IndentTicks(self, value):
self._indent_ticks.Value = value
@property
def MinProfit(self):
return self._min_profit.Value
@MinProfit.setter
def MinProfit(self, value):
self._min_profit.Value = value
@property
def ExpirationMinutes(self):
return self._expiration_minutes.Value
@ExpirationMinutes.setter
def ExpirationMinutes(self, value):
self._expiration_minutes.Value = value
@property
def AbsoluteFixation(self):
return self._absolute_fixation.Value
@AbsoluteFixation.setter
def AbsoluteFixation(self, value):
self._absolute_fixation.Value = value
@property
def StabilizationTicks(self):
return self._stabilization_ticks.Value
@StabilizationTicks.setter
def StabilizationTicks(self, value):
self._stabilization_ticks.Value = value
@property
def OrderVolume(self):
return self._order_volume.Value
@OrderVolume.setter
def OrderVolume(self, value):
self._order_volume.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(autotrade_pending_stops_strategy, self).OnStarted2(time)
self._prev_open = 0.0
self._prev_close = 0.0
self._has_prev_candle = False
self._entry_price = 0.0
self.Volume = self._order_volume.Value
self._tick_size = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self._tick_size <= 0.0:
self._tick_size = 1.0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
open_price = float(candle.OpenPrice)
if not self._has_prev_candle:
self._prev_open = open_price
self._prev_close = close
self._has_prev_candle = True
self._ensure_pending_orders(candle)
return
if self.Position == 0:
self._ensure_pending_orders(candle)
else:
self._manage_open_position(candle)
self._prev_open = open_price
self._prev_close = close
def _ensure_pending_orders(self, candle):
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
order_vol = float(self._order_volume.Value)
indent = int(self._indent_ticks.Value) * self._tick_size
buy_price = close + indent
sell_price = close - indent
pos = float(self.Position)
if high >= buy_price and pos <= 0:
if pos < 0:
self.BuyMarket(abs(pos))
self.BuyMarket(order_vol)
self._entry_price = buy_price
elif low <= sell_price and pos >= 0:
if pos > 0:
self.SellMarket(abs(pos))
self.SellMarket(order_vol)
self._entry_price = sell_price
def _manage_open_position(self, candle):
if self._entry_price == 0.0:
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self.Position > 0:
price_diff = close - self._entry_price
else:
price_diff = self._entry_price - close
prev_body_size = abs(self._prev_close - self._prev_open)
exit_by_profit = price_diff > 0.0 and prev_body_size < close * 0.001
exit_by_loss = price_diff < -close * 0.005
if self.Position > 0 and (exit_by_profit or exit_by_loss):
self.SellMarket()
elif self.Position < 0 and (exit_by_profit or exit_by_loss):
self.BuyMarket()
def OnReseted(self):
super(autotrade_pending_stops_strategy, self).OnReseted()
self._prev_open = 0.0
self._prev_close = 0.0
self._has_prev_candle = False
self._tick_size = 1.0
self._entry_price = 0.0
def CreateClone(self):
return autotrade_pending_stops_strategy()