Совместимость стратегий с платформами StockSharp
При разработке торговых стратегий в StockSharp важно учитывать их совместимость с различными платформами: Designer, Shell, Runner и облачным тестированием. Следуя приведенным ниже рекомендациям, вы создадите стратегию, которая будет корректно работать во всех средах.
Параметры в конструкторе стратегии
Избегайте параметров в конструкторе
Для обеспечения совместимости с платформами StockSharp, особенно с облачным тестированием, не следует добавлять параметры в конструктор стратегии:
// Правильно: конструктор без параметров
public class SmaStrategy : Strategy
{
public SmaStrategy()
{
// Инициализация параметров
}
}
// Неправильно: конструктор с параметрами
public class SmaStrategy : Strategy
{
public SmaStrategy(int longLength, int shortLength) // Не используйте такой подход
{
// ...
}
}
Платформы StockSharp создают экземпляры стратегий, используя конструктор без параметров. Если ваша стратегия требует конструктор с параметрами, она не сможет быть корректно инициализирована.
Использование StrategyParam вместо обычных свойств
Преимущества StrategyParam
Вместо создания обычных свойств C# с последующим переопределением методов Save
и Load
, используйте StrategyParam<T> для всех настраиваемых параметров:
// Правильно: использование StrategyParam
private readonly StrategyParam<int> _longSmaLength;
public int LongSmaLength
{
get => _longSmaLength.Value;
set => _longSmaLength.Value = value;
}
public SmaStrategy()
{
_longSmaLength = Param(nameof(LongSmaLength), 80)
.SetDisplay("Long SMA length", string.Empty, "Base settings");
}
// Неправильно: использование обычных свойств
private int _longSmaLength = 80; // Не используйте такой подход
public int LongSmaLength
{
get => _longSmaLength;
set => _longSmaLength = value;
}
Параметры, созданные через StrategyParam<T>, будут автоматически:
- Отображаться в пользовательском интерфейсе платформ
- Сохраняться и загружаться без переопределения методов
Save
иLoad
- Использоваться в оптимизации
- Корректно сериализоваться при отправке в облачное тестирование
Работа с графическим интерфейсом
Используйте абстракции вместо прямого обращения к UI
Вместо прямого обращения к элементам пользовательского интерфейса, используйте абстракции, предоставляемые StockSharp:
// Правильный подход: использование IChart
protected override void OnStarted(DateTimeOffset time)
{
base.OnStarted(time);
// Получаем график, предоставленный средой выполнения
_chart = GetChart();
if (_chart != null)
{
// График доступен (например, в Designer или Shell)
InitChart();
}
else
{
// График недоступен (например, в Runner или облачном тестировании)
// Стратегия продолжает работу без визуализации
}
}
private void InitChart()
{
// Настройка графика через абстрактный интерфейс
_chart.ClearAreas();
var area = _chart.AddArea();
_chartCandleElement = area.AddCandles();
// ...
}
Метод Strategy.GetChart() возвращает интерфейс IChart, если график доступен в текущей среде выполнения. Если стратегия запущена в консольном Runner или облачном тестировании, где нет графического интерфейса, метод вернет null
.
Интерфейс IChart предоставляет методы для работы с графиком:
- AddArea - для добавления области на график
- RemoveArea - для удаления области
- AddElement - для добавления элемента на график
- RemoveElement - для удаления элемента
- Reset - для сброса значений элементов
Проверяйте доступность графика
Всегда проверяйте доступность графика перед его использованием:
private void DrawCandlesAndIndicators(ICandleMessage candle, IIndicatorValue longSma, IIndicatorValue shortSma)
{
if (_chart == null) return; // Важная проверка
var data = _chart.CreateData();
data.Group(candle.OpenTime)
.Add(_chartCandleElement, candle)
.Add(_longSmaIndicatorElement, longSma)
.Add(_shortSmaIndicatorElement, shortSma);
_chart.Draw(data);
}
Потоки и синхронизация
Избегайте создания дополнительных потоков
В StockSharp не требуется создавать дополнительные потоки для обработки данных. Все события (маркет-данные, транзакции) приходят в одном потоке:
// Правильно: использование стандартных обработчиков событий
private void ProcessCandle(ICandleMessage candle)
{
// Обработка свечи в основном потоке
var longSmaIsFormedPrev = _longSma.IsFormed;
var ls = _longSma.Process(candle);
var ss = _shortSma.Process(candle);
// ...
}
// Неправильно: создание дополнительных потоков
private void ProcessCandle(ICandleMessage candle)
{
// НЕ делайте так
Task.Run(() => {
var longSmaIsFormedPrev = _longSma.IsFormed;
// ...
});
}
Избегайте объектов синхронизации
Поскольку все события обрабатываются в одном потоке, нет необходимости в использовании объектов синхронизации:
// Правильно: обычная обработка без синхронизации
private void ProcessCandle(ICandleMessage candle)
{
var ls = _longSma.Process(candle);
var ss = _shortSma.Process(candle);
// ...
}
// Неправильно: излишняя синхронизация
private readonly object _syncLock = new object(); // Не нужно
private void ProcessCandle(ICandleMessage candle)
{
lock (_syncLock) // Не нужно
{
var ls = _longSma.Process(candle);
// ...
}
}
Внешние ресурсы
Используйте инфраструктуру StockSharp
Вместо прямого обращения к внешним ресурсам (файлы, базы данных, сеть), используйте возможности, предоставляемые платформами StockSharp:
// Правильно: использование встроенных механизмов для сохранения данных
protected override void OnStopped()
{
// Данные сохраняются автоматически через параметры стратегии
base.OnStopped();
}
// Неправильно: прямое обращение к внешним ресурсам
protected override void OnStopped()
{
// НЕ делайте так
File.WriteAllText("results.txt", $"PnL: {PnL}");
// или так
using (var connection = new SqlConnection("..."))
{
// ...
}
base.OnStopped();
}
Хранение данных
Для сохранения результатов работы стратегии используйте:
- Параметры стратегии для настроек и конфигурации
- Встроенные механизмы хранения в Designer и Shell
- Статистику для сбора метрик торговли
Save и Load методы
Методы Strategy.Save и Strategy.Load специально предназначены для сохранения дополнительных данных стратегии, которые не являются настройками или параметрами. Это идеальное место для сохранения данных, необходимых для восстановления состояния стратегии:
public override void Save(SettingsStorage settings)
{
base.Save(settings); // Сначала сохраняем параметры стратегии
// Затем сохраняем собственные данные
settings.SetValue("CustomState", _customState);
settings.SetValue("LastSignalTime", _lastSignalTime);
}
public override void Load(SettingsStorage settings)
{
base.Load(settings); // Сначала загружаем параметры стратегии
// Затем загружаем собственные данные
if (settings.Contains("CustomState"))
_customState = settings.GetValue<string>("CustomState");
if (settings.Contains("LastSignalTime"))
_lastSignalTime = settings.GetValue<DateTimeOffset>("LastSignalTime");
}
При этом основные настраиваемые параметры всё равно следует реализовывать через StrategyParam<T>, как описано выше, поскольку это обеспечит их автоматическое отображение в пользовательском интерфейсе.
Подписка на маркет-данные
Используйте правила вместо прямой подписки
Для обработки маркет-данных рекомендуется использовать Событийную модель и правила:
protected override void OnStarted(DateTimeOffset time)
{
base.OnStarted(time);
_shortSma = new SimpleMovingAverage { Length = ShortSmaLength };
_longSma = new SimpleMovingAverage { Length = LongSmaLength };
Indicators.Add(_shortSma);
Indicators.Add(_longSma);
var subscription = new Subscription(Series, Security);
// Правильно: использование правил для обработки данных
Connector
.WhenCandlesFinished(subscription)
.Do(ProcessCandle)
.Apply(this);
Connector.Subscribe(subscription);
}
Правила имеют несколько важных преимуществ перед обычными обработчиками событий:
Автоматическая отписка - правила сами отписываются от событий, когда стратегия останавливается или когда они больше не нужны. Вам не нужно вручную управлять подписками.
Высокоуровневый API - правила предоставляют более понятный и удобный интерфейс, чем стандартные обработчики событий. Например,
WhenCandlesFinished
гораздо нагляднее, чем подписка на событиеCandleReceived
с последующей проверкой состояния свечи.Комбинирование условий - правила можно объединять с помощью операторов
And
,Or
и других, создавая сложные условия активации:
// Пример комбинирования правил
Security
.WhenNewTrade()
.And(Portfolio.WhenMoneyChanged())
.Do(() => {
// Код, который выполнится только когда будет новая сделка
// И изменится баланс портфеля
})
.Apply(this);
- Управление жизненным циклом - правила можно делать одноразовыми (
Once()
), устанавливать условия отмены (Until()
), добавлять отложенные действия и т.д.
## Примеры совместимой стратегии
Ниже приведен пример стратегии, которая следует всем рекомендациям и будет корректно работать во всех платформах StockSharp:
```cs
public class SmaStrategy : Strategy
{
private readonly StrategyParam<DataType> _series;
private readonly StrategyParam<int> _longSmaLength;
private readonly StrategyParam<int> _shortSmaLength;
public DataType Series
{
get => _series.Value;
set => _series.Value = value;
}
public int LongSmaLength
{
get => _longSmaLength.Value;
set => _longSmaLength.Value = value;
}
public int ShortSmaLength
{
get => _shortSmaLength.Value;
set => _shortSmaLength.Value = value;
}
private SimpleMovingAverage _longSma;
private SimpleMovingAverage _shortSma;
private IChart _chart;
private IChartCandleElement _chartCandleElement;
private IChartIndicatorElement _longSmaIndicatorElement;
private IChartIndicatorElement _shortSmaIndicatorElement;
public SmaStrategy()
{
_longSmaLength = Param(nameof(LongSmaLength), 80)
.SetDisplay("Long SMA length", string.Empty, "Base settings")
.SetCanOptimize(true);
_shortSmaLength = Param(nameof(ShortSmaLength), 30)
.SetDisplay("Short SMA length", string.Empty, "Base settings")
.SetCanOptimize(true);
_series = Param(nameof(Series), DataType.TimeFrame(TimeSpan.FromMinutes(15)))
.SetDisplay("Series", string.Empty, "Base settings");
}
protected override void OnStarted(DateTimeOffset time)
{
base.OnStarted(time);
_longSma = new SimpleMovingAverage { Length = LongSmaLength };
_shortSma = new SimpleMovingAverage { Length = ShortSmaLength };
Indicators.Add(_shortSma);
Indicators.Add(_longSma);
// Инициализация графика, если он доступен
_chart = GetChart();
if (_chart != null)
InitChart();
var subscription = new Subscription(Series, Security);
Connector
.WhenCandlesFinished(subscription)
.Do(ProcessCandle)
.Apply(this);
Connector.Subscribe(subscription);
}
private void InitChart()
{
_chart.ClearAreas();
var area = _chart.AddArea();
_chartCandleElement = area.AddCandles();
_longSmaIndicatorElement = area.AddIndicator(_longSma);
_longSmaIndicatorElement.Color = System.Drawing.Color.Brown;
_longSmaIndicatorElement.DrawStyle = DrawStyles.Line;
_shortSmaIndicatorElement = area.AddIndicator(_shortSma);
_shortSmaIndicatorElement.Color = System.Drawing.Color.Blue;
_shortSmaIndicatorElement.DrawStyle = DrawStyles.Line;
}
private void ProcessCandle(ICandleMessage candle)
{
var ls = _longSma.Process(candle);
var ss = _shortSma.Process(candle);
// Отрисовка на графике, если он доступен
if (_chart != null)
{
var data = _chart.CreateData();
data.Group(candle.OpenTime)
.Add(_chartCandleElement, candle)
.Add(_longSmaIndicatorElement, ls)
.Add(_shortSmaIndicatorElement, ss);
_chart.Draw(data);
}
if (!_longSma.IsFormed)
return;
var isShortLessCurrent = _shortSma.GetCurrentValue() < _longSma.GetCurrentValue();
var isShortLessPrev = _shortSma.GetValue(1) < _longSma.GetValue(1);
if (isShortLessCurrent == isShortLessPrev)
return;
// Торговая логика
var volume = Volume + Math.Abs(Position);
if (isShortLessCurrent)
SellMarket(volume);
else
BuyMarket(volume);
}
}