Ver no GitHub

Estratégia Burg Extrapolator

Visão Geral

A Estratégia Burg Extrapolator replica o assessor especialista MetaTrader "Burg Extrapolator" usando a API de alto nível do StockSharp. O sistema aplica um modelo autorregressivo (AR) resolvido com o método Burg para prever futuros preços de abertura. As decisões de trading são impulsionadas pela amplitude da trajetória da previsão: quando a distância prevista entre futuros máximos e mínimos excede os limites configurados, a estratégia abre ou fecha posições.

Lógica Principal

  1. Preparação de dados
    • Coleta PastBars preços de abertura em cada vela finalizada.
    • Opcionalmente transforma a série em valores de momentum logarítmico ou de taxa de variação.
    • Normaliza os preços subtraindo a média móvel quando preços brutos são usados.
  2. Modelagem autorregressiva
    • Estima coeficientes AR pelo método Burg com uma ordem determinada por ModelOrderFraction.
    • Extrapola vários passos à frente (horizonte de previsão = PastBars - order - 1) e reconstrói as previsões de preço.
  3. Geração de sinais
    • Rastreia os preços máximos e mínimos previstos.
    • Se o swing da previsão excede MinProfitPips, gera um sinal de entrada na direção respectiva.
    • Se o swing da previsão excede MaxLossPips, emite um sinal de saída para posições existentes.
  4. Execução de ordens
    • Posições são abertas com ordens de mercado usando o volume calculado baseado em risco.
    • Quando um stop ou sinal oposto ocorre, a estratégia fecha posições com ordens de mercado.

Parâmetros

Parâmetro Descrição
RiskPercent Percentagem do capital arriscado por operação. Usado para dimensionar ordens quando uma distância de stop-loss está disponível.
MaxPositions Volume cumulativo máximo expresso como múltiplos do tamanho de ordem permitido por direção.
MinProfitPips Swing de lucro previsto mínimo (em pips) necessário para abrir novas posições.
MaxLossPips Drawdown previsto máximo permitido (em pips) que acionará saídas de posição.
TakeProfitPips Distância de take-profit estático (em pips). Definir como zero para desativar.
StopLossPips Distância de stop-loss estático (em pips). Necessário para dimensionamento de risco.
TrailingStopPips Distância de trailing stop (em pips). Funciona apenas quando o stop-loss está habilitado.
PastBars Número de barras históricas usadas como entrada no modelo Burg.
ModelOrderFraction Fração de PastBars que define a ordem AR (truncamento inteiro).
UseMomentum Habilita o pré-processamento de momentum logarítmico (log(p[i]/p[i-1])).
UseRateOfChange Habilita o pré-processamento de taxa de variação (p[i]/p[i-1]-1) quando o momentum está desativado.
OrderVolume Tamanho de ordem de reserva quando o dimensionamento baseado em risco não pode ser calculado.
CandleType Tipo de dados (período) das velas usadas para cálculos.

Regras de Trading

  • Entrada: Quando a trajetória prevista indica um swing maior que MinProfitPips, abrir uma posição comprada se o preço projetado mais alto aparece primeiro, ou abrir uma posição vendida se a projeção mais baixa aparece primeiro.
  • Saída: Fechar posições quando o swing da previsão excede MaxLossPips ou quando o sinal de entrada oposto é detectado.
  • Proteção: Usa StartProtection para configurar stop-loss, take-profit e trailing stop opcionais em unidades de preço absolutas derivadas de pips.
  • Dimensionamento de posição: Se tanto StopLossPips quanto RiskPercent forem positivos, o volume da operação é calculado como risk_amount / (stop_distance). Caso contrário, OrderVolume é usado.

Notas de Implementação

  • Trabalha exclusivamente com velas finalizadas para evitar viés de antecipação.
  • Evita chamadas GetValue de indicadores processando valores diretamente dentro do callback Bind.
  • Respeita as convenções da API de alto nível do StockSharp, usando SubscribeCandles e StartProtection para gestão de risco.
  • A lógica de trailing reflete o EA original ao habilitar trailing stops gerenciados pela plataforma.

Dicas de Uso

  • Escolha PastBars e ModelOrderFraction cuidadosamente; ordens altas podem levar a sobreajuste ou previsões instáveis.
  • O horizonte de previsão é igual a PastBars - order - 1; certifique-se de que o horizonte seja de pelo menos algumas barras mantendo ModelOrderFraction abaixo de 1.
  • Os modos Momentum e ROC requerem preços positivos. Instrumentos que podem cruzar zero devem usar o modo de preço bruto.
  • Para mercados com pips fracionados, a estratégia escala automaticamente o tamanho do pip usando as casas decimais do instrumento (×10 para 3 ou 5 decimais).

Limitações

  • O modelo AR assume estacionaridade; tendências fortes ou mudanças de regime podem reduzir a precisão.
  • Os sinais baseados em previsão são sensíveis ao ruído — considere combinar com filtros adicionais se usar em trading ao vivo.
  • O dimensionamento de risco preciso requer avaliação de portfólio e uma distância de stop-loss válida; caso contrário, volumes padrão são usados.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;


namespace StockSharp.Samples.Strategies;



/// <summary>
/// Strategy that extrapolates future prices using the Burg autoregressive model and opens trades when forecasted swings exceed thresholds.
/// Converted from the MetaTrader Burg Extrapolator expert.
/// </summary>
public class BurgExtrapolatorStrategy : Strategy
{
	private readonly StrategyParam<decimal> _riskPercent;
	private readonly StrategyParam<int> _maxPositions;
	private readonly StrategyParam<decimal> _minProfitPips;
	private readonly StrategyParam<decimal> _maxLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<int> _pastBars;
	private readonly StrategyParam<decimal> _modelOrderFraction;
	private readonly StrategyParam<bool> _useMomentum;
	private readonly StrategyParam<bool> _useRateOfChange;
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<DataType> _candleType;

	private decimal[] _openHistory = Array.Empty<decimal>();
	private decimal[] _inputSeries = Array.Empty<decimal>();
	private double[] _inputBuffer = Array.Empty<double>();
	private double[] _coefficients = Array.Empty<double>();
	private double[] _predictions = Array.Empty<double>();
	private double[] _forwardErrors = Array.Empty<double>();
	private double[] _backwardErrors = Array.Empty<double>();
	private decimal[] _priceForecast = Array.Empty<decimal>();

	private int _historyCapacity;
	private int _openCount;
	private int _modelOrder;
	private int _forecastSteps;
	private int _effectivePastBars;
	private decimal _pipSize;

	/// <summary>
	/// Risk percent of equity per trade.
	/// </summary>
	public decimal RiskPercent
	{
		get => _riskPercent.Value;
		set => _riskPercent.Value = value;
	}

	/// <summary>
	/// Maximum simultaneous positions in the same direction.
	/// </summary>
	public int MaxPositions
	{
		get => _maxPositions.Value;
		set => _maxPositions.Value = value;
	}

	/// <summary>
	/// Minimum predicted profit in pips required to open a position.
	/// </summary>
	public decimal MinProfitPips
	{
		get => _minProfitPips.Value;
		set => _minProfitPips.Value = value;
	}

	/// <summary>
	/// Maximum tolerated loss in pips that triggers position close.
	/// </summary>
	public decimal MaxLossPips
	{
		get => _maxLossPips.Value;
		set => _maxLossPips.Value = value;
	}

	/// <summary>
	/// Take profit distance in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Stop loss distance in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in pips.
	/// </summary>
	public decimal TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	/// <summary>
	/// Number of past bars used for the Burg model input.
	/// </summary>
	public int PastBars
	{
		get => _pastBars.Value;
		set => _pastBars.Value = value;
	}

	/// <summary>
	/// Fraction of past bars that determines the autoregressive order.
	/// </summary>
	public decimal ModelOrderFraction
	{
		get => _modelOrderFraction.Value;
		set => _modelOrderFraction.Value = value;
	}

	/// <summary>
	/// Enables logarithmic momentum input instead of raw prices.
	/// </summary>
	public bool UseMomentum
	{
		get => _useMomentum.Value;
		set => _useMomentum.Value = value;
	}

	/// <summary>
	/// Enables rate of change input when momentum is disabled.
	/// </summary>
	public bool UseRateOfChange
	{
		get => _useRateOfChange.Value;
		set => _useRateOfChange.Value = value;
	}

	/// <summary>
	/// Base order volume when risk calculation is not available.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Candle type used by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes <see cref="BurgExtrapolatorStrategy"/>.
	/// </summary>
	public BurgExtrapolatorStrategy()
	{
		_riskPercent = Param(nameof(RiskPercent), 5m)
		.SetDisplay("Risk %", "Risk percent per trade", "Money")
		.SetNotNegative();

		_maxPositions = Param(nameof(MaxPositions), 1)
		.SetDisplay("Max Positions", "Maximum simultaneous trades", "Risk")
		.SetGreaterThanZero();

		_minProfitPips = Param(nameof(MinProfitPips), 2m)
		.SetDisplay("Min Profit", "Minimum predicted profit (pips)", "Signals")
		.SetNotNegative();

		_maxLossPips = Param(nameof(MaxLossPips), 5m)
		.SetDisplay("Max Loss", "Maximum tolerated loss (pips)", "Risk")
		.SetNotNegative();

		_takeProfitPips = Param(nameof(TakeProfitPips), 0m)
		.SetDisplay("Take Profit", "Take profit distance (pips)", "Risk")
		.SetNotNegative();

		_stopLossPips = Param(nameof(StopLossPips), 5m)
		.SetDisplay("Stop Loss", "Stop loss distance (pips)", "Risk")
		.SetNotNegative();

		_trailingStopPips = Param(nameof(TrailingStopPips), 10m)
		.SetDisplay("Trailing Stop", "Trailing stop distance (pips)", "Risk")
		.SetNotNegative();

		_pastBars = Param(nameof(PastBars), 50)
		.SetDisplay("Past Bars", "Bars used for Burg model", "Model")
		.SetGreaterThanZero();

		_modelOrderFraction = Param(nameof(ModelOrderFraction), 0.37m)
		.SetDisplay("Model Order", "Fraction of bars used for AR order", "Model")
		.SetRange(0.1m, 0.9m);

		_useMomentum = Param(nameof(UseMomentum), true)
		.SetDisplay("Use Momentum", "Use logarithmic momentum input", "Model");

		_useRateOfChange = Param(nameof(UseRateOfChange), false)
		.SetDisplay("Use ROC", "Use rate of change input when momentum is off", "Model");

		_orderVolume = Param(nameof(OrderVolume), 1m)
		.SetDisplay("Order Volume", "Fallback order volume", "Money")
		.SetGreaterThanZero();

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Type of candles to use", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

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

		ResetBuffers();
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_pipSize = Security?.PriceStep ?? 1m;
		var decimals = Security?.Decimals ?? 0;
		if (decimals is 3 or 5)
		_pipSize *= 10m;

		EnsureCapacity();

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

		StartProtection(
		takeProfit: TakeProfitPips > 0m ? new Unit(TakeProfitPips * _pipSize, UnitTypes.Absolute) : null,
		stopLoss: StopLossPips > 0m ? new Unit(StopLossPips * _pipSize, UnitTypes.Absolute) : null,
		isStopTrailing: TrailingStopPips > 0m);
	}

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

		EnsureCapacity();

		PushOpen(candle.OpenPrice);

		if (_openCount < _historyCapacity)
		return;

		var currentOpen = _openHistory[_openCount - 1];
		if (!TryBuildInputSeries(out var average))
		return;

		// no bound indicators; skip only until enough bars collected

		if (!TryCalculateSignals(average, currentOpen, out var openSignal, out var closeSignal))
		return;

		var hasPosition = Position != 0m;
		if (hasPosition)
		{
			if (Position > 0m && (closeSignal == -1 || openSignal == -1))
			{
				SellMarket();
				return;
			}

			if (Position < 0m && (closeSignal == 1 || openSignal == 1))
			{
				BuyMarket();
				return;
			}
		}

		if (openSignal == 0)
		return;

		var volume = CalculateOrderVolume();
		if (volume <= 0m)
		return;

		var maxExposure = MaxPositions * volume;

		if (openSignal > 0)
		{
			if (Position < maxExposure)
			{
				var remaining = maxExposure - Math.Max(Position, 0m);
				var tradeVolume = Math.Min(volume, remaining);
				if (tradeVolume > 0m)
				BuyMarket();
			}
		}
		else if (openSignal < 0)
		{
			var shortExposure = Math.Abs(Math.Min(Position, 0m));
			if (shortExposure < maxExposure)
			{
				var remaining = maxExposure - shortExposure;
				var tradeVolume = Math.Min(volume, remaining);
				if (tradeVolume > 0m)
				SellMarket();
			}
		}
	}

	private void ResetBuffers()
	{
		_openHistory = Array.Empty<decimal>();
		_inputSeries = Array.Empty<decimal>();
		_inputBuffer = Array.Empty<double>();
		_coefficients = Array.Empty<double>();
		_predictions = Array.Empty<double>();
		_forwardErrors = Array.Empty<double>();
		_backwardErrors = Array.Empty<double>();
		_priceForecast = Array.Empty<decimal>();
		_pipSize = 0m;
		_historyCapacity = 0;
		_openCount = 0;
		_modelOrder = 1;
		_forecastSteps = 1;
		_effectivePastBars = 0;
	}

	private void EnsureCapacity()
	{
		var bars = Math.Max(PastBars, 3);
		var momentumEnabled = UseMomentum;
		var rocEnabled = !momentumEnabled && UseRateOfChange;
		var requiredHistory = momentumEnabled || rocEnabled ? bars + 1 : bars;

		if (_effectivePastBars != bars)
		{
			_effectivePastBars = bars;
			_inputSeries = new decimal[bars];
			_inputBuffer = new double[bars];
			_forwardErrors = new double[bars];
			_backwardErrors = new double[bars];
			_openHistory = new decimal[requiredHistory];
			_historyCapacity = requiredHistory;
			_openCount = 0;
		}
		else if (_historyCapacity != requiredHistory)
		{
			_openHistory = new decimal[requiredHistory];
			_historyCapacity = requiredHistory;
			_openCount = 0;
		}

		var order = (int)Math.Floor((double)(ModelOrderFraction * bars));
		if (order < 1)
		order = 1;
		if (order >= bars)
		order = bars - 1;
		if (order < 1)
		order = 1;

		var nf = bars - order - 1;
		if (nf < 1)
		nf = 1;

		if (_coefficients.Length != order + 1)
		_coefficients = new double[order + 1];

		if (_predictions.Length != nf + 1)
		_predictions = new double[nf + 1];

		if (_priceForecast.Length != nf + 1)
		_priceForecast = new decimal[nf + 1];

		_modelOrder = order;
		_forecastSteps = nf;
	}

	private void PushOpen(decimal open)
	{
		if (_historyCapacity == 0)
		return;

		if (_openCount < _historyCapacity)
		{
			_openHistory[_openCount++] = open;
		}
		else
		{
			Array.Copy(_openHistory, 1, _openHistory, 0, _historyCapacity - 1);
			_openHistory[_historyCapacity - 1] = open;
		}
	}

	private bool TryBuildInputSeries(out decimal average)
	{
		average = 0m;
		var bars = _effectivePastBars;
		if (bars == 0 || _openCount < _historyCapacity)
		return false;

		var momentumEnabled = UseMomentum;
		var rocEnabled = !momentumEnabled && UseRateOfChange;

		if (momentumEnabled)
		{
			for (var i = 0; i < bars; i++)
			{
				var prev = _openHistory[i];
				var next = _openHistory[i + 1];
				if (prev <= 0m || next <= 0m)
				{
					_inputSeries[i] = 0m;
				}
				else
				{
					var ratio = next / prev;
					_inputSeries[i] = (decimal)Math.Log((double)ratio);
				}
			}
		}
		else if (rocEnabled)
		{
			for (var i = 0; i < bars; i++)
			{
				var prev = _openHistory[i];
				var next = _openHistory[i + 1];
				if (prev == 0m)
				{
					_inputSeries[i] = 0m;
				}
				else
				{
					_inputSeries[i] = next / prev - 1m;
				}
			}
		}
		else
		{
			for (var i = 0; i < bars; i++)
			average += _openHistory[i];
			average /= bars;

			for (var i = 0; i < bars; i++)
			_inputSeries[i] = _openHistory[i] - average;
		}

		for (var i = 0; i < bars; i++)
		_inputBuffer[i] = (double)_inputSeries[i];

		return true;
	}

	private bool TryCalculateSignals(decimal average, decimal currentOpen, out int openSignal, out int closeSignal)
	{
		openSignal = 0;
		closeSignal = 0;

		var bars = _effectivePastBars;
		if (bars == 0 || _modelOrder < 1 || _forecastSteps < 1)
		return false;

		Array.Clear(_coefficients, 0, _coefficients.Length);
		Array.Clear(_predictions, 0, _predictions.Length);
		Array.Copy(_inputBuffer, _forwardErrors, bars);
		Array.Copy(_inputBuffer, _backwardErrors, bars);

		ComputeBurgCoefficients(bars);
		ForecastSeries(bars);

		var momentumEnabled = UseMomentum;
		var rocEnabled = !momentumEnabled && UseRateOfChange;

		if (momentumEnabled)
		{
			_priceForecast[0] = currentOpen;
			for (var i = 1; i <= _forecastSteps; i++)
			{
				var prev = _priceForecast[i - 1];
				var next = prev * (decimal)Math.Exp(_predictions[i]);
				_priceForecast[i] = next;
			}
		}
		else if (rocEnabled)
		{
			_priceForecast[0] = currentOpen;
			for (var i = 1; i <= _forecastSteps; i++)
			{
				var prev = _priceForecast[i - 1];
				_priceForecast[i] = prev * (1m + (decimal)_predictions[i]);
			}
		}
		else
		{
			for (var i = 0; i <= _forecastSteps; i++)
			_priceForecast[i] = (decimal)_predictions[i] + average;
		}

		var minProfit = MinProfitPips * _pipSize;
		var maxLoss = MaxLossPips * _pipSize;
		var ymax = _priceForecast[0];
		var ymin = _priceForecast[0];
		var imax = 0;
		var imin = 0;

		for (var i = 1; i < _forecastSteps; i++)
		{
			var value = _priceForecast[i];

			if (value > ymax && openSignal == 0)
			{
				ymax = value;
				imax = i;

				if (imin == 0 && ymax - ymin >= maxLoss)
				closeSignal = 1;

				if (imin == 0 && ymax - ymin >= minProfit)
				openSignal = 1;
			}

			if (value < ymin && openSignal == 0)
			{
				ymin = value;
				imin = i;

				if (imax == 0 && ymax - ymin >= maxLoss)
				closeSignal = -1;

				if (imax == 0 && ymax - ymin >= minProfit)
				openSignal = -1;
			}
		}

		return true;
	}

	private void ComputeBurgCoefficients(int bars)
	{
		var den = 0.0;
		for (var i = 0; i < bars; i++)
		{
			den += _inputBuffer[i] * _inputBuffer[i];
		}
		den *= 2.0;

		var reflection = 0.0;

		for (var k = 1; k <= _modelOrder; k++)
		{
			double num = 0.0;
			for (var i = k; i < bars; i++)
			{
				num += _forwardErrors[i] * _backwardErrors[i - 1];
			}

			var left = _forwardErrors[k - 1];
			var right = _backwardErrors[bars - 1];
			var denom = (1.0 - reflection * reflection) * den - left * left - right * right;
			reflection = Math.Abs(denom) > double.Epsilon ? -2.0 * num / denom : 0.0;

			_coefficients[k] = reflection;
			var half = k / 2;
			for (var i = 1; i <= half; i++)
			{
				var ki = k - i;
				var temp = _coefficients[i];
				_coefficients[i] += reflection * _coefficients[ki];
				if (i != ki)
				{
					_coefficients[ki] += reflection * temp;
				}
			}

			if (k < _modelOrder)
			{
				for (var i = bars - 1; i >= k; i--)
				{
					var temp = _forwardErrors[i];
					_forwardErrors[i] += reflection * _backwardErrors[i - 1];
					_backwardErrors[i] = _backwardErrors[i - 1] + reflection * temp;
				}
			}
		}
	}

	private void ForecastSeries(int bars)
	{
		for (var n = bars - 1; n < bars + _forecastSteps; n++)
		{
			double sum = 0.0;
			for (var i = 1; i <= _modelOrder; i++)
			{
				var index = n - i;
				if (index < bars)
				{
					sum -= _coefficients[i] * _inputBuffer[index];
				}
				else
				{
					var pfIndex = index - bars + 1;
					if (pfIndex >= 0 && pfIndex < _predictions.Length)
					{
						sum -= _coefficients[i] * _predictions[pfIndex];
					}
				}
			}

			var targetIndex = n - bars + 1;
			if (targetIndex >= 0 && targetIndex < _predictions.Length)
			{
				_predictions[targetIndex] = sum;
			}
		}
	}

	private decimal CalculateOrderVolume()
	{
		if (StopLossPips <= 0m || RiskPercent <= 0m)
		{
			return OrderVolume;
		}

		var equity = Portfolio?.CurrentValue ?? 0m;
		if (equity <= 0m)
		{
			return OrderVolume;
		}

		var riskAmount = equity * RiskPercent / 100m;
		var stopDistance = StopLossPips * _pipSize;
		if (stopDistance <= 0m)
		{
			return OrderVolume;
		}

		var volume = riskAmount / stopDistance;
		return volume > 0m ? volume : OrderVolume;
	}
}