Auf GitHub ansehen

Spreader-Strategie

Überblick

Die Spreader-Strategie ist ein Pair-Trading-Ansatz, der vom ursprünglichen MetaTrader-Expert-Advisor „Spreader" inspiriert wurde. Die Strategie überwacht zwei positiv korrelierte Instrumente und versucht, von kurzfristigen Divergenzen zu profitieren, während ein marktneutrales Profil aufrechterhalten wird. Sobald die kombinierte Position das gewünschte Geldertragsziel erreicht, schließt die Strategie beide Beine und wartet auf das nächste Setup.

Der Algorithmus ist standardmäßig für Einminutenkerzen ausgelegt und spiegelt das Verhalten des Original-EA wider, aber der Zeitrahmen kann angepasst werden, wenn die Strategie in Designer, Shell oder den API-Runner geladen wird.

Handelslogik

  1. Datenvorbereitung

    • Abonniert Kerzen für das primäre Wertpapier (das der Strategie zugewiesene) und das sekundäre Wertpapier.
    • Speichert die letzten 2 * ShiftLength + 1 Schlusskurswerte für jedes Instrument. Die Standard-Shift-Länge beträgt 30 Bars.
    • Reagiert nur auf abgeschlossene Kerzen und erfordert, dass beide Instrumente eine Bar mit der gleichen Eröffnungszeit erzeugen.
  2. Trendfilter

    • Berechnet die Preisänderungen zwischen dem aktuellen Schluss und dem Schluss ShiftLength Bars zuvor sowie die Änderung zwischen den mittleren und ältesten Samples für beide Instrumente.
    • Wenn die zwei Änderungen für eines der Instrumente das gleiche Vorzeichen haben, interpretiert die Strategie dies als anhaltenden Trend und überspringt den Handel.
  3. Korrelationsprüfung

    • Stellt sicher, dass das Vorzeichen der letzten Änderung bei beiden Instrumenten identisch ist. Wenn das Vorzeichen abweicht, gilt die Korrelation als negativ und kein Spread wird eröffnet.
  4. Volatilitätsausrichtung

    • Berechnet den absoluten Betrag der jüngsten Schwankungen (a für das primäre Bein, b für das sekundäre Bein) und verwendet ihr Verhältnis zur Skalierung des Absicherungsvolumens. Verhältnisse außerhalb des Bereichs [0.3, 3] werden abgelehnt, da sie instabiles Verhalten anzeigen.
  5. Einstieg

    • Wählt die Richtung des primären Beins durch Vergleich der normalisierten Schwankungen: Wenn die primäre Bewegung stärker ist, kauft die Strategie das primäre Instrument und verkauft das sekundäre Bein; andernfalls verkauft sie das primäre Bein und kauft das sekundäre.
    • Orders werden mit Marktausführung gesendet und Volumina werden normalisiert, um den Lot-Schritt sowie Mindest- und Höchstgrenzen jedes Wertpapiers einzuhalten.
  6. Positionsmanagement

    • Wenn nur das sekundäre Bein offen ist (z.B. aufgrund von Konnektivitätsproblemen), öffnet die Strategie das fehlende primäre Bein in der entgegengesetzten Richtung, um die Absicherung wiederherzustellen.
    • Wenn nur das primäre Bein verbleibt, wird es sofort geschlossen, um direktionales Exposure zu vermeiden.
    • Wenn beide Beine vorhanden sind, überwacht die Strategie den kombinierten variablen Gewinn und schließt beide Positionen, sobald das konfigurierte Geldertragsziel erreicht ist.
  7. Sicherheitsprüfungen

    • Der Handel ist deaktiviert, wenn die Kontraktgröße (Multiplikator) der beiden Wertpapiere unterschiedlich ist, da der Original-EA gleiche Kontraktspezifikationen voraussetzt.
    • Alle Handelsanfragen werden ignoriert, bis die Strategie verbunden, synchronisiert und durch die Hostingumgebung handelsberechtigt ist (IsFormedAndOnlineAndAllowTrading).

Parameter

Parameter Beschreibung
SecondSecurity Instrument, das das Absicherungsbein des Spreads bildet. Dieser Parameter ist erforderlich.
PrimaryVolume Basis-Order-Volumen für das primäre Instrument. Das sekundäre Volumen wird mit dem Schwankungsverhältnis skaliert.
TargetProfit Absoluter Gewinn, ausgedrückt in der Kontowährung, nach dem beide Beine geschlossen werden.
ShiftLength Anzahl der Bars, die beim Vergleich der jüngsten Schwankungen verwendet werden. Die Strategie verwendet 2 * ShiftLength + 1 Kerzen von jedem Instrument.
CandleType Kerzenserie für die Analyse. Standardmäßig 1-Minuten-Zeitrahmen.

Tipps

  • Die Strategie funktioniert am besten bei Instrumenten mit stabiler positiver Korrelation und ähnlichen Volatilitätsprofilen (z.B. eng verwandte Währungspaare oder Indexfutures).
  • Kontraktspezifikationen sollten ausgerichtet sein (Tick-Größe, Lot-Schritt, Margin); andernfalls kann die Volumen-Normalisierung die Ordergrößen erheblich reduzieren.
  • Da die Strategie auf Kerzendaten basiert, stellen Sie sicher, dass beide Instrumente synchronisierte Bar-Updates vom Datenanbieter erhalten.

Anforderungen

  • Zwei liquide Instrumente mit positiver Korrelation.
  • Zugang zu Marktdaten und Handelsberechtigungen für beide Instrumente über StockSharp-Konnektoren.
  • Portfolio der Strategieinstanz zugewiesen.
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 SpreaderStrategy : 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 SpreaderStrategy()
	{
		_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;
	}
}