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