Ver no GitHub

Estratégia de cesta de backbone (StockSharp)

Visão geral

A Backbone Basket Strategy transporta o consultor especialista original MetaTrader 4 "Backbone.mq4" para o StockSharp API de alto nível. O sistema coleta extremos de compra/venda para determinar a direção inicial da negociação e depois alterna entre cestas longas e curtas. Cada cesta é construída gradualmente, adicionando uma ordem de mercado por vela concluída até que a contagem configurada de MaxTrades seja atingida ou as ordens de proteção fechem a posição. O controle de risco é mantido por meio de um modelo de risco fracionário que dimensiona o volume de negociação pelo valor da conta e pela distância do stop-loss.

Fluxo de dados de mercado

  • Velas (CandleType) – velas concluídas acompanham a tomada de decisões; apenas uma ordem pode ser emitida por barra finalizada, exatamente como no script MT4.
  • Instantâneos da carteira de pedidos – os melhores valores de lance e venda são rastreados para reproduzir cálculos de trailing-stop e a lógica inicial de descoberta "extrema".
  • Estado da estratégia – a base StockSharp Strategy mantém a posição atual, o preço médio de entrada e o PnL usado para gerenciar ordens de proteção.

Lógica de negociação

  1. Calibração inicial – embora nenhuma direção seja definida, a estratégia registra o lance mais alto e o pedido mais baixo vistos. Quando o preço retrocede TrailingStopPoints * PriceStep a partir desses extremos, a primeira direção da cesta é escolhida.
  2. Sequenciamento de pedidos
    • Se a última negociação concluída foi curta (_lastPositionDirection == -1) e não há negociações abertas, uma nova ordem de mercado de compra será enviada.
    • Se a negociação anterior foi longa (_lastPositionDirection == 1) e a cesta ainda tiver capacidade, serão enviadas ordens de compra adicionais nas velas subsequentes.
    • Regras simétricas se aplicam a ordens de venda quando a última negociação foi longa.
  3. Dimensionamento de volume – cada novo pedido chama o análogo Vol() inspirado no MT4. O valor da conta disponível (valor atual → saldo → saldo inicial) é multiplicado por MaxRisk e dividido pela distância stop-loss convertida em dinheiro usando PriceStepCost. O resultado é alinhado com VolumeStep, limitado por MinVolume/MaxVolume e rejeitado se cair abaixo do tamanho mínimo de negociação.
  4. Ordens de proteção – assim que uma negociação é executada, a estratégia coloca uma única ordem de stop-loss e take-profit que cobre toda a cesta. As distâncias são expressas em "pontos" (etapas de preço), assim como na versão MQL.
  5. Trailing stop – quando StopLossPoints e TrailingStopPoints são positivos, a ordem de stop é reemitida para garantir lucros sempre que o preço se move mais do que a distância final além do preço de entrada registrado. As cestas longas utilizam como referência o melhor lance; cestas curtas usam o melhor pedido.
  6. Conclusão da cesta – se a ordem de stop-loss ou de take-profit for executada, todos os contadores internos serão reiniciados, deixando LastPosition inalterado, de modo que a próxima vela inicia uma cesta na direção oposta, refletindo o comportamento original de EA.

Gestão de capital

  • Usa a mesma fórmula fracionária 1 / (MaxTrades / MaxRisk - openTrades) do especialista MQL.
  • O capital de risco é estimado a partir de Portfolio.CurrentValue, recorrendo a CurrentBalance ou BeginBalance.
  • O volume é descartado se o tamanho calculado estiver abaixo do MinVolume do instrumento após o alinhamento com VolumeStep.
  • As ordens stop-loss e take-profit são recriadas sempre que o volume muda, para que a proteção sempre cubra toda a cesta.

Parâmetros

Nome Padrão Descrição
CandleType Período de 15 minutos Intervalo de vela usado para desencadear novas decisões.
MaxRisk 0,5 Fração da carteira considerada no dimensionamento do próximo pedido. Deve ser positivo.
MaxTrades 10 Número máximo de negociações que podem ser acumuladas na cesta atual.
TakeProfitPoints 170 Distância de lucro medida em etapas de preço. Defina como 0 para desativar.
StopLossPoints 40 Distância de stop-loss medida em etapas de preço. Necessário para dimensionamento de posição e rastreamento.
TrailingStopPoints 300 Distância do trailing-stop em etapas de preço. Defina como 0 para manter uma parada estática.

Notas de conversão

  • O EA original modifica cada pedido individualmente; a versão StockSharp gerencia um stop-loss e um take-profit agregados por cesta porque as posições StockSharp são compensadas por padrão.
  • O dimensionamento do volume depende de Security.PriceStepCost. Se o conector não fornecer esse valor, a estratégia retornará à propriedade Volume configurada.
  • As atualizações finais são aplicadas quando uma nova vela chega, correspondendo ao comportamento "uma vez por barra" do script MT4 (que só agiu quando Bars > PrevBars).
  • A lógica alternada mantém a última direção executada em _lastPositionDirection, portanto, assim que uma cesta é fechada, a próxima vela abre automaticamente uma cesta na direção oposta, assim como o código-fonte.
  • Somente a implementação C# é fornecida; não há porta Python neste diretório.

Dicas de uso

  • Atribua instrumentos com PriceStep, PriceStepCost precisos e metadados de volume para obter tamanhos de posição realistas.
  • Ao fazer backtesting, certifique-se de que o feed do livro de pedidos esteja disponível para que a lógica do trailing stop possa acessar os melhores valores de compra/venda.
  • Para desativar o escalonamento agressivo, aumente MaxTrades ou reduza MaxRisk para que a substituição de Vol() retorne volumes menores.
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;
			}
		}
	}
}