Lean  $LEAN_TAG$
OptionPositionCollection.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.Linq;
18 using QuantConnect.Util;
19 using System.Collections;
20 using System.Collections.Generic;
21 using System.Collections.Immutable;
23 
25 {
26  /// <summary>
27  /// Provides indexing of option contracts
28  /// </summary>
29  public class OptionPositionCollection : IEnumerable<OptionPosition>
30  {
31  /// <summary>
32  /// Gets an empty instance of <see cref="OptionPositionCollection"/>
33  /// </summary>
35  ImmutableDictionary<Symbol, OptionPosition>.Empty,
36  ImmutableDictionary<OptionRight, ImmutableHashSet<Symbol>>.Empty,
37  ImmutableDictionary<PositionSide, ImmutableHashSet<Symbol>>.Empty,
38  ImmutableSortedDictionary<decimal, ImmutableHashSet<Symbol>>.Empty,
39  ImmutableSortedDictionary<DateTime, ImmutableHashSet<Symbol>>.Empty
40  );
41 
42  private readonly ImmutableDictionary<Symbol, OptionPosition> _positions;
43  private readonly ImmutableDictionary<OptionRight, ImmutableHashSet<Symbol>> _rights;
44  private readonly ImmutableDictionary<PositionSide, ImmutableHashSet<Symbol>> _sides;
45  private readonly ImmutableSortedDictionary<decimal, ImmutableHashSet<Symbol>> _strikes;
46  private readonly ImmutableSortedDictionary<DateTime, ImmutableHashSet<Symbol>> _expirations;
47 
48  /// <summary>
49  /// Gets the underlying security's symbol
50  /// </summary>
52 
53  /// <summary>
54  /// Gets the total count of unique positions, including the underlying
55  /// </summary>
56  public int Count => _positions.Count;
57 
58  /// <summary>
59  /// Gets whether or not there's any positions in this collection.
60  /// </summary>
61  public bool IsEmpty => _positions.IsEmpty;
62 
63  /// <summary>
64  /// Gets the quantity of underlying shares held
65  /// TODO : Change to UnderlyingLots
66  /// </summary>
68 
69  /// <summary>
70  /// Gets the number of unique put contracts held (long or short)
71  /// </summary>
72  public int UniquePuts => _rights[OptionRight.Put].Count;
73 
74  /// <summary>
75  /// Gets the unique number of expirations
76  /// </summary>
77  public int UniqueExpirations => _expirations.Count;
78 
79  /// <summary>
80  /// Gets the number of unique call contracts held (long or short)
81  /// </summary>
82  public int UniqueCalls => _rights[OptionRight.Call].Count;
83 
84  /// <summary>
85  /// Determines if this collection contains a position in the underlying
86  /// </summary>
87  public bool HasUnderlying => UnderlyingQuantity != 0;
88 
89  /// <summary>
90  /// Gets the <see cref="Underlying"/> position
91  /// </summary>
93 
94  /// <summary>
95  /// Gets all unique strike prices in the collection, in ascending order.
96  /// </summary>
97  public IEnumerable<decimal> Strikes => _strikes.Keys;
98 
99  /// <summary>
100  /// Gets all unique expiration dates in the collection, in chronological order.
101  /// </summary>
102  public IEnumerable<DateTime> Expirations => _expirations.Keys;
103 
104  /// <summary>
105  /// Initializes a new instance of the <see cref="OptionPositionCollection"/> class
106  /// </summary>
107  /// <param name="positions">All positions</param>
108  /// <param name="rights">Index of position symbols by option right</param>
109  /// <param name="sides">Index of position symbols by position side (short/long/none)</param>
110  /// <param name="strikes">Index of position symbols by strike price</param>
111  /// <param name="expirations">Index of position symbols by expiration</param>
113  ImmutableDictionary<Symbol, OptionPosition> positions,
114  ImmutableDictionary<OptionRight, ImmutableHashSet<Symbol>> rights,
115  ImmutableDictionary<PositionSide, ImmutableHashSet<Symbol>> sides,
116  ImmutableSortedDictionary<decimal, ImmutableHashSet<Symbol>> strikes,
117  ImmutableSortedDictionary<DateTime, ImmutableHashSet<Symbol>> expirations
118  )
119  {
120  _sides = sides;
121  _rights = rights;
122  _strikes = strikes;
123  _positions = positions;
124  _expirations = expirations;
125 
126  if (_rights.Count != 2)
127  {
128  // ensure we always have both rights indexed, even if empty
129  ImmutableHashSet<Symbol> value;
130  if (!_rights.TryGetValue(OptionRight.Call, out value))
131  {
132  _rights = _rights.SetItem(OptionRight.Call, ImmutableHashSet<Symbol>.Empty);
133  }
134  if (!_rights.TryGetValue(OptionRight.Put, out value))
135  {
136  _rights = _rights.SetItem(OptionRight.Put, ImmutableHashSet<Symbol>.Empty);
137  }
138  }
139 
140  if (_sides.Count != 3)
141  {
142  // ensure we always have all three sides indexed, even if empty
143  ImmutableHashSet<Symbol> value;
144  if (!_sides.TryGetValue(PositionSide.None, out value))
145  {
146  _sides = _sides.SetItem(PositionSide.None, ImmutableHashSet<Symbol>.Empty);
147  }
148  if (!_sides.TryGetValue(PositionSide.Short, out value))
149  {
150  _sides = _sides.SetItem(PositionSide.Short, ImmutableHashSet<Symbol>.Empty);
151  }
152  if (!_sides.TryGetValue(PositionSide.Long, out value))
153  {
154  _sides = _sides.SetItem(PositionSide.Long, ImmutableHashSet<Symbol>.Empty);
155  }
156  }
157 
158  if (!positions.IsEmpty)
159  {
160  // assumption here is that 'positions' includes the underlying equity position and
161  // ONLY option contracts, so all symbols have the underlying equity symbol embedded
162  // via the Underlying property, except of course, for the underlying itself.
163  var underlying = positions.First().Key;
164  if (underlying.HasUnderlying)
165  {
166  underlying = underlying.Underlying;
167  }
168 
169  // OptionPosition is struct, so no worry about null ref via .Quantity
170  var underlyingQuantity = positions.GetValueOrDefault(underlying).Quantity;
171  UnderlyingPosition = new OptionPosition(underlying, underlyingQuantity);
172  }
173 #if DEBUG
174  var errors = Validate().ToList();
175  if (errors.Count > 0)
176  {
177  throw new ArgumentException("OptionPositionCollection validation failed: "
178  + Environment.NewLine + string.Join(Environment.NewLine, errors)
179  );
180  }
181 #endif
182  }
183 
184  /// <summary>
185  /// Determines if a position is held in the specified <paramref name="symbol"/>
186  /// </summary>
187  public bool HasPosition(Symbol symbol)
188  {
189  OptionPosition position;
190  return TryGetPosition(symbol, out position) && position.Quantity != 0;
191  }
192 
193  /// <summary>
194  /// Retrieves the <see cref="OptionPosition"/> for the specified <paramref name="symbol"/>
195  /// if one exists in this collection.
196  /// </summary>
197  public bool TryGetPosition(Symbol symbol, out OptionPosition position)
198  {
199  return _positions.TryGetValue(symbol, out position);
200  }
201 
202  /// <summary>
203  /// Creates a new <see cref="OptionPositionCollection"/> from the specified enumerable of <paramref name="positions"/>
204  /// </summary>
205  public static OptionPositionCollection FromPositions(IEnumerable<OptionPosition> positions)
206  {
207  return Empty.AddRange(positions);
208  }
209 
210  /// <summary>
211  /// Creates a new <see cref="OptionPositionCollection"/> from the specified enumerable of <paramref name="positions"/>
212  /// </summary>
213  public static OptionPositionCollection FromPositions(IEnumerable<IPosition> positions, decimal contractMultiplier)
214  {
215  return Empty.AddRange(positions.Select(position =>
216  {
217  var quantity = (int)position.Quantity;
218  if (position.Symbol.SecurityType.HasOptions())
219  {
220  quantity = (int) (quantity / contractMultiplier);
221  }
222  return new OptionPosition(position.Symbol, quantity);
223  }));
224  }
225 
226  /// <summary>
227  /// Creates a new <see cref="OptionPositionCollection"/> from the specified <paramref name="holdings"/>,
228  /// filtering based on the <paramref name="underlying"/>
229  /// </summary>
230  public static OptionPositionCollection Create(Symbol underlying, decimal contractMultiplier, IEnumerable<SecurityHolding> holdings)
231  {
232  var positions = Empty;
233  foreach (var holding in holdings)
234  {
235  var symbol = holding.Symbol;
236  if (!symbol.HasUnderlying)
237  {
238  if (symbol == underlying)
239  {
240  var underlyingLots = (int) (holding.Quantity / contractMultiplier);
241  positions = positions.Add(new OptionPosition(symbol, underlyingLots));
242  }
243 
244  continue;
245  }
246 
247  if (symbol.Underlying != underlying)
248  {
249  continue;
250  }
251 
252  var position = new OptionPosition(symbol, (int) holding.Quantity);
253  positions = positions.Add(position);
254  }
255 
256  return positions;
257  }
258 
259  /// <summary>
260  /// Creates a new collection that is the result of adding the specified <paramref name="position"/> to this collection.
261  /// </summary>
263  {
264  if (!position.HasQuantity)
265  {
266  // adding nothing doesn't change the collection
267  return this;
268  }
269 
270  var sides = _sides;
271  var rights = _rights;
272  var strikes = _strikes;
273  var positions = _positions;
274  var expirations = _expirations;
275 
276  var exists = false;
277  OptionPosition existing;
278  var symbol = position.Symbol;
279  if (positions.TryGetValue(symbol, out existing))
280  {
281  exists = true;
282  position += existing;
283  }
284 
285  if (position.HasQuantity)
286  {
287  positions = positions.SetItem(symbol, position);
288  if (!exists && symbol.HasUnderlying)
289  {
290  // update indexes when adding a new option contract
291  sides = sides.Add(position.Side, symbol);
292  rights = rights.Add(position.Right, symbol);
293  strikes = strikes.Add(position.Strike, symbol);
294  positions = positions.SetItem(symbol, position);
295  expirations = expirations.Add(position.Expiration, symbol);
296  }
297  }
298  else
299  {
300  // if the position's quantity went to zero, remove it entirely from the collection when
301  // removing, be sure to remove strike/expiration indexes. we purposefully keep the rights
302  // index populated, even with a zero count entry because it's bounded to 2 items (put/call)
303 
304  positions = positions.Remove(symbol);
305  if (symbol.HasUnderlying)
306  {
307  // keep call/put entries even if goes to zero
308  var rightsValue = rights[position.Right].Remove(symbol);
309  rights = rights.SetItem(position.Right, rightsValue);
310 
311  // keep short/none/long entries even if goes to zero
312  var sidesValue = sides[position.Side].Remove(symbol);
313  sides = sides.SetItem(position.Side, sidesValue);
314 
315  var strikesValue = strikes[position.Strike].Remove(symbol);
316  strikes = strikesValue.Count > 0
317  ? strikes.SetItem(position.Strike, strikesValue)
318  : strikes.Remove(position.Strike);
319 
320  var expirationsValue = expirations[position.Expiration].Remove(symbol);
321  expirations = expirationsValue.Count > 0
322  ? expirations.SetItem(position.Expiration, expirationsValue)
323  : expirations.Remove(position.Expiration);
324  }
325  }
326 
327  return new OptionPositionCollection(positions, rights, sides, strikes, expirations);
328  }
329 
330  /// <summary>
331  /// Creates a new collection that is the result of removing the specified <paramref name="position"/>
332  /// </summary>
334  {
335  return Add(position.Negate());
336  }
337 
338  /// <summary>
339  /// Creates a new collection that is the result of adding the specified <paramref name="positions"/> to this collection.
340  /// </summary>
342  {
343  return AddRange((IEnumerable<OptionPosition>) positions);
344  }
345 
346  /// <summary>
347  /// Creates a new collection that is the result of adding the specified <paramref name="positions"/> to this collection.
348  /// </summary>
349  public OptionPositionCollection AddRange(IEnumerable<OptionPosition> positions)
350  {
351  return positions.Aggregate(this, (current, position) => current + position);
352  }
353 
354  /// <summary>
355  /// Creates a new collection that is the result of removing the specified <paramref name="positions"/>
356  /// </summary>
357  public OptionPositionCollection RemoveRange(IEnumerable<OptionPosition> positions)
358  {
359  return AddRange(positions.Select(position => position.Negate()));
360  }
361 
362  /// <summary>
363  /// Slices this collection, returning a new collection containing only
364  /// positions with the specified <paramref name="right"/>
365  /// </summary>
366  public OptionPositionCollection Slice(OptionRight right, bool includeUnderlying = true)
367  {
368  var rights = _rights.Remove(right.Invert());
369 
370  var positions = ImmutableDictionary<Symbol, OptionPosition>.Empty;
371  if (includeUnderlying && HasUnderlying)
372  {
373  positions = positions.Add(Underlying, UnderlyingPosition);
374  }
375 
376  var sides = ImmutableDictionary<PositionSide, ImmutableHashSet<Symbol>>.Empty;
377  var strikes = ImmutableSortedDictionary<decimal, ImmutableHashSet<Symbol>>.Empty;
378  var expirations = ImmutableSortedDictionary<DateTime, ImmutableHashSet<Symbol>>.Empty;
379  foreach (var symbol in rights.SelectMany(kvp => kvp.Value))
380  {
381  var position = _positions[symbol];
382  sides = sides.Add(position.Side, symbol);
383  positions = positions.Add(symbol, position);
384  strikes = strikes.Add(position.Strike, symbol);
385  expirations = expirations.Add(position.Expiration, symbol);
386  }
387 
388  return new OptionPositionCollection(positions, rights, sides, strikes, expirations);
389  }
390 
391  /// <summary>
392  /// Slices this collection, returning a new collection containing only
393  /// positions with the specified <paramref name="side"/>
394  /// </summary>
395  public OptionPositionCollection Slice(PositionSide side, bool includeUnderlying = true)
396  {
397  var otherSides = GetOtherSides(side);
398  var sides = _sides.Remove(otherSides[0]).Remove(otherSides[1]);
399 
400  var positions = ImmutableDictionary<Symbol, OptionPosition>.Empty;
401  if (includeUnderlying && HasUnderlying)
402  {
403  positions = positions.Add(Underlying, UnderlyingPosition);
404  }
405 
406  var rights = ImmutableDictionary<OptionRight, ImmutableHashSet<Symbol>>.Empty;
407  var strikes = ImmutableSortedDictionary<decimal, ImmutableHashSet<Symbol>>.Empty;
408  var expirations = ImmutableSortedDictionary<DateTime, ImmutableHashSet<Symbol>>.Empty;
409  foreach (var symbol in sides.SelectMany(kvp => kvp.Value))
410  {
411  var position = _positions[symbol];
412  rights = rights.Add(position.Right, symbol);
413  positions = positions.Add(symbol, position);
414  strikes = strikes.Add(position.Strike, symbol);
415  expirations = expirations.Add(position.Expiration, symbol);
416  }
417 
418  return new OptionPositionCollection(positions, rights, sides, strikes, expirations);
419  }
420 
421  /// <summary>
422  /// Slices this collection, returning a new collection containing only
423  /// positions matching the specified <paramref name="comparison"/> and <paramref name="strike"/>
424  /// </summary>
425  public OptionPositionCollection Slice(BinaryComparison comparison, decimal strike, bool includeUnderlying = true)
426  {
427  var strikes = comparison.Filter(_strikes, strike);
428  if (strikes.IsEmpty)
429  {
430  return includeUnderlying && HasUnderlying ? Empty.Add(UnderlyingPosition) : Empty;
431  }
432 
433  var positions = ImmutableDictionary<Symbol, OptionPosition>.Empty;
434  if (includeUnderlying)
435  {
436  OptionPosition underlyingPosition;
437  if (_positions.TryGetValue(Underlying, out underlyingPosition))
438  {
439  positions = positions.Add(Underlying, underlyingPosition);
440  }
441  }
442 
443  var sides = ImmutableDictionary<PositionSide, ImmutableHashSet<Symbol>>.Empty;
444  var rights = ImmutableDictionary<OptionRight, ImmutableHashSet<Symbol>>.Empty;
445  var expirations = ImmutableSortedDictionary<DateTime, ImmutableHashSet<Symbol>>.Empty;
446  foreach (var symbol in strikes.SelectMany(kvp => kvp.Value))
447  {
448  var position = _positions[symbol];
449  sides = sides.Add(position.Side, symbol);
450  positions = positions.Add(symbol, position);
451  rights = rights.Add(symbol.ID.OptionRight, symbol);
452  expirations = expirations.Add(symbol.ID.Date, symbol);
453  }
454 
455  return new OptionPositionCollection(positions, rights, sides, strikes, expirations);
456  }
457 
458  /// <summary>
459  /// Slices this collection, returning a new collection containing only
460  /// positions matching the specified <paramref name="comparison"/> and <paramref name="expiration"/>
461  /// </summary>
462  public OptionPositionCollection Slice(BinaryComparison comparison, DateTime expiration, bool includeUnderlying = true)
463  {
464  var expirations = comparison.Filter(_expirations, expiration);
465  if (expirations.IsEmpty)
466  {
467  return includeUnderlying && HasUnderlying ? Empty.Add(UnderlyingPosition) : Empty;
468  }
469 
470  var positions = ImmutableDictionary<Symbol, OptionPosition>.Empty;
471  if (includeUnderlying)
472  {
473  OptionPosition underlyingPosition;
474  if (_positions.TryGetValue(Underlying, out underlyingPosition))
475  {
476  positions = positions.Add(Underlying, underlyingPosition);
477  }
478  }
479 
480  var sides = ImmutableDictionary<PositionSide, ImmutableHashSet<Symbol>>.Empty;
481  var rights = ImmutableDictionary<OptionRight, ImmutableHashSet<Symbol>>.Empty;
482  var strikes = ImmutableSortedDictionary<decimal, ImmutableHashSet<Symbol>>.Empty;
483  foreach (var symbol in expirations.SelectMany(kvp => kvp.Value))
484  {
485  var position = _positions[symbol];
486  sides = sides.Add(position.Side, symbol);
487  positions = positions.Add(symbol, position);
488  rights = rights.Add(symbol.ID.OptionRight, symbol);
489  strikes = strikes.Add(symbol.ID.StrikePrice, symbol);
490  }
491 
492  return new OptionPositionCollection(positions, rights, sides, strikes, expirations);
493  }
494 
495  /// <summary>
496  /// Returns the set of <see cref="OptionPosition"/> with the specified <paramref name="symbols"/>
497  /// </summary>
498  public IEnumerable<OptionPosition> ForSymbols(IEnumerable<Symbol> symbols)
499  {
500  foreach (var symbol in symbols)
501  {
502  OptionPosition position;
503  if (_positions.TryGetValue(symbol, out position))
504  {
505  yield return position;
506  }
507  }
508  }
509 
510  /// <summary>
511  /// Returns the set of <see cref="OptionPosition"/> with the specified <paramref name="right"/>
512  /// </summary>
513  public IEnumerable<OptionPosition> ForRight(OptionRight right)
514  {
515  ImmutableHashSet<Symbol> symbols;
516  return _rights.TryGetValue(right, out symbols)
517  ? ForSymbols(symbols)
518  : Enumerable.Empty<OptionPosition>();
519  }
520 
521  /// <summary>
522  /// Returns the set of <see cref="OptionPosition"/> with the specified <paramref name="side"/>
523  /// </summary>
524  public IEnumerable<OptionPosition> ForSide(PositionSide side)
525  {
526  ImmutableHashSet<Symbol> symbols;
527  return _sides.TryGetValue(side, out symbols)
528  ? ForSymbols(symbols)
529  : Enumerable.Empty<OptionPosition>();
530  }
531 
532  /// <summary>
533  /// Returns the set of <see cref="OptionPosition"/> with the specified <paramref name="strike"/>
534  /// </summary>
535  public IEnumerable<OptionPosition> ForStrike(decimal strike)
536  {
537  ImmutableHashSet<Symbol> symbols;
538  return _strikes.TryGetValue(strike, out symbols)
539  ? ForSymbols(symbols)
540  : Enumerable.Empty<OptionPosition>();
541  }
542 
543  /// <summary>
544  /// Returns the set of <see cref="OptionPosition"/> with the specified <paramref name="expiration"/>
545  /// </summary>
546  public IEnumerable<OptionPosition> ForExpiration(DateTime expiration)
547  {
548  ImmutableHashSet<Symbol> symbols;
549  return _expirations.TryGetValue(expiration, out symbols)
550  ? ForSymbols(symbols)
551  : Enumerable.Empty<OptionPosition>();
552  }
553 
554  /// <summary>Returns a string that represents the current object.</summary>
555  /// <returns>A string that represents the current object.</returns>
556  public override string ToString()
557  {
558  if (Count == 0)
559  {
560  return "Empty";
561  }
562 
563  return HasUnderlying
564  ? $"{UnderlyingQuantity} {Underlying.Value}: {_positions.Count - 1} contract positions"
565  : $"{Underlying.Value}: {_positions.Count} contract positions";
566  }
567 
568  /// <summary>Returns an enumerator that iterates through the collection.</summary>
569  /// <returns>An enumerator that can be used to iterate through the collection.</returns>
570  public IEnumerator<OptionPosition> GetEnumerator()
571  {
572  return _positions.Select(kvp => kvp.Value).GetEnumerator();
573  }
574 
575  /// <summary>
576  /// Validates this collection returning an enumerable of validation errors.
577  /// This should only be invoked via tests and is automatically invoked via
578  /// the constructor in DEBUG builds.
579  /// </summary>
580  internal IEnumerable<string> Validate()
581  {
582  foreach (var kvp in _positions)
583  {
584  var position = kvp.Value;
585  var symbol = position.Symbol;
586  if (position.Quantity == 0)
587  {
588  yield return $"{position}: Quantity == 0";
589  }
590 
591  if (!symbol.HasUnderlying)
592  {
593  continue;
594  }
595 
596  ImmutableHashSet<Symbol> strikes;
597  if (!_strikes.TryGetValue(position.Strike, out strikes) || !strikes.Contains(symbol))
598  {
599  yield return $"{position}: Not indexed by strike price";
600  }
601 
602  ImmutableHashSet<Symbol> expirations;
603  if (!_expirations.TryGetValue(position.Expiration, out expirations) || !expirations.Contains(symbol))
604  {
605  yield return $"{position}: Not indexed by expiration date";
606  }
607  }
608  }
609 
610  private static readonly PositionSide[] OtherSidesForNone = {PositionSide.Short, PositionSide.Long};
611  private static readonly PositionSide[] OtherSidesForShort = {PositionSide.None, PositionSide.Long};
612  private static readonly PositionSide[] OtherSidesForLong = {PositionSide.Short, PositionSide.None};
613  private static PositionSide[] GetOtherSides(PositionSide side)
614  {
615  switch (side)
616  {
617  case PositionSide.Short: return OtherSidesForShort;
618  case PositionSide.None: return OtherSidesForNone;
619  case PositionSide.Long: return OtherSidesForLong;
620  default:
621  throw new ArgumentOutOfRangeException(nameof(side), side, null);
622  }
623  }
624 
625  IEnumerator IEnumerable.GetEnumerator()
626  {
627  return GetEnumerator();
628  }
629 
630  /// <summary>
631  /// OptionPositionCollection + Operator
632  /// </summary>
633  /// <param name="positions">Collection to add to</param>
634  /// <param name="position">OptionPosition to add</param>
635  /// <returns>OptionPositionCollection with the new position added</returns>
637  {
638  return positions.Add(position);
639  }
640 
641  /// <summary>
642  /// OptionPositionCollection - Operator
643  /// </summary>
644  /// <param name="positions">Collection to remove from</param>
645  /// <param name="position">OptionPosition to remove</param>
646  /// <returns>OptionPositionCollection with the position removed</returns>
648  {
649  return positions.Remove(position);
650  }
651  }
652 }