// TODO : Implement matching multiple permutations and using the objective function to select the best solution /// <summary> /// Using the definitions provided in <see cref="Options"/>, attempts to match all <paramref name="positions"/>. /// The resulting <see cref="OptionStrategyMatch"/> presents a single, valid solution for matching as many positions /// as possible. /// </summary> public OptionStrategyMatch MatchOnce(OptionPositionCollection positions) { // these definitions are enumerated according to the configured IOptionStrategyDefinitionEnumerator var strategies = new List <OptionStrategy>(); foreach (var definition in Options.Definitions) { // simplest implementation here is to match one at a time, updating positions in between // a better implementation would be to evaluate all possible matches and make decisions // prioritizing positions that would require more margin if not matched OptionStrategyDefinitionMatch match; while (definition.TryMatchOnce(Options, positions, out match)) { positions = match.RemoveFrom(positions); strategies.Add(match.CreateStrategy()); } if (positions.IsEmpty) { break; } } return(new OptionStrategyMatch(strategies)); }
/// <summary> /// Filters the provided <paramref name="positions"/> collection such that any remaining positions are all /// valid options that match this leg definition instance. /// </summary> public OptionPositionCollection Filter(IReadOnlyList <OptionPosition> legs, OptionPositionCollection positions, bool includeUnderlying = true) { // first filter down to applicable right positions = positions.Slice(Right, includeUnderlying); if (positions.IsEmpty) { return(positions); } // second filter according to the required side var side = (PositionSide)Math.Sign(Quantity); positions = positions.Slice(side, includeUnderlying); if (positions.IsEmpty) { return(positions); } // these are ordered such that indexed filters are performed force and // opaque/complex predicates follow since they require full table scans foreach (var predicate in _predicates) { positions = predicate.Filter(legs, positions, includeUnderlying); if (positions.IsEmpty) { break; } } // at this point, every position in the positions // collection is a valid match for this definition return(positions); }
/// <summary> /// Filters the specified <paramref name="positions"/> by applying this predicate based on the referenced legs. /// </summary> public OptionPositionCollection Filter(IReadOnlyList <OptionPosition> legs, OptionPositionCollection positions, bool includeUnderlying) { if (!IsIndexed) { // if the predicate references non-indexed properties or contains complex/multiple conditions then // we'll need to do a full table scan. this is not always avoidable, but we should try to avoid it return(OptionPositionCollection.Empty.AddRange( positions.Where(position => _predicate(legs, position)) )); } var referenceValue = _reference.Resolve(legs); switch (_reference.Target) { case PredicateTargetValue.Right: return(positions.Slice((OptionRight)referenceValue, includeUnderlying)); case PredicateTargetValue.Strike: return(positions.Slice(_comparison, (decimal)referenceValue, includeUnderlying)); case PredicateTargetValue.Expiration: return(positions.Slice(_comparison, (DateTime)referenceValue, includeUnderlying)); default: throw new ArgumentOutOfRangeException(); } }
/// <summary> /// Deducts the matched positions from the specified <paramref name="positions"/> taking into account the multiplier /// </summary> public OptionPositionCollection RemoveFrom(OptionPositionCollection positions) { var optionPositions = Legs.Select(leg => leg.CreateOptionPosition(Multiplier)); if (Definition.UnderlyingLots != 0) { optionPositions = optionPositions.Concat(new[] { new OptionPosition(Legs[0].Position.Symbol.Underlying, Definition.UnderlyingLots * Multiplier) }); } return(positions.RemoveRange(optionPositions)); }
/// <summary> /// Computes the delta in matched vs unmatched positions, which gives precedence to solutions that match more contracts. /// </summary> public decimal ComputeScore(OptionPositionCollection input, OptionStrategyMatch match, OptionPositionCollection unmatched) { var value = 0m; foreach (var strategy in match.Strategies) { foreach (var leg in strategy.OptionLegs.Concat <OptionStrategy.LegData>(strategy.UnderlyingLegs)) { value += leg.Quantity; } } return(value - unmatched.Count); }
/// <summary> /// Yields all possible matches for this leg definition held within the collection of <paramref name="positions"/> /// </summary> /// <param name="options">Strategy matcher options guiding matching behaviors</param> /// <param name="legs">The preceding legs already matched for the parent strategy definition</param> /// <param name="positions">The remaining, unmatched positions available to be matched against</param> /// <returns>An enumerable of potential matches</returns> public IEnumerable <OptionStrategyLegDefinitionMatch> Match( OptionStrategyMatcherOptions options, IReadOnlyList <OptionPosition> legs, OptionPositionCollection positions ) { foreach (var position in options.Enumerate(Filter(legs, positions, false))) { var multiplier = position.Quantity / Quantity; if (multiplier != 0) { yield return(new OptionStrategyLegDefinitionMatch(multiplier, position.WithQuantity(multiplier * Quantity) )); } } }
/// <summary> /// Enumerates the provided <paramref name="positions"/>. Positions enumerated first are more /// likely to be matched than those appearing later in the enumeration. /// </summary> public IEnumerable <OptionPosition> Enumerate(OptionPositionCollection positions) { if (positions.IsEmpty) { yield break; } var marketPrice = _marketPriceProvider(positions.Underlying); var longPositions = new List <OptionPosition>(); var shortPuts = new SortedDictionary <decimal, OptionPosition>(); var shortCalls = new SortedDictionary <decimal, OptionPosition>(); foreach (var position in positions) { if (!position.Symbol.HasUnderlying) { yield return(position); } if (position.Quantity > 0) { longPositions.Add(position); } else { switch (position.Right) { case OptionRight.Put: shortPuts.Add(position.Strike, position); break; case OptionRight.Call: shortCalls.Add(position.Strike, position); break; default: throw new ApplicationException( "The skies are falling, the oceans rising - you're having a bad time" ); } } } throw new NotImplementedException("This implementation needs to be completed."); }
private IEnumerable <OptionStrategyDefinitionMatch> Match( OptionStrategyMatcherOptions options, ImmutableList <OptionStrategyLegDefinitionMatch> legMatches, ImmutableList <OptionPosition> legPositions, OptionPositionCollection positions, int multiplier ) { var nextLegIndex = legPositions.Count; if (nextLegIndex == Legs.Count) { if (nextLegIndex > 0) { yield return(new OptionStrategyDefinitionMatch(this, legMatches, multiplier)); } } else if (positions.Count >= LegCount - nextLegIndex) { // grab the next leg definition and perform the match, restricting total to configured maximum per leg var nextLeg = Legs[nextLegIndex]; var maxLegMatch = options.GetMaximumLegMatches(nextLegIndex); foreach (var legMatch in nextLeg.Match(options, legPositions, positions).Take(maxLegMatch)) { // add match to the match we're constructing and deduct matched position from positions collection // we track the min multiplier in line so when we're done, we have the total number of matches for // the matched set of positions in this 'thread' (OptionStrategy.Quantity) foreach (var definitionMatch in Match(options, legMatches.Add(legMatch), legPositions.Add(legMatch.Position), positions - legMatch.Position, Math.Min(multiplier, legMatch.Multiplier) )) { yield return(definitionMatch); } } } else { // positions.Count < LegsCount indicates a failed match // could include partial matches, would allow an algorithm to determine if adding a // new position could help reduce overall margin exposure by completing a strategy } }
/// <summary> /// Determines all possible matches for this definition using the provided <paramref name="positions"/>. /// This includes OVERLAPPING matches. It's up to the actual matcher to make decisions based on which /// matches to accept. This allows the matcher to prioritize matching certain positions over others. /// </summary> public IEnumerable <OptionStrategyDefinitionMatch> Match( OptionStrategyMatcherOptions options, OptionPositionCollection positions ) { // TODO : Pass OptionStrategyMatcherOptions in and respect applicable options if (positions.Count < LegCount) { return(Enumerable.Empty <OptionStrategyDefinitionMatch>()); } var multiplier = int.MaxValue; // first check underlying lots has correct sign and sufficient magnitude var underlyingLotsSign = Math.Sign(UnderlyingLots); if (underlyingLotsSign != 0) { var underlyingPositionSign = Math.Sign(positions.UnderlyingQuantity); if (underlyingLotsSign != underlyingPositionSign || Math.Abs(positions.UnderlyingQuantity) < Math.Abs(UnderlyingLots)) { return(Enumerable.Empty <OptionStrategyDefinitionMatch>()); } // set multiplier for underlying multiplier = positions.UnderlyingQuantity / UnderlyingLots; } // TODO : Consider add OptionStrategyLegDefinition for underlying for consistency purposes. // Might want to enforce that it's always the first leg definition as well for easier slicing. return(Match(options, ImmutableList <OptionStrategyLegDefinitionMatch> .Empty, ImmutableList <OptionPosition> .Empty, positions, multiplier ).Distinct()); }
public IEnumerable <OptionPosition> Enumerate(OptionPositionCollection positions) { return(_enumerate(positions)); }
/// <summary> /// Deducts the matched positions from the specified <paramref name="positions"/> /// </summary> public OptionPositionCollection RemoveFrom(OptionPositionCollection positions) { return(positions.RemoveRange(Legs.Select(leg => leg.Position))); }
/// <summary> /// Determines all possible matches for this definition using the provided <paramref name="positions"/>. /// This includes OVERLAPPING matches. It's up to the actual matcher to make decisions based on which /// matches to accept. This allows the matcher to prioritize matching certain positions over others. /// </summary> public IEnumerable <OptionStrategyDefinitionMatch> Match(OptionPositionCollection positions) { return(Match(OptionStrategyMatcherOptions.ForDefinitions(this), positions)); }
/// <summary> /// Attempts to match the positions to this definition exactly once, by evaluating the enumerable and /// taking the first entry matched. If not match is found, then false is returned and <paramref name="match"/> /// will be null. /// </summary> public bool TryMatchOnce(OptionStrategyMatcherOptions options, OptionPositionCollection positions, out OptionStrategyDefinitionMatch match) { match = Match(options, positions).FirstOrDefault(); return(match != null); }