Skip to content

Commit

Permalink
Merge pull request #125 from jhonabreul/feature-eurex
Browse files Browse the repository at this point in the history
EUREX EuroStoxx50 futures and index support
  • Loading branch information
jhonabreul authored Sep 12, 2024
2 parents 2c188c7 + 5f745b5 commit f206733
Show file tree
Hide file tree
Showing 8 changed files with 398 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,110 @@ public void CannotSubscribeToCFDWithUnsupportedMarket()
Assert.IsNull(enumerator);
}

[Test]
public void CanSubscribeToEurexFutures()
{
// Wait a bit to make sure previous tests already disconnected from IB
Thread.Sleep(2000);

using var ib = new InteractiveBrokersBrokerage(new QCAlgorithm(), new OrderProvider(), new SecurityProvider());
ib.Connect();

var canonicalFuture = Symbol.Create("FESX", SecurityType.Future, Market.EUREX);
var contracts = TestUtils.GetFutureContracts(canonicalFuture, 3).ToList();
Assert.AreEqual(3, contracts.Count);

var resolutions = new[] { Resolution.Tick, Resolution.Second };
var configs = contracts.SelectMany(symbol => resolutions.SelectMany(resolution =>
{
return resolution switch
{
Resolution.Tick => new[] { GetSubscriptionDataConfig<Tick>(symbol, resolution) },
_ => new[]
{
GetSubscriptionDataConfig<TradeBar>(symbol, resolution),
GetSubscriptionDataConfig<QuoteBar>(symbol, resolution)
}
};
}));

var cancelationToken = new CancellationTokenSource();
var data = new List<IBaseData>();

foreach (var config in configs)
{
ProcessFeed(
ib.Subscribe(config, (s, e) =>
{
var dataPoint = ((NewDataAvailableEventArgs)e).DataPoint;
lock (data)
{
data.Add(dataPoint);
}
}),
cancelationToken,
(tick) => Log(tick));
}

Thread.Sleep(10 * 1000);
cancelationToken.Cancel();
cancelationToken.Dispose();

var symbolsWithData = data.Select(tick => tick.Symbol).Distinct().ToList();
CollectionAssert.AreEquivalent(contracts, symbolsWithData);

var dataTypesWithData = data.Select(tick => tick.GetType()).Distinct().ToList();
var expectedDataTypes = configs.Select(config => config.Type).Distinct().ToList();
Assert.AreEqual(expectedDataTypes.Count, dataTypesWithData.Count);
}

[Test]
public void CanSubscribeToEurexIndex()
{
// Wait a bit to make sure previous tests already disconnected from IB
Thread.Sleep(2000);

using var ib = new InteractiveBrokersBrokerage(new QCAlgorithm(), new OrderProvider(), new SecurityProvider());
ib.Connect();

var index = Symbol.Create("SX5E", SecurityType.Index, Market.EUREX);

var resolutions = new[] { Resolution.Tick, Resolution.Second };
var configs = resolutions.Select(resolution => resolution == Resolution.Tick
? GetSubscriptionDataConfig<Tick>(index, resolution)
: GetSubscriptionDataConfig<TradeBar>(index, resolution));

var cancelationToken = new CancellationTokenSource();
var data = new List<IBaseData>();

foreach (var config in configs)
{
ProcessFeed(
ib.Subscribe(config, (s, e) =>
{
var dataPoint = ((NewDataAvailableEventArgs)e).DataPoint;
lock (data)
{
data.Add(dataPoint);
}
}),
cancelationToken,
(tick) => Log(tick));
}

Thread.Sleep(20 * 1000);
cancelationToken.Cancel();
cancelationToken.Dispose();

var symbolsWithData = data.Select(tick => tick.Symbol).Distinct().ToList();
Assert.AreEqual(1, symbolsWithData.Count);
Assert.AreEqual(index, symbolsWithData[0]);

var dataTypesWithData = data.Select(tick => tick.GetType()).Distinct().ToList();
var expectedDataTypes = configs.Select(config => config.Type).Distinct().ToList();
Assert.AreEqual(expectedDataTypes.Count, dataTypesWithData.Count);
}

protected SubscriptionDataConfig GetSubscriptionDataConfig<T>(Symbol symbol, Resolution resolution)
{
var entry = MarketHoursDatabase.FromDataFolder().GetEntry(symbol.ID.Market, symbol, symbol.SecurityType);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using NUnit.Framework;
using QuantConnect.Algorithm;
using QuantConnect.Brokerages.InteractiveBrokers;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
using System.Linq;

namespace QuantConnect.Tests.Brokerages.InteractiveBrokers
{
[TestFixture]
[Explicit("These tests require the IBGateway to be installed.")]
public class InteractiveBrokersEurexOrderTests : BrokerageTests
{
private static Symbol EuroStoxx50FutureSymbol = TestUtils.GetFutureContracts(Symbol.Create("FESX", SecurityType.Future, Market.EUREX), 1).Single();

protected override Symbol Symbol => EuroStoxx50FutureSymbol;
protected override SecurityType SecurityType => Symbol.SecurityType;

private static TestCaseData[] EuroStoxx50FuturesOrderTest()
{
return new[]
{
new TestCaseData(new MarketOrderTestParameters(EuroStoxx50FutureSymbol)),
new TestCaseData(new LimitOrderTestParameters(EuroStoxx50FutureSymbol, 10000m, 10m)),
new TestCaseData(new StopMarketOrderTestParameters(EuroStoxx50FutureSymbol, 10000m, 10m)),
new TestCaseData(new StopLimitOrderTestParameters(EuroStoxx50FutureSymbol, 10000m, 10m)),
new TestCaseData(new LimitIfTouchedOrderTestParameters(EuroStoxx50FutureSymbol, 10000m, 10m)),
};
}

#region EuroStoxx50 Futures

[Test, TestCaseSource(nameof(EuroStoxx50FuturesOrderTest))]
public override void CancelOrders(OrderTestParameters parameters)
{
base.CancelOrders(parameters);
}

[Test, TestCaseSource(nameof(EuroStoxx50FuturesOrderTest))]
public override void LongFromZero(OrderTestParameters parameters)
{
base.LongFromZero(parameters);
}

[Test, TestCaseSource(nameof(EuroStoxx50FuturesOrderTest))]
public override void CloseFromLong(OrderTestParameters parameters)
{
base.CloseFromLong(parameters);
}

[Test, TestCaseSource(nameof(EuroStoxx50FuturesOrderTest))]
public override void ShortFromZero(OrderTestParameters parameters)
{
base.ShortFromZero(parameters);
}

[Test, TestCaseSource(nameof(EuroStoxx50FuturesOrderTest))]
public override void CloseFromShort(OrderTestParameters parameters)
{
base.CloseFromShort(parameters);
}

[Test, TestCaseSource(nameof(EuroStoxx50FuturesOrderTest))]
public override void ShortFromLong(OrderTestParameters parameters)
{
base.ShortFromLong(parameters);
}

[Test, TestCaseSource(nameof(EuroStoxx50FuturesOrderTest))]
public override void LongFromShort(OrderTestParameters parameters)
{
base.LongFromShort(parameters);
}

#endregion

protected override bool IsAsync()
{
return true;
}

protected override decimal GetAskPrice(Symbol symbol)
{
return 1m;
}

protected override IBrokerage CreateBrokerage(IOrderProvider orderProvider, ISecurityProvider securityProvider)
{
return new InteractiveBrokersBrokerage(new QCAlgorithm(), orderProvider, securityProvider);
}

protected override void DisposeBrokerage(IBrokerage brokerage)
{
if (brokerage != null)
{
brokerage.Disconnect();
brokerage.Dispose();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@
* limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using IBApi;
using NUnit.Framework;
using QuantConnect.Algorithm;
using QuantConnect.Brokerages.InteractiveBrokers;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Logging;

namespace QuantConnect.Tests.Brokerages.InteractiveBrokers
Expand Down Expand Up @@ -287,7 +286,7 @@ public void CreatesExpectedFuturesContracts()
{
var contract = new Contract
{
Symbol = symbolMapper.GetBrokerageRootSymbol(ticker),
Symbol = symbolMapper.GetBrokerageRootSymbol(ticker, SecurityType.Future),
Currency = Currencies.USD,
Exchange = null,
SecType = "FUT"
Expand Down Expand Up @@ -317,18 +316,10 @@ public void CreateExpectedFutureContractsWithDifferentCurrencies()

var tickersByMarket = new Dictionary<string, string[]>
{
{
Market.HKFE,
new[]
{
"HSI"
}
},
{
Market.CME,
new[]
{
"ACD",
"AJY",
"ANE"
}
Expand All @@ -340,26 +331,38 @@ public void CreateExpectedFutureContractsWithDifferentCurrencies()
{
"ZC"
}
}
},
{
Market.EUREX,
new[]
{
"FESX"
}
},
};

foreach (var kvp in tickersByMarket)
Assert.Multiple(() =>
{
var market = kvp.Key;
var tickers = kvp.Value;

foreach (var ticker in tickers)
foreach (var kvp in tickersByMarket)
{
var currentSymbol = Symbol.Create(ticker, SecurityType.Future, market);
var symbolsFound = ib.LookupSymbols(currentSymbol, false);
Assert.IsNotEmpty(symbolsFound);
var market = kvp.Key;
var tickers = kvp.Value;

foreach (var symbol in symbolsFound)
foreach (var ticker in tickers)
{
Log.Trace($"Symbol found in IB: {symbol}");
Log.Trace($"Market: {market} - Future Ticker: {ticker}");

var currentSymbol = Symbol.Create(ticker, SecurityType.Future, market);
var symbolsFound = ib.LookupSymbols(currentSymbol, false);
Assert.IsNotEmpty(symbolsFound, $"No contracts found for Market: {market} - Future Ticker: {ticker}");

foreach (var symbol in symbolsFound)
{
Log.Trace($" - Symbol found in IB: {symbol} :: {symbol.Value} :: {symbol.ID.Date}");
}
}
}
}
});
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ public void ReturnsCorrectBrokerageSymbol()
symbol = Symbol.Create("BRK.B", SecurityType.Equity, Market.USA);
brokerageSymbol = mapper.GetBrokerageSymbol(symbol);
Assert.AreEqual("BRK B", brokerageSymbol);

symbol = Symbol.CreateCanonicalOption(symbol);
brokerageSymbol = mapper.GetBrokerageSymbol(symbol);
Assert.AreEqual("BRK B", brokerageSymbol);
}

[TestCase("AAPL", "AAPL")]
Expand Down Expand Up @@ -204,5 +208,38 @@ public void FuturesOptionsWithUnderlyingContractMonthMappedByRuleResolvesUnderly

Assert.AreEqual(expectedUnderlyingSymbol, futureOption.Underlying);
}

private static TestCaseData[] LeanIbSymbolMappingTestCases = new TestCaseData[]
{
new("6B", "GBP", SecurityType.Future),
new("AW", "AIGCI", SecurityType.Future),
new("FESX", "ESTX50", SecurityType.Future),
new("SX5E", "ESTX50", SecurityType.Index),
};

[TestCaseSource(nameof(LeanIbSymbolMappingTestCases))]
public void MapsLeanToIBSymbolDependingOnSecurityType(string leanTicker, string ibTicker, SecurityType securityType)
{
var mapper = new InteractiveBrokersSymbolMapper(TestGlobals.MapFileProvider);

var leanSymbol = Symbol.Create(leanTicker, securityType, Market.InteractiveBrokers);
var resultIbTicker = mapper.GetBrokerageSymbol(leanSymbol);

Assert.AreEqual(ibTicker, resultIbTicker);
}

[TestCaseSource(nameof(LeanIbSymbolMappingTestCases))]
public void MapsIBToLeanSymbolDependingOnSecurityType(string leanTicker, string ibTicker, SecurityType securityType)
{
var mapper = new InteractiveBrokersSymbolMapper(TestGlobals.MapFileProvider);

var expiry = new DateTime(2024, 09, 20);
var expectedLeanSymbol = securityType == SecurityType.Future
? Symbol.CreateFuture(leanTicker, Market.InteractiveBrokers, expiry)
: Symbol.Create(leanTicker, securityType, Market.InteractiveBrokers);
var resultLeanSymbol = mapper.GetLeanSymbol(ibTicker, securityType, expectedLeanSymbol.ID.Market, expiry);

Assert.AreEqual(expectedLeanSymbol, resultLeanSymbol);
}
}
}
Loading

0 comments on commit f206733

Please sign in to comment.