/*
* 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);
}
}
}