/* * 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 QuantConnect.Orders; using QuantConnect.Orders.Fees; using System.Diagnostics.CodeAnalysis; using QuantConnect.Algorithm.Framework.Portfolio; namespace QuantConnect.Securities { /// /// Provides a base class for all buying power models /// public class BuyingPowerModel : IBuyingPowerModel { /// /// Gets an implementation of that /// does not check for sufficient buying power /// public static readonly IBuyingPowerModel Null = new NullBuyingPowerModel(); private decimal _initialMarginRequirement; private decimal _maintenanceMarginRequirement; /// /// The percentage used to determine the required unused buying power for the account. /// protected decimal RequiredFreeBuyingPowerPercent { get; set; } /// /// Initializes a new instance of the with no leverage (1x) /// public BuyingPowerModel() : this(1m) { } /// /// Initializes a new instance of the /// /// The percentage of an order's absolute cost /// that must be held in free cash in order to place the order /// The percentage of the holding's absolute /// cost that must be held in free cash in order to avoid a margin call /// The percentage used to determine the required /// unused buying power for the account. public BuyingPowerModel( decimal initialMarginRequirement, decimal maintenanceMarginRequirement, decimal requiredFreeBuyingPowerPercent ) { if (initialMarginRequirement < 0 || initialMarginRequirement > 1) { throw new ArgumentException(Messages.BuyingPowerModel.InvalidInitialMarginRequirement); } if (maintenanceMarginRequirement < 0 || maintenanceMarginRequirement > 1) { throw new ArgumentException(Messages.BuyingPowerModel.InvalidMaintenanceMarginRequirement); } if (requiredFreeBuyingPowerPercent < 0 || requiredFreeBuyingPowerPercent > 1) { throw new ArgumentException(Messages.BuyingPowerModel.InvalidFreeBuyingPowerPercentRequirement); } _initialMarginRequirement = initialMarginRequirement; _maintenanceMarginRequirement = maintenanceMarginRequirement; RequiredFreeBuyingPowerPercent = requiredFreeBuyingPowerPercent; } /// /// Initializes a new instance of the /// /// The leverage /// The percentage used to determine the required /// unused buying power for the account. public BuyingPowerModel(decimal leverage, decimal requiredFreeBuyingPowerPercent = 0) { if (leverage < 1) { throw new ArgumentException(Messages.BuyingPowerModel.InvalidLeverage); } if (requiredFreeBuyingPowerPercent < 0 || requiredFreeBuyingPowerPercent > 1) { throw new ArgumentException(Messages.BuyingPowerModel.InvalidFreeBuyingPowerPercentRequirement); } _initialMarginRequirement = 1 / leverage; _maintenanceMarginRequirement = 1 / leverage; RequiredFreeBuyingPowerPercent = requiredFreeBuyingPowerPercent; } /// /// Gets the current leverage of the security /// /// The security to get leverage for /// The current leverage in the security public virtual decimal GetLeverage(Security security) { return 1 / _initialMarginRequirement; } /// /// Sets the leverage for the applicable securities, i.e, equities /// /// /// This is added to maintain backwards compatibility with the old margin/leverage system /// /// /// The new leverage public virtual void SetLeverage(Security security, decimal leverage) { if (leverage < 1) { throw new ArgumentException(Messages.BuyingPowerModel.InvalidLeverage); } var margin = 1 / leverage; _initialMarginRequirement = margin; _maintenanceMarginRequirement = margin; } /// /// Gets the total margin required to execute the specified order in units of the account currency including fees /// /// An object containing the portfolio, the security and the order /// The total margin in terms of the currency quoted in the order public virtual InitialMargin GetInitialMarginRequiredForOrder( InitialMarginRequiredForOrderParameters parameters ) { //Get the order value from the non-abstract order classes (MarketOrder, LimitOrder, StopMarketOrder) //Market order is approximated from the current security price and set in the MarketOrder Method in QCAlgorithm. var fees = parameters.Security.FeeModel.GetOrderFee( new OrderFeeParameters(parameters.Security, parameters.Order)).Value; var feesInAccountCurrency = parameters.CurrencyConverter. ConvertToAccountCurrency(fees).Amount; var orderMargin = this.GetInitialMarginRequirement(parameters.Security, parameters.Order.Quantity); return orderMargin + Math.Sign(orderMargin) * feesInAccountCurrency; } /// /// Gets the margin currently allocated to the specified holding /// /// An object containing the security and holdings quantity/cost/value /// The maintenance margin required for the provided holdings quantity/cost/value public virtual MaintenanceMargin GetMaintenanceMargin(MaintenanceMarginParameters parameters) { return parameters.AbsoluteHoldingsValue * _maintenanceMarginRequirement; } /// /// Gets the margin cash available for a trade /// /// The algorithm's portfolio /// The security to be traded /// The direction of the trade /// The margin available for the trade protected virtual decimal GetMarginRemaining( SecurityPortfolioManager portfolio, Security security, OrderDirection direction ) { var totalPortfolioValue = portfolio.TotalPortfolioValue; var result = portfolio.GetMarginRemaining(totalPortfolioValue); if (direction != OrderDirection.Hold) { var holdings = security.Holdings; //If the order is in the same direction as holdings, our remaining cash is our cash //In the opposite direction, our remaining cash is 2 x current value of assets + our cash if (holdings.IsLong) { switch (direction) { case OrderDirection.Sell: result += // portion of margin to close the existing position this.GetMaintenanceMargin(security) + // portion of margin to open the new position this.GetInitialMarginRequirement(security, security.Holdings.AbsoluteQuantity); break; } } else if (holdings.IsShort) { switch (direction) { case OrderDirection.Buy: result += // portion of margin to close the existing position this.GetMaintenanceMargin(security) + // portion of margin to open the new position this.GetInitialMarginRequirement(security, security.Holdings.AbsoluteQuantity); break; } } } result -= totalPortfolioValue * RequiredFreeBuyingPowerPercent; return result < 0 ? 0 : result; } /// /// The margin that must be held in order to increase the position by the provided quantity /// /// An object containing the security and quantity of shares /// The initial margin required for the provided security and quantity public virtual InitialMargin GetInitialMarginRequirement(InitialMarginParameters parameters) { var security = parameters.Security; var quantity = parameters.Quantity; return security.QuoteCurrency.ConversionRate * security.SymbolProperties.ContractMultiplier * security.Price * quantity * _initialMarginRequirement; } /// /// Check if there is sufficient buying power to execute this order. /// /// An object containing the portfolio, the security and the order /// Returns buying power information for an order public virtual HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(HasSufficientBuyingPowerForOrderParameters parameters) { // short circuit the div 0 case if (parameters.Order.Quantity == 0) { return parameters.Sufficient(); } var ticket = parameters.Portfolio.Transactions.GetOrderTicket(parameters.Order.Id); if (ticket == null) { return parameters.Insufficient(Messages.BuyingPowerModel.InsufficientBuyingPowerDueToNullOrderTicket(parameters.Order)); } if (parameters.Order.Type == OrderType.OptionExercise) { // for option assignment and exercise orders we look into the requirements to process the underlying security transaction var option = (Option.Option) parameters.Security; var underlying = option.Underlying; if (option.IsAutoExercised(underlying.Close) && underlying.IsTradable) { var quantity = option.GetExerciseQuantity(parameters.Order.Quantity); var newOrder = new LimitOrder { Id = parameters.Order.Id, Time = parameters.Order.Time, LimitPrice = option.StrikePrice, Symbol = underlying.Symbol, Quantity = quantity }; // we continue with this call for underlying var parametersForUnderlying = parameters.ForUnderlying(newOrder); var freeMargin = underlying.BuyingPowerModel.GetBuyingPower(parametersForUnderlying.Portfolio, parametersForUnderlying.Security, parametersForUnderlying.Order.Direction); // we add the margin used by the option itself freeMargin += GetMaintenanceMargin(MaintenanceMarginParameters.ForQuantityAtCurrentPrice(option, -parameters.Order.Quantity)); var initialMarginRequired = underlying.BuyingPowerModel.GetInitialMarginRequiredForOrder( new InitialMarginRequiredForOrderParameters(parameters.Portfolio.CashBook, underlying, newOrder)); return HasSufficientBuyingPowerForOrder(parametersForUnderlying, ticket, freeMargin, initialMarginRequired); } return parameters.Sufficient(); } return HasSufficientBuyingPowerForOrder(parameters, ticket); } private HasSufficientBuyingPowerForOrderResult HasSufficientBuyingPowerForOrder(HasSufficientBuyingPowerForOrderParameters parameters, OrderTicket ticket, decimal? freeMarginToUse = null, decimal? initialMarginRequired = null) { // When order only reduces or closes a security position, capital is always sufficient if (parameters.Security.Holdings.Quantity * parameters.Order.Quantity < 0 && Math.Abs(parameters.Security.Holdings.Quantity) >= Math.Abs(parameters.Order.Quantity)) { return parameters.Sufficient(); } var freeMargin = freeMarginToUse ?? GetMarginRemaining(parameters.Portfolio, parameters.Security, parameters.Order.Direction); var initialMarginRequiredForOrder = initialMarginRequired ?? GetInitialMarginRequiredForOrder( new InitialMarginRequiredForOrderParameters( parameters.Portfolio.CashBook, parameters.Security, parameters.Order )); // pro-rate the initial margin required for order based on how much has already been filled var percentUnfilled = (Math.Abs(parameters.Order.Quantity) - Math.Abs(ticket.QuantityFilled)) / Math.Abs(parameters.Order.Quantity); var initialMarginRequiredForRemainderOfOrder = percentUnfilled * initialMarginRequiredForOrder; if (Math.Abs(initialMarginRequiredForRemainderOfOrder) > freeMargin) { return parameters.Insufficient(Messages.BuyingPowerModel.InsufficientBuyingPowerDueToUnsufficientMargin(parameters.Order, initialMarginRequiredForRemainderOfOrder, freeMargin)); } return parameters.Sufficient(); } /// /// Get the maximum market order quantity to obtain a delta in the buying power used by a security. /// The deltas sign defines the position side to apply it to, positive long, negative short. /// /// An object containing the portfolio, the security and the delta buying power /// Returns the maximum allowed market order quantity and if zero, also the reason /// Used by the margin call model to reduce the position by a delta percent. public virtual GetMaximumOrderQuantityResult GetMaximumOrderQuantityForDeltaBuyingPower( GetMaximumOrderQuantityForDeltaBuyingPowerParameters parameters) { var usedBuyingPower = parameters.Security.BuyingPowerModel.GetReservedBuyingPowerForPosition( new ReservedBuyingPowerForPositionParameters(parameters.Security)).AbsoluteUsedBuyingPower; var signedUsedBuyingPower = usedBuyingPower * (parameters.Security.Holdings.IsLong ? 1 : -1); var targetBuyingPower = signedUsedBuyingPower + parameters.DeltaBuyingPower; var target = 0m; if (parameters.Portfolio.TotalPortfolioValue != 0) { target = targetBuyingPower / parameters.Portfolio.TotalPortfolioValue; } return GetMaximumOrderQuantityForTargetBuyingPower( new GetMaximumOrderQuantityForTargetBuyingPowerParameters(parameters.Portfolio, parameters.Security, target, parameters.MinimumOrderMarginPortfolioPercentage, parameters.SilenceNonErrorReasons)); } /// /// Get the maximum market order quantity to obtain a position with a given buying power percentage. /// Will not take into account free buying power. /// /// An object containing the portfolio, the security and the target signed buying power percentage /// Returns the maximum allowed market order quantity and if zero, also the reason /// This implementation ensures that our resulting holdings is less than the target, but it does not necessarily /// maximize the holdings to meet the target. To do that we need a minimizing algorithm that reduces the difference between /// the target final margin value and the target holdings margin. public virtual GetMaximumOrderQuantityResult GetMaximumOrderQuantityForTargetBuyingPower(GetMaximumOrderQuantityForTargetBuyingPowerParameters parameters) { // this is expensive so lets fetch it once var totalPortfolioValue = parameters.Portfolio.TotalPortfolioValue; // adjust target buying power to comply with required Free Buying Power Percent var signedTargetFinalMarginValue = parameters.TargetBuyingPower * (totalPortfolioValue - totalPortfolioValue * RequiredFreeBuyingPowerPercent); // if targeting zero, simply return the negative of the quantity if (signedTargetFinalMarginValue == 0) { return new GetMaximumOrderQuantityResult(-parameters.Security.Holdings.Quantity, string.Empty, false); } // we use initial margin requirement here to avoid the duplicate PortfolioTarget.Percent situation: // PortfolioTarget.Percent(1) -> fills -> PortfolioTarget.Percent(1) _could_ detect free buying power if we use Maintenance requirement here var signedCurrentUsedMargin = this.GetInitialMarginRequirement(parameters.Security, parameters.Security.Holdings.Quantity); // determine the unit price in terms of the account currency var utcTime = parameters.Security.LocalTime.ConvertToUtc(parameters.Security.Exchange.TimeZone); // determine the margin required for 1 unit var absUnitMargin = this.GetInitialMarginRequirement(parameters.Security, 1); if (absUnitMargin == 0) { return new GetMaximumOrderQuantityResult(0, parameters.Security.Symbol.GetZeroPriceMessage()); } // Check that the change of margin is above our models minimum percentage change var absDifferenceOfMargin = Math.Abs(signedTargetFinalMarginValue - signedCurrentUsedMargin); if (!BuyingPowerModelExtensions.AboveMinimumOrderMarginPortfolioPercentage(parameters.Portfolio, parameters.MinimumOrderMarginPortfolioPercentage, absDifferenceOfMargin)) { string reason = null; if (!parameters.SilenceNonErrorReasons) { var minimumValue = totalPortfolioValue * parameters.MinimumOrderMarginPortfolioPercentage; reason = Messages.BuyingPowerModel.TargetOrderMarginNotAboveMinimum(absDifferenceOfMargin, minimumValue); } if (!PortfolioTarget.MinimumOrderMarginPercentageWarningSent.HasValue) { // will trigger the warning if it has not already been sent PortfolioTarget.MinimumOrderMarginPercentageWarningSent = false; } return new GetMaximumOrderQuantityResult(0, reason, false); } // Use the following loop to converge on a value that places us under our target allocation when adjusted for fees var lastOrderQuantity = 0m; // For safety check decimal orderFees = 0m; decimal signedTargetHoldingsMargin; decimal orderQuantity; do { // Calculate our order quantity orderQuantity = GetAmountToOrder(parameters.Security, signedTargetFinalMarginValue, absUnitMargin, out signedTargetHoldingsMargin); if (orderQuantity == 0) { string reason = null; if (!parameters.SilenceNonErrorReasons) { reason = Messages.BuyingPowerModel.OrderQuantityLessThanLotSize(parameters.Security, signedTargetFinalMarginValue - signedCurrentUsedMargin); } return new GetMaximumOrderQuantityResult(0, reason, false); } // generate the order var order = new MarketOrder(parameters.Security.Symbol, orderQuantity, utcTime); var fees = parameters.Security.FeeModel.GetOrderFee( new OrderFeeParameters(parameters.Security, order)).Value; orderFees = parameters.Portfolio.CashBook.ConvertToAccountCurrency(fees).Amount; // Update our target portfolio margin allocated when considering fees, then calculate the new FinalOrderMargin signedTargetFinalMarginValue = (totalPortfolioValue - orderFees - totalPortfolioValue * RequiredFreeBuyingPowerPercent) * parameters.TargetBuyingPower; // Start safe check after first loop, stops endless recursion if (lastOrderQuantity == orderQuantity) { var message = Messages.BuyingPowerModel.FailedToConvergeOnTheTargetMargin(parameters, signedTargetFinalMarginValue, orderFees); // Need to add underlying value to message to reproduce with options if (parameters.Security is Option.Option option && option.Underlying != null) { var underlying = option.Underlying; message += " " + Messages.BuyingPowerModel.FailedToConvergeOnTheTargetMarginUnderlyingSecurityInfo(underlying); } throw new ArgumentException(message); } lastOrderQuantity = orderQuantity; } // Ensure that our target holdings margin will be less than or equal to our target allocated margin while (Math.Abs(signedTargetHoldingsMargin) > Math.Abs(signedTargetFinalMarginValue)); // add directionality back in return new GetMaximumOrderQuantityResult(orderQuantity); } /// /// Helper function that determines the amount to order to get to a given target safely. /// Meaning it will either be at or just below target always. /// /// Security we are to determine order size for /// Target margin allocated /// Margin requirement for one unit; used in our initial order guess /// Output the final margin allocated to this security /// The size of the order to get safely to our target public decimal GetAmountToOrder([NotNull]Security security, decimal targetMargin, decimal marginForOneUnit, out decimal finalMargin) { var lotSize = security.SymbolProperties.LotSize; // Start with order size that puts us back to 0, in theory this means current margin is 0 // so we can calculate holdings to get to the new target margin directly. This is very helpful for // odd cases where margin requirements aren't linear. var orderSize = -security.Holdings.Quantity; // Use the margin for one unit to make our initial guess. orderSize += targetMargin / marginForOneUnit; // Determine the rounding mode for this order size var roundingMode = targetMargin < 0 // Ending in short position; orders need to be rounded towards positive so we end up under our target ? MidpointRounding.ToPositiveInfinity // Ending in long position; orders need to be rounded towards negative so we end up under our target : MidpointRounding.ToNegativeInfinity; // Round this order size appropriately orderSize = orderSize.DiscretelyRoundBy(lotSize, roundingMode); // Use our model to calculate this final margin as a final check finalMargin = this.GetInitialMarginRequirement(security, orderSize + security.Holdings.Quantity); // Until our absolute final margin is equal to or below target we need to adjust; ensures we don't overshoot target // This isn't usually the case, but for non-linear margin per unit cases this may be necessary. // For example https://www.quantconnect.com/forum/discussion/12470, (covered in OptionMarginBuyingPowerModelTests) var marginDifference = finalMargin - targetMargin; while ((targetMargin < 0 && marginDifference < 0) || (targetMargin > 0 && marginDifference > 0)) { // TODO: Can this be smarter about its adjustment, instead of just stepping by lotsize? // We adjust according to the target margin being a short or long orderSize += targetMargin < 0 ? lotSize : -lotSize; // Recalculate final margin with this adjusted orderSize finalMargin = this.GetInitialMarginRequirement(security, orderSize + security.Holdings.Quantity); // Safety check, does not occur in any of our testing, but to be sure we don't enter a endless loop // have this guy check that the difference between the two is not growing. var newDifference = finalMargin - targetMargin; if (Math.Abs(newDifference) > Math.Abs(marginDifference) && Math.Sign(newDifference) == Math.Sign(marginDifference)) { // We have a problem and are correcting in the wrong direction var errorMessage = "BuyingPowerModel().GetAmountToOrder(): " + Messages.BuyingPowerModel.MarginBeingAdjustedInTheWrongDirection(targetMargin, marginForOneUnit, security); // Need to add underlying value to message to reproduce with options if (security is Option.Option option && option.Underlying != null) { errorMessage += " " + Messages.BuyingPowerModel.MarginBeingAdjustedInTheWrongDirectionUnderlyingSecurityInfo(option.Underlying); } throw new ArgumentException(errorMessage); } marginDifference = newDifference; } return orderSize; } /// /// Gets the amount of buying power reserved to maintain the specified position /// /// A parameters object containing the security /// The reserved buying power in account currency public virtual ReservedBuyingPowerForPosition GetReservedBuyingPowerForPosition(ReservedBuyingPowerForPositionParameters parameters) { var maintenanceMargin = this.GetMaintenanceMargin(parameters.Security); return parameters.ResultInAccountCurrency(maintenanceMargin); } /// /// Gets the buying power available for a trade /// /// A parameters object containing the algorithm's portfolio, security, and order direction /// The buying power available for the trade public virtual BuyingPower GetBuyingPower(BuyingPowerParameters parameters) { var marginRemaining = GetMarginRemaining(parameters.Portfolio, parameters.Security, parameters.Direction); return parameters.ResultInAccountCurrency(marginRemaining); } } }