Открыть на GitHub

Стратегия Backbone Basket (StockSharp)

Обзор

Backbone — перенос эксперта MetaTrader 4 "Backbone.mq4" на высокоуровневый API StockSharp. Стратегия сначала накапливает экстремальные значения бид/аск, чтобы определить исходное направление, затем последовательно формирует корзину ордеров в выбранную сторону. На каждой завершённой свече допускается не более одной новой заявки. Корзина закрывается достижением лимита MaxTrades или срабатыванием защитных ордеров. Расчёт объёма основан на доле риска от капитала и расстоянии до стоп-лосса, как в оригинальном роботе.

Поток данных

  • Свечи (CandleType) — решения принимаются только после закрытия свечи, полностью повторяя условие MT4 Bars > PrevBars.
  • Стакан котировок — лучшие цены покупателя/продавца используются для поиска начальных экстремумов и обновления трейлинг-стопа.
  • Состояние стратегии — базовый класс Strategy предоставляет текущую позицию, среднюю цену входа и PnL, что позволяет управлять защитными ордерами без дополнительных структур.

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

  1. Инициализация — пока направление не определено, фиксируется максимальный бид и минимальный аск. При откате от экстремума на TrailingStopPoints * PriceStep выбирается первоначальная сторона.
  2. Очередность ордеров:
    • Если последний совершённый трейд был коротким (_lastPositionDirection == -1) и позиция отсутствует, выставляется рыночная покупка.
    • Если предыдущий трейд был длинным (_lastPositionDirection == 1) и число сделок меньше MaxTrades, на следующих свечах добавляются новые покупки.
    • Для продаж действует симметричное правило.
  3. Размер позиции — каждая заявка использует формулу Vol() из MT4: берётся доступный капитал (CurrentValue → CurrentBalance → BeginBalance), умножается на MaxRisk и делится на стоимость стоп-лосса в деньгах (PriceStepCost * количество шагов). Объём приводится к VolumeStep, ограничивается MinVolume/MaxVolume и отклоняется, если после округления меньше минимального шага.
  4. Защитные ордера — после исполнения заявки размещаются единые стоп-лосс и тейк-профит на всю корзину. Дистанции задаются в пунктах (шаги цены), как и в исходном коде.
  5. Трейлинг-стоп — при положительных StopLossPoints и TrailingStopPoints стоп переносится всякий раз, когда цена уходит дальше заданного смещения от средней цены входа. Для длинных позиций опорной величиной служит лучший бид, для коротких — лучший аск.
  6. Завершение корзины — при срабатывании стопа или тейк-профита счётчики сбрасываются, однако _lastPositionDirection сохраняет направление последней сделки, поэтому следующая свеча откроет корзину в противоположную сторону, как в MQL-версии.

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

  • Используется оригинальная формула 1 / (MaxTrades / MaxRisk - openTrades).
  • Капитал для риска берётся из Portfolio.CurrentValue, затем CurrentBalance, затем BeginBalance.
  • Если после учёта VolumeStep объём меньше MinVolume, сделка не отправляется.
  • При изменении позиции стоп/тейк-профит регистрируются заново, чтобы всегда покрывать полный объём.

Параметры

Параметр Значение по умолчанию Описание
CandleType 15 минут Таймфрейм, по закрытию которого принимаются решения.
MaxRisk 0.5 Доля капитала, используемая для расчёта нового объёма. Значение должно быть > 0.
MaxTrades 10 Максимальное число сделок в одной корзине.
TakeProfitPoints 170 Дистанция до тейк-профита в шагах цены. 0 отключает тейк-профит.
StopLossPoints 40 Дистанция до стоп-лосса в шагах цены. Обязательна для расчёта объёма и трейлинга.
TrailingStopPoints 300 Дистанция трейлинг-стопа в шагах цены. При 0 стоп остаётся статичным.

Особенности конверсии

  • В MT4 стопы/тейки привязывались к каждой заявке отдельно. В StockSharp используется один агрегированный защитный ордер на весь нетто-объём, что соответствует модели позиций платформы.
  • Размер позиции зависит от Security.PriceStepCost. Если поставщик данных не предоставляет стоимость шага, стратегия использует значение Volume как запасной вариант.
  • Трейлинг обновляется лишь на закрытии свечей, повторяя одноразовую обработку бара в исходном коде.
  • _lastPositionDirection сохраняет направление последнего трейда, обеспечивая чередование длинных и коротких корзин после закрытия.
  • В каталоге присутствует только C#-реализация; версия на Python не создавалась.

Рекомендации

  • Выбирайте инструменты с корректно заполненными PriceStep, PriceStepCost, VolumeStep, иначе расчёт объёма может быть некорректным.
  • При тестировании включайте поток стакана, чтобы механизм трейлинг-стопа получал актуальные бид/аск.
  • Для снижения агрессивности набора позиций уменьшите MaxRisk или увеличьте MaxTrades, что уменьшит расчётный объём Vol().
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Backbone strategy that alternates between long and short based on retracements from recent extremes.
/// Uses ATR to detect when price has pulled back enough from its high/low to enter a new position.
/// </summary>
public class BackboneBasketStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _retraceMultiplier;

	private decimal _highestPrice;
	private decimal _lowestPrice;
	private decimal _entryPrice;
	private int _lastDirection; // 1 = long, -1 = short, 0 = none

	public BackboneBasketStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used for analysis.", "General");

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetDisplay("ATR Period", "Period for ATR indicator.", "Indicators");

		_retraceMultiplier = Param(nameof(RetraceMultiplier), 2m)
			.SetDisplay("Retrace Multiplier", "ATR multiplier for retracement threshold.", "Signals");
	}

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

	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	public decimal RetraceMultiplier
	{
		get => _retraceMultiplier.Value;
		set => _retraceMultiplier.Value = value;
	}

	/// <inheritdoc />
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_highestPrice = 0;
		_lowestPrice = decimal.MaxValue;
		_entryPrice = 0;
		_lastDirection = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_highestPrice = 0;
		_lowestPrice = decimal.MaxValue;
		_entryPrice = 0;
		_lastDirection = 0;

		var atr = new AverageTrueRange { Length = AtrPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(atr, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, atr);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal atrValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (atrValue <= 0)
			return;

		var close = candle.ClosePrice;

		// Track extremes
		if (close > _highestPrice)
			_highestPrice = close;
		if (close < _lowestPrice)
			_lowestPrice = close;

		var threshold = atrValue * RetraceMultiplier;

		// Exit existing positions on retracement
		if (Position > 0 && close < _highestPrice - threshold)
		{
			SellMarket();
			_entryPrice = 0;
			_lastDirection = 1;
			_lowestPrice = close;
		}
		else if (Position < 0 && close > _lowestPrice + threshold)
		{
			BuyMarket();
			_entryPrice = 0;
			_lastDirection = -1;
			_highestPrice = close;
		}

		// Entry logic - alternate direction
		if (Position == 0)
		{
			if (_lastDirection != 1 && close < _highestPrice - threshold)
			{
				// Price pulled back from high - sell
				SellMarket();
				_entryPrice = close;
				_lastDirection = -1;
				_lowestPrice = close;
			}
			else if (_lastDirection != -1 && close > _lowestPrice + threshold)
			{
				// Price bounced from low - buy
				BuyMarket();
				_entryPrice = close;
				_lastDirection = 1;
				_highestPrice = close;
			}
		}
	}
}