diff --git a/QuantConnect.InteractiveBrokersBrokerage.Tests/InteractiveBrokersBrokerageDataQueueHandlerTest.cs b/QuantConnect.InteractiveBrokersBrokerage.Tests/InteractiveBrokersBrokerageDataQueueHandlerTest.cs index e4597be..b9794fb 100644 --- a/QuantConnect.InteractiveBrokersBrokerage.Tests/InteractiveBrokersBrokerageDataQueueHandlerTest.cs +++ b/QuantConnect.InteractiveBrokersBrokerage.Tests/InteractiveBrokersBrokerageDataQueueHandlerTest.cs @@ -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(symbol, resolution) }, + _ => new[] + { + GetSubscriptionDataConfig(symbol, resolution), + GetSubscriptionDataConfig(symbol, resolution) + } + }; + })); + + var cancelationToken = new CancellationTokenSource(); + var data = new List(); + + 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(index, resolution) + : GetSubscriptionDataConfig(index, resolution)); + + var cancelationToken = new CancellationTokenSource(); + var data = new List(); + + 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(Symbol symbol, Resolution resolution) { var entry = MarketHoursDatabase.FromDataFolder().GetEntry(symbol.ID.Market, symbol, symbol.SecurityType); diff --git a/QuantConnect.InteractiveBrokersBrokerage.Tests/InteractiveBrokersEurexOrderTests.cs b/QuantConnect.InteractiveBrokersBrokerage.Tests/InteractiveBrokersEurexOrderTests.cs new file mode 100644 index 0000000..2555c1a --- /dev/null +++ b/QuantConnect.InteractiveBrokersBrokerage.Tests/InteractiveBrokersEurexOrderTests.cs @@ -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(); + } + } + } +} diff --git a/QuantConnect.InteractiveBrokersBrokerage.Tests/InteractiveBrokersFuturesTests.cs b/QuantConnect.InteractiveBrokersBrokerage.Tests/InteractiveBrokersFuturesTests.cs index 7346e25..73c7d8e 100644 --- a/QuantConnect.InteractiveBrokersBrokerage.Tests/InteractiveBrokersFuturesTests.cs +++ b/QuantConnect.InteractiveBrokersBrokerage.Tests/InteractiveBrokersFuturesTests.cs @@ -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 @@ -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" @@ -317,18 +316,10 @@ public void CreateExpectedFutureContractsWithDifferentCurrencies() var tickersByMarket = new Dictionary { - { - Market.HKFE, - new[] - { - "HSI" - } - }, { Market.CME, new[] { - "ACD", "AJY", "ANE" } @@ -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}"); + } } } - } + }); } } } diff --git a/QuantConnect.InteractiveBrokersBrokerage.Tests/InteractiveBrokersSymbolMapperTests.cs b/QuantConnect.InteractiveBrokersBrokerage.Tests/InteractiveBrokersSymbolMapperTests.cs index a691b04..d445b72 100644 --- a/QuantConnect.InteractiveBrokersBrokerage.Tests/InteractiveBrokersSymbolMapperTests.cs +++ b/QuantConnect.InteractiveBrokersBrokerage.Tests/InteractiveBrokersSymbolMapperTests.cs @@ -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")] @@ -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); + } } } diff --git a/QuantConnect.InteractiveBrokersBrokerage.Tests/TestUtils.cs b/QuantConnect.InteractiveBrokersBrokerage.Tests/TestUtils.cs new file mode 100644 index 0000000..b1ef9fa --- /dev/null +++ b/QuantConnect.InteractiveBrokersBrokerage.Tests/TestUtils.cs @@ -0,0 +1,41 @@ +/* + * 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 QuantConnect.Securities.Future; +using System.Collections.Generic; +using System; + +namespace QuantConnect.Tests.Brokerages.InteractiveBrokers +{ + public class TestUtils + { + public static IEnumerable GetFutureContracts(Symbol canonical, int count) + { + var expiryFunction = FuturesExpiryFunctions.FuturesExpiryFunction(canonical); + var lastExpiryMonth = DateTime.UtcNow; + + // Get the next N contracts + for (var i = 0; i < count; i++) + { + var expiry = expiryFunction(lastExpiryMonth); + var contract = Symbol.CreateFuture(canonical.ID.Symbol, canonical.ID.Market, expiry); + + yield return contract; + + lastExpiryMonth = expiry.AddMonths(1); + } + } + } +} diff --git a/QuantConnect.InteractiveBrokersBrokerage/InteractiveBrokers/IB-symbol-map.json b/QuantConnect.InteractiveBrokersBrokerage/InteractiveBrokers/IB-symbol-map.json index dfe2fef..aef885f 100644 --- a/QuantConnect.InteractiveBrokersBrokerage/InteractiveBrokers/IB-symbol-map.json +++ b/QuantConnect.InteractiveBrokersBrokerage/InteractiveBrokers/IB-symbol-map.json @@ -1,22 +1,27 @@ /* This is a manually created file that contains mappings from IB own naming to original symbols defined by respective exchanges. */ { - // Futures - "GBP": "6B", - "CAD": "6C", - "JPY": "6J", - "CHF": "6S", - "EUR": "6E", - "AUD": "6A", - "NZD": "6N", - "VIX": "VX", - "AIGCI": "AW", - "AC": "EH", - "BRE": "6L", - "MXP": "6M", - "RUR": "6R", - "ZAR": "6Z", - "BRR": "BTC", - "ETHUSDRR": "ETH", - "DA": "DC", - "BQX": "BIO" + "Future": { + "GBP": "6B", + "CAD": "6C", + "JPY": "6J", + "CHF": "6S", + "EUR": "6E", + "AUD": "6A", + "NZD": "6N", + "VIX": "VX", + "AIGCI": "AW", + "AC": "EH", + "BRE": "6L", + "MXP": "6M", + "RUR": "6R", + "ZAR": "6Z", + "BRR": "BTC", + "ETHUSDRR": "ETH", + "DA": "DC", + "BQX": "BIO", + "ESTX50": "FESX" + }, + "Index": { + "ESTX50": "SX5E" + } } diff --git a/QuantConnect.InteractiveBrokersBrokerage/InteractiveBrokersBrokerage.cs b/QuantConnect.InteractiveBrokersBrokerage/InteractiveBrokersBrokerage.cs index 72ae609..1cc1142 100644 --- a/QuantConnect.InteractiveBrokersBrokerage/InteractiveBrokersBrokerage.cs +++ b/QuantConnect.InteractiveBrokersBrokerage/InteractiveBrokersBrokerage.cs @@ -188,7 +188,8 @@ public sealed class InteractiveBrokersBrokerage : Brokerage, IDataQueueHandler, { Market.CBOT, "CBOT" }, { Market.ICE, "NYBOT" }, { Market.CFE, "CFE" }, - { Market.NYSELIFFE, "NYSELIFFE" } + { Market.NYSELIFFE, "NYSELIFFE" }, + { Market.EUREX, "EUREX" } }; private readonly SymbolPropertiesDatabase _symbolPropertiesDatabase = SymbolPropertiesDatabase.FromDataFolder(); @@ -3426,7 +3427,7 @@ private Symbol MapSymbol(Contract contract) // Handle future options as a Future, up until we actually return the future. if (isFutureOption || securityType == SecurityType.Future) { - var leanSymbol = _symbolMapper.GetLeanRootSymbol(ibSymbol); + var leanSymbol = _symbolMapper.GetLeanRootSymbol(ibSymbol, securityType); var defaultMarket = market; if (!_symbolPropertiesDatabase.TryGetMarket(leanSymbol, SecurityType.Future, out market)) @@ -3782,7 +3783,7 @@ private static bool CanSubscribe(Symbol symbol) (securityType == SecurityType.Forex && market == Market.Oanda) || (securityType == SecurityType.Option && market == Market.USA) || (securityType == SecurityType.IndexOption && market == Market.USA) || - (securityType == SecurityType.Index && market == Market.USA) || + (securityType == SecurityType.Index && (market == Market.USA || market == Market.EUREX)) || (securityType == SecurityType.FutureOption) || (securityType == SecurityType.Future) || (securityType == SecurityType.Cfd && market == Market.InteractiveBrokers); @@ -4067,7 +4068,7 @@ public IEnumerable LookupSymbols(Symbol symbol, bool includeExpired, str // setting up lookup request var contract = new Contract { - Symbol = _symbolMapper.GetBrokerageRootSymbol(lookupName), + Symbol = _symbolMapper.GetBrokerageRootSymbol(lookupName, symbol.SecurityType), Currency = securityCurrency ?? symbolProperties.QuoteCurrency, Exchange = exchangeSpecifier, SecType = ConvertSecurityType(symbol.SecurityType), diff --git a/QuantConnect.InteractiveBrokersBrokerage/InteractiveBrokersSymbolMapper.cs b/QuantConnect.InteractiveBrokersBrokerage/InteractiveBrokersSymbolMapper.cs index 63a9eed..b171d0a 100644 --- a/QuantConnect.InteractiveBrokersBrokerage/InteractiveBrokersSymbolMapper.cs +++ b/QuantConnect.InteractiveBrokersBrokerage/InteractiveBrokersSymbolMapper.cs @@ -39,9 +39,9 @@ public class InteractiveBrokersSymbolMapper : ISymbolMapper { private readonly IMapFileProvider _mapFileProvider; - // we have a special treatment of futures, because IB renamed several exchange tickers (like GBP instead of 6B). We fix this: - // We map those tickers back to their original names using the map below - private readonly Dictionary _ibNameMap = new Dictionary(); + // we have a special treatment of futures and other security types, because IB renamed several exchange tickers (like GBP instead of 6B). + // We fix this: We map those tickers back to their original names using the map below + private readonly Dictionary> _ibNameMap = new Dictionary>(); /// /// Constructs InteractiveBrokersSymbolMapper. Default parameters are used. @@ -56,7 +56,7 @@ public InteractiveBrokersSymbolMapper(IMapFileProvider mapFileProvider) : /// Constructs InteractiveBrokersSymbolMapper /// /// New names map (IB -> LEAN) - public InteractiveBrokersSymbolMapper(Dictionary ibNameMap) + public InteractiveBrokersSymbolMapper(Dictionary> ibNameMap) { _ibNameMap = ibNameMap; } @@ -69,7 +69,7 @@ public InteractiveBrokersSymbolMapper(string ibNameMapFullName) { if (File.Exists(ibNameMapFullName)) { - _ibNameMap = JsonConvert.DeserializeObject>(File.ReadAllText(ibNameMapFullName)); + _ibNameMap = JsonConvert.DeserializeObject>>(File.ReadAllText(ibNameMapFullName)); } } /// @@ -84,13 +84,6 @@ public string GetBrokerageSymbol(Symbol symbol) throw new ArgumentException("Invalid symbol: " + (symbol == null ? "null" : symbol.ToString())); } - var ticker = GetMappedTicker(symbol); - - if (string.IsNullOrWhiteSpace(ticker)) - { - throw new ArgumentException("Invalid symbol: " + symbol.ToString()); - } - if (symbol.ID.SecurityType != SecurityType.Forex && symbol.ID.SecurityType != SecurityType.Equity && symbol.ID.SecurityType != SecurityType.Index && @@ -103,37 +96,19 @@ public string GetBrokerageSymbol(Symbol symbol) throw new ArgumentException("Invalid security type: " + symbol.ID.SecurityType); } - if (symbol.ID.SecurityType == SecurityType.Forex && ticker.Length != 6) + var ticker = GetMappedTicker(symbol); + + if (string.IsNullOrWhiteSpace(ticker)) { - throw new ArgumentException("Forex symbol length must be equal to 6: " + symbol.Value); + throw new ArgumentException("Invalid symbol: " + symbol.ToString()); } - switch (symbol.ID.SecurityType) + if (symbol.ID.SecurityType == SecurityType.Forex && ticker.Length != 6) { - case SecurityType.Option: - case SecurityType.IndexOption: - // Final case is for equities. We use the mapped value to select - // the equity we want to trade. We skip mapping for index options. - return GetMappedTicker(symbol.Underlying); - - case SecurityType.FutureOption: - // We use the underlying Future Symbol since IB doesn't use - // the Futures Options' ticker, but rather uses the underlying's - // Symbol, mapped to the brokerage. - return GetBrokerageSymbol(symbol.Underlying); - - case SecurityType.Future: - case SecurityType.Cfd: - return GetBrokerageRootSymbol(symbol.ID.Symbol); - - case SecurityType.Equity: - return ticker.Replace(".", " "); - - case SecurityType.Index: - return ticker; + throw new ArgumentException("Forex symbol length must be equal to 6: " + symbol.Value); } - return ticker; + return GetBrokerageRootSymbol(ticker, symbol.SecurityType); } /// @@ -163,31 +138,30 @@ public string GetBrokerageSymbol(Symbol symbol) try { + var ticker = GetLeanRootSymbol(brokerageSymbol, securityType); switch (securityType) { case SecurityType.Future: - return Symbol.CreateFuture(GetLeanRootSymbol(brokerageSymbol), market, expirationDate); + return Symbol.CreateFuture(ticker, market, expirationDate); case SecurityType.Option: - // See SecurityType.Equity case. The equity underlying may include a space, e.g. BRK B. - brokerageSymbol = brokerageSymbol.Replace(" ", "."); - return Symbol.CreateOption(brokerageSymbol, market, OptionStyle.American, optionRight, strike, expirationDate); + return Symbol.CreateOption(ticker, market, OptionStyle.American, optionRight, strike, expirationDate); case SecurityType.IndexOption: // Index Options have their expiry offset from their last trading date by one day. We add one day // to get the expected expiration date. return Symbol.CreateOption( - Symbol.Create(IndexOptionSymbol.MapToUnderlying(brokerageSymbol), SecurityType.Index, market), - brokerageSymbol, + Symbol.Create(IndexOptionSymbol.MapToUnderlying(ticker), SecurityType.Index, market), + ticker, market, securityType.DefaultOptionStyle(), optionRight, strike, - IndexOptionSymbol.GetExpiryDate(brokerageSymbol, expirationDate)); + IndexOptionSymbol.GetExpiryDate(ticker, expirationDate)); case SecurityType.FutureOption: var future = FuturesOptionsUnderlyingMapper.GetUnderlyingFutureFromFutureOption( - GetLeanRootSymbol(brokerageSymbol), + GetLeanRootSymbol(ticker, securityType), market, expirationDate, DateTime.Now); @@ -206,13 +180,9 @@ public string GetBrokerageSymbol(Symbol symbol) optionRight, strike, expirationDate); - - case SecurityType.Equity: - brokerageSymbol = brokerageSymbol.Replace(" ", "."); - break; } - return Symbol.Create(brokerageSymbol, securityType, market); + return Symbol.Create(ticker, securityType, market); } catch (Exception exception) { @@ -226,11 +196,15 @@ public string GetBrokerageSymbol(Symbol symbol) /// /// LEAN root symbol /// - public string GetBrokerageRootSymbol(string rootSymbol) + public string GetBrokerageRootSymbol(string rootSymbol, SecurityType securityType) { - var brokerageSymbol = _ibNameMap.FirstOrDefault(kv => kv.Value == rootSymbol); + var brokerageSymbol = rootSymbol; + if (_ibNameMap.TryGetValue(securityType, out var symbolMap)) + { + brokerageSymbol = symbolMap.FirstOrDefault(kv => kv.Value == rootSymbol).Key; + } - return brokerageSymbol.Key ?? rootSymbol; + return brokerageSymbol ?? rootSymbol; } /// @@ -238,9 +212,18 @@ public string GetBrokerageRootSymbol(string rootSymbol) /// /// IB Brokerage root symbol /// - public string GetLeanRootSymbol(string brokerageRootSymbol) + public string GetLeanRootSymbol(string brokerageRootSymbol, SecurityType securityType) { - return _ibNameMap.ContainsKey(brokerageRootSymbol) ? _ibNameMap[brokerageRootSymbol] : brokerageRootSymbol; + var ticker = _ibNameMap.TryGetValue(securityType, out var symbolMap) && symbolMap.TryGetValue(brokerageRootSymbol, out var rootSymbol) + ? rootSymbol + : brokerageRootSymbol; + + if (securityType == SecurityType.Equity || securityType == SecurityType.Option) + { + ticker = ticker.Replace(" ", "."); + } + + return ticker; } /// @@ -271,7 +254,7 @@ public Contract ParseMalformedContractFutureSymbol(Contract malformedContract, S var contractMonthExpiration = DateTime.ParseExact(match[2].Value, "MMM", CultureInfo.CurrentCulture).Month; var contractYearExpiration = match[3].Value; - var leanSymbol = GetLeanRootSymbol(contractSymbol); + var leanSymbol = GetLeanRootSymbol(contractSymbol, SecurityType.Future); string market; if (!symbolPropertiesDatabase.TryGetMarket(leanSymbol, SecurityType.Future, out market)) { @@ -296,11 +279,16 @@ public Contract ParseMalformedContractFutureSymbol(Contract malformedContract, S private string GetMappedTicker(Symbol symbol) { + if (symbol.ID.SecurityType.IsOption()) + { + symbol = symbol.Underlying; + } + var ticker = symbol.ID.Symbol; if (symbol.ID.SecurityType == SecurityType.Equity) { var mapFile = _mapFileProvider.Get(AuxiliaryDataKey.Create(symbol)).ResolveMapFile(symbol); - ticker = mapFile.GetMappedSymbol(DateTime.UtcNow, symbol.Value); + ticker = mapFile.GetMappedSymbol(DateTime.UtcNow, symbol.Value).Replace(".", " "); } return ticker;