GitHub で見る

Symbol Swap Strategy

The Symbol Swap Strategy is the StockSharp port of the MetaTrader 5 utility "Symbol Swap". The original MQL5 program opens a panel where a trader can enter any ticker, immediately switch the current chart to that symbol, and monitor a compact data window with the latest time, OHLC prices, tick volume, and spread. This C# conversion keeps the same responsibilities while relying exclusively on StockSharp's high-level subscription API.

Behaviour

  1. On start the strategy resolves the instrument to watch. It first tries WatchedSecurityId; if the field is empty it falls back to Strategy.Security that is configured in the launcher.
  2. Candle data of the chosen CandleType is streamed through SubscribeCandles(...). Finished bars deliver the open, high, low, close, and tick volume that populate the panel.
  3. Real-time best bid/ask values arrive via SubscribeLevel1(...). The spread is recalculated on every quote update to mirror the MQL data window.
  4. The formatted block is either written to the strategy log (OutputMode = Log) or rendered on a chart (OutputMode = Chart) with DrawText(...), recreating the floating panel from MetaTrader.
  5. Calling SwapSecurity("TICKER") during execution resolves the new security through SecurityProvider.LookupById and seamlessly resubscribes both the candle and Level 1 feeds to the requested instrument.

The strategy is informational only; it does not place orders. It can run standalone as a market dashboard or alongside other trading bots.

Parameters

Name Description Default
CandleType Time frame that defines the candle subscription used to build OHLC and tick volume data. TimeFrame(1 minute)
WatchedSecurityId Optional instrument identifier. Leave empty to use Strategy.Security. empty
OutputMode Rendering destination of the information block. Choose between Chart (overlay near the price) or Log (strategy log). Chart

Public methods

Method Description
SwapSecurity(string securityId) Resolves the provided ticker through the active SecurityProvider and immediately switches the panel to that symbol. The method can be called multiple times; each call clears previous candle/Level 1 subscriptions before adding the new feeds.

Usage notes

  • Ensure the connector exposes the requested identifier; otherwise SecurityProvider.LookupById throws an exception.
  • When OutputMode = Chart, the strategy automatically creates a chart area, draws the subscribed candles, and overlays the status block. For log mode only the textual updates are produced.
  • Tick volume equals the candle's TotalVolume, which is how MetaTrader reports its per-bar tick count.
  • Spread is shown only when both best bid and best ask are available. Otherwise the field displays n/a.

Conversion details

  • The MetaTrader timer loop is replaced with StockSharp subscriptions. Candles trigger once per finished bar and Level 1 quotes refresh the spread in real time.
  • The MQL panel labels are represented by a single multi-line text block. The text uses the exact ordering from the original tool: Time, Period, Symbol, Close, Open, High, Low, Tick Volume, Spread.
  • Runtime symbol swaps no longer need manual Market Watch management—the strategy resolves instruments directly via the StockSharp security provider.
  • Only high-level API calls are used (SubscribeCandles, SubscribeLevel1, DrawText, AddInfo). There are no manual indicator calculations or direct connector manipulations, satisfying the repository coding rules.
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;
			}
		}
	}
}