Symbol Swap Strategy — это порт утилиты MetaTrader 5 «Symbol Swap». Оригинальный MQL5-скрипт открывает панель, где трейдер вводит тикер, мгновенно переключает график на выбранный инструмент и получает компактное окно данных с текущим временем, ценами OHLC, тиковым объёмом и спредом. Переписанная на C# версия сохраняет те же обязанности и использует только высокоуровневый API StockSharp.
Логика работы
При запуске стратегия определяет инструмент для наблюдения. Сначала используется WatchedSecurityId, а если поле пустое — берётся Strategy.Security, настроенный в терминале.
Свечные данные выбранного CandleType поступают через SubscribeCandles(...). После закрытия бара методу передаются значения Open, High, Low, Close и тиковый объём, которыми заполняется панель.
Котировки best bid/ask обновляются в реальном времени через SubscribeLevel1(...). На каждом изменении рассчитывается спред, полностью повторяя поведение окна данных MetaTrader.
Сформированный блок выводится либо в лог стратегии (OutputMode = Log), либо на график (OutputMode = Chart) посредством DrawText(...), что визуально воспроизводит плавающую панель из MQL.
Вызов 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;
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class symbol_swap_strategy(Strategy):
def __init__(self):
super(symbol_swap_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candle series for signals", "General")
self._sma_period = self.Param("SmaPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("SMA Period", "Moving average period", "Indicators")
self._spread_threshold = self.Param("SpreadThreshold", 3.0) \
.SetGreaterThanZero() \
.SetDisplay("Spread Threshold", "Price deviation from SMA to trigger entry", "Signals")
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def SmaPeriod(self):
return self._sma_period.Value
@SmaPeriod.setter
def SmaPeriod(self, value):
self._sma_period.Value = value
@property
def SpreadThreshold(self):
return self._spread_threshold.Value
@SpreadThreshold.setter
def SpreadThreshold(self, value):
self._spread_threshold.Value = value
def OnReseted(self):
super(symbol_swap_strategy, self).OnReseted()
self._entry_price = 0.0
def OnStarted2(self, time):
super(symbol_swap_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = self.SmaPeriod
self.SubscribeCandles(self.CandleType) \
.Bind(sma, self._process_candle) \
.Start()
def _process_candle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormed:
return
price = float(candle.ClosePrice)
sma_v = float(sma_value)
threshold = float(self.SpreadThreshold)
# Exit on mean reversion
if self.Position != 0 and self._entry_price > 0:
pnl = price - self._entry_price if self.Position > 0 else self._entry_price - price
if pnl >= threshold or pnl <= -threshold * 2:
if self.Position > 0:
self.SellMarket(abs(self.Position))
else:
self.BuyMarket(abs(self.Position))
self._entry_price = 0.0
return
# Entry on deviation
if self.Position == 0:
deviation = price - sma_v
if deviation > threshold:
self.SellMarket()
self._entry_price = price
elif deviation < -threshold:
self.BuyMarket()
self._entry_price = price
def CreateClone(self):
return symbol_swap_strategy()