public void MatchesAgainstFullPositionCollection()
        {
            // sort definitions by leg count so that we match more complex definitions first
            var options = OptionStrategyMatcherOptions.ForDefinitions(OptionStrategyDefinitions.AllDefinitions
                                                                      .OrderByDescending(definition => definition.LegCount)
                                                                      );
            var matcher   = new OptionStrategyMatcher(options);
            var positions = OptionPositionCollection.Empty.AddRange(Option.Position(Option.Underlying, +20),
                                                                    Option.Position(Option.Call[100, -4]), Option.Position(Option.Put[105, -4]),
                                                                    Option.Position(Option.Call[105, +4]), Option.Position(Option.Put[110, +4]),
                                                                    Option.Position(Option.Call[110, -3]), Option.Position(Option.Put[115, -3]),
                                                                    Option.Position(Option.Call[115, +3]), Option.Position(Option.Put[120, +3]),
                                                                    Option.Position(Option.Call[120, -5]), Option.Position(Option.Put[125, -5]),
                                                                    Option.Position(Option.Call[124, +5]), Option.Position(Option.Put[130, +5])
                                                                    );

            var match = matcher.MatchOnce(positions);

            foreach (var strategy in match.Strategies)
            {
                Console.WriteLine($"{strategy.Name}");
                foreach (var leg in strategy.OptionLegs)
                {
                    // steal OptionPosition's ToString() implementation
                    Console.Write($"\t{new OptionPosition(leg.Symbol, leg.Quantity)}");
                }
            }
        }
        private IEnumerable <IPositionGroup> GetPositionGroups(IEnumerable <IPosition> positions)
        {
            foreach (var positionsByUnderlying in positions
                     .Where(position => position.Symbol.SecurityType.HasOptions() || position.Symbol.SecurityType.IsOption())
                     .GroupBy(position => position.Symbol.HasUnderlying? position.Symbol.Underlying : position.Symbol))
            {
                var optionPosition = positionsByUnderlying.FirstOrDefault(position => position.Symbol.SecurityType.IsOption());
                if (optionPosition == null)
                {
                    // if there isn't any option position we aren't really interested, can't create any option strategy!
                    continue;
                }
                var contractMultiplier = (_securities[optionPosition.Symbol].SymbolProperties as OptionSymbolProperties)?.ContractUnitOfTrade ?? 100;

                var optionPositionCollection = OptionPositionCollection.FromPositions(positionsByUnderlying, contractMultiplier);

                var matches = _strategyMatcher.MatchOnce(optionPositionCollection);
                if (matches.Strategies.Count == 0)
                {
                    continue;
                }

                foreach (var matchedStrategy in matches.Strategies)
                {
                    var positionsToGroup = matchedStrategy.OptionLegs
                                           .Select(optionLeg => (IPosition) new Position(optionLeg.Symbol, optionLeg.Quantity, 1))
                                           .Concat(matchedStrategy.UnderlyingLegs.Select(underlyingLeg => new Position(underlyingLeg.Symbol, underlyingLeg.Quantity * contractMultiplier, 1)))
                                           .ToArray();

                    yield return(new PositionGroup(new OptionStrategyPositionGroupBuyingPowerModel(matchedStrategy), positionsToGroup));
                }
            }
        }
        public void DoesNotDoubleCountPositions()
        {
            // this test aims to verify that match solutions do not reference the same position in multiple matches
            // this behavior is different than the OptionStrategyDefinition.Match, which by design produces all possible
            // matches which permits the same position to appear in different matches, allowing the matcher to pick matches

            // this test aims to verify that we can match the same definition multiple times if positions allows
            // 0: -C110 +C105
            // 1: -C115 +C100
            var positions = OptionPositionCollection.Empty.AddRange(
                Position(Call[110], -1),
                Position(Call[115], -1),
                Position(Call[100]),
                Position(Call[105])
                );

            var matcher = new OptionStrategyMatcher(OptionStrategyMatcherOptions.ForDefinitions(BearCallSpread));
            var matches = matcher.MatchOnce(positions);

            Assert.AreEqual(2, matches.Strategies.Count);
        }