Die CM Panel Strategie ist ein manueller Pending-Order-Helfer, der das Verhalten des originalen MetaTrader 5-Skripts "cm panel" nachbildet. Anstatt Bildschirmsteuerungen zu zeichnen, stellt der StockSharp-Port interaktive Parameter bereit, die wie Buttons funktionieren: Das Setzen eines Flags auf true platziert oder storniert Pending-Stop-Orders und das Flag setzt sich sofort auf false zurück, was den Push-Button-Workflow des Panels imitiert. Die Strategie hält separate Konfigurationen für Kauf- und Verkaufsorders, einschließlich Abstände, Volumina und Schutzziele in Punkten.
Die Konvertierung basiert vollständig auf der High-Level-API von StockSharp. Pending-Orders werden mit den Helfern BuyStop und SellStop eingereicht, während der Post-Fill-Schutz durch die Registrierung unabhängiger Stop-Loss- und Take-Profit-Orders implementiert wird. Preis- und Volumenwerte werden automatisch an den Tick-Größe und Lot-Schritt des Wertpapiers angepasst, sodass die Strategie Börsenbeschränkungen ohne manuelle Normalisierung einhält.
Handelslogik
Wenn der Benutzer PlaceBuyStop auf true setzt, liest die Strategie den besten Ask (mit Fallback auf den letzten Handelspreis, falls notwendig) und verschiebt ihn um BuyStopOffsetPoints, die in Preiseinheiten umgerechnet werden. Eine Buy-Stop-Order mit dem Volumen BuyVolume wird auf dem resultierenden Niveau eingereicht. Die gewünschten Stop-Loss- und Take-Profit-Preise werden sofort berechnet und als ausstehende Schutzziele gespeichert.
Wenn der Benutzer PlaceSellStop auf true setzt, wird der beste Bid (oder letzter Handel) nach unten um SellStopOffsetPoints verschoben. Eine Sell-Stop-Order mit dem Volumen SellVolume wird auf diesem Preis platziert und die entsprechenden Schutzniveaus werden aufgezeichnet.
Nachdem eine ausstehende Stop-Order gehandelt wird, platziert die Strategie automatisch die aufgezeichneten Schutzorders:
Long-Positionen erhalten einen SellStop-Stop-Loss unterhalb des Einstiegspreises und einen SellLimit-Take-Profit darüber.
Short-Positionen erhalten einen BuyStop-Stop-Loss oberhalb des Einstiegspreises und einen BuyLimit-Take-Profit darunter.
Jede Schutzorder wird nur einmal eingereicht; wenn eine ausgeführt wird, wird die andere storniert, um das einzelne SL/TP-Paar von MetaTrader zu emulieren.
Wenn das CancelPendingOrders-Flag gesetzt wird, werden alle aktiven Buy- oder Sell-Stop-Orders storniert, die von der Strategie erstellt wurden. Schutzorders, die bereits offene Positionen sichern, werden absichtlich unberührt gelassen, damit laufende Trades geschützt bleiben.
Volumina werden an VolumeStep, MinVolume und MaxVolume des Wertpapiers angepasst. Wenn die resultierende Größe ungültig wird (z.B. unter dem Mindestlot), wird die Operation abgebrochen und stattdessen eine Warnung protokolliert.
Alle Preisabstände werden in Punkten ausgedrückt und mit dem PriceStep des Wertpapiers umgerechnet. Wenn der Schritt unbekannt ist, wird ein konservativer Fallback von 0.0001 angewendet, sodass das Panel auf Symbolen ohne Tick-Metadaten nutzbar bleibt.
Parameter
Name
Typ
Standard
Beschreibung
BuyVolume
decimal
0.10
Volumen, das mit jeder Buy-Stop-Order gesendet wird, nach Berücksichtigung des Lot-Schritts des Instruments.
SellVolume
decimal
0.10
Volumen, das mit jeder Sell-Stop-Order gesendet wird, nach Berücksichtigung des Lot-Schritts des Instruments.
BuyStopOffsetPoints
int
100
Abstand in Punkten, der über dem aktuellen Ask hinzugefügt wird, um den ausstehenden Buy-Stop zu positionieren.
SellStopOffsetPoints
int
100
Abstand in Punkten, der vom aktuellen Bid subtrahiert wird, um den ausstehenden Sell-Stop zu positionieren.
BuyStopLossPoints
int
100
Stop-Loss-Abstand (in Punkten) für Long-Positionen, die durch den Buy-Stop ausgelöst werden. Auf null setzen, um die Schutzorder zu überspringen.
SellStopLossPoints
int
100
Stop-Loss-Abstand (in Punkten) für Short-Positionen, die durch den Sell-Stop ausgelöst werden. Auf null setzen, um die Schutzorder zu überspringen.
BuyTakeProfitPoints
int
150
Take-Profit-Abstand (in Punkten) für Long-Positionen, die durch den Buy-Stop ausgelöst werden. Auf null setzen, um die Schutzorder zu überspringen.
SellTakeProfitPoints
int
150
Take-Profit-Abstand (in Punkten) für Short-Positionen, die durch den Sell-Stop ausgelöst werden. Auf null setzen, um die Schutzorder zu überspringen.
PlaceBuyStop
bool
false
Flag, das einmalig eine Buy-Stop-Order platziert. Der Wert setzt sich nach der Verarbeitung automatisch auf false zurück.
PlaceSellStop
bool
false
Flag, das einmalig eine Sell-Stop-Order platziert. Der Wert setzt sich nach der Verarbeitung automatisch auf false zurück.
CancelPendingOrders
bool
false
Flag, das alle aktiven ausstehenden Stop-Orders storniert, die vom Panel erstellt wurden.
Unterschiede zur MetaTrader-Version
MetaTrader hängt Stop-Loss- und Take-Profit-Niveaus direkt an ausstehende Orders. StockSharp behält dasselbe Verhalten bei, indem dedizierte Schutzorders unmittelbar nach einer Einstiegsfüllung generiert werden.
Die StockSharp-Implementierung passt Volumina und Preise transparent an die Wertpapier-Metadaten an und beseitigt den Bedarf für manuelle Normalisierung mit _Point, _Digits oder Volumenrundung.
Stop-Level-Einschränkungen des Handelsplatzes werden nicht automatisch abgefragt. Benutzer sollten Offsets konfigurieren, die den Mindestabstand des Brokers respektieren, genau wie in MetaTrader.
Das Lösch-Toggle (CancelPendingOrders) storniert nur ausstehende Stops. Bestehende Schutzorders für offene Positionen bleiben aktiv, damit Live-Trades geschützt bleiben.
Verwendungstipps
Portfolio und Wertpapier zuweisen, bevor Action-Flags gesetzt werden; andernfalls protokolliert die Strategie eine Warnung und ignoriert die Anfrage.
Um den ursprünglichen Panel-Workflow zu emulieren, die Strategie zur Designer- oder Runner-UI hinzufügen, die Parameter im Eigenschaften-Raster exponieren und die Booleans kippen, wenn Orders eingereicht oder storniert werden sollen.
Da die Logik auf den besten Bid/Ask-Kursen beruht, sicherstellen, dass Level-1-Daten gestreamt werden. Wenn die besten Preise fehlen, greift der Code auf den letzten Handelspreis zurück, aber ausstehende Orders können näher am Markt landen als beabsichtigt.
Die Punktabstände anpassen, um den Mindest-Stop-Level des Instruments zu respektieren. Der Helfer erzwingt keine broker-spezifischen Puffer automatisch.
Schutzabstände auf null setzen, wenn nackte Stop-Orders ohne begleitende SL/TP-Niveaus platziert werden sollen.
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>
/// Trading panel strategy that enters positions using configurable offset distances
/// and manages them with stop-loss and take-profit levels.
/// Simplified from the CM Panel MetaTrader script.
/// </summary>
public class CmPanelStrategy : Strategy
{
private readonly StrategyParam<int> _buyOffsetPoints;
private readonly StrategyParam<int> _sellOffsetPoints;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
private decimal _priceStep;
/// <summary>
/// Buy trigger offset in points above SMA.
/// </summary>
public int BuyOffsetPoints
{
get => _buyOffsetPoints.Value;
set => _buyOffsetPoints.Value = value;
}
/// <summary>
/// Sell trigger offset in points below SMA.
/// </summary>
public int SellOffsetPoints
{
get => _sellOffsetPoints.Value;
set => _sellOffsetPoints.Value = value;
}
/// <summary>
/// Stop-loss distance in points.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance in points.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Candle type for monitoring.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public CmPanelStrategy()
{
_buyOffsetPoints = Param(nameof(BuyOffsetPoints), 100)
.SetNotNegative()
.SetDisplay("Buy Offset", "Distance above SMA for buy entry (points)", "Distances");
_sellOffsetPoints = Param(nameof(SellOffsetPoints), 100)
.SetNotNegative()
.SetDisplay("Sell Offset", "Distance below SMA for sell entry (points)", "Distances");
_stopLossPoints = Param(nameof(StopLossPoints), 100)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss distance in points", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 150)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit distance in points", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle series for signals", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
_priceStep = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_priceStep = Security?.PriceStep ?? 0.01m;
var sma = new SimpleMovingAverage { Length = 20 };
SubscribeCandles(CandleType)
.Bind(sma, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormed)
return;
var price = candle.ClosePrice;
var step = _priceStep > 0m ? _priceStep : 0.01m;
// Check stop-loss / take-profit for open positions
if (Position != 0 && _entryPrice > 0m)
{
if (Position > 0)
{
if (_stopPrice.HasValue && price <= _stopPrice.Value)
{
SellMarket(Math.Abs(Position));
ResetPosition();
return;
}
if (_takePrice.HasValue && price >= _takePrice.Value)
{
SellMarket(Math.Abs(Position));
ResetPosition();
return;
}
}
else if (Position < 0)
{
if (_stopPrice.HasValue && price >= _stopPrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetPosition();
return;
}
if (_takePrice.HasValue && price <= _takePrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetPosition();
return;
}
}
}
// Entry: price crosses above SMA + offset => buy, below SMA - offset => sell
if (Position == 0)
{
var buyLevel = smaValue + BuyOffsetPoints * step;
var sellLevel = smaValue - SellOffsetPoints * step;
if (price >= buyLevel)
{
BuyMarket();
_entryPrice = price;
_stopPrice = StopLossPoints > 0 ? price - StopLossPoints * step : null;
_takePrice = TakeProfitPoints > 0 ? price + TakeProfitPoints * step : null;
}
else if (price <= sellLevel)
{
SellMarket();
_entryPrice = price;
_stopPrice = StopLossPoints > 0 ? price + StopLossPoints * step : null;
_takePrice = TakeProfitPoints > 0 ? price - TakeProfitPoints * step : null;
}
}
}
private void ResetPosition()
{
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
}
}
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.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class cm_panel_strategy(Strategy):
def __init__(self):
super(cm_panel_strategy, self).__init__()
self._buy_offset_points = self.Param("BuyOffsetPoints", 100) \
.SetDisplay("Buy Offset", "Distance above SMA for buy entry (points)", "Distances")
self._sell_offset_points = self.Param("SellOffsetPoints", 100) \
.SetDisplay("Sell Offset", "Distance below SMA for sell entry (points)", "Distances")
self._stop_loss_points = self.Param("StopLossPoints", 100) \
.SetDisplay("Stop Loss", "Stop-loss distance in points", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 150) \
.SetDisplay("Take Profit", "Take-profit distance in points", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candle series for signals", "General")
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
self._price_step = 0.0
@property
def buy_offset_points(self):
return self._buy_offset_points.Value
@property
def sell_offset_points(self):
return self._sell_offset_points.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(cm_panel_strategy, self).OnReseted()
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
self._price_step = 0.0
def OnStarted2(self, time):
super(cm_panel_strategy, self).OnStarted2(time)
step = self.Security.PriceStep if self.Security is not None else None
self._price_step = float(step) if step is not None and float(step) > 0 else 0.01
sma = SimpleMovingAverage()
sma.Length = 20
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self._process_candle).Start()
def _process_candle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormed:
return
price = float(candle.ClosePrice)
step = self._price_step if self._price_step > 0 else 0.01
sma_val = float(sma_value)
# Check SL/TP for open positions
if self.Position != 0 and self._entry_price > 0:
if self.Position > 0:
if self._stop_price is not None and price <= self._stop_price:
self.SellMarket(abs(self.Position))
self._reset_position()
return
if self._take_price is not None and price >= self._take_price:
self.SellMarket(abs(self.Position))
self._reset_position()
return
elif self.Position < 0:
if self._stop_price is not None and price >= self._stop_price:
self.BuyMarket(abs(self.Position))
self._reset_position()
return
if self._take_price is not None and price <= self._take_price:
self.BuyMarket(abs(self.Position))
self._reset_position()
return
# Entry signals
if self.Position == 0:
buy_level = sma_val + self.buy_offset_points * step
sell_level = sma_val - self.sell_offset_points * step
if price >= buy_level:
self.BuyMarket()
self._entry_price = price
self._stop_price = price - self.stop_loss_points * step if self.stop_loss_points > 0 else None
self._take_price = price + self.take_profit_points * step if self.take_profit_points > 0 else None
elif price <= sell_level:
self.SellMarket()
self._entry_price = price
self._stop_price = price + self.stop_loss_points * step if self.stop_loss_points > 0 else None
self._take_price = price - self.take_profit_points * step if self.take_profit_points > 0 else None
def _reset_position(self):
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
def CreateClone(self):
return cm_panel_strategy()