Arbitrage-Strategie
Überblick
ArbitrageStrategy ist eine Arbitrage-Strategie zwischen einem Futures-Kontrakt und seinem Basiswert. Sie verfolgt Spreads zwischen Instrumenten und eröffnet Positionen, wenn Arbitragegelegenheiten entstehen.
Hauptkomponenten
Die Strategie erbt von Strategy und verwendet Parameter zur Konfiguration:
public class ArbitrageStrategy : Strategy
{
private enum ArbitrageState
{
Contango, // Futures-Preis ist höher als der Basiswert
Backwardation, // Preis des Basiswerts ist höher als der Futures-Preis
None, // Keine Position
OrderRegistration // Im Prozess der Orderregistrierung
}
// Strategieparameter
private readonly StrategyParam<Security> _futureSecurity;
private readonly StrategyParam<Security> _stockSecurity;
private readonly StrategyParam<Portfolio> _futurePortfolio;
private readonly StrategyParam<Portfolio> _stockPortfolio;
private readonly StrategyParam<decimal> _stockMultiplicator;
private readonly StrategyParam<decimal> _futureVolume;
private readonly StrategyParam<decimal> _stockVolume;
private readonly StrategyParam<decimal> _profitToExit;
private readonly StrategyParam<decimal> _spreadToGenerateSignal;
}
Strategieparameter
Die Strategie erlaubt die Anpassung der folgenden Parameter:
- FutureSecurity - Futures-Instrument
- StockSecurity - Instrument des Basiswerts
- FuturePortfolio - Portfolio für den Futures-Handel
- StockPortfolio - Portfolio für den Handel mit dem Basiswert
- StockMultiplicator - Multiplikator für den Basiswert (z. B. Lotgröße)
- FutureVolume - Volumen für den Futures-Handel
- StockVolume - Volumen für den Handel mit dem Basiswert
- ProfitToExit - Gewinnschwelle für den Positionsausstieg
- SpreadToGenerateSignal - Spread-Schwellenwert zur Erzeugung eines Einstiegssignals
Initialisierung der Strategie
In der Methode OnStarted2 werden Parameter validiert sowie Abonnements für Orderbücher und eigene Trades erstellt:
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (FutureSecurity == null)
throw new InvalidOperationException("Future-Instrument ist nicht angegeben.");
if (StockSecurity == null)
throw new InvalidOperationException("Aktieninstrument ist nicht angegeben.");
if (FuturePortfolio == null)
throw new InvalidOperationException("Future-Portfolio ist nicht angegeben.");
if (StockPortfolio == null)
throw new InvalidOperationException("Aktienportfolio ist nicht angegeben.");
_futId = FutureSecurity.ToSecurityId();
_stockId = StockSecurity.ToSecurityId();
// Orderbuchaktualisierungen für beide Instrumente abonnieren
var futureDepthSubscription = new Subscription(DataType.MarketDepth, FutureSecurity);
var stockDepthSubscription = new Subscription(DataType.MarketDepth, StockSecurity);
futureDepthSubscription.WhenOrderBookReceived(this).Do(ProcessMarketDepth).Apply(this);
stockDepthSubscription.WhenOrderBookReceived(this).Do(ProcessMarketDepth).Apply(this);
// Eigene Trades abonnieren, um Ausführungspreise zu verfolgen
this
.WhenOwnTradeReceived()
.Do(OnOwnTradeReceived)
.Apply(this);
// Anforderungen für Marktdatenabonnements senden
Subscribe(futureDepthSubscription);
Subscribe(stockDepthSubscription);
}
Verarbeitung von Marktdaten
Die Methode ProcessMarketDepth wird bei einer Orderbuchaktualisierung aufgerufen und implementiert die Hauptlogik:
private void ProcessMarketDepth(IOrderBookMessage depth)
{
// Letztes Orderbuch je Instrument aktualisieren
if (depth.SecurityId == _futId)
_lastFut = depth;
else if (depth.SecurityId == _stockId)
_lastSt = depth;
// Auf Daten für beide Instrumente warten
if (_lastFut is null || _lastSt is null)
return;
// Volumengewichtete Durchschnittspreise für bestimmte Volumina berechnen
_futBid = GetAveragePrice(_lastFut, Sides.Sell, FutureVolume);
_futAck = GetAveragePrice(_lastFut, Sides.Buy, FutureVolume);
_stBid = GetAveragePrice(_lastSt, Sides.Sell, StockVolume) * StockMultiplicator;
_stAsk = GetAveragePrice(_lastSt, Sides.Buy, StockVolume) * StockMultiplicator;
// Preise validieren
if (_futBid == 0 || _futAck == 0 || _stBid == 0 || _stAsk == 0)
return;
// Spreads berechnen
var contangoSpread = _futBid - _stAsk; // Futures-Preis > Preis des Basiswerts
var backwardationSpread = _stBid - _futAck; // Preis des Basiswerts > Futures-Preis
decimal spread;
ArbitrageState arbitrageSignal;
// Beste Arbitragegelegenheit bestimmen
if (backwardationSpread > contangoSpread)
{
arbitrageSignal = ArbitrageState.Backwardation;
spread = backwardationSpread;
}
else
{
arbitrageSignal = ArbitrageState.Contango;
spread = contangoSpread;
}
// Aktuellen Zustand und Spreads protokollieren
LogInfo($"Current state {_currentState}, enter spread = {_enterSpread}");
LogInfo($"{ArbitrageState.Backwardation} spread = {backwardationSpread}");
LogInfo($"{ArbitrageState.Contango} spread = {contangoSpread}");
LogInfo($"Einstieg über Spread:{SpreadToGenerateSignal}. Ausstieg über Gewinn:{ProfitToExit}");
// Gewinn anhand der aktuellen Marktbedingungen neu berechnen
if (_currentState != ArbitrageState.None && _currentState != ArbitrageState.OrderRegistration)
{
CalculateProfit();
LogInfo($"Profit: {_profit}");
}
// Signale anhand des aktuellen Zustands und der Marktbedingungen verarbeiten
ProcessSignals(arbitrageSignal, spread);
}
Handelslogik
Signalverarbeitung sowie Ein- und Ausstiegsentscheidungen sind in der Methode ProcessSignals implementiert:
private void ProcessSignals(ArbitrageState arbitrageSignal, decimal spread)
{
// Neue Position eröffnen, wenn keine Position offen ist und der Spread den Schwellenwert überschreitet
if (_currentState == ArbitrageState.None && spread > SpreadToGenerateSignal)
{
_currentState = ArbitrageState.OrderRegistration;
if (arbitrageSignal == ArbitrageState.Backwardation)
{
ExecuteBackwardation();
}
else
{
ExecuteContango();
}
}
// Aus Backwardation-Position aussteigen, wenn die Gewinnschwelle erreicht ist
else if (_currentState == ArbitrageState.Backwardation && _profit >= ProfitToExit)
{
_currentState = ArbitrageState.OrderRegistration;
CloseBackwardationPosition();
}
// Aus Contango-Position aussteigen, wenn die Gewinnschwelle erreicht ist
else if (_currentState == ArbitrageState.Contango && _profit >= ProfitToExit)
{
_currentState = ArbitrageState.OrderRegistration;
CloseContangoPosition();
}
}
Gewinnberechnung
Die Methode CalculateProfit berechnet den aktuellen Gewinn anhand der Einstiegspreise und der aktuellen Preise:
private void CalculateProfit()
{
switch (_currentState)
{
case ArbitrageState.Backwardation:
// Futures kaufen, Basiswert verkaufen - Gewinn, wenn Futures-Preis steigt und Basiswertpreis fällt
_profit = (_stockExitPrice * StockMultiplicator - _stAsk) + (_futBid - _futureBuyPrice);
break;
case ArbitrageState.Contango:
// Futures verkaufen, Basiswert kaufen - Gewinn, wenn Futures-Preis fällt und Basiswertpreis steigt
_profit = (_futureExitPrice - _futAck) + (_stBid - _stockBuyPrice * StockMultiplicator);
break;
default:
_profit = 0;
break;
}
}
Ordererzeugung
Zur Ausführung von Arbitrage-Strategien werden Methoden zur Ordererzeugung verwendet:
private (Order buy, Order sell) GenerateOrdersBackwardation()
{
var futureBuy = CreateOrder(Sides.Buy, FutureVolume);
futureBuy.Portfolio = FuturePortfolio;
futureBuy.Security = FutureSecurity;
futureBuy.Type = OrderTypes.Market;
var stockSell = CreateOrder(Sides.Sell, StockVolume);
stockSell.Portfolio = StockPortfolio;
stockSell.Security = StockSecurity;
stockSell.Type = OrderTypes.Market;
return (futureBuy, stockSell);
}
private (Order sell, Order buy) GenerateOrdersContango()
{
var futureSell = CreateOrder(Sides.Sell, FutureVolume);
futureSell.Portfolio = FuturePortfolio;
futureSell.Security = FutureSecurity;
futureSell.Type = OrderTypes.Market;
var stockBuy = CreateOrder(Sides.Buy, StockVolume);
stockBuy.Portfolio = StockPortfolio;
stockBuy.Security = StockSecurity;
stockBuy.Type = OrderTypes.Market;
return (futureSell, stockBuy);
}
Funktionen
- Die Strategie unterstützt die Arbeit mit zwei verschiedenen Instrumenten und zwei Portfolios.
- Für schnelle Ausführung werden Market-Orders verwendet.
- Regeln (IMarketRule) werden genutzt, um die Orderausführung zu verfolgen.
- Für genauere Preise wird ein volumengewichteter Durchschnittspreis anhand des Volumens berechnet.
- Die Arbitrage-Logik berücksichtigt sowohl direkte (Contango) als auch inverse (Backwardation) Spreads.
- Automatische Gewinnberechnung und Ausstieg beim Erreichen des Zielschwellenwerts werden unterstützt.