19 using System.Collections;
20 using System.Collections.Generic;
21 using System.Collections.Immutable;
35 ImmutableDictionary<Symbol, OptionPosition>.
Empty,
38 ImmutableSortedDictionary<decimal, ImmutableHashSet<Symbol>>.
Empty,
39 ImmutableSortedDictionary<DateTime, ImmutableHashSet<Symbol>>.
Empty
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;
56 public int Count => _positions.Count;
61 public bool IsEmpty => _positions.IsEmpty;
97 public IEnumerable<decimal>
Strikes => _strikes.Keys;
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
123 _positions = positions;
124 _expirations = expirations;
126 if (_rights.Count != 2)
129 ImmutableHashSet<Symbol> value;
130 if (!_rights.TryGetValue(
OptionRight.Call, out value))
132 _rights = _rights.SetItem(
OptionRight.Call, ImmutableHashSet<Symbol>.Empty);
134 if (!_rights.TryGetValue(
OptionRight.Put, out value))
136 _rights = _rights.SetItem(
OptionRight.Put, ImmutableHashSet<Symbol>.Empty);
140 if (_sides.Count != 3)
143 ImmutableHashSet<Symbol> value;
146 _sides = _sides.SetItem(
PositionSide.None, ImmutableHashSet<Symbol>.Empty);
150 _sides = _sides.SetItem(
PositionSide.Short, ImmutableHashSet<Symbol>.Empty);
154 _sides = _sides.SetItem(
PositionSide.Long, ImmutableHashSet<Symbol>.Empty);
158 if (!positions.IsEmpty)
163 var underlying = positions.First().Key;
164 if (underlying.HasUnderlying)
166 underlying = underlying.Underlying;
170 var underlyingQuantity = positions.GetValueOrDefault(underlying).Quantity;
175 if (errors.Count > 0)
177 throw new ArgumentException(
"OptionPositionCollection validation failed: "
178 + Environment.NewLine +
string.Join(Environment.NewLine, errors)
190 return TryGetPosition(symbol, out position) && position.Quantity != 0;
199 return _positions.TryGetValue(symbol, out position);
217 var quantity = (int)position.Quantity;
218 if (position.Symbol.SecurityType.HasOptions())
220 quantity = (int) (quantity / contractMultiplier);
232 var positions = Empty;
233 foreach (var holding
in holdings)
235 var symbol = holding.Symbol;
236 if (!symbol.HasUnderlying)
238 if (symbol == underlying)
240 var underlyingLots = (int) (holding.Quantity / contractMultiplier);
247 if (symbol.Underlying != underlying)
252 var position =
new OptionPosition(symbol, (
int) holding.Quantity);
253 positions = positions.Add(position);
271 var rights = _rights;
272 var strikes = _strikes;
273 var positions = _positions;
274 var expirations = _expirations;
278 var symbol = position.
Symbol;
279 if (positions.TryGetValue(symbol, out existing))
282 position += existing;
287 positions = positions.SetItem(symbol, position);
288 if (!exists && symbol.HasUnderlying)
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);
304 positions = positions.Remove(symbol);
305 if (symbol.HasUnderlying)
308 var rightsValue = rights[position.
Right].Remove(symbol);
309 rights = rights.SetItem(position.
Right, rightsValue);
312 var sidesValue = sides[position.
Side].Remove(symbol);
313 sides = sides.SetItem(position.
Side, sidesValue);
315 var strikesValue = strikes[position.
Strike].Remove(symbol);
316 strikes = strikesValue.Count > 0
317 ? strikes.SetItem(position.
Strike, strikesValue)
318 : strikes.Remove(position.
Strike);
320 var expirationsValue = expirations[position.
Expiration].Remove(symbol);
321 expirations = expirationsValue.Count > 0
322 ? expirations.SetItem(position.
Expiration, expirationsValue)
335 return Add(position.
Negate());
343 return AddRange((IEnumerable<OptionPosition>) positions);
351 return positions.Aggregate(
this, (current, position) => current + position);
359 return AddRange(positions.Select(position => position.Negate()));
368 var rights = _rights.
Remove(right.Invert());
370 var positions = ImmutableDictionary<Symbol, OptionPosition>.
Empty;
371 if (includeUnderlying && HasUnderlying)
373 positions = positions.
Add(Underlying, UnderlyingPosition);
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))
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);
397 var otherSides = GetOtherSides(side);
398 var sides = _sides.Remove(otherSides[0]).Remove(otherSides[1]);
400 var positions = ImmutableDictionary<Symbol, OptionPosition>.Empty;
401 if (includeUnderlying && HasUnderlying)
403 positions = positions.Add(Underlying, UnderlyingPosition);
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))
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);
427 var strikes = comparison.Filter(_strikes, strike);
430 return includeUnderlying && HasUnderlying ? Empty.Add(UnderlyingPosition) : Empty;
433 var positions = ImmutableDictionary<Symbol, OptionPosition>.Empty;
434 if (includeUnderlying)
437 if (_positions.TryGetValue(Underlying, out underlyingPosition))
439 positions = positions.Add(Underlying, underlyingPosition);
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))
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);
464 var expirations = comparison.Filter(_expirations, expiration);
465 if (expirations.IsEmpty)
467 return includeUnderlying && HasUnderlying ? Empty.Add(UnderlyingPosition) : Empty;
470 var positions = ImmutableDictionary<Symbol, OptionPosition>.Empty;
471 if (includeUnderlying)
474 if (_positions.TryGetValue(Underlying, out underlyingPosition))
476 positions = positions.Add(Underlying, underlyingPosition);
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))
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);
498 public IEnumerable<OptionPosition>
ForSymbols(IEnumerable<Symbol> symbols)
500 foreach (var symbol
in symbols)
503 if (_positions.TryGetValue(symbol, out position))
505 yield
return position;
515 ImmutableHashSet<Symbol> symbols;
516 return _rights.TryGetValue(right, out symbols)
517 ? ForSymbols(symbols)
526 ImmutableHashSet<Symbol> symbols;
527 return _sides.TryGetValue(side, out symbols)
528 ? ForSymbols(symbols)
535 public IEnumerable<OptionPosition>
ForStrike(decimal strike)
537 ImmutableHashSet<Symbol> symbols;
538 return _strikes.TryGetValue(strike, out symbols)
539 ? ForSymbols(symbols)
548 ImmutableHashSet<Symbol> symbols;
549 return _expirations.TryGetValue(expiration, out symbols)
550 ? ForSymbols(symbols)
564 ? $
"{UnderlyingQuantity} {Underlying.Value}: {_positions.Count - 1} contract positions"
565 : $
"{Underlying.Value}: {_positions.Count} contract positions";
572 return _positions.Select(kvp => kvp.Value).GetEnumerator();
580 internal IEnumerable<string>
Validate()
582 foreach (var kvp
in _positions)
584 var position = kvp.Value;
585 var symbol = position.Symbol;
586 if (position.Quantity == 0)
588 yield
return $
"{position}: Quantity == 0";
591 if (!symbol.HasUnderlying)
596 ImmutableHashSet<Symbol> strikes;
597 if (!_strikes.TryGetValue(position.Strike, out strikes) || !strikes.Contains(symbol))
599 yield
return $
"{position}: Not indexed by strike price";
602 ImmutableHashSet<Symbol> expirations;
603 if (!_expirations.TryGetValue(position.Expiration, out expirations) || !expirations.Contains(symbol))
605 yield
return $
"{position}: Not indexed by expiration date";
621 throw new ArgumentOutOfRangeException(nameof(side), side,
null);
625 IEnumerator IEnumerable.GetEnumerator()
627 return GetEnumerator();
638 return positions.
Add(position);
649 return positions.
Remove(position);