/* * 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 System; using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using QuantConnect.Data.Market; using QuantConnect.Python; using QuantConnect.Util; namespace QuantConnect.Data.UniverseSelection { /// /// Represents a universe of options data /// public class OptionUniverse : BaseChainUniverseData { /// /// Cache for the symbols to avoid creating them multiple times /// /// Key: securityType, market, ticker, expiry, strike, right private static readonly Dictionary<(SecurityType, string, string, DateTime, decimal, OptionRight), Symbol> _symbolsCache = new(); private const int StartingGreeksCsvIndex = 7; /// /// Open interest value of the option /// public override decimal OpenInterest { get { ThrowIfNotAnOption(nameof(OpenInterest)); return base.OpenInterest; } } /// /// Implied volatility value of the option /// public decimal ImpliedVolatility { get { ThrowIfNotAnOption(nameof(ImpliedVolatility)); return CsvLine.GetDecimalFromCsv(6); } } /// /// Greeks values of the option /// public Greeks Greeks { get { ThrowIfNotAnOption(nameof(Greeks)); return new PreCalculatedGreeks(CsvLine); } } /// /// Creates a new instance of the class /// public OptionUniverse() { } /// /// Creates a new instance of the class /// public OptionUniverse(DateTime date, Symbol symbol, string csv) : base(date, symbol, csv) { } /// /// Creates a new instance of the class as a copy of the given instance /// public OptionUniverse(OptionUniverse other) : base(other) { } /// /// Reader converts each line of the data source into BaseData objects. Each data type creates its own factory method, and returns a new instance of the object /// each time it is called. /// /// Subscription data config setup object /// Stream reader of the source document /// Date of the requested data /// true if we're in live mode, false for backtesting mode /// Instance of the T:BaseData object generated by this line of the CSV [StubsIgnore] public override BaseData Reader(SubscriptionDataConfig config, StreamReader stream, DateTime date, bool isLiveMode) { if (stream == null || stream.EndOfStream) { return null; } var firstChar = (char)stream.Peek(); if (firstChar == '#') { // Skip header stream.ReadLine(); return null; } Symbol symbol; if (!char.IsDigit(firstChar)) { // This is the underlying line symbol = config.Symbol.Underlying; // Skip the first 3 cells, expiry, strike and right, which will be empty for the underlying stream.GetChar(); stream.GetChar(); stream.GetChar(); } else { var expiry = stream.GetDateTime("yyyyMMdd"); var strike = stream.GetDecimal(); var right = char.ToUpperInvariant(stream.GetChar()) == 'C' ? OptionRight.Call : OptionRight.Put; var targetOption = config.Symbol.SecurityType != SecurityType.IndexOption ? null : config.Symbol.ID.Symbol; var cacheKey = (config.SecurityType, config.Market, targetOption ?? config.Symbol.Underlying.Value, expiry, strike, right); if (!TryGetCachedSymbol(cacheKey, out symbol)) { symbol = Symbol.CreateOption(config.Symbol.Underlying, targetOption, config.Symbol.ID.Market, config.Symbol.SecurityType.DefaultOptionStyle(), right, strike, expiry); CacheSymbol(cacheKey, symbol); } } return new OptionUniverse(date, symbol, stream.ReadLine()); } /// /// Adds a new data point to this collection. /// If the data point is for the underlying, it will be stored in the property. /// /// The new data point to add public override void Add(BaseData newDataPoint) { if (newDataPoint is BaseChainUniverseData optionUniverseDataPoint) { if (optionUniverseDataPoint.Symbol.HasUnderlying) { optionUniverseDataPoint.Underlying = Underlying; base.Add(optionUniverseDataPoint); } else { Underlying = optionUniverseDataPoint; foreach (BaseChainUniverseData data in Data) { data.Underlying = optionUniverseDataPoint; } } } } /// /// Creates a copy of the instance /// /// Clone of the instance public override BaseData Clone() { return new OptionUniverse(this); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string GetOptionSymbolCsv(Symbol symbol) { if (!symbol.SecurityType.IsOption()) { return ",,"; } return $"{symbol.ID.Date:yyyyMMdd},{symbol.ID.StrikePrice},{(symbol.ID.OptionRight == OptionRight.Call ? 'C' : 'P')}"; } /// /// Gets the CSV string representation of this universe entry /// public static string ToCsv(Symbol symbol, decimal open, decimal high, decimal low, decimal close, decimal volume, decimal? openInterest, decimal? impliedVolatility, Greeks greeks) { if (symbol.SecurityType == SecurityType.Future || symbol.SecurityType == SecurityType.FutureOption) { return $"{GetOptionSymbolCsv(symbol)},{open},{high},{low},{close},{volume},{openInterest}"; } return $"{GetOptionSymbolCsv(symbol)},{open},{high},{low},{close},{volume}," + $"{openInterest},{impliedVolatility},{greeks?.Delta},{greeks?.Gamma},{greeks?.Vega},{greeks?.Theta},{greeks?.Rho}"; } /// /// Implicit conversion into /// /// The option universe data to be converted #pragma warning disable CA2225 // Operator overloads have named alternates public static implicit operator Symbol(OptionUniverse data) #pragma warning restore CA2225 // Operator overloads have named alternates { return data.Symbol; } /// /// Gets the CSV header string for this universe entry /// public static string CsvHeader(SecurityType securityType) { // FOPs don't have greeks if (securityType == SecurityType.FutureOption || securityType == SecurityType.Future) { return "expiry,strike,right,open,high,low,close,volume,open_interest"; } return "expiry,strike,right,open,high,low,close,volume,open_interest,implied_volatility,delta,gamma,vega,theta,rho"; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ThrowIfNotAnOption(string propertyName) { if (!Symbol.SecurityType.IsOption()) { throw new InvalidOperationException($"{propertyName} is only available for options."); } } /// /// Pre-calculated greeks lazily parsed from csv line. /// It parses the greeks values from the csv line only when they are requested to avoid holding decimals in memory. /// private class PreCalculatedGreeks : Greeks { private readonly string _csvLine; public override decimal Delta => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex); public override decimal Gamma => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 1); public override decimal Vega => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 2); public override decimal Theta => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 3); public override decimal Rho => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 4); [PandasIgnore] public override decimal Lambda => decimal.Zero; /// /// Initializes a new default instance of the class /// public PreCalculatedGreeks(string csvLine) { _csvLine = csvLine; } /// /// Gets a string representation of the greeks values /// public override string ToString() { return $"D: {Delta}, G: {Gamma}, V: {Vega}, T: {Theta}, R: {Rho}"; } } /// /// Tries to get a symbol from the cache /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected static bool TryGetCachedSymbol((SecurityType, string, string, DateTime, decimal, OptionRight) key, out Symbol symbol) { lock (_symbolsCache) { return _symbolsCache.TryGetValue(key, out symbol); } } /// /// Caches a symbol /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected static void CacheSymbol((SecurityType, string, string, DateTime, decimal, OptionRight) key, Symbol symbol) { lock (_symbolsCache) { // limit the cache size to help with memory usage if (_symbolsCache.Count >= 500000) { _symbolsCache.Clear(); } _symbolsCache.TryAdd(key, symbol); } } } }