GitHub で見る

ポジション一覧戦略

概要

ポジション一覧戦略は、現在のポートフォリオポジションを定期的に戦略ログに出力することで、元のMetaTraderスクリプトの動作を再現します。注文を一切配置しない監視専用のヘルパーです。代わりに、オープンポジションのスナップショットを構築して、オペレーターがDesignerまたはStockSharpログから直接シンボル、方向、サイズ、エントリー価格、現在の利益を確認できるようにします。

主な機能

  • タイマー駆動のポジション報告で、戦略が開始した直後に最初のスナップショットが配信されます。
  • 戦略証券またはストラテジーID(MetaTraderのマジックナンバーの類比)によるオプションのフィルタリング。
  • ポジションID、最終変更時刻、サイド、数量、平均価格、利益を含む詳細なログ出力。
  • 環境がビジーのときにタイマーコールバックの重複を防ぐスレッドセーフな処理。

パラメーター

名前 説明 デフォルト値
StrategyIdFilter スキップするストラテジーID。空のままにすると、すべてのポジションが報告されます。 空文字列
SelectionMode すべてのシンボルまたはStrategy.Securityのみからのポジションが報告されるかを制御します。 AllSymbols
TimerInterval 連続するポジションスナップショット間の間隔。 6秒

動作の仕組み

  1. OnStarted中に、戦略はポートフォリオが接続されていてタイマーの間隔が正であることを確認します。
  2. 最初のレポートが即座に生成され、その後設定された間隔で繰り返されるように、ゼロ遅延でSystem.Threading.Timerが作成されます。
  3. 各タイマーティックはProcessPositionsを呼び出し、Portfolio.Positionsを繰り返し処理し、オプションのシンボルとストラテジーIDフィルターを適用し、StringBuilderにフォーマットされた行を追加します。
  4. 少なくとも1つのポジションがフィルターを通過すると、組み立てられたテーブルがLogInfoでログに書き込まれます。何も一致しない場合、代わりに簡潔な通知が記録されます。
  5. タイマーの重複はインタロックされたガードで防がれ、遅いI/Oが同時実行をトリガーできないようにします。

使用上の注意

  • 戦略を開始する前にPortfolioConnectorの両方を割り当てます。SelectionModeCurrentSymbolに設定されている場合は、監視したいインストゥルメントにStrategy.Securityも設定してください。
  • MetaTraderのmagicフィルターをエミュレートするには、他の戦略が注文を送信するときにStrategyIdとして使用する文字列値でStrategyIdFilterを埋めてください。それらのポジションはレポートから除外されます。
  • 戦略はポジションを変更したり注文を登録したりしないため、情報ウィジェットとしてライブトレーディングロジックと並行して実行することが安全です。
  • ログ出力はカラムヘッダーIdx | Symbol | PositionId | LastChange | Side | Quantity | AvgPrice | PnLの下にグループ化されているため、必要に応じて外部ツールで簡単に解析できます。

MQLバージョンとの違い

  • MetaTraderは符号なし64ビットのmagic番号を使用します。StockSharpポジションはストラテジーIDを文字列として公開するため、フィルターはテキスト値を受け入れます。
  • チャートコメントに書き込む代わりに、このポートはLogInfoでスナップショットを記録し、Designer、Runner、またはすべてのログリスナーで表示されます。
  • StockSharpバージョンは重複するタイマー呼び出しに対して保護し、重い負荷の下でも応答性を保ちます。
  • タイムスタンプはStockSharpポジションの更新を反映するPosition.LastChangeTimeに依存しており、MQLスクリプトはチケット作成時刻を表示していました。
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>
/// Logs the current position on every candle. Simplified from the original multi-position listing.
/// When position changes direction based on candle close, trades accordingly.
/// </summary>
public class ListPositionsStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _logInterval;

	private int _candleCount;
	private decimal? _prevClose;

	/// <summary>
	/// Candle type for monitoring.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Number of candles between position log entries.
	/// </summary>
	public int LogInterval
	{
		get => _logInterval.Value;
		set => _logInterval.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public ListPositionsStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe for monitoring", "General");

		_logInterval = Param(nameof(LogInterval), 10)
			.SetGreaterThanZero()
			.SetDisplay("Log Interval", "Log position every N candles", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, CandleType);
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_candleCount = 0;
		_prevClose = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		SubscribeCandles(CandleType)
			.Bind(ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormed)
			return;

		_candleCount++;

		if (_prevClose != null)
		{
			if (candle.ClosePrice > _prevClose.Value && Position <= 0)
				BuyMarket();
			else if (candle.ClosePrice < _prevClose.Value && Position >= 0)
				SellMarket();
		}

		if (_candleCount % LogInterval == 0)
		{
			LogInfo($"Position: {Position}, Price: {candle.ClosePrice:0.#####}, Equity: {Portfolio?.CurrentValue:0.##}");
		}

		_prevClose = candle.ClosePrice;
	}
}