/// <summary> /// Resolves the position groups that exist within the specified collection of positions. /// </summary> /// <param name="positions">The collection of positions</param> /// <returns>An enumerable of position groups</returns> public PositionGroupCollection Resolve(PositionCollection positions) { var result = new PositionGroupCollection(positions .Select(position => new PositionGroup(_buyingPowerModel, position)).ToList() ); positions.Clear(); return(result); }
/// <summary> /// Merges this position group collection with the provided <paramref name="other"/> collection. /// </summary> public PositionGroupCollection CombineWith(PositionGroupCollection other) { var result = this; foreach (var positionGroup in other) { result = result.Add(positionGroup); } return(result); }
/// <summary> /// Resolves the algorithm's position groups from all of its holdings /// </summary> private void ResolvePositionGroups() { if (_requiresGroupResolution) { _requiresGroupResolution = false; // TODO : Replace w/ special IPosition impl to always equal security.Quantity and we'll // use them explicitly for resolution collection so we don't do this each time var investedPositions = _securities.Where(kvp => kvp.Value.Invested).Select(kvp => (IPosition) new Position(kvp.Value)); var positionsCollection = new PositionCollection(investedPositions); Groups = ResolvePositionGroups(positionsCollection); } }
/// <summary> /// Attempts to group the specified positions into a new <see cref="IPositionGroup"/> using an /// appropriate <see cref="IPositionGroupBuyingPowerModel"/> for position groups created via this /// resolver. /// </summary> /// <param name="newPositions">The positions to be grouped</param> /// <param name="currentPositions">The currently grouped positions</param> /// <param name="group">The grouped positions when this resolver is able to, otherwise null</param> /// <returns>True if this resolver can group the specified positions, otherwise false</returns> public bool TryGroup(IReadOnlyCollection <IPosition> newPositions, PositionGroupCollection currentPositions, out IPositionGroup group) { foreach (var resolver in _resolvers) { if (resolver.TryGroup(newPositions, currentPositions, out group)) { return(true); } } group = null; return(false); }
/// <summary> /// Attempts to group the specified positions into a new <see cref="IPositionGroup"/> using an /// appropriate <see cref="IPositionGroupBuyingPowerModel"/> for position groups created via this /// resolver. /// </summary> /// <param name="newPositions">The positions to be grouped</param> /// <param name="currentPositions">The currently grouped positions</param> /// <param name="group">The grouped positions when this resolver is able to, otherwise null</param> /// <returns>True if this resolver can group the specified positions, otherwise false</returns> public bool TryGroup(IReadOnlyCollection <IPosition> newPositions, PositionGroupCollection currentPositions, out IPositionGroup group) { // we can only create default groupings containing a single security if (newPositions.Count != 1) { group = null; return(false); } var key = new PositionGroupKey(_buyingPowerModel, newPositions); group = new PositionGroup(key, newPositions.ToDictionary(p => p.Symbol)); return(true); }
/// <summary> /// Determines the position groups that would be evaluated for grouping of the specified /// positions were passed into the <see cref="Resolve"/> method. /// </summary> /// <remarks> /// 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 /// </remarks> /// <param name="groups">The existing position groups</param> /// <param name="positions">The positions being changed</param> /// <returns>An enumerable containing the position groups that could be impacted by the specified position changes</returns> public IEnumerable <IPositionGroup> GetImpactedGroups(PositionGroupCollection groups, IReadOnlyCollection <IPosition> positions) { foreach (var resolver in _resolvers) { var seen = new HashSet <PositionGroupKey>(); foreach (var group in resolver.GetImpactedGroups(groups, positions)) { if (seen.Add(group.Key)) { yield return(group); } } } }
/// <summary> /// Resolves the position groups that exist within the specified collection of positions. /// </summary> /// <param name="positions">The collection of positions</param> /// <returns>An enumerable of position groups</returns> public PositionGroupCollection Resolve(PositionCollection positions) { var result = PositionGroupCollection.Empty; var groups = GetPositionGroups(positions).ToList(); if (groups.Count != 0) { result = new PositionGroupCollection(groups); // we are expected to remove any positions which we resolved into a position group positions.Remove(result); } return(result); }
/// <summary> /// Attempts to group the specified positions into a new <see cref="IPositionGroup"/> using an /// appropriate <see cref="IPositionGroupBuyingPowerModel"/> for position groups created via this /// resolver. /// </summary> /// <param name="newPositions">The positions to be grouped</param> /// <param name="currentPositions">The currently grouped positions</param> /// <param name="group">The grouped positions when this resolver is able to, otherwise null</param> /// <returns>True if this resolver can group the specified positions, otherwise false</returns> public bool TryGroup(IReadOnlyCollection <IPosition> newPositions, PositionGroupCollection currentPositions, out IPositionGroup @group) { var impactedGroups = GetImpactedGroups(currentPositions, newPositions); var positionsToConsiderInNewGroup = impactedGroups.SelectMany(positionGroup => positionGroup.Positions); @group = GetPositionGroups(newPositions.Concat(positionsToConsiderInNewGroup)).Where(positionGroup => { // from the resolved position groups we will take those which use our buying power model and which are related to the new positions to be executed if (positionGroup.BuyingPowerModel.GetType() == typeof(OptionStrategyPositionGroupBuyingPowerModel)) { return(newPositions.Any(position => positionGroup.TryGetPosition(position.Symbol, out position))); } return(false); }).FirstOrDefault(); return(@group != null); }
/// <summary> /// Initializes a new instance of the <see cref="PositionManager"/> class /// </summary> /// <param name="securities">The algorithm's security manager</param> public PositionManager(SecurityManager securities) { _securities = securities; Groups = PositionGroupCollection.Empty; _defaultModel = new SecurityPositionGroupBuyingPowerModel(); _resolver = new CompositePositionGroupResolver(new OptionStrategyPositionGroupResolver(securities), new SecurityPositionGroupResolver(_defaultModel)); // we must be notified each time our holdings change, so each time a security is added, we // want to bind to its SecurityHolding.QuantityChanged event so we can trigger the resolver securities.CollectionChanged += (sender, args) => { var items = args.NewItems ?? new List <object>(); if (args.OldItems != null) { foreach (var item in args.OldItems) { items.Add(item); } } foreach (Security security in items) { if (args.Action == NotifyCollectionChangedAction.Add) { security.Holdings.QuantityChanged += HoldingsOnQuantityChanged; if (security.Invested) { // if this security has holdings then we'll need to resolve position groups _requiresGroupResolution = true; } } else if (args.Action == NotifyCollectionChangedAction.Remove) { security.Holdings.QuantityChanged -= HoldingsOnQuantityChanged; if (security.Invested) { // only trigger group resolution if we had holdings in the removed security _requiresGroupResolution = true; } } } }; }
/// <summary> /// Determines the position groups that would be evaluated for grouping of the specified /// positions were passed into the <see cref="Resolve"/> method. /// </summary> /// <remarks> /// 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 /// </remarks> /// <param name="groups">The existing position groups</param> /// <param name="positions">The positions being changed</param> /// <returns>An enumerable containing the position groups that could be impacted by the specified position changes</returns> public IEnumerable <IPositionGroup> GetImpactedGroups(PositionGroupCollection groups, IReadOnlyCollection <IPosition> positions) { var symbolsSet = positions.Where(position => position.Symbol.SecurityType.HasOptions() || position.Symbol.SecurityType.IsOption()) .SelectMany(position => { return(position.Symbol.HasUnderlying ? new[] { position.Symbol, position.Symbol.Underlying } : new[] { position.Symbol }); }) .ToHashSet(); if (symbolsSet.Count == 0) { return(Enumerable.Empty <IPositionGroup>()); } // will select groups for which we actually hold some security quantity and any of the changed symbols or underlying are in it if they are options return(groups.Where(group => group.Quantity != 0 && group.Positions.Any(position1 => symbolsSet.Contains(position1.Symbol) || position1.Symbol.HasUnderlying && position1.Symbol.SecurityType.IsOption() && symbolsSet.Contains(position1.Symbol.Underlying)))); }
/// <summary> /// Determines the position groups that would be evaluated for grouping of the specified /// positions were passed into the <see cref="IPositionGroupResolver.Resolve"/> method. /// </summary> /// <remarks> /// 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 /// </remarks> /// <param name="groups">The existing position groups</param> /// <param name="positions">The positions being changed</param> /// <returns>An enumerable containing the position groups that could be impacted by the specified position changes</returns> public IEnumerable <IPositionGroup> GetImpactedGroups( PositionGroupCollection groups, IReadOnlyCollection <IPosition> positions ) { var seen = new HashSet <PositionGroupKey>(); foreach (var position in positions) { IReadOnlyCollection <IPositionGroup> groupsForSymbol; if (!groups.TryGetGroups(position.Symbol, out groupsForSymbol)) { continue; } foreach (var group in groupsForSymbol) { if (seen.Add(group.Key)) { yield return(group); } } } }