Открыть на GitHub

Стратегия «Symbol Swap Panel»

Обзор

Symbol Swap Panel Strategy — это конвертация панели «Symbol Swap Panel» из MQL в среду StockSharp. Оригинальная панель позволяла вводить тикер, мгновенно переключать график и наблюдать в реальном времени цены открытия, максимума, минимума, закрытия, тиковый объём и спред. В новой реализации стратегия запускается на любом инструменте и выводит те же данные через логи, сохраняя возможность быстро переключаться на другую бумагу.

Основное поведение

  • Оформляет подписку на свечи и Level1 для активного инструмента.
  • По каждой завершённой свече публикует подробный лог с ценами OHLC, суммарным объёмом и актуальным спредом.
  • Кеширует котировки Bid/Ask и пересчитывает спред, как это делала исходная панель.
  • Обрабатывает ручные запросы на смену инструмента и перестраивает подписки без перезапуска стратегии.
  • Запоминает последний применённый символ, чтобы игнорировать повторные бессмысленные переключения.

Параметры

Название Тип Описание
TargetSecurityId string Идентификатор инструмента, на который нужно переключиться при запросе. Пустая строка приводит к предупреждению и игнорируется.
CandleType DataType Тип свечей, используемый для периодических обновлений (по умолчанию часовые свечи — аналог исходного таймфрейма).
SwapRequested bool Флаг ручного переключения. Установка в true инициирует смену инструмента, после обработки автоматически возвращается в false.

Подписки на данные

  • Свечи по текущему инструменту согласно параметру CandleType.
  • Поток Level1 для отслеживания лучших цен и расчёта спреда.
  • При смене инструмента старые подписки корректно останавливаются и заменяются новыми.

Последовательность работы

  1. При запуске стратегия использует Strategy.Security, либо пытается разрешить символ из TargetSecurityId.
  2. Создаются подписки на свечи и Level1 для выбранного инструмента.
  3. Каждая завершённая свеча генерирует лог с текстом, соответствующим оригинальным меткам панели.
  4. Обновления Level1 поддерживают актуальные значения Bid/Ask и спреда.
  5. Установка SwapRequested = true при валидном TargetSecurityId мгновенно переключает стратегию на новый инструмент и перезапускает подписки.

Рекомендации по использованию

  • Стратегия предназначена для мониторинга и не подаёт торговых заявок.
  • Спред рассчитывается только при наличии положительных значений Bid и Ask.
  • Невалидные или неизвестные тикеры вызывают предупреждение в логах, при этом текущие подписки не прерываются.
  • Если требуется более частое обновление, уменьшите таймфрейм в параметре CandleType.

Сохранённые возможности MQL-версии

  • Ручное переключение инструмента по текстовому идентификатору.
  • Отображение цен OHLC, объёма и спреда выбранного символа в реальном времени.
  • Защита от пустых вводов и несуществующих символов (в StockSharp это предупреждения).

Отличия от исходной реализации

  • Вместо панельных надписей вся информация выводится в логах, что ближе к типичному сценарию использования StockSharp.
  • Переключение реализовано через переназначение Strategy.Security и пересоздание подписок, а не через управление окном терминала.
  • Таймер в MQL заменён обработкой закрытия свечей, чтобы использовать высокоуровневый API StockSharp.

Требования

  • Соединение StockSharp с доступом к нужным инструментам.
  • Источник данных Level1 для вычисления спреда.
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>
/// Price monitoring strategy that logs OHLC metrics and trades on candle patterns.
/// Simplified from the "Symbol Swap Panel" MQL display widget.
/// </summary>
public class SymbolSwapPanelStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _maPeriod;

	private SimpleMovingAverage _sma;
	private decimal _entryPrice;
	private decimal _prevClose;

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

	/// <summary>
	/// Moving average period for trend signals.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public SymbolSwapPanelStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle series for monitoring and signals", "General");

		_maPeriod = Param(nameof(MaPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "Moving average period for entry signals", "Indicators");
	}

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

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

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

		_sma = new SimpleMovingAverage { Length = MaPeriod };

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

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

		var price = candle.ClosePrice;
		var high = candle.HighPrice;
		var low = candle.LowPrice;

		// Log price info
		LogInfo(
			$"Time: {candle.CloseTime:O}, O: {candle.OpenPrice}, H: {high}, L: {low}, C: {price}, " +
			$"Vol: {candle.TotalVolume}, SMA: {smaValue:F5}");

		// Exit: reversal or profit target
		if (Position != 0 && _entryPrice > 0m)
		{
			var pnl = Position > 0
				? price - _entryPrice
				: _entryPrice - price;

			// Exit on trend reversal
			if ((Position > 0 && price < smaValue) ||
				(Position < 0 && price > smaValue))
			{
				if (Position > 0)
					SellMarket(Math.Abs(Position));
				else
					BuyMarket(Math.Abs(Position));

				_entryPrice = 0m;
				_prevClose = price;
				return;
			}
		}

		// Entry: follow MA trend with momentum confirmation
		if (Position == 0 && _prevClose > 0m)
		{
			if (price > smaValue && _prevClose <= smaValue)
			{
				BuyMarket();
				_entryPrice = price;
			}
			else if (price < smaValue && _prevClose >= smaValue)
			{
				SellMarket();
				_entryPrice = price;
			}
		}

		_prevClose = price;
	}
}