Открыть на GitHub

Стратегия BladeRunner

Общее описание

BladeRunner — перевод советника MetaTrader, который объединяет пробой фракталов с фильтрами тренда и импульса. В версии для StockSharp сохранена многофреймовая структура оригинала: базовая серия свечей управляет сделками, серия старшего таймфрейма проверяет импульс, а самая медленная серия формирует фильтр MACD. Сделки открываются рыночными приказами с масштабированием позиции и автоматической установкой стоп-лосса и тейк-профита в шагах цены.

Логика торговли

  1. Фрактальный фильтр. Алгоритм отслеживает завершённые свечи на наличие фракталов Билла Вильямса. Бычий фрактал принимается, если свеча два бара назад обновила максимум, а подтверждающая свеча открылась ниже уровня фрактала и ниже 20-периодной LWMA типичной цены. Для медвежьих сигналов условия зеркальные.
  2. Трендовый фильтр. Быстрая и медленная LWMA на основной серии свечей определяют направление тенденции. Для покупок быстая LWMA должна находиться выше медленной, для продаж — ниже.
  3. Импульсный фильтр. Осциллятор Momentum на старшем таймфрейме должен отклониться от уровня 100 минимум на заданный порог в одном из трёх последних значений. Так воспроизводится проверка Momentum из MQL-версии.
  4. MACD-фильтр. MACD на медленной серии свечей требует, чтобы основная линия была выше (для лонга) или ниже (для шорта) сигнальной линии, что соответствует месячному фильтру оригинала.
  5. Пробой уровня. Закрытие последней основной свечи должно преодолеть запомненный уровень фрактала до открытия позиции.

Когда все фильтры совпадают, стратегия открывает позицию заданным объёмом. Текущая противоположная позиция закрывается перед разворотом. Дополнительные входы разрешены до достижения максимального числа масштабирований.

Реализация

  • Используются три подписки на свечи через high-level API. Каждая подписка сразу связывается с нужными индикаторами без добавления в коллекцию стратегии.
  • Все LWMA рассчитываются по типичной цене (HLC/3), чтобы воспроизвести логику MQL. MACD также использует типичную цену.
  • Для обнаружения фракталов хранится скользящее окно завершённых свечей и соответствующих значений фильтра. Запоминается только последнее валидированное направление фрактала, что исключает повторные сигналы по той же формации.
  • История Momentum хранится в фиксированном массиве из трёх значений, без динамических выделений памяти.
  • Перед размещением ордера объём корректируется под шаг, минимум и максимум инструмента.
  • Вызов StartProtection автоматически навешивает стоп-лосс и тейк-профит в шагах цены, что соответствует фиксированным пунктам из MetaTrader.

Параметры

Параметр Описание Значение по умолчанию
CandleType Основная серия свечей для генерации сигналов. Свечи 15 минут
MomentumCandleType Старший таймфрейм для фильтра Momentum. Свечи 1 час
MacdCandleType Серия свечей для MACD-фильтра. Дневные свечи
FastMaPeriod Период быстрой LWMA. 6
SlowMaPeriod Период медленной LWMA. 85
FilterMaPeriod LWMA, используемая при проверке фрактала. 20
MomentumPeriod Период расчёта индикатора Momentum. 14
MomentumThreshold Минимальное отклонение Momentum от 100. 0.3
FractalLookback Размер окна для поиска фракталов. 200
MaxTrades Максимум доливок в одном направлении. 3
OrderVolume Базовый объём рыночного ордера. 1 контракт
TakeProfitSteps Тейк-профит в шагах цены. 50
StopLossSteps Стоп-лосс в шагах цены. 20

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

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

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

  • Функции остановки по балансу, трейлинг-стоп и перенос в безубыток из MQL не перенесены. Их можно реализовать отдельными компонентами StockSharp.
  • Денежный трейлинг и пуш-уведомления исключены, поскольку платформа поддерживает собственные уведомления.
  • По умолчанию MACD рассчитывается на дневных свечах. При необходимости можно переключить MacdCandleType на месячный таймфрейм, если он доступен у поставщика данных.
  • Валидация фракталов опирается на последнее подтверждающее значение в окне, что повторяет эффект оригинального цикла без трудоёмкого пересчёта.

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

  1. Настройте таймфреймы свечей в соответствии с доступными потоками данных.
  2. Подберите OrderVolume, TakeProfitSteps и StopLossSteps с учётом шага цены и минимального объёма инструмента.
  3. Оптимизируйте порог Momentum и периоды LWMA на исторических данных перед боевым запуском.
  4. При необходимости включите отображение графиков, чтобы визуально контролировать расположение LWMA и подтверждение фракталов.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

public class BladeRunnerStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public BladeRunnerStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}