Auf GitHub ansehen

CM Panel Strategie

Überblick

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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;
	}
}