/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Linq; using System.Collections.Generic; namespace QuantConnect.Securities.Positions { /// /// Provides an implementation of that invokes multiple wrapped implementations /// in succession. Each successive call to will receive /// the remaining positions that have yet to be grouped. Any non-grouped positions are placed into identity groups. /// public class CompositePositionGroupResolver : IPositionGroupResolver { /// /// Gets the count of registered resolvers /// public int Count => _resolvers.Count; private readonly List _resolvers; /// /// Initializes a new instance of the class /// /// The position group resolvers to be invoked in order public CompositePositionGroupResolver(params IPositionGroupResolver[] resolvers) : this((IEnumerable)resolvers) { } /// /// Initializes a new instance of the class /// /// The position group resolvers to be invoked in order public CompositePositionGroupResolver(IEnumerable resolvers) { _resolvers = resolvers.ToList(); } /// /// Adds the specified to the end of the list of resolvers. This resolver will run last. /// /// The resolver to be added public void Add(IPositionGroupResolver resolver) { _resolvers.Add(resolver); } /// /// Inserts the specified into the list of resolvers at the specified index. /// /// The resolver to be inserted /// The zero based index indicating where to insert the resolver, zero inserts to the beginning /// of the list making this resolver un first and inserts the resolver to the end of the list /// making this resolver run last public void Add(IPositionGroupResolver resolver, int index) { // insert handles bounds checking _resolvers.Insert(index, resolver); } /// /// Removes the specified from the list of resolvers /// /// The resolver to be removed /// True if the resolver was removed, false if it wasn't found in the list public bool Remove(IPositionGroupResolver resolver) { return _resolvers.Remove(resolver); } /// /// Resolves the optimal set of from the provided . /// Implementations are required to deduct grouped positions from the collection. /// public PositionGroupCollection Resolve(PositionCollection positions) { // we start with no groups, each resolver's result will get merged in var groups = PositionGroupCollection.Empty; // each call to ResolvePositionGroups is expected to deduct grouped positions from the PositionCollection foreach (var resolver in _resolvers) { var resolved = resolver.Resolve(positions); groups = groups.CombineWith(resolved); } if (positions.Count > 0) { throw new InvalidOperationException("All positions must be resolved into groups."); } return groups; } /// /// Attempts to group the specified positions into a new using an /// appropriate for position groups created via this /// resolver. /// /// The positions to be grouped /// The currently grouped positions /// The grouped positions when this resolver is able to, otherwise null /// True if this resolver can group the specified positions, otherwise false public bool TryGroup(IReadOnlyCollection newPositions, PositionGroupCollection currentPositions, out IPositionGroup group) { foreach (var resolver in _resolvers) { if (resolver.TryGroup(newPositions, currentPositions, out group)) { return true; } } group = null; return false; } /// /// Determines the position groups that would be evaluated for grouping of the specified /// positions were passed into the method. /// /// /// This function allows us to determine a set of impacted groups and run the resolver on just /// those groups in order to support what-if analysis /// /// The existing position groups /// The positions being changed /// An enumerable containing the position groups that could be impacted by the specified position changes public IEnumerable GetImpactedGroups(PositionGroupCollection groups, IReadOnlyCollection positions) { // we keep track of yielded groups for all resolvers var seen = new HashSet(); foreach (var resolver in _resolvers) { foreach (var group in resolver.GetImpactedGroups(groups, positions)) { if (seen.Add(group.Key)) { yield return group; } } } } } }