/* * 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 QuantConnect.Orders; using System.Collections.Generic; using System.Collections.Specialized; namespace QuantConnect.Securities.Positions { /// /// Responsible for managing the resolution of position groups for an algorithm /// public class SecurityPositionGroupModel : ExtendedDictionary { /// /// Gets an implementation of that will not group multiple securities /// public static readonly SecurityPositionGroupModel Null = new NullSecurityPositionGroupModel(); private bool _requiresGroupResolution; private SecurityManager _securities; private PositionGroupCollection _groups; private IPositionGroupResolver _resolver; /// /// Get's the single security position group buying power model to use /// protected virtual IPositionGroupBuyingPowerModel PositionGroupBuyingPowerModel { get; } = new SecurityPositionGroupBuyingPowerModel(); /// /// Gets the set of currently resolved position groups /// public PositionGroupCollection Groups { get { ResolvePositionGroups(); return _groups; } private set { _groups = value; } } /// /// Gets whether or not the algorithm is using only default position groups /// public bool IsOnlyDefaultGroups => Groups.IsOnlyDefaultGroups; /// /// Gets the number of position groups in this collection /// public override int Count => Groups.Count; /// /// Gets all the available position group keys /// protected override IEnumerable GetKeys => Groups.Keys; /// /// Gets all the available position groups /// protected override IEnumerable GetValues => Groups.Values; /// /// Gets all the items in the dictionary /// /// All the items in the dictionary public override IEnumerable> GetItems() => Groups.GetGroups(); /// /// Initializes a new instance of the class /// /// The algorithm's security manager public virtual void Initialize(SecurityManager securities) { _securities = securities; Groups = PositionGroupCollection.Empty; _resolver = GetPositionGroupResolver(); foreach (var security in _securities.Values) { // if any security already present let's wire the holdings change event security.Holdings.QuantityChanged += HoldingsOnQuantityChanged; } // 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(); 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; } } } }; } /// /// Gets the matching the specified . If one is not found, /// then a new empty position group is returned. /// public override IPositionGroup this[PositionGroupKey key] { get => Groups[key]; set => throw new NotImplementedException("Read-only collection. Cannot set value."); } /// /// Creates a position group for the specified order, pulling /// /// The order /// The resulting position group /// A new position group matching the provided order public bool TryCreatePositionGroup(List orders, out IPositionGroup group) { var newPositions = orders.Select(order => order.CreatePositions(_securities)).SelectMany(x => x).ToList(); // We send new and current positions to try resolve any strategy being executed by multiple orders // else the PositionGroup we will get out here will just be the default in those cases if (!_resolver.TryGroup(newPositions, Groups, out group)) { return false; } return true; } /// /// Resolves position groups using the specified collection of positions /// /// The positions to be grouped /// A collection of position groups containing all of the provided positions public PositionGroupCollection ResolvePositionGroups(PositionCollection positions) { return _resolver.Resolve(positions); } /// /// Determines which position groups could be impacted by changes in the specified positions /// /// The positions to be changed /// All position groups that need to be re-evaluated due to changes in the positions public IEnumerable GetImpactedGroups(IReadOnlyCollection positions) { return _resolver.GetImpactedGroups(Groups, positions); } /// /// Creates a for the security's default position group /// public PositionGroupKey CreateDefaultKey(Security security) { return new PositionGroupKey(PositionGroupBuyingPowerModel, security); } /// /// Gets or creates the default position group for the specified /// /// /// TODO: position group used here is the default, is this what callers want? /// public IPositionGroup GetOrCreateDefaultGroup(Security security) { var key = CreateDefaultKey(security); return Groups[key]; } /// /// Get the position group resolver instance to use /// /// The position group resolver instance protected virtual IPositionGroupResolver GetPositionGroupResolver() { return new CompositePositionGroupResolver(new OptionStrategyPositionGroupResolver(_securities), new SecurityPositionGroupResolver(PositionGroupBuyingPowerModel)); } private void HoldingsOnQuantityChanged(object sender, SecurityHoldingQuantityChangedEventArgs e) { _requiresGroupResolution = true; } /// /// Resolves the algorithm's position groups from all of its holdings /// 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); } } /// /// Tries to get the position group matching the specified key /// /// The key to search for /// The position group matching the specified key /// True if a group with the specified key was found, false otherwise public override bool TryGetValue(PositionGroupKey key, out IPositionGroup value) { return Groups.TryGetGroup(key, out value); } } }