/// <summary> /// Check if there is sufficient buying power for the position group to execute this order. /// </summary> /// <param name="parameters">An object containing the portfolio, the position group and the order</param> /// <returns>Returns buying power information for an order against a position group</returns> public virtual HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder( HasSufficientPositionGroupBuyingPowerForOrderParameters parameters ) { // The addition of position groups requires that we not only check initial margin requirements, but also // that we confirm that after the changes have been applied and the new groups resolved our maintenance // margin is still in a valid range (less than TPV). For this model, we use the security's sufficient buying // power impl to confirm initial margin requirements and lean heavily on GetReservedBuyingPowerImpact for // help with confirming that our expected maintenance margin is still less than TPV. // 1. Confirm we have sufficient buying power to execute the trade using security's BP model // 2. Confirm we pass position group specific checks // 3. Confirm we haven't exceeded maintenance margin limits via GetReservedBuyingPowerImpact's delta // 1. Confirm we meet initial margin requirements, accounting for buffer var availableBuyingPower = this.GetPositionGroupBuyingPower( parameters.Portfolio, parameters.PositionGroup, parameters.Order.Direction ); // 2. Confirm we pass position group specific checks var result = PassesPositionGroupSpecificBuyingPowerForOrderChecks(parameters, availableBuyingPower); if (result?.IsSufficient == false) { return(result); } // 3. Confirm that the new groupings arising from the change doesn't make maintenance margin exceed TPV var args = new ReservedBuyingPowerImpactParameters(parameters.Portfolio, parameters.PositionGroup, parameters.Order); var deltaBuyingPower = GetReservedBuyingPowerImpact(args).Delta; if (deltaBuyingPower <= availableBuyingPower) { return(parameters.Sufficient()); } return(parameters.Insufficient(Invariant( $"Id: {parameters.Order.Id}, Maintenance Margin Delta: {deltaBuyingPower.Normalize()}, Free Margin: {availableBuyingPower.Value.Normalize()}" ))); }
/// <summary> /// Computes the impact on the portfolio's buying power from adding the position group to the portfolio. This is /// a 'what if' analysis to determine what the state of the portfolio would be if these changes were applied. The /// delta (before - after) is the margin requirement for adding the positions and if the margin used after the changes /// are applied is less than the total portfolio value, this indicates sufficient capital. /// </summary> /// <param name="parameters">An object containing the portfolio and a position group containing the contemplated /// changes to the portfolio</param> /// <returns>Returns the portfolio's total portfolio value and margin used before and after the position changes are applied</returns> public virtual ReservedBuyingPowerImpact GetReservedBuyingPowerImpact(ReservedBuyingPowerImpactParameters parameters) { // This process aims to avoid having to compute buying power on the entire portfolio and instead determines // the set of groups that can be impacted by the changes being contemplated. The only real way to determine // the change in maintenance margin is to determine what groups we'll have after the changes and compute the // margin based on that. // 1. Determine impacted groups (depends on IPositionGroupResolver.GetImpactedGroups) // 2. Compute the currently reserved buying power of impacted groups // 3. Create position collection using impacted groups and apply contemplated changes // 4. Resolve new position groups using position collection with applied contemplated changes // 5. Compute the contemplated reserved buying power on these newly resolved groups // 1. Determine impacted groups var positionManager = parameters.Portfolio.Positions; // 2. Compute current reserved buying power var current = 0m; var impactedGroups = new List <IPositionGroup>(); // 3. Determine set of impacted positions to be grouped var positions = parameters.Order.CreatePositions(parameters.Portfolio.Securities).ToList(); var impactedPositions = positions.ToDictionary(p => p.Symbol); foreach (var impactedGroup in positionManager.GetImpactedGroups(positions)) { impactedGroups.Add(impactedGroup); current += impactedGroup.BuyingPowerModel.GetReservedBuyingPowerForPositionGroup( parameters.Portfolio, impactedGroup ); foreach (var position in impactedGroup) { IPosition existing; if (impactedPositions.TryGetValue(position.Symbol, out existing)) { // if it already exists then combine it with the existing impactedPositions[position.Symbol] = existing.Combine(position); } else { impactedPositions[position.Symbol] = position; } } } // 4. Resolve new position groups var contemplatedGroups = positionManager.ResolvePositionGroups(new PositionCollection(impactedPositions.Values)); // 5. Compute contemplated reserved buying power var contemplated = 0m; foreach (var contemplatedGroup in contemplatedGroups) { contemplated += contemplatedGroup.BuyingPowerModel.GetMaintenanceMargin( parameters.Portfolio, contemplatedGroup ); } return(new ReservedBuyingPowerImpact( current, contemplated, impactedGroups, parameters.ContemplatedChanges, contemplatedGroups )); }