/*
* 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;
}
}
}
}
}
}