Symbol Swap Panel Strategy — это конвертация панели «Symbol Swap Panel» из MQL в среду StockSharp. Оригинальная панель позволяла вводить тикер, мгновенно переключать график и наблюдать в реальном времени цены открытия, максимума, минимума, закрытия, тиковый объём и спред. В новой реализации стратегия запускается на любом инструменте и выводит те же данные через логи, сохраняя возможность быстро переключаться на другую бумагу.
Основное поведение
Оформляет подписку на свечи и Level1 для активного инструмента.
По каждой завершённой свече публикует подробный лог с ценами OHLC, суммарным объёмом и актуальным спредом.
Кеширует котировки Bid/Ask и пересчитывает спред, как это делала исходная панель.
Обрабатывает ручные запросы на смену инструмента и перестраивает подписки без перезапуска стратегии.
Запоминает последний применённый символ, чтобы игнорировать повторные бессмысленные переключения.
Параметры
Название
Тип
Описание
TargetSecurityId
string
Идентификатор инструмента, на который нужно переключиться при запросе. Пустая строка приводит к предупреждению и игнорируется.
CandleType
DataType
Тип свечей, используемый для периодических обновлений (по умолчанию часовые свечи — аналог исходного таймфрейма).
SwapRequested
bool
Флаг ручного переключения. Установка в true инициирует смену инструмента, после обработки автоматически возвращается в false.
Подписки на данные
Свечи по текущему инструменту согласно параметру CandleType.
Поток Level1 для отслеживания лучших цен и расчёта спреда.
При смене инструмента старые подписки корректно останавливаются и заменяются новыми.
Последовательность работы
При запуске стратегия использует Strategy.Security, либо пытается разрешить символ из TargetSecurityId.
Создаются подписки на свечи и Level1 для выбранного инструмента.
Каждая завершённая свеча генерирует лог с текстом, соответствующим оригинальным меткам панели.
Обновления Level1 поддерживают актуальные значения Bid/Ask и спреда.
Установка 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;
}
}
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_panel_strategy(Strategy):
def __init__(self):
super(symbol_swap_panel_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Candle series for monitoring and signals", "General")
self._ma_period = self.Param("MaPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("MA Period", "Moving average period for entry signals", "Indicators")
self._entry_price = 0.0
self._prev_close = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def MaPeriod(self):
return self._ma_period.Value
@MaPeriod.setter
def MaPeriod(self, value):
self._ma_period.Value = value
def OnReseted(self):
super(symbol_swap_panel_strategy, self).OnReseted()
self._entry_price = 0.0
self._prev_close = 0.0
def OnStarted2(self, time):
super(symbol_swap_panel_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = self.MaPeriod
self.SubscribeCandles(self.CandleType) \
.Bind(sma, self._process_candle) \
.Start()
def _process_candle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
price = float(candle.ClosePrice)
sma_v = float(sma_value)
# Exit: reversal against trend
if self.Position != 0 and self._entry_price > 0:
if (self.Position > 0 and price < sma_v) or \
(self.Position < 0 and price > sma_v):
if self.Position > 0:
self.SellMarket(abs(self.Position))
else:
self.BuyMarket(abs(self.Position))
self._entry_price = 0.0
self._prev_close = price
return
# Entry: follow MA trend with momentum confirmation
if self.Position == 0 and self._prev_close > 0:
if price > sma_v and self._prev_close <= sma_v:
self.BuyMarket()
self._entry_price = price
elif price < sma_v and self._prev_close >= sma_v:
self.SellMarket()
self._entry_price = price
self._prev_close = price
def CreateClone(self):
return symbol_swap_panel_strategy()