Auf GitHub ansehen

Burg Extrapolator Strategie

Übersicht

Die Burg Extrapolator Strategie repliziert den MetaTrader Expert Advisor "Burg Extrapolator" mit der StockSharp High-Level-API. Das System wendet ein mit der Burg-Methode gelöstes autoregressives (AR) Modell an, um zukünftige Eröffnungspreise vorherzusagen. Handelsentscheidungen werden durch die Amplitude der Prognosepfad-Amplitude gesteuert: Wenn die vorhergesagte Distanz zwischen zukünftigen Hochs und Tiefs die konfigurierten Schwellenwerte überschreitet, öffnet oder schließt die Strategie Positionen.

Kernlogik

  1. Datenvorbereitung
    • Sammelt PastBars Eröffnungspreise bei jeder fertigen Kerze.
    • Transformiert optional die Reihe in logarithmische Momentum- oder Rate-of-Change-Werte.
    • Normalisiert Preise durch Subtraktion des gleitenden Durchschnitts bei Verwendung von Rohpreisen.
  2. Autoregressives Modelling
    • Schätzt AR-Koeffizienten durch die Burg-Methode mit einer durch ModelOrderFraction bestimmten Ordnung.
    • Extrapoliert mehrere Schritte voraus (Prognosehorizont = PastBars - order - 1) und rekonstruiert Preisvorhersagen.
  3. Signalgenerierung
    • Verfolgt die maximalen und minimalen vorhergesagten Preise.
    • Wenn der Prognose-Swing MinProfitPips überschreitet, wird ein Einstiegssignal in der jeweiligen Richtung generiert.
    • Wenn der Prognose-Swing MaxLossPips überschreitet, wird ein Ausstiegssignal für bestehende Positionen ausgegeben.
  4. Orderausführung
    • Positionen werden mit Marktorders unter Verwendung des berechneten risikobasierten Volumens eröffnet.
    • Wenn ein Stop oder ein entgegengesetztes Signal auftritt, schließt die Strategie Positionen mit Marktorders.

Parameter

Parameter Beschreibung
RiskPercent Pro Trade riskierter Eigenkapitalprozentsatz. Dient zur Ordergrößenbestimmung, wenn ein Stop-Loss-Abstand verfügbar ist.
MaxPositions Maximales kumuliertes Volumen als Vielfaches der zulässigen Ordergröße pro Richtung.
MinProfitPips Mindest-vorhergesagter Gewinn-Swing (in Pips), der zum Öffnen neuer Positionen erforderlich ist.
MaxLossPips Maximal zulässiger vorhergesagter Drawdown (in Pips), der Positionsausstiege auslöst.
TakeProfitPips Statischer Take-Profit-Abstand (in Pips). Auf null setzen zum Deaktivieren.
StopLossPips Statischer Stop-Loss-Abstand (in Pips). Erforderlich für Risikosizing.
TrailingStopPips Trailing-Stop-Abstand (in Pips). Funktioniert nur wenn Stop-Loss aktiviert ist.
PastBars Anzahl historischer Balken als Eingabe in das Burg-Modell.
ModelOrderFraction Fraktion von PastBars, die die AR-Ordnung definiert (ganzzahlige Kürzung).
UseMomentum Aktiviert logarithmische Momentum-Vorverarbeitung (log(p[i]/p[i-1])).
UseRateOfChange Aktiviert Rate-of-Change-Vorverarbeitung (p[i]/p[i-1]-1), wenn Momentum deaktiviert ist.
OrderVolume Fallback-Ordergröße, wenn risikobasiertes Sizing nicht berechnet werden kann.
CandleType Datentyp (Zeitrahmen) der für Berechnungen verwendeten Kerzen.

Handelsregeln

  • Einstieg: Wenn der vorhergesagte Pfad einen Swing größer als MinProfitPips anzeigt, eine Long-Position öffnen, wenn der höchste projizierte Preis zuerst kommt, oder eine Short-Position, wenn die niedrigste Projektion zuerst erscheint.
  • Ausstieg: Positionen schließen, wenn der Prognose-Swing MaxLossPips überschreitet oder das entgegengesetzte Einstiegssignal erkannt wird.
  • Schutz: Verwendet StartProtection zur Konfiguration optionaler Stop-Loss, Take-Profit und Trailing Stop in absoluten Kurseinheiten abgeleitet von Pips.
  • Positionssizing: Wenn sowohl StopLossPips als auch RiskPercent positiv sind, wird das Tradingvolumen als risk_amount / (stop_distance) berechnet. Andernfalls wird OrderVolume verwendet.

Implementierungshinweise

  • Arbeitet ausschließlich mit fertigen Kerzen, um Look-Ahead-Bias zu vermeiden.
  • Vermeidet Indikator-GetValue-Aufrufe durch direkte Verarbeitung von Werten innerhalb des Bind-Callbacks.
  • Respektiert die Konventionen der StockSharp High-Level-API mit SubscribeCandles und StartProtection für das Risikomanagement.
  • Trailing-Logik spiegelt den Original-EA durch Aktivierung plattformverwalteter Trailing Stops wider.

Verwendungstipps

  • PastBars und ModelOrderFraction sorgfältig wählen; hohe Ordnungen können zu Overfitting oder instabilen Prognosen führen.
  • Der Prognosehorizont entspricht PastBars - order - 1; sicherstellen, dass der Horizont mindestens einige Balken beträgt, indem ModelOrderFraction unter 1 gehalten wird.
  • Momentum- und ROC-Modi erfordern positive Preise. Instrumente, die null kreuzen können, sollten beim Rohpreismodus bleiben.
  • Für Märkte mit fraktionalen Pips skaliert die Strategie die Pip-Größe automatisch unter Verwendung der Dezimalstellen des Instruments (×10 für 3 oder 5 Dezimalstellen).

Einschränkungen

  • Das AR-Modell setzt Stationarität voraus; starke Trends oder Regimewechsel können die Genauigkeit reduzieren.
  • Prognosebasierte Signale sind empfindlich gegenüber Rauschen – in Betracht ziehen, mit zusätzlichen Filtern zu kombinieren, wenn im Live-Handel verwendet.
  • Genaues Risikosizing erfordert Portfoliobewertung und einen gültigen Stop-Loss-Abstand; andernfalls werden Standardvolumina verwendet.
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;
	}
}