Открыть на GitHub

Стратегия Manual Trading Lightweight Utility

Обзор

Оригинальный советник MetaTrader "Manual Trading Lightweight Utility" представляет собой компактную панель с кнопками для переключения между рыночными, лимитными и стоповыми заявками, раздельного управления объёмами и автоматического добавления стоп-лосса и тейк-профита. Эта версия на C# переносит весь рабочий процесс в StockSharp: каждая кнопка панели превращена в параметр стратегии. Алгоритм не генерирует собственных сигналов — он ждёт ручной команды и выполняет её через высокоуровневый API, параллельно контролируя защитные выходы.

Воссозданная функциональность

  • Разовые запросы на покупку и продажу. Булевы флаги BuyRequest и SellRequest имитируют кнопки панели. Переключение параметра в true приводит к единовременному выставлению рыночной, лимитной или стоповой заявки в зависимости от выбранного режима и автоматически возвращает флаг в false.
  • Автоматическое или ручное ценообразование отложек. Для каждой стороны можно использовать те же смещения по пунктам (LimitOrderPoints и StopOrderPoints), что и в MetaTrader, либо указать абсолютную цену. При автоматическом расчёте используется лучший бид/аск, а при их отсутствии — цена закрытия последней свечи.
  • Независимые объёмы. Можно работать с общим объёмом (DefaultVolume) или включить UseIndividualVolumes, чтобы задать отдельные значения для покупки и продажи — аналог переключателя Lot Control в исходном скрипте.
  • Защита в пунктах. Параметры TakeProfitPoints и StopLossPoints переводятся в денежные отступы с помощью биржевого PriceStep. Стратегия отслеживает завершённые свечи и закрывает позицию рыночной сделкой при пробое защитного уровня.
  • Журнал с комментариями. Каждое ручное действие фиксируется в логе вместе с текстом OrderComment, что заменяет визуальный отклик панели.

Логика работы

  1. Стратегия подписывается на свечной поток, заданный параметром CandleType. Закрытые свечи дают опорные цены для расчёта отступов и контроля рисков.
  2. На каждой завершённой свече алгоритм:
    • Обновляет свойство Volume значением DefaultVolume — это удобно для визуального контроля в интерфейсе StockSharp.
    • Отслеживает изменение параметров BuyRequest и SellRequest и помечает активные запросы.
    • После проверки IsFormedAndOnlineAndAllowTrading() исполняет ожидающие команды, вычисляет цены отложенных ордеров и пишет результат в журнал.
    • Передаёт управление блоку управления рисками, который запоминает цену входа при изменении позиции и закрывает её рыночной сделкой при срабатывании стопа или тейк-профита.
  3. После возврата позиции в ноль внутреннее состояние обнуляется, чтобы следующая ручная команда начиналась «с чистого листа».

Параметры

  • CandleType — тип свечей, на основе которых вычисляются цены и контролируются риски.
  • BuyOrderMode / SellOrderMode — режимы MarketExecution, PendingLimit или PendingStop для каждой стороны.
  • UseAutomaticBuyPrice / UseAutomaticSellPrice — включают автоматический расчёт цены отложенных ордеров. При отключении используются значения BuyManualPrice / SellManualPrice.
  • BuyManualPrice / SellManualPrice — абсолютные цены отложенных ордеров при отключённом автоматическом режиме (оставьте 0, чтобы игнорировать).
  • DefaultVolume — общий объём сделок, если индивидуальные объёмы выключены.
  • UseIndividualVolumes — включает аналога Lot Control. При true объёмы из BuyVolume и SellVolume подменяют общее значение.
  • BuyVolume / SellVolume — отдельные объёмы для покупки и продажи.
  • TakeProfitPoints / StopLossPoints — расстояния защитных уровней в пунктах MetaTrader. Ноль отключает соответствующую защиту.
  • LimitOrderPoints / StopOrderPoints — смещения для автоматических лимитных и стоповых цен в пунктах.
  • BuyRequest / SellRequest — одноразовые переключатели, имитирующие кнопки панели. После обработки автоматически возвращаются в false.
  • OrderComment — комментарий, который будет добавлен к логам при исполнении команды.

Рекомендации по использованию

  1. Подберите CandleType под желаемую детализацию. По умолчанию используется минутный таймфрейм, что близко к тиковой реакции оригинального советника и при этом совместимо с историческими данными.
  2. Определитесь с объёмами: используйте DefaultVolume для симметричной торговли или включите UseIndividualVolumes и задайте значения BuyVolume / SellVolume. Объёмы должны быть строго положительными.
  3. Выберите способ расчёта цен отложенных ордеров. Автоматический режим повторяет логику MetaTrader, умножая число пунктов на PriceStep. Ручной режим требует указать абсолютные значения.
  4. Задайте TakeProfitPoints и StopLossPoints. При ненулевых значениях стратегия переводит их в денежные расстояния. Если у инструмента не задан PriceStep, в лог выводится предупреждение и защита отключается.
  5. Чтобы отправить заявку, переключите BuyRequest или SellRequest в true. На следующей закрытой свече стратегия выполнит команду, зафиксирует её в журнале и вернёт флаг в false, чтобы не повторять действие автоматически.
  6. Повторите команду при необходимости, снова переключив параметр. Если цена не может быть рассчитана (например, ручное значение равно нулю), запрос будет проигнорирован — скорректируйте настройки и повторите переключение.

Отличия от оригинального скрипта

  • Графические объекты MetaTrader заменены параметрами StockSharp. Каждая кнопка или поле панели теперь доступна из интерфейса платформы или через автоматизацию.
  • Защитные уровни закрываются рыночными сделками при пробое, а не отдельными стоп/лимит ордерами. Это позволяет использовать высокоуровневый API без управления жизненным циклом заявок.
  • При отсутствии котировок лучшего бида/аска автоматические цены рассчитываются по цене закрытия последней свечи, что гарантирует воспроизводимость в тестах без стакана.

Дополнительно

  • Цена входа обновляется при каждом изменении чистой позиции. Если вы увеличиваете позицию, защитные уровни пересчитываются относительно свечи, где произошёл новый вход.
  • В расчёт стоп-лосса включается текущий спред: к пунктовому расстоянию добавляется известная величина спреда (или один шаг цены при его отсутствии), как и в оригинальном советнике.
  • Журнал содержит комментарий, тип ордера, цену (для отложенных заявок) и объём, что формирует понятный отчёт о ручных действиях.
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Manual Trading Lightweight Utility strategy: WMA trend following.
/// Buys when price is above WMA and rising, sells when below and falling.
/// </summary>
public class ManualTradingLightweightUtilityStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _wmaPeriod;

	private decimal _prevWma;
	private bool _hasPrev;
	private bool _wasBullish;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int WmaPeriod { get => _wmaPeriod.Value; set => _wmaPeriod.Value = value; }

	public ManualTradingLightweightUtilityStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_wmaPeriod = Param(nameof(WmaPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("WMA Period", "Weighted MA period", "Indicators");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevWma = 0m;
		_hasPrev = false;
		_wasBullish = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_hasPrev = false;
		var wma = new WeightedMovingAverage { Length = WmaPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(wma, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal wma)
	{
		if (candle.State != CandleStates.Finished) return;
		var close = candle.ClosePrice;
		var isBullish = close > wma && wma > _prevWma;

		if (_hasPrev)
		{
			if (isBullish && !_wasBullish && Position <= 0)
				BuyMarket();
			else if (!isBullish && close < wma && wma < _prevWma && _wasBullish && Position >= 0)
				SellMarket();
		}

		_prevWma = wma;
		_hasPrev = true;
		_wasBullish = isBullish;
	}
}