public void ExpiryFunctionsReturnExpectedResults(string futureTicker, string market, DateTime expected) { var future = Symbol.Create(futureTicker, SecurityType.Future, market); var futureOption = Symbol.CreateOption(future, market, default(OptionStyle), default(OptionRight), default(decimal), SecurityIdentifier.DefaultDate); var december = new DateTime(2020, 12, 1); var actual = FuturesOptionsExpiryFunctions.FuturesOptionExpiry(futureOption, december); Assert.AreEqual(expected, actual); }
/// <summary> /// Converts an InteractiveBrokers symbol to a Lean symbol instance /// </summary> /// <param name="brokerageSymbol">The InteractiveBrokers symbol</param> /// <param name="securityType">The security type</param> /// <param name="market">The market</param> /// <param name="expirationDate">Expiration date of the security(if applicable)</param> /// <param name="strike">The strike of the security (if applicable)</param> /// <param name="optionRight">The option right of the security (if applicable)</param> /// <returns>A new Lean Symbol instance</returns> public Symbol GetLeanSymbol(string brokerageSymbol, SecurityType securityType, string market, DateTime expirationDate = default(DateTime), decimal strike = 0, OptionRight optionRight = 0) { if (string.IsNullOrWhiteSpace(brokerageSymbol)) { throw new ArgumentException("Invalid symbol: " + brokerageSymbol); } if (securityType != SecurityType.Forex && securityType != SecurityType.Equity && securityType != SecurityType.Option && securityType != SecurityType.Future && securityType != SecurityType.FutureOption) { throw new ArgumentException("Invalid security type: " + securityType); } try { switch (securityType) { case SecurityType.Future: return(Symbol.CreateFuture(GetLeanRootSymbol(brokerageSymbol), market, expirationDate)); case SecurityType.Option: return(Symbol.CreateOption(brokerageSymbol, market, OptionStyle.American, optionRight, strike, expirationDate)); case SecurityType.FutureOption: var canonicalFutureSymbol = Symbol.Create(GetLeanRootSymbol(brokerageSymbol), SecurityType.Future, market); var futureContractMonth = FuturesOptionsExpiryFunctions.GetFutureContractMonth(canonicalFutureSymbol, expirationDate); var futureExpiry = FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFutureSymbol)(futureContractMonth); return(Symbol.CreateOption( Symbol.CreateFuture( brokerageSymbol, market, futureExpiry), market, OptionStyle.American, optionRight, strike, expirationDate)); case SecurityType.Equity: brokerageSymbol = brokerageSymbol.Replace(" ", "."); break; } return(Symbol.Create(brokerageSymbol, securityType, market)); } catch (Exception) { throw new ArgumentException($"Invalid symbol: {brokerageSymbol}, security type: {securityType}, market: {market}."); } }
[TestCase("GC", Market.COMEX, 11, 1)] // No mapping is done for this Symbol as expected, although rules exist. public void FutureContractMonthDelta(string futureTicker, string market, int expiryMonth, int expectedDelta) { var contractMonth = new DateTime(2020, 12, 1); var future = Symbol.Create(futureTicker, SecurityType.Future, market); var option = Symbol.CreateOption( future, market, default(OptionStyle), default(OptionRight), default(decimal), SecurityIdentifier.DefaultDate); var futureOptionExpiry = FuturesOptionsExpiryFunctions.FuturesOptionExpiry(option, contractMonth); Assert.AreEqual(expectedDelta, contractMonth.Month - futureOptionExpiry.Month); }
public void GetUnderlyingSymbolFromFutureOption(string futureTicker, string market, int year, int month, int day, int fopContractYear, int fopContractMonth, bool nullExpected) { var optionTicker = FuturesOptionsSymbolMappings.Map(futureTicker); var expectedFuture = Symbol.CreateFuture(futureTicker, market, new DateTime(year, month, day)); var canonicalFutureOption = Symbol.CreateOption(expectedFuture, market, default(OptionStyle), default(OptionRight), default(decimal), SecurityIdentifier.DefaultDate); var futureContractMonthDelta = FuturesExpiryUtilityFunctions.GetDeltaBetweenContractMonthAndContractExpiry(futureTicker, expectedFuture.ID.Date); var futureContractMonth = expectedFuture.ID.Date.AddMonths(futureContractMonthDelta); var futuresOptionsExpiration = FuturesOptionsExpiryFunctions.FuturesOptionExpiry(canonicalFutureOption, futureContractMonth); var actualFuture = FuturesOptionsUnderlyingMapper.GetUnderlyingFutureFromFutureOption(optionTicker, market, futuresOptionsExpiration, new DateTime(2021, 1, 1)); if (nullExpected) { // There were no futures that appeared on the or subsequent contract months from the future option. Assert.IsNull(actualFuture); } else { Assert.AreEqual(expectedFuture, actualFuture); } }
private IEnumerable <Symbol> GetFutureOptionContractList(Symbol futureContractSymbol, DateTime date) { var symbols = new List <Symbol>(); var retries = 0; var maxRetries = 5; while (++retries <= maxRetries) { try { _rateGate.WaitToProceed(); var productResponse = _client.GetAsync(CMEProductSlateURL.Replace(CMESymbolReplace, futureContractSymbol.ID.Symbol)) .SynchronouslyAwaitTaskResult(); productResponse.EnsureSuccessStatusCode(); var productResults = JsonConvert.DeserializeObject <CMEProductSlateV2ListResponse>(productResponse.Content .ReadAsStringAsync() .SynchronouslyAwaitTaskResult()); productResponse.Dispose(); // We want to gather the future product to get the future options ID var futureProductId = productResults.Products.Where(p => p.Globex == futureContractSymbol.ID.Symbol && p.GlobexTraded && p.Cleared == "Futures") .Select(p => p.Id) .Single(); var optionsTradesAndExpiries = CMEOptionsTradeDateAndExpirations.Replace(CMEProductCodeReplace, futureProductId.ToStringInvariant()); _rateGate.WaitToProceed(); var optionsTradesAndExpiriesResponse = _client.GetAsync(optionsTradesAndExpiries).SynchronouslyAwaitTaskResult(); optionsTradesAndExpiriesResponse.EnsureSuccessStatusCode(); var tradesAndExpiriesResponse = JsonConvert.DeserializeObject <List <CMEOptionsTradeDatesAndExpiration> >(optionsTradesAndExpiriesResponse.Content .ReadAsStringAsync() .SynchronouslyAwaitTaskResult()); optionsTradesAndExpiriesResponse.Dispose(); // For now, only support American options on CME var selectedOption = tradesAndExpiriesResponse .FirstOrDefault(x => !x.Daily && !x.Weekly && !x.Sto && x.OptionType == "AME"); if (selectedOption == null) { Log.Error($"LiveOptionChainProvider.GetFutureOptionContractList(): Found no matching future options for contract {futureContractSymbol}"); yield break; } // Gather the month code and the year's last number to query the next API, which expects an expiration as `<MONTH_CODE><YEAR_LAST_NUMBER>` var canonicalFuture = Symbol.Create(futureContractSymbol.ID.Symbol, SecurityType.Future, futureContractSymbol.ID.Market); var expiryFunction = FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFuture); var futureContractExpiration = selectedOption.Expirations .Select(x => new KeyValuePair <CMEOptionsExpiration, DateTime>(x, expiryFunction(new DateTime(x.Expiration.Year, x.Expiration.Month, 1)))) .FirstOrDefault(x => x.Value.Year == futureContractSymbol.ID.Date.Year && x.Value.Month == futureContractSymbol.ID.Date.Month) .Key; if (futureContractExpiration == null) { Log.Error($"LiveOptionChainProvider.GetFutureOptionContractList(): Found no future options with matching expiry year and month for contract {futureContractSymbol}"); yield break; } var futureContractMonthCode = futureContractExpiration.Expiration.Code; _rateGate.WaitToProceed(); // Subtract one day from now for settlement API since settlement may not be available for today yet var optionChainQuotesResponseResult = _client.GetAsync(CMEOptionChainQuotesURL .Replace(CMEProductCodeReplace, selectedOption.ProductId.ToStringInvariant()) .Replace(CMEProductExpirationReplace, futureContractMonthCode) + Math.Floor((DateTime.UtcNow - _epoch).TotalMilliseconds).ToStringInvariant()); optionChainQuotesResponseResult.Result.EnsureSuccessStatusCode(); var futureOptionChain = JsonConvert.DeserializeObject <CMEOptionChainQuotes>(optionChainQuotesResponseResult.Result.Content .ReadAsStringAsync() .SynchronouslyAwaitTaskResult()) .Quotes .DistinctBy(s => s.StrikePrice) .ToList(); optionChainQuotesResponseResult.Dispose(); // Each CME contract can have arbitrary scaling applied to the strike price, so we normalize it to the // underlying's price via static entries. var optionStrikePriceScaleFactor = CMEStrikePriceScalingFactors.GetScaleFactor(futureContractSymbol); var canonicalOption = Symbol.CreateOption( futureContractSymbol, futureContractSymbol.ID.Market, futureContractSymbol.SecurityType.DefaultOptionStyle(), default(OptionRight), default(decimal), SecurityIdentifier.DefaultDate); foreach (var optionChainEntry in futureOptionChain) { var futureOptionExpiry = FuturesOptionsExpiryFunctions.GetFutureOptionExpiryFromFutureExpiry(futureContractSymbol, canonicalOption); var scaledStrikePrice = optionChainEntry.StrikePrice / optionStrikePriceScaleFactor; // Calls and puts share the same strike, create two symbols per each to avoid iterating twice. symbols.Add(Symbol.CreateOption( futureContractSymbol, futureContractSymbol.ID.Market, OptionStyle.American, OptionRight.Call, scaledStrikePrice, futureOptionExpiry)); symbols.Add(Symbol.CreateOption( futureContractSymbol, futureContractSymbol.ID.Market, OptionStyle.American, OptionRight.Put, scaledStrikePrice, futureOptionExpiry)); } break; } catch (HttpRequestException err) { if (retries != maxRetries) { Log.Error(err, $"Failed to retrieve futures options chain from CME, retrying ({retries} / {maxRetries})"); continue; } Log.Error(err, $"Failed to retrieve futures options chain from CME, returning empty result ({retries} / {retries})"); } } foreach (var symbol in symbols) { yield return(symbol); } }
/// <summary> /// Creates a future option Symbol from the provided ticker /// </summary> /// <param name="ticker">The future option ticker, for example 'ESZ0 P3590'</param> /// <param name="strikeScale">Optional the future option strike scale factor</param> public static Symbol ParseFutureOptionSymbol(string ticker, int strikeScale = 1) { var split = ticker.Split(' '); if (split.Length != 2) { return(null); } var parsed = ParseFutureTicker(split[0]); if (parsed == null) { return(null); } ticker = parsed.Underlying; OptionRight right; if (split[1][0] == 'P' || split[1][0] == 'p') { right = OptionRight.Put; } else if (split[1][0] == 'C' || split[1][0] == 'c') { right = OptionRight.Call; } else { return(null); } var strike = split[1].Substring(1); if (parsed.ExpirationYearShort < 10) { parsed.ExpirationYearShort += 20; } var expirationYearParsed = 2000 + parsed.ExpirationYearShort; var expirationDate = new DateTime(expirationYearParsed, parsed.ExpirationMonth, 1); var strikePrice = decimal.Parse(strike, NumberStyles.Any, CultureInfo.InvariantCulture); var futureTicker = FuturesOptionsSymbolMappings.MapFromOption(ticker); if (!SymbolPropertiesDatabase.FromDataFolder().TryGetMarket(futureTicker, SecurityType.Future, out var market)) { Log.Debug($"SymbolRepresentation.ParseFutureOptionSymbol(): No market found for '{futureTicker}'"); return(null); } var canonicalFuture = Symbol.Create(futureTicker, SecurityType.Future, market); var futureExpiry = FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFuture)(expirationDate); var future = Symbol.CreateFuture(futureTicker, market, futureExpiry); var futureOptionExpiry = FuturesOptionsExpiryFunctions.GetFutureOptionExpiryFromFutureExpiry(future); return(Symbol.CreateOption(future, market, OptionStyle.American, right, strikePrice / strikeScale, futureOptionExpiry)); }