20 using System.Collections.Generic;
91 var impactedGroups =
new List<IPositionGroup>();
94 var positions = parameters.
Orders.Select(o => o.CreatePositions(parameters.
Portfolio.
Securities)).SelectMany(p => p).ToList();
96 var impactedPositions = positions.ToDictionary(p => p.Symbol);
98 foreach (var impactedGroup
in positionManager.GetImpactedGroups(positions))
100 impactedGroups.Add(impactedGroup);
101 current += impactedGroup.BuyingPowerModel.GetReservedBuyingPowerForPositionGroup(
102 parameters.Portfolio, impactedGroup
105 foreach (var position
in impactedGroup)
108 if (impactedPositions.TryGetValue(position.Symbol, out existing))
111 impactedPositions[position.Symbol] = existing.Combine(position);
115 impactedPositions[position.Symbol] = position;
121 var contemplatedGroups = positionManager.ResolvePositionGroups(
new PositionCollection(impactedPositions.Values));
126 return new ReservedBuyingPowerImpact(current, contemplated, impactedGroups, parameters.ContemplatedChanges, contemplatedGroups);
134 List<IPosition> ordersPositions)
136 var contemplatedMargin = 0m;
137 foreach (var contemplatedGroup
in contemplatedGroups)
141 contemplatedMargin += contemplatedGroup.BuyingPowerModel.GetInitialMarginRequirement(portfolio, contemplatedGroup);
144 return contemplatedMargin;
170 if (deltaBuyingPower < 0)
179 if (result?.IsSufficient ==
false)
187 if (deltaBuyingPower <= availableBuyingPower)
192 return parameters.
Insufficient(Invariant($
@"Id: {string.Join(",
", parameters.Orders.Select(o => o.Id))}, Maintenance Margin Delta: {
193 deltaBuyingPower.Normalize()}, Free Margin: {availableBuyingPower.Normalize()}"
205 decimal availableBuyingPower
273 var inverted =
false;
277 if (parameters.
PositionGroup.IsInvertedOf(currentPositionGroup))
280 targetBuyingPower = -targetBuyingPower;
285 var targetBufferFactor = bufferFactor * targetBuyingPower;
286 var totalPortfolioValue = portfolio.TotalPortfolioValue;
287 var targetFinalMargin = targetBufferFactor * totalPortfolioValue;
290 if (targetFinalMargin == 0)
292 var quantity = -Math.Abs(currentPositionGroup.Quantity);
293 return parameters.
Result(inverted ? -quantity : quantity);
297 var currentUsedMargin = 0m;
298 if (currentPositionGroup.Quantity != 0)
300 currentUsedMargin = Math.Abs(currentPositionGroup.BuyingPowerModel.GetInitialMarginRequirement(portfolio, currentPositionGroup));
304 var absDifferenceOfMargin = Math.Abs(targetFinalMargin - currentUsedMargin);
308 string reason =
null;
321 var unitMargin = Math.Abs(groupUnit.BuyingPowerModel.GetInitialMarginRequirement(portfolio, groupUnit));
322 if (unitMargin == 0m)
325 var zeroPricedPosition = parameters.
PositionGroup.FirstOrDefault(
326 p => portfolio.Securities.GetValueOrDefault(p.Symbol)?.Price == 0m
328 return parameters.
Error(zeroPricedPosition?.
Symbol.GetZeroPriceMessage()
333 var lastPositionGroupOrderQuantity = 0m;
335 decimal targetHoldingsMargin;
336 decimal positionGroupQuantity;
341 groupUnit, unitMargin, out targetHoldingsMargin);
342 if (positionGroupQuantity == 0)
344 string reason =
null;
357 targetFinalMargin = (totalPortfolioValue - orderFees) * targetBufferFactor;
360 if (lastPositionGroupOrderQuantity == positionGroupQuantity)
363 positionGroupQuantity, orderFees, parameters));
366 lastPositionGroupOrderQuantity = positionGroupQuantity;
370 while (Math.Abs(targetHoldingsMargin) > Math.Abs(targetFinalMargin));
372 return parameters.
Result(inverted ? -positionGroupQuantity : positionGroupQuantity);
392 parameters.
Portfolio, currentPositionGroup
398 if (parameters.
PositionGroup.IsInvertedOf(currentPositionGroup))
437 var isInverted = parameters.
PositionGroup.IsInvertedOf(existing);
447 buyingPower += Math.Abs(existing.Key.BuyingPowerModel.GetInitialMarginRequirement(parameters.
Portfolio, existing));
472 foreach (var position
in positionGroup)
474 var security = portfolio.Securities[position.Symbol];
475 var order =
new MarketOrder(position.Symbol, position.Quantity, utcTime);
476 var positionOrderFee = security.FeeModel.GetOrderFee(
new OrderFeeParameters(security, order)).Value;
490 decimal absUnitMargin, out ArgumentException error)
493 if (Math.Abs(currentMarginDifference) > Math.Abs(lastMarginDifference) &&
494 Math.Sign(currentMarginDifference) == Math.Sign(lastMarginDifference)
495 || currentMarginDifference == lastMarginDifference)
498 if (groupUnit.Count == 1)
501 var security = portfolio.
Securities[groupUnit.Single().Symbol];
502 message =
"GetMaximumPositionGroupOrderQuantityForTargetBuyingPower failed to converge to target margin " +
503 Invariant($
"{targetMargin}. Current margin is {currentMargin}. Position group quantity {positionGroupQuantity}. ") +
504 Invariant($
"Lot size is {security.SymbolProperties.LotSize}.Security symbol ") +
505 Invariant($
"{security.Symbol}. Margin unit {absUnitMargin}.");
509 message =
"GetMaximumPositionGroupOrderQuantityForTargetBuyingPower failed to converge to target margin " +
510 Invariant($
"{targetMargin}. Current margin is {currentMargin}. Position group quantity {positionGroupQuantity}. ") +
511 Invariant($
"Position Group Unit is {groupUnit.Key}. Position Group Name ") +
512 Invariant($
"{groupUnit.GetUserFriendlyName()}. Margin unit {absUnitMargin}.");
515 error =
new ArgumentException(message);
528 if (ReferenceEquals(
null, other))
533 if (ReferenceEquals(
this, other))
538 return GetType() == other.GetType();
546 if (ReferenceEquals(
null, obj))
551 if (ReferenceEquals(
this, obj))
556 if (obj.GetType() != GetType())
568 return GetType().GetHashCode();
584 decimal currentUsedMargin, decimal targetFinalMargin,
IPositionGroup groupUnit, decimal unitMargin,
585 out decimal finalMargin)
588 var quantityStep = targetFinalMargin > currentUsedMargin ? +1 : -1;
593 var currentGroupAbsQuantity = Math.Abs(currentPositionGroup.
Quantity);
594 var positionGroupQuantity = targetFinalMargin < 0 ? -currentGroupAbsQuantity + quantityStep : quantityStep;
597 var finalPositionGroup = currentPositionGroup.WithQuantity(currentGroupAbsQuantity + positionGroupQuantity, portfolio.
Positions);
598 finalMargin = Math.Abs(finalPositionGroup.BuyingPowerModel.GetInitialMarginRequirement(portfolio, finalPositionGroup));
602 var prevFinalMargin = finalMargin;
605 var absTargetFinalMargin = Math.Abs(targetFinalMargin);
606 var getMarginDifference = (decimal currentFinalMargin) =>
607 targetFinalMargin < 0 ? absTargetFinalMargin - currentFinalMargin : currentFinalMargin - absTargetFinalMargin;
609 var marginDifference = getMarginDifference(finalMargin);
610 while ((quantityStep < 0 && marginDifference > 0) || (quantityStep > 0 && marginDifference < 0))
612 positionGroupQuantity += quantityStep;
613 finalPositionGroup = currentPositionGroup.WithQuantity(currentGroupAbsQuantity + positionGroupQuantity, portfolio.
Positions);
614 finalMargin = Math.Abs(finalPositionGroup.BuyingPowerModel.GetInitialMarginRequirement(portfolio, finalPositionGroup));
616 var newMarginDifference = getMarginDifference(finalMargin);
617 if (
UnableToConverge(newMarginDifference, marginDifference, groupUnit, portfolio, positionGroupQuantity,
618 targetFinalMargin, currentUsedMargin, unitMargin, out var error))
623 marginDifference = newMarginDifference;
628 if (finalMargin > absTargetFinalMargin)
630 finalMargin = prevFinalMargin;
631 return positionGroupQuantity - quantityStep;
634 return positionGroupQuantity;