GitHub で見る

CM Panel 戦略

概要

CM Panel 戦略 は、オリジナルのMetaTrader 5スクリプト「cm panel」の動作を再現する手動の指値注文ヘルパーです。画面上のコントロールを描画する代わりに、StockSharpポートはボタンのように機能するインタラクティブなパラメーターを公開します:フラグを true に設定するとペンディングのストップ注文が発注またはキャンセルされ、フラグはすぐに false にリセットされ、パネルのプッシュボタンのワークフローを模倣します。この戦略は、距離、ボリューム、ポイントで表された保護ターゲットを含め、買い注文と売り注文のために別々の設定を維持します。

変換はStockSharpの高レベルAPIのみに依存しています。ペンディング注文は BuyStopSellStop ヘルパーで送信され、フィル後の保護は独立したストップロスとテイクプロフィット注文を登録することで実装されます。価格とボリュームの値はアセットのティックサイズとロットステップに自動的に適応されるため、戦略は手動の正規化なしに取引所の制約を尊重します。

取引ロジック

  1. ユーザーが PlaceBuyStoptrue に切り替えると、戦略は最良のask(必要に応じて最後の取引価格にフォールバック)を読み取り、価格単位に変換した BuyStopOffsetPoints でシフトします。ボリューム BuyVolume で得られたレベルに買いストップ注文が送信されます。希望するストップロスとテイクプロフィット価格が即座に計算され、ペンディング保護ターゲットとして保存されます。
  2. ユーザーが PlaceSellStoptrue に切り替えると、最良のbid(または最後の取引)が SellStopOffsetPoints で下方にシフトされます。ボリューム SellVolume でその価格に売りストップ注文が発注され、対応する保護レベルが記録されます。
  3. ペンディングのストップ注文が約定した後、戦略は記録された保護注文を自動的に発注します:
    • ロングポジションはエントリー価格の下に SellStop のストップロスを受け取り、その上に SellLimit のテイクプロフィットを受け取ります。
    • ショートポジションはエントリー価格の上に BuyStop のストップロスを受け取り、その下に BuyLimit のテイクプロフィットを受け取ります。 各保護注文は1度だけ送信されます;一方が約定した場合、もう一方はキャンセルされ、MetaTraderの単一SL/TPペアをエミュレートします。
  4. CancelPendingOrders フラグが切り替えられると、戦略によって作成されたアクティブな買いまたは売りストップ注文がキャンセルされます。すでにオープンポジションを保護している保護注文は、進行中の取引が保護されたままになるように意図的にそのままにされます。
  5. ボリュームはアセットの VolumeStepMinVolumeMaxVolume に調整されます。結果のサイズが無効になった場合(例えば最小ロットを下回る)、操作は中止され、注文を送信する代わりに警告が記録されます。
  6. すべての価格距離はポイントで表現され、アセットの PriceStep を使用して変換されます。ステップが不明な場合、ティックメタデータなしのシンボルでパネルが引き続き使用可能になるよう、0.0001 の保守的なフォールバックが適用されます。

パラメーター

名前 デフォルト 説明
BuyVolume decimal 0.10 インストルメントのロットステップを尊重した後、各買いストップ注文で送信されるボリューム。
SellVolume decimal 0.10 インストルメントのロットステップを尊重した後、各売りストップ注文で送信されるボリューム。
BuyStopOffsetPoints int 100 ペンディングの買いストップを配置するために現在のaskより上に追加されるポイントでの距離。
SellStopOffsetPoints int 100 ペンディングの売りストップを配置するために現在のbidから引かれるポイントでの距離。
BuyStopLossPoints int 100 買いストップによって発動されたロングポジションのストップロス距離(ポイント)。保護注文をスキップするにはゼロに設定する。
SellStopLossPoints int 100 売りストップによって発動されたショートポジションのストップロス距離(ポイント)。保護注文をスキップするにはゼロに設定する。
BuyTakeProfitPoints int 150 買いストップによって発動されたロングポジションのテイクプロフィット距離(ポイント)。保護注文をスキップするにはゼロに設定する。
SellTakeProfitPoints int 150 売りストップによって発動されたショートポジションのテイクプロフィット距離(ポイント)。保護注文をスキップするにはゼロに設定する。
PlaceBuyStop bool false 一度買いストップ注文を発注するトグル。処理後、値は自動的に false にリセットされる。
PlaceSellStop bool false 一度売りストップ注文を発注するトグル。処理後、値は自動的に false にリセットされる。
CancelPendingOrders bool false パネルによって作成されたすべてのアクティブなペンディングストップ注文をキャンセルするトグル。

MetaTraderバージョンとの違い

  • MetaTraderはストップロスとテイクプロフィットレベルをペンディング注文に直接添付します。StockSharpはエントリーが約定した直後に専用の保護注文を生成することで同じ動作を維持します。
  • StockSharp実装はボリュームと価格をアセットのメタデータに透過的に適応させ、_Point_Digits、またはボリューム丸めによる手動正規化の必要性を排除します。
  • 取引会場からのストップレベル制限は自動的にクエリされません。ユーザーはMetaTraderと同様に、ブローカーの最小距離を尊重するオフセットを設定する必要があります。
  • 削除トグル(CancelPendingOrders)はペンディングストップのみをキャンセルします。オープンポジションを保護している既存の保護注文はアクティブなままで、ライブ取引が保護されます。

使用のヒント

  • アクションフラグを切り替える前にポートフォリオとアセットを割り当てる;そうしないと、戦略は警告を記録してリクエストを無視します。
  • オリジナルのパネルワークフローをエミュレートするには、戦略をDesignerまたはRunner UIに追加し、プロパティグリッドにパラメーターを公開し、注文を送信またはキャンセルしたいときにブール値を切り替えます。
  • ロジックは最良のbid/ask見積もりに依存するため、Level 1データがストリーミングされていることを確認してください。最良価格がない場合、コードは最後の取引価格にフォールバックしますが、ペンディング注文が意図よりも市場に近くなる可能性があります。
  • インストルメントの最小ストップレベルを尊重するようにポイント距離を調整する。ヘルパーはブローカー固有のバッファを自動的に適用しません。
  • 付随するSL/TPレベルなしでネイキッドストップ注文を発注したい場合は、保護距離をゼロに設定します。
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;
	}
}