Lean  $LEAN_TAG$
OptionUniverse.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.IO;
18 using System.Runtime.CompilerServices;
20 using QuantConnect.Python;
22 using QuantConnect.Util;
23 
25 {
26  /// <summary>
27  /// Represents a universe of options data
28  /// </summary>
30  {
31  private const int StartingGreeksCsvIndex = 7;
32 
33  // We keep the properties as they are in the csv file to reduce memory usage (strings vs decimals)
34  private readonly string _csvLine;
35 
36  /// <summary>
37  /// The security identifier of the option symbol
38  /// </summary>
39  [PandasIgnore]
41 
42  /// <summary>
43  /// Price of the option/underlying
44  /// </summary>
45  [PandasIgnore]
46  public override decimal Value => Close;
47 
48  /// <summary>
49  /// Open price of the option/underlying
50  /// </summary>
51  public decimal Open
52  {
53  get
54  {
55  // Parse the values every time to avoid keeping them in memory
56  return _csvLine.GetDecimalFromCsv(0);
57  }
58  }
59 
60  /// <summary>
61  /// High price of the option/underlying
62  /// </summary>
63  public decimal High
64  {
65  get
66  {
67  return _csvLine.GetDecimalFromCsv(1);
68  }
69  }
70 
71  /// <summary>
72  /// Low price of the option/underlying
73  /// </summary>
74  public decimal Low
75  {
76  get
77  {
78  return _csvLine.GetDecimalFromCsv(2);
79  }
80  }
81 
82  /// <summary>
83  /// Close price of the option/underlying
84  /// </summary>
85  public decimal Close
86  {
87  get
88  {
89  return _csvLine.GetDecimalFromCsv(3);
90  }
91  }
92 
93  /// <summary>
94  /// Volume of the option/underlying
95  /// </summary>
96  public decimal Volume
97  {
98  get
99  {
100  return _csvLine.GetDecimalFromCsv(4);
101  }
102  }
103 
104  /// <summary>
105  /// Open interest value of the option
106  /// </summary>
107  public decimal OpenInterest
108  {
109  get
110  {
111  ThrowIfNotAnOption(nameof(OpenInterest));
112  return _csvLine.GetDecimalFromCsv(5);
113  }
114  }
115 
116  /// <summary>
117  /// Implied volatility value of the option
118  /// </summary>
119  public decimal ImpliedVolatility
120  {
121  get
122  {
123  ThrowIfNotAnOption(nameof(ImpliedVolatility));
124  return _csvLine.GetDecimalFromCsv(6);
125  }
126  }
127 
128  /// <summary>
129  /// Greeks values of the option
130  /// </summary>
131  public Greeks Greeks
132  {
133  get
134  {
135  ThrowIfNotAnOption(nameof(Greeks));
136  return new PreCalculatedGreeks(_csvLine);
137  }
138  }
139 
140  /// <summary>
141  /// Time that the data became available to use
142  /// </summary>
143  public override DateTime EndTime
144  {
145  get { return Time + QuantConnect.Time.OneDay; }
146  set { Time = value - QuantConnect.Time.OneDay; }
147  }
148 
149  /// <summary>
150  /// Creates a new instance of the <see cref="OptionUniverse"/> class
151  /// </summary>
152  public OptionUniverse()
153  {
154  }
155 
156  /// <summary>
157  /// Creates a new instance of the <see cref="OptionUniverse"/> class
158  /// </summary>
159  public OptionUniverse(DateTime date, Symbol symbol, string csv)
160  : base(date, date, symbol, null, null)
161  {
162  _csvLine = csv;
163  }
164 
165  /// <summary>
166  /// Creates a new instance of the <see cref="OptionUniverse"/> class as a copy of the given instance
167  /// </summary>
169  : base(other)
170  {
171  _csvLine = other._csvLine;
172  }
173 
174  /// <summary>
175  /// Return the URL string source of the file. This will be converted to a stream
176  /// </summary>
177  /// <param name="config">Configuration object</param>
178  /// <param name="date">Date of this source file</param>
179  /// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
180  /// <returns>String URL of source file.</returns>
181  public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
182  {
184  path = Path.Combine(path, $"{date:yyyyMMdd}.csv");
185 
186  return new SubscriptionDataSource(path, SubscriptionTransportMedium.LocalFile, FileFormat.FoldingCollection);
187  }
188 
189  /// <summary>
190  /// 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
191  /// each time it is called.
192  /// </summary>
193  /// <param name="config">Subscription data config setup object</param>
194  /// <param name="stream">Stream reader of the source document</param>
195  /// <param name="date">Date of the requested data</param>
196  /// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
197  /// <returns>Instance of the T:BaseData object generated by this line of the CSV</returns>
198  public override BaseData Reader(SubscriptionDataConfig config, StreamReader stream, DateTime date, bool isLiveMode)
199  {
200  if (stream == null || stream.EndOfStream)
201  {
202  return null;
203  }
204 
205  var sidStr = stream.GetString();
206 
207  if (sidStr.StartsWith("#", StringComparison.InvariantCulture))
208  {
209  stream.ReadLine();
210  return null;
211  }
212 
213  var symbolValue = stream.GetString();
214  var remainingLine = stream.ReadLine();
215 
216  var key = $"{sidStr}:{symbolValue}";
217 
218  if (!TryGetCachedSymbol(key, out var symbol))
219  {
220  var sid = SecurityIdentifier.Parse(sidStr);
221 
222  if (sid.HasUnderlying)
223  {
224  // Let's try to get the underlying symbol from the cache
225  SymbolRepresentation.TryDecomposeOptionTickerOSI(symbolValue, sid.SecurityType,
226  out var _, out var underlyingValue, out var _, out var _, out var _);
227  var underlyingKey = $"{sid.Underlying}:{underlyingValue}";
228  var underlyingWasCached = TryGetCachedSymbol(underlyingKey, out var underlyingSymbol);
229 
230  symbol = Symbol.CreateOption(sid, symbolValue, underlyingSymbol);
231 
232  if (!underlyingWasCached)
233  {
234  CacheSymbol(underlyingKey, symbol.Underlying);
235  }
236  }
237  else
238  {
239  symbol = new Symbol(sid, symbolValue);
240  }
241 
242  CacheSymbol(key, symbol);
243  }
244 
245  return new OptionUniverse(date, symbol, remainingLine);
246  }
247 
248  /// <summary>
249  /// Adds a new data point to this collection.
250  /// If the data point is for the underlying, it will be stored in the <see cref="BaseDataCollection.Underlying"/> property.
251  /// </summary>
252  /// <param name="newDataPoint">The new data point to add</param>
253  public override void Add(BaseData newDataPoint)
254  {
255  if (newDataPoint is OptionUniverse optionUniverseDataPoint)
256  {
257  if (optionUniverseDataPoint.Symbol.HasUnderlying)
258  {
259  optionUniverseDataPoint.Underlying = Underlying;
260  base.Add(optionUniverseDataPoint);
261  }
262  else
263  {
264  Underlying = optionUniverseDataPoint;
265  foreach (OptionUniverse data in Data)
266  {
267  data.Underlying = optionUniverseDataPoint;
268  }
269  }
270  }
271  }
272 
273  /// <summary>
274  /// Creates a copy of the instance
275  /// </summary>
276  /// <returns>Clone of the instance</returns>
277  public override BaseData Clone()
278  {
279  return new OptionUniverse(this);
280  }
281 
282  /// <summary>
283  /// Gets the default resolution for this data and security type
284  /// </summary>
285  /// <remarks>This is a method and not a property so that python
286  /// custom data types can override it</remarks>
287  public override Resolution DefaultResolution()
288  {
289  return Resolution.Daily;
290  }
291 
292  /// <summary>
293  /// Gets the CSV string representation of this universe entry
294  /// </summary>
295  public static string ToCsv(Symbol symbol, decimal open, decimal high, decimal low, decimal close, decimal volume, decimal? openInterest,
296  decimal? impliedVolatility, Greeks greeks)
297  {
298  return $"{symbol.ID},{symbol.Value},{open},{high},{low},{close},{volume},"
299  + $"{openInterest},{impliedVolatility},{greeks?.Delta},{greeks?.Gamma},{greeks?.Vega},{greeks?.Theta},{greeks?.Rho}";
300  }
301 
302  /// <summary>
303  /// Implicit conversion into <see cref="Symbol"/>
304  /// </summary>
305  /// <param name="data">The option universe data to be converted</param>
306  public static implicit operator Symbol(OptionUniverse data)
307  {
308  return data.Symbol;
309  }
310 
311  /// <summary>
312  /// Gets the symbol of the option
313  /// </summary>
314  public Symbol ToSymbol()
315  {
316  return (Symbol)this;
317  }
318 
319  /// <summary>
320  /// Gets the CSV header string for this universe entry
321  /// </summary>
322  public static string CsvHeader => "symbol_id,symbol_value,open,high,low,close,volume,open_interest,implied_volatility,delta,gamma,vega,theta,rho";
323 
324  [MethodImpl(MethodImplOptions.AggressiveInlining)]
325  private void ThrowIfNotAnOption(string propertyName)
326  {
327  if (!Symbol.SecurityType.IsOption())
328  {
329  throw new InvalidOperationException($"{propertyName} is only available for options.");
330  }
331  }
332 
333  /// <summary>
334  /// Pre-calculated greeks lazily parsed from csv line.
335  /// It parses the greeks values from the csv line only when they are requested to avoid holding decimals in memory.
336  /// </summary>
337  private class PreCalculatedGreeks : Greeks
338  {
339  private readonly string _csvLine;
340 
341  public override decimal Delta => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex);
342 
343  public override decimal Gamma => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 1);
344 
345  public override decimal Vega => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 2);
346 
347  public override decimal Theta => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 3);
348 
349  public override decimal Rho => _csvLine.GetDecimalFromCsv(StartingGreeksCsvIndex + 4);
350 
351  [PandasIgnore]
352  public override decimal Lambda => decimal.Zero;
353 
354  /// <summary>
355  /// Initializes a new default instance of the <see cref="PreCalculatedGreeks"/> class
356  /// </summary>
357  public PreCalculatedGreeks(string csvLine)
358  {
359  _csvLine = csvLine;
360  }
361 
362  /// <summary>
363  /// Gets a string representation of the greeks values
364  /// </summary>
365  public override string ToString()
366  {
367  return $"D: {Delta}, G: {Gamma}, V: {Vega}, T: {Theta}, R: {Rho}";
368  }
369  }
370  }
371 }