Стратегия Spread Data Collector — это перенос на StockSharp утилиты MetaTrader 5 «Spread data collector» (MQL №33314). Оригинальный советник не совершает сделок: он просто слушает поток котировок Bid/Ask и подсчитывает, сколько тиков попадает в заранее заданные диапазоны спреда. При смене торгового года или остановке советник выводит суммарную статистику. Вариант на C# полностью повторяет логику, используя высокоуровневый API SubscribeLevel1() и делая границы диапазонов настраиваемыми.
Как работает
При старте стратегия подписывается на обновления уровня 1 (Bid/Ask) по основному инструменту Security.
Как только доступны обе цены, рассчитывается спред и сравнивается с границами, пересчитанными из пунктов в абсолютную величину через Security.PriceStep.
Ведётся шесть счётчиков:
Спред строго меньше первой границы.
Спред между первой и второй границами.
Спред между второй и третьей границами.
Спред между третьей и четвёртой границами.
Спред между четвёртой и пятой границами.
Спред выше пятой границы.
Год определяется по времени биржи в сообщении Level1ChangeMessage.ServerTime. При смене года стратегия печатает отчёт за предыдущий и сбрасывает счётчики.
При остановке стратегии выводится отчёт за текущий год, после чего работа завершается.
Таким образом, стратегия сохраняет «безопасный» характер оригинальной утилиты: никакие заявки не отправляются, можно спокойно анализировать качество ликвидности и динамику спредов.
Параметры
Все настройки задаются в пунктах (по терминологии MetaTrader). Фактическая величина пересчитывается как пункты × Security.PriceStep.
Параметр
Значение по умолчанию
Назначение
FirstBucketPoints
10
Верхняя граница первого диапазона. Спреды строго ниже неё увеличивают первый счётчик.
SecondBucketPoints
20
Верхняя граница второго диапазона. Спреды из интервала [FirstBucketPoints, SecondBucketPoints) фиксируются во втором счётчике.
ThirdBucketPoints
30
Верхняя граница третьего диапазона. Спреды из интервала [SecondBucketPoints, ThirdBucketPoints) добавляются к третьему счётчику.
FourthBucketPoints
40
Верхняя граница четвёртого диапазона. Спреды из интервала [ThirdBucketPoints, FourthBucketPoints) идут в четвёртый счётчик.
FifthBucketPoints
50
Верхняя граница пятого диапазона. Спреды из интервала [FourthBucketPoints, FifthBucketPoints) увеличивают пятый счётчик.
Все пороги должны строго возрастать. Если Security.PriceStep не задан или неположителен, стратегия при запуске выбросит исключение, чтобы пользователь сразу заметил ошибку настройки.
Формат полностью повторяет вывод функции Print в MetaTrader, поэтому можно легко сравнивать результаты двух платформ. Используйте просмотрщик логов StockSharp или перенаправьте вывод в файл.
Порядок использования
Укажите инструмент в Strategy.Security и убедитесь, что его PriceStep соответствует понятию «пункт» в MetaTrader (для большинства Forex-пар это 0.0001).
При необходимости скорректируйте границы диапазонов, сохранив строгий рост значений.
Запустите стратегию и оставьте её работать: никаких заявок она не выставляет.
Анализируйте годовые отчёты, чтобы оценить динамику спреда и качество ликвидности.
Стратегию можно запускать параллельно с боевыми торговыми системами. Она почти не потребляет ресурсов и помогает собирать долгосрочную статистику для риск-менеджмента и контроля поставщиков ликвидности.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Spread Data Collector strategy: RSI mean reversion.
/// Buys when RSI crosses below oversold, sells when RSI crosses above overbought.
/// </summary>
public class SpreadDataCollectorStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _oversold;
private readonly StrategyParam<decimal> _overbought;
private decimal _prevRsi;
private bool _hasPrev;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public decimal Oversold { get => _oversold.Value; set => _oversold.Value = value; }
public decimal Overbought { get => _overbought.Value; set => _overbought.Value = value; }
public SpreadDataCollectorStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period", "Indicators");
_oversold = Param(nameof(Oversold), 30m)
.SetDisplay("Oversold", "RSI oversold level", "Levels");
_overbought = Param(nameof(Overbought), 70m)
.SetDisplay("Overbought", "RSI overbought level", "Levels");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevRsi = 0;
_hasPrev = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(rsi, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished) return;
if (_hasPrev)
{
if (_prevRsi >= Oversold && rsiValue < Oversold && Position <= 0)
BuyMarket();
else if (_prevRsi <= Overbought && rsiValue > Overbought && Position >= 0)
SellMarket();
}
_prevRsi = rsiValue;
_hasPrev = true;
}
}