Открыть на GitHub

Стратегия TRAYLERv

Обзор

TRAYLERv — это перенос эксперта MetaTrader 4 TRAYLERv, который изначально выступал помощником по сопровождению сделок. Он не генерировал сигналы, а автоматически подтягивал защитные ордера и позволял быстро удалить все отложенные заявки. В версии для StockSharp реализовано то же поведение с использованием высокоуровневого API: стратегия подписывается на свечи, отслеживает фракталы Билла Уильямса и управляет ордерами через стандартные методы Buy/Sell.

Стратегия не открывает позиции самостоятельно. Предполагается, что сделки создаются вручную либо другим алгоритмом. После появления позиции TRAYLERv берёт на себя поддержку стопов и тейк-профитов по оригинальной логике, сохраняя названия параметров и их смысл.

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

  1. Подписка на выбранный тип свечей (по умолчанию минутные) и накопление истории. После появления пяти завершённых баров стратегия начинает фиксировать локальные максимумы и минимумы, точно повторяя расчёт MT4-индикатора iFractals.
  2. После закрытия каждой свечи с чётной минутой проверяется текущая позиция:
    • Длинная позиция: ищется ближайший нисходящий фрактал в пределах StopFractalDepth баров (по умолчанию 7). Если найден, регистрируется или переносится стоп-заявка SellStop ниже минимума фрактала с учётом текущего спрэда и двух шагов цены. Если фрактала нет, берётся минимум бара трёх свечей назад и уменьшается на два шага. При наличии прибыли и включённом параметре UseTakeProfit ищется восходящий фрактал глубиной до TakeProfitFractalDepth (21 бар) и ставится SellLimit немного ниже уровня фрактала.
    • Короткая позиция: выполняются зеркальные действия — стоп перемещается по верхним фракталам, тейк-профит рассчитывается по нижним фракталам, а к цене добавляется запас над максимумом, чтобы исключить ложные срабатывания.
  3. Включение DeleteAllPendingOrders приводит к отмене всех активных отложенных заявок на подключении. Параметр DeleteOwnPendingOrders ограничивает очистку только инструментом стратегии. Оба переключателя полностью соответствуют опциям MT4-советника.
  4. При отсутствии позиции все защитные ордера, созданные стратегией, отменяются, чтобы не оставлять «хвостов» в стакане заявок.

Управление рисками

  • Защитные ордера выставляются функциями SellStop, BuyStop, SellLimit, BuyLimit с объёмом, равным абсолютной величине текущей позиции.
  • Стопы и тейк-профиты независимы. Если выключить UseTakeProfit, лимитные ордера удаляются, но механизм подтягивания стопов продолжит работу.
  • Для корректировки уровней используется текущий спрэд, вычисляемый по лучшим котировкам. Если он недоступен, применяется минимальный шаг цены, чтобы не ставить ордер прямо по рыночной стоимости.
  • Перед регистрацией цены округляются к шагу цены инструмента, а объёмы приводятся к VolumeStep и учитывают ограничения MinVolume/MaxVolume.

Параметры

Параметр Описание Значение по умолчанию
OrderVolume Рекомендованный объём позиции. Сохранён для совместимости, в расчётах не участвует. 0.1
DeleteAllPendingOrders При значении true удаляет все отложенные заявки после каждой свечи. false
DeleteOwnPendingOrders При значении true удаляет только заявки по текущему инструменту. false
UseTakeProfit Включает управление тейк-профитом на основе фракталов. true
EnableSound Наследованный флаг звуковых уведомлений MT4, в версии StockSharp не используется. true
ShowCommentary Флаг отображения комментариев в MT4, оставлен для полноты настроек. true
StopFractalDepth Глубина поиска фрактала для подтягивания стоп-лосса. 7
TakeProfitFractalDepth Глубина поиска фрактала для тейк-профита. 21
CandleType Тип свечей, используемый в расчётах. По умолчанию — минутные. 1 минута

Особенности реализации

  • Используется высокоуровневая подписка SubscribeCandles().Bind(...), поэтому расчёты ведутся только по завершённым барам — так же, как это делал оригинальный советник на тиках.
  • Фракталы рассчитываются вручную на основе списка последних свечей, что исключает необходимость подключать дополнительные индикаторы и повторяет поведение iFractals из MetaTrader.
  • Все уровни проходят нормализацию к шагу цены, а объёмы — к шагу объёма. Это гарантирует, что сформированные ордера валидны для любой торговой площадки.
  • Python-реализация не предусмотрена, поэтому папка PY отсутствует согласно требованиям по конвертации.
using System;

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

namespace StockSharp.Samples.Strategies;

public class TraylerStrategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _cooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevClose;
	private decimal _prevEma;
	private bool _hasPrev;
	private int _cooldownRemaining;

	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public TraylerStrategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 50).SetDisplay("EMA Period", "EMA lookback", "Indicators");
		_cooldownCandles = Param(nameof(CooldownCandles), 200).SetDisplay("Cooldown", "Candles between signals", "General");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevClose = default;
		_prevEma = default;
		_hasPrev = default;
		_cooldownRemaining = default;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevClose = 0;
		_prevEma = 0;
		_hasPrev = false;
		_cooldownRemaining = 0;

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal ema)
	{
		if (candle.State != CandleStates.Finished) return;
		var close = candle.ClosePrice;
		if (!_hasPrev) { _prevClose = close; _prevEma = ema; _hasPrev = true; return; }

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevClose = close;
			_prevEma = ema;
			return;
		}

		if (_prevClose <= _prevEma && close > ema && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownCandles;
		}
		else if (_prevClose >= _prevEma && close < ema && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownCandles;
		}
		_prevClose = close;
		_prevEma = ema;
	}
}