Strategie Zwei Pending Orders Öffnen
Übersicht
Diese Strategie repliziert den MetaTrader Expert Advisor, der gleichzeitig eine Buy-Stop- und eine Sell-Stop-Order rund um den aktuellen Spread platziert. Sie arbeitet auf einem einzelnen Instrument und verwendet High-Level StockSharp API-Aufrufe, um das Order Book zu abonnieren, Pending Orders zu verwalten und Portfolio-Risikokontrollen zu handhaben. Sobald eine Pending Order gefüllt wird, wird die entgegengesetzte Order storniert und die aktive Position wird mit Stop-Loss-, Take-Profit- und Trailing-Stop-Regeln verwaltet.
Handelslogik
- Das Order Book abonnieren und die besten Bid- und Ask-Preise lesen.
- Wenn keine offene Position oder aktive Einstiegsorder vorhanden ist, das Einstiegsvolumen berechnen und zwei Stop-Orders platzieren:
- Buy Stop bei Ask + EntryOffsetPoints × PriceStep.
- Sell Stop bei Bid − EntryOffsetPoints × PriceStep.
- Wenn eine Stop-Order ausgeführt wird:
- Die entgegengesetzte Pending Order stornieren.
- Den Ausführungspreis als neuen Eintrittspreis speichern.
- Die initialen Stop-Loss- und Take-Profit-Niveaus in Kursschritten relativ zur Füllung berechnen.
- Während die Position aktiv ist, das Order Book überwachen:
- Longs schließen, wenn der Bid das Stop-Loss- oder Take-Profit-Niveau erreicht.
- Shorts schließen, wenn der Ask das Stop-Loss- oder Take-Profit-Niveau erreicht.
- Den Trailing Stop aktivieren, nachdem sich der Preis um die Trailing-Distanz zugunsten des Trades bewegt hat, und das Stop-Niveau entsprechend verschieben.
- Wenn die Position zu flach zurückkehrt, den internen Status zurücksetzen und ein frisches Paar Stop-Orders platzieren.
Ausstiege werden mit Marktorders ausgeführt, sobald ein Schutzniveau berührt wird. Dies hält die Logik nahe an der MQL-Implementierung, ohne auf Lower-Level Order-Modifikations-APIs angewiesen zu sein.
Geldverwaltung
Die Strategie kann entweder ein festes Volumen oder dynamisches risikobasiertes Sizing verwenden:
- Festes Volumen – die konstante Lotgröße verwenden, die durch den Parameter
FixedVolume definiert wird.
- Geldverwaltung – wenn aktiviert, das Volumen aus dem Portfolio-Eigenkapital, dem Risikoprozentsatz und dem Stop-Loss-Abstand in Kursschritten berechnen. Volumina werden auf den Volumen-Schritt des Instruments gerundet und zwischen Minimum- und Maximumwerten des Instruments begrenzt.
Parameter
| Parameter |
Beschreibung |
UseMoneyManagement |
Aktiviert risikobasiertes Positionssizing. Standard: true. |
RiskPercent |
Prozentsatz des Portfolio-Eigenkapitals, der pro Trade riskiert wird, wenn die Geldverwaltung aktiv ist. Standard: 2. |
FixedVolume |
Lotgröße, die verwendet wird, wenn die Geldverwaltung deaktiviert ist. Standard: 1. |
StopLossPoints |
Stop-Loss-Abstand in Kursschritten vom Eintrittspreis. Standard: 100. |
TakeProfitPoints |
Take-Profit-Abstand in Kursschritten vom Eintrittspreis. Standard: 300. |
TrailingStopPoints |
Trailing-Stop-Abstand in Kursschritten. Ein Wert von 0 deaktiviert den Trailing. Standard: 50. |
EntryOffsetPoints |
Abstand in Kursschritten, der verwendet wird, um die Pending Orders vom Spread entfernt zu platzieren. Standard: 50. |
SlippagePoints |
Zusätzlicher Puffer in Kursschritten für Slippage reserviert. Derzeit informativ und nicht direkt verwendet. Standard: 5. |
Hinweise
- Die Strategie ist auf den Order Book-Feed angewiesen. Stellen Sie sicher, dass Markttiefe-Daten für das ausgewählte Instrument verfügbar sind.
- Die Stop-Loss- und Take-Profit-Ausführung verwendet Marktorders, sobald der Bid/Ask das Niveau kreuzt, und entspricht dem Verhalten der ursprünglichen MQL-Trailing-Stop-Logik.
- Trailing Stops beginnen erst, nachdem sich der Preis um die konfigurierte Trailing-Distanz vom Einstieg bewegt hat.
- Der Code verwendet Tabulatoreinrückung, englische Kommentare und High-Level StockSharp-Methoden gemäß den Projektrichtlinien.
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>
/// Strategy that simulates placing both buy stop and sell stop orders around the current price.
/// It uses candle-based breakout detection and manages the resulting position
/// with fixed stop loss, take profit and optional trailing stop levels.
/// </summary>
public class OpenTwoPendingOrdersStrategy : Strategy
{
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<decimal> _entryOffsetPoints;
private readonly StrategyParam<DataType> _candleType;
private decimal? _pendingBuyPrice;
private decimal? _pendingSellPrice;
private decimal? _entryPrice;
private decimal? _stopLevel;
private decimal? _takeLevel;
private decimal _highestSinceEntry;
private decimal _lowestSinceEntry;
private int _cooldown;
/// <summary>
/// Stop loss distance expressed in price steps.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance expressed in price steps.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Trailing stop distance expressed in price steps.
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// Distance in price steps used to place the pending entries away from the current price.
/// </summary>
public decimal EntryOffsetPoints
{
get => _entryOffsetPoints.Value;
set => _entryOffsetPoints.Value = value;
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="OpenTwoPendingOrdersStrategy"/>.
/// </summary>
public OpenTwoPendingOrdersStrategy()
{
_stopLossPoints = Param(nameof(StopLossPoints), 5000m)
.SetDisplay("Stop Loss (steps)", "Stop loss distance in price steps", "Risk")
.SetOptimize(20m, 300m, 20m);
_takeProfitPoints = Param(nameof(TakeProfitPoints), 8000m)
.SetDisplay("Take Profit (steps)", "Take profit distance in price steps", "Risk")
.SetOptimize(50m, 600m, 50m);
_trailingStopPoints = Param(nameof(TrailingStopPoints), 3000m)
.SetDisplay("Trailing Stop (steps)", "Trailing stop distance in price steps", "Risk")
.SetOptimize(10m, 200m, 10m);
_entryOffsetPoints = Param(nameof(EntryOffsetPoints), 1000m)
.SetDisplay("Entry Offset (steps)", "Offset from close for pending entries", "Execution")
.SetOptimize(10m, 150m, 10m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
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;
if (_cooldown > 0)
{
_cooldown--;
return;
}
var step = GetStep();
// Manage existing position
if (Position != 0 && _entryPrice.HasValue)
{
ManagePosition(candle, step);
// If position was closed, reset and set up new pending entries
if (Position == 0)
{
ResetState();
_cooldown = 20;
}
return;
}
// Check pending entries
if (_pendingBuyPrice.HasValue && _pendingSellPrice.HasValue)
{
var buyLevel = _pendingBuyPrice.Value;
var sellLevel = _pendingSellPrice.Value;
// Buy stop triggered: price went up to pending buy level
if (candle.HighPrice >= buyLevel)
{
_pendingBuyPrice = null;
_pendingSellPrice = null;
BuyMarket();
InitializePositionLevels(true, buyLevel, step);
return;
}
// Sell stop triggered: price went down to pending sell level
if (candle.LowPrice <= sellLevel)
{
_pendingBuyPrice = null;
_pendingSellPrice = null;
SellMarket();
InitializePositionLevels(false, sellLevel, step);
return;
}
}
else
{
// No pending entries, set up new ones
SetupPendingEntries(candle.ClosePrice, step);
}
}
private void SetupPendingEntries(decimal currentPrice, decimal step)
{
var offset = EntryOffsetPoints * step;
_pendingBuyPrice = currentPrice + offset;
_pendingSellPrice = currentPrice - offset;
}
private void InitializePositionLevels(bool isLong, decimal entryPrice, decimal step)
{
_entryPrice = entryPrice;
_highestSinceEntry = entryPrice;
_lowestSinceEntry = entryPrice;
_stopLevel = StopLossPoints > 0m
? entryPrice + (isLong ? -StopLossPoints : StopLossPoints) * step
: null;
_takeLevel = TakeProfitPoints > 0m
? entryPrice + (isLong ? TakeProfitPoints : -TakeProfitPoints) * step
: null;
}
private void ManagePosition(ICandleMessage candle, decimal step)
{
if (Position > 0)
{
_highestSinceEntry = Math.Max(_highestSinceEntry, candle.HighPrice);
if (_stopLevel.HasValue && candle.LowPrice <= _stopLevel.Value)
{
SellMarket();
return;
}
if (_takeLevel.HasValue && candle.HighPrice >= _takeLevel.Value)
{
SellMarket();
return;
}
UpdateTrailingStop(true, step);
}
else if (Position < 0)
{
_lowestSinceEntry = Math.Min(_lowestSinceEntry, candle.LowPrice);
if (_stopLevel.HasValue && candle.HighPrice >= _stopLevel.Value)
{
BuyMarket();
return;
}
if (_takeLevel.HasValue && candle.LowPrice <= _takeLevel.Value)
{
BuyMarket();
return;
}
UpdateTrailingStop(false, step);
}
}
private void UpdateTrailingStop(bool isLong, decimal step)
{
if (TrailingStopPoints <= 0m || _entryPrice == null)
return;
var trailingDistance = TrailingStopPoints * step;
if (trailingDistance <= 0m)
return;
if (isLong)
{
if (_highestSinceEntry - _entryPrice.Value >= trailingDistance)
{
var desiredStop = _highestSinceEntry - trailingDistance;
if (_stopLevel == null || desiredStop > _stopLevel.Value)
_stopLevel = desiredStop;
}
}
else
{
if (_entryPrice.Value - _lowestSinceEntry >= trailingDistance)
{
var desiredStop = _lowestSinceEntry + trailingDistance;
if (_stopLevel == null || desiredStop < _stopLevel.Value)
_stopLevel = desiredStop;
}
}
}
private void ResetState()
{
_pendingBuyPrice = null;
_pendingSellPrice = null;
_entryPrice = null;
_stopLevel = null;
_takeLevel = null;
_highestSinceEntry = 0m;
_lowestSinceEntry = 0m;
_cooldown = 0;
}
private decimal GetStep()
{
var step = Security?.PriceStep ?? 0m;
return step > 0m ? step : 0.01m;
}
}
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
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class open_two_pending_orders_strategy(Strategy):
def __init__(self):
super(open_two_pending_orders_strategy, self).__init__()
self._sl_points = self.Param("StopLossPoints", 5000.0).SetDisplay("Stop Loss (steps)", "Stop loss distance in price steps", "Risk")
self._tp_points = self.Param("TakeProfitPoints", 8000.0).SetDisplay("Take Profit (steps)", "Take profit distance in price steps", "Risk")
self._trail_points = self.Param("TrailingStopPoints", 3000.0).SetDisplay("Trailing Stop (steps)", "Trailing stop distance in price steps", "Risk")
self._entry_offset = self.Param("EntryOffsetPoints", 1000.0).SetDisplay("Entry Offset (steps)", "Offset from close for pending entries", "Execution")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Type of candles", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(open_two_pending_orders_strategy, self).OnReseted()
self._reset_state()
def _reset_state(self):
self._pending_buy = None
self._pending_sell = None
self._entry_price = None
self._stop_level = None
self._take_level = None
self._highest = 0.0
self._lowest = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(open_two_pending_orders_strategy, self).OnStarted2(time)
self._reset_state()
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def _get_step(self):
if self.Security is not None and self.Security.PriceStep is not None and self.Security.PriceStep > 0:
return float(self.Security.PriceStep)
return 0.01
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
if self._cooldown > 0:
self._cooldown -= 1
return
step = self._get_step()
# Manage existing position
if self.Position != 0 and self._entry_price is not None:
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
if self.Position > 0:
self._highest = max(self._highest, h)
if self._stop_level is not None and lo <= self._stop_level:
self.SellMarket()
elif self._take_level is not None and h >= self._take_level:
self.SellMarket()
else:
# trailing
if float(self._trail_points.Value) > 0:
trail_dist = float(self._trail_points.Value) * step
if self._highest - self._entry_price >= trail_dist:
desired = self._highest - trail_dist
if self._stop_level is None or desired > self._stop_level:
self._stop_level = desired
elif self.Position < 0:
self._lowest = min(self._lowest, lo)
if self._stop_level is not None and h >= self._stop_level:
self.BuyMarket()
elif self._take_level is not None and lo <= self._take_level:
self.BuyMarket()
else:
if float(self._trail_points.Value) > 0:
trail_dist = float(self._trail_points.Value) * step
if self._entry_price - self._lowest >= trail_dist:
desired = self._lowest + trail_dist
if self._stop_level is None or desired < self._stop_level:
self._stop_level = desired
if self.Position == 0:
self._reset_state()
self._cooldown = 20
return
# Check pending entries
if self._pending_buy is not None and self._pending_sell is not None:
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
if h >= self._pending_buy:
entry = self._pending_buy
self._pending_buy = None
self._pending_sell = None
self.BuyMarket()
self._entry_price = entry
self._highest = entry
self._lowest = entry
sl = float(self._sl_points.Value)
tp = float(self._tp_points.Value)
self._stop_level = entry - sl * step if sl > 0 else None
self._take_level = entry + tp * step if tp > 0 else None
return
if lo <= self._pending_sell:
entry = self._pending_sell
self._pending_buy = None
self._pending_sell = None
self.SellMarket()
self._entry_price = entry
self._highest = entry
self._lowest = entry
sl = float(self._sl_points.Value)
tp = float(self._tp_points.Value)
self._stop_level = entry + sl * step if sl > 0 else None
self._take_level = entry - tp * step if tp > 0 else None
return
else:
offset = float(self._entry_offset.Value) * step
self._pending_buy = float(candle.ClosePrice) + offset
self._pending_sell = float(candle.ClosePrice) - offset
def CreateClone(self):
return open_two_pending_orders_strategy()