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
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.
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.
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.
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.
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.
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.
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class spreader_strategy(Strategy):
def __init__(self):
super(spreader_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(spreader_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(spreader_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return spreader_strategy()