Открыть на GitHub

Стратегия List Positions

Обзор

List Positions Strategy повторяет поведение исходного скрипта MetaTrader: через фиксированные интервалы выводит список текущих позиций портфеля в журнал стратегии. Это вспомогательный инструмент мониторинга, который не размещает заявки. Он формирует снимок открытых позиций, чтобы трейдер мог в Designer или других компонентах StockSharp оперативно увидеть инструмент, направление, объём, цену входа и текущую прибыль.

Ключевые особенности

  • Регулярный отчёт по таймеру с немедленным построением первой выборки после старта.
  • Опциональные фильтры по базовому инструменту стратегии и по идентификатору стратегии (аналог MQL magic number).
  • Подробная строка отчёта: номер позиции, время последнего изменения, сторона сделки, объём, средняя цена и прибыль.
  • Защита от параллельного выполнения таймера посредством атомарного индикатора занятости.

Параметры

Имя Описание Значение по умолчанию
StrategyIdFilter Идентификатор стратегии, который нужно исключить из отчёта. Пустое значение означает, что выводятся все позиции. Пустая строка
SelectionMode Определяет, нужно ли перечислять все инструменты портфеля или только Strategy.Security. AllSymbols
TimerInterval Интервал между последовательными снимками позиций. 6 секунд

Логика работы

  1. В методе OnStarted стратегия проверяет наличие портфеля и корректность интервала таймера.
  2. Создаётся System.Threading.Timer с нулевой задержкой первого срабатывания, поэтому отчёт появляется сразу и далее повторяется через заданные промежутки времени.
  3. Каждый запуск таймера вызывает ProcessPositions, где выполняется перебор Portfolio.Positions, применяются фильтры по инструменту и идентификатору, а данные добавляются в StringBuilder.
  4. Если хотя бы одна позиция удовлетворяет условиям, готовый текст выводится через LogInfo. При отсутствии совпадений записывается короткое уведомление.
  5. Блокировка на основе Interlocked не допускает повторного входа в обработчик, пока предыдущий запуск ещё выполняется.

Практические рекомендации

  • Перед запуском задайте Portfolio и Connector. При выборе режима CurrentSymbol обязательно укажите Strategy.Security для отслеживаемого инструмента.
  • Чтобы скрыть позиции других торговых роботов, укажите в StrategyIdFilter строковый StrategyId, который они используют. Такие позиции будут пропущены.
  • Стратегия не изменяет позиции и не регистрирует заявки, поэтому её можно запускать параллельно с торговыми алгоритмами в качестве информационной панели.
  • В журнал выводится таблица с заголовком Idx | Symbol | PositionId | LastChange | Side | Quantity | AvgPrice | PnL, что облегчает анализ и парсинг.

Отличия от версии на MQL

  • В MetaTrader фильтр реализован через 64-битное число magic; в StockSharp позиции содержат строковый идентификатор стратегии, поэтому фильтр принимает текстовое значение.
  • Скрипт MQL писал данные в комментарий графика, а порт на StockSharp использует LogInfo, доступный в Designer, Runner и других приложениях.
  • Добавлена блокировка от одновременных срабатываний таймера, что делает стратегию устойчивой при высокой нагрузке.
  • Метка времени берётся из Position.LastChangeTime, отражающей обновление позиции в StockSharp, тогда как в оригинале использовалось время открытия.
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;
	}
}