Открыть на GitHub

Стратегия Symbol Swap

Symbol Swap Strategy — это порт утилиты MetaTrader 5 «Symbol Swap». Оригинальный MQL5-скрипт открывает панель, где трейдер вводит тикер, мгновенно переключает график на выбранный инструмент и получает компактное окно данных с текущим временем, ценами OHLC, тиковым объёмом и спредом. Переписанная на C# версия сохраняет те же обязанности и использует только высокоуровневый API StockSharp.

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

  1. При запуске стратегия определяет инструмент для наблюдения. Сначала используется WatchedSecurityId, а если поле пустое — берётся Strategy.Security, настроенный в терминале.
  2. Свечные данные выбранного CandleType поступают через SubscribeCandles(...). После закрытия бара методу передаются значения Open, High, Low, Close и тиковый объём, которыми заполняется панель.
  3. Котировки best bid/ask обновляются в реальном времени через SubscribeLevel1(...). На каждом изменении рассчитывается спред, полностью повторяя поведение окна данных MetaTrader.
  4. Сформированный блок выводится либо в лог стратегии (OutputMode = Log), либо на график (OutputMode = Chart) посредством DrawText(...), что визуально воспроизводит плавающую панель из MQL.
  5. Вызов SwapSecurity("TICKER") во время работы находит новый инструмент через SecurityProvider.LookupById и без паузы пересоздаёт свечную и Level 1 подписки под выбранный символ.

Стратегия носит информационный характер и не выставляет ордера. Её можно использовать как самостоятельную панель мониторинга или запускать вместе с другими роботами.

Параметры

Имя Описание Значение по умолчанию
CandleType Таймфрейм свечей, которые формируют значения OHLC и тиковый объём. TimeFrame(1 minute)
WatchedSecurityId Необязательный идентификатор инструмента. Оставьте пустым, чтобы использовать Strategy.Security. пусто
OutputMode Куда выводится информационный блок: Chart (над графиком) или Log (в лог стратегии). Chart

Публичные методы

Метод Назначение
SwapSecurity(string securityId) Ищет тикер через активный SecurityProvider и сразу переключает панель на новый инструмент. При каждом вызове прежние подписки удаляются и создаются заново для нового символа.

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

  • Убедитесь, что коннектор предоставляет указанный идентификатор. Иначе SecurityProvider.LookupById выбросит исключение.
  • При OutputMode = Chart стратегия автоматически создаёт область графика, рисует свечи выбранного таймфрейма и накладывает текстовый блок. В режиме Log обновления выводятся только текстом.
  • Тиковый объём соответствует полю TotalVolume свечи — именно так MetaTrader считает количество тиков в баре.
  • Спред отображается только при наличии обоих значений best bid и best ask, иначе выводится n/a.

Особенности конверсии

  • Таймер из MQL заменён на подписки StockSharp. Свечи дают события по закрытии бара, а Level 1 — при каждом изменении котировки.
  • Девять MQL-ярлыков собраны в один многострочный текст с исходным порядком: Time, Period, Symbol, Close, Open, High, Low, Tick Volume, Spread.
  • Больше не требуется вручную добавлять тикер в Market Watch — стратегия использует SecurityProvider для поиска инструментов.
  • Применяются только высокоуровневые вызовы (SubscribeCandles, SubscribeLevel1, DrawText, AddInfo), что полностью удовлетворяет правилам репозитория.
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>
/// Market monitoring strategy that tracks price metrics and trades on significant
/// spread changes. Simplified from the MetaTrader "Symbol Swap" display panel.
/// </summary>
public class SymbolSwapStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _smaPeriod;
	private readonly StrategyParam<decimal> _spreadThreshold;

	private SimpleMovingAverage _sma;
	private decimal _entryPrice;

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

	/// <summary>
	/// SMA period for trend detection.
	/// </summary>
	public int SmaPeriod
	{
		get => _smaPeriod.Value;
		set => _smaPeriod.Value = value;
	}

	/// <summary>
	/// Price deviation threshold for entry signals.
	/// </summary>
	public decimal SpreadThreshold
	{
		get => _spreadThreshold.Value;
		set => _spreadThreshold.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public SymbolSwapStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Candle series for signals", "General");

		_smaPeriod = Param(nameof(SmaPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("SMA Period", "Moving average period", "Indicators");

		_spreadThreshold = Param(nameof(SpreadThreshold), 3m)
			.SetGreaterThanZero()
			.SetDisplay("Spread Threshold", "Price deviation from SMA to trigger entry", "Signals");
	}

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

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

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

		_sma = new SimpleMovingAverage { Length = SmaPeriod };

		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;

		// Exit on mean reversion
		if (Position != 0 && _entryPrice > 0m)
		{
			var pnl = Position > 0
				? price - _entryPrice
				: _entryPrice - price;

			// Exit on profit or loss threshold
			if (pnl >= SpreadThreshold || pnl <= -SpreadThreshold * 2m)
			{
				if (Position > 0)
					SellMarket(Math.Abs(Position));
				else
					BuyMarket(Math.Abs(Position));

				_entryPrice = 0m;
				return;
			}
		}

		// Entry on deviation
		if (Position == 0)
		{
			var deviation = price - smaValue;

			if (deviation > SpreadThreshold)
			{
				SellMarket();
				_entryPrice = price;
			}
			else if (deviation < -SpreadThreshold)
			{
				BuyMarket();
				_entryPrice = price;
			}
		}
	}
}