Открыть на GitHub

Control Panel Strategy

Обзор

Control Panel Strategy переносит ручную панель управления из исходного скрипта MQL5 в высокоуровневый API StockSharp. Класс предоставляет методы, которые повторяют каждую кнопку панели: переключатели объёмов, рыночные заявки на покупку и продажу, закрытие позиции, разворот и отдельную процедуру переноса стопа в безубыток. Стратегия также умеет автоматически выставлять защитные стоп-лоссы и тейк-профиты вокруг средней цены входа, как и оригинальный эксперт.

Вместо рисования элементов на графике реализация в StockSharp предлагает типизированный интерфейс. Его можно вызывать из пользовательского интерфейса, скриптов или внешних сервисов. Стратегия хранит выбранные пресеты объёмов, нормализует их по шагу инструмента и отправляет рыночные/стоп/лимитные заявки через встроенные методы BuyMarket, SellMarket, SellStop, BuyLimit и т. д.

Параметры

  • VolumeList – список объёмов через точку с запятой. Используются только первые девять значений, чтобы сохранить совместимость с разметкой MQL. Пробелы игнорируются, некорректные числа пропускаются.
  • CurrentVolume – суммарный объём, собранный из отмеченных пресетов. Сеттер округляет значение с использованием Security.VolumeStep (если задан) либо до двух знаков после запятой. Параметр можно задавать вручную при интеграции с внешним UI.
  • BreakEvenSteps – количество шагов цены, добавляемое к цене входа при вызове ApplyBreakEven(). Если у инструмента отсутствует PriceStep, параметр трактуется как прямой ценовой сдвиг.
  • StopLossSteps – первоначальный отступ стоп-лосса в шагах цены. Нулевое значение отключает автоматическую установку стопов при открытии/изменении позиции.
  • TakeProfitSteps – первоначальный отступ тейк-профита. Работает аналогично стоп-лоссу.

Ручные действия

Публичные методы позволяют привязать стратегию к кнопкам, хоткеям или внешним скриптам:

  • ToggleVolumeSelection(int index) – имитирует переключатели объёмов, добавляя или убирая выбранный пресет. При неверном индексе выбрасывается исключение.
  • ResetVolumeSelection() – сбрасывает все пресеты и устанавливает CurrentVolume в ноль.
  • ExecuteBuy() / ExecuteSell() – отправляют рыночные заявки с текущим объёмом. Возвращают false, если объём равен нулю.
  • CloseAllPositions() – закрывает текущую позицию рыночной заявкой противоположного направления.
  • ReversePosition() – закрывает текущую позицию и сразу открывает новую в противоположную сторону с тем же выбранным объёмом – полный аналог кнопки «Reverse».
  • ApplyBreakEven() – пересчитывает защитный стоп как средняя цена ± BreakEvenSteps * PriceStep и выставляет новую стоп-заявку (SellStop для длинной позиции, BuyStop для короткой). Метод возвращает true только при наличии открытой позиции и положительном смещении.

При каждом изменении позиции метод OnPositionChanged пересобирает защитные заявки: сначала отменяет старые стоп/тейк, затем создаёт новые на основе актуальной средней цены и заданных отступов. После закрытия позиции (вручную или по защитным ордерам) активные заявки снимаются, чтобы избежать «зависших» заявок на бирже.

Сценарий использования

  1. Настройте нужные пресеты объёмов в VolumeList, например 0.05; 0.10; 0.25; 0.50; 1.00.
  2. Отметьте один или несколько пресетов с помощью ToggleVolumeSelection. Параметр CurrentVolume покажет суммарный объём с учётом округления.
  3. Вызовите ExecuteBuy или ExecuteSell, чтобы открыть позицию. Если StopLossSteps или TakeProfitSteps больше нуля, стратегия автоматически выставит SellStop/BuyStop и SellLimit/BuyLimit относительно средней цены входа.
  4. Когда цена движется в прибыль, используйте ApplyBreakEven, чтобы перенести стоп в безубыток с заданным запасом.
  5. CloseAllPositions полностью закрывает позицию, а ReversePosition закрывает и сразу переворачивает её, сохраняя выбранный объём.
  6. ResetVolumeSelection очищает панель перед следующим циклом торговли.

Примечания

  • Логика безубытка и защитных ордеров опирается на PositionAvgPrice и Security.PriceStep. Перед запуском убедитесь, что метаданные инструмента заполнены.
  • В OnStarted вызывается StartProtection(), чтобы движок StockSharp отслеживал связанные стопы и тейки.
  • Методы являются синхронными обёртками над ордерными помощниками StockSharp. Если адаптер требует строгой последовательности подтверждений, дождитесь событий ордеров перед следующей командой.
  • Класс удобно встраивать в собственные панели WPF/WinForms, REST-сервисы или консольные утилиты, сопоставляя события интерфейса с описанными методами.
using System;
using System.Collections.Generic;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class ControlPanelStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _momPeriod;
	private decimal? _prevMom;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int MomPeriod { get => _momPeriod.Value; set => _momPeriod.Value = value; }

	public ControlPanelStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
		_momPeriod = Param(nameof(MomPeriod), 10).SetGreaterThanZero().SetDisplay("Momentum Period", "Momentum lookback", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevMom = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevMom = null;
		var mom = new Momentum { Length = MomPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(mom, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal momVal)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!IsFormedAndOnlineAndAllowTrading()) { _prevMom = momVal; return; }
		if (_prevMom == null) { _prevMom = momVal; return; }
		if (_prevMom.Value < 100m && momVal >= 100m && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
		else if (_prevMom.Value > 100m && momVal <= 100m && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
		_prevMom = momVal;
	}
}