/* * 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.Collections.Generic; using System.Linq; using QuantConnect.Interfaces; using QuantConnect.Orders; using QuantConnect.Securities.Positions; namespace QuantConnect.Securities { /// /// Represents the model responsible for picking which orders should be executed during a margin call /// /// /// This is a default implementation that orders the generated margin call orders by the unrealized /// profit (losers first) and executes each order synchronously until we're within the margin requirements /// public class DefaultMarginCallModel : IMarginCallModel { /// /// The percent margin buffer to use when checking whether the total margin used is /// above the total portfolio value to generate margin call orders /// private readonly decimal _marginBuffer; /// /// Gets the portfolio that margin calls will be transacted against /// protected SecurityPortfolioManager Portfolio { get; } /// /// Gets the default order properties to be used in margin call orders /// protected IOrderProperties DefaultOrderProperties { get; } /// /// Initializes a new instance of the class /// /// The portfolio object to receive margin calls /// The default order properties to be used in margin call orders /// /// The percent margin buffer to use when checking whether the total margin used is /// above the total portfolio value to generate margin call orders /// public DefaultMarginCallModel(SecurityPortfolioManager portfolio, IOrderProperties defaultOrderProperties, decimal marginBuffer = 0.10m) { Portfolio = portfolio; DefaultOrderProperties = defaultOrderProperties; _marginBuffer = marginBuffer; } /// /// Scan the portfolio and the updated data for a potential margin call situation which may get the holdings below zero! /// If there is a margin call, liquidate the portfolio immediately before the portfolio gets sub zero. /// /// Set to true if a warning should be issued to the algorithm /// True for a margin call on the holdings. public List GetMarginCallOrders(out bool issueMarginCallWarning) { issueMarginCallWarning = false; var totalMarginUsed = Portfolio.TotalMarginUsed; // don't issue a margin call if we're not using margin if (totalMarginUsed <= 0) { return new List(); } var totalPortfolioValue = Portfolio.TotalPortfolioValue; var marginRemaining = Portfolio.GetMarginRemaining(totalPortfolioValue); // issue a margin warning when we're down to 5% margin remaining if (marginRemaining <= totalPortfolioValue * 0.05m) { issueMarginCallWarning = true; } // generate a listing of margin call orders var marginCallOrders = new List(); // if we still have margin remaining then there's no need for a margin call if (marginRemaining <= 0) { if (totalMarginUsed > totalPortfolioValue * (1 + _marginBuffer)) { foreach (var positionGroup in Portfolio.Positions.Groups) { var positionMarginCallOrders = GenerateMarginCallOrders( new MarginCallOrdersParameters(positionGroup, totalPortfolioValue, totalMarginUsed)).ToList(); if (positionMarginCallOrders.Count > 0 && positionMarginCallOrders.All(x => x.Quantity != 0)) { marginCallOrders.AddRange(positionMarginCallOrders); } } } issueMarginCallWarning = marginCallOrders.Count > 0; } return marginCallOrders; } /// /// Generates a new order for the specified security taking into account the total margin /// used by the account. Returns null when no margin call is to be issued. /// /// The set of parameters required to generate the margin call orders /// An order object representing a liquidation order to be executed to bring the account within margin requirements protected virtual IEnumerable GenerateMarginCallOrders(MarginCallOrdersParameters parameters) { var positionGroup = parameters.PositionGroup; if (positionGroup.Positions.Any(position => Portfolio.Securities[position.Symbol].QuoteCurrency.ConversionRate == 0)) { // check for div 0 - there's no conv rate, so we can't place an order return Enumerable.Empty(); } // compute the amount of quote currency we need to liquidate in order to get within margin requirements var deltaAccountCurrency = parameters.TotalUsedMargin - parameters.TotalPortfolioValue; var currentlyUsedBuyingPower = positionGroup.BuyingPowerModel.GetReservedBuyingPowerForPositionGroup(Portfolio, positionGroup); // if currentlyUsedBuyingPower > deltaAccountCurrency, means we can keep using the diff in buying power var buyingPowerToKeep = Math.Max(0, currentlyUsedBuyingPower - deltaAccountCurrency); // we want a reduction so we send the inverse side of our position var deltaBuyingPower = (currentlyUsedBuyingPower - buyingPowerToKeep) * -Math.Sign(positionGroup.Quantity); var result = positionGroup.BuyingPowerModel.GetMaximumLotsForDeltaBuyingPower(new GetMaximumLotsForDeltaBuyingPowerParameters( Portfolio, positionGroup, deltaBuyingPower, // margin is negative, we need to reduce positions, no minimum minimumOrderMarginPortfolioPercentage: 0 )); var absQuantity = Math.Abs(result.NumberOfLots); var orderType = positionGroup.Count > 1 ? OrderType.ComboMarket : OrderType.Market; GroupOrderManager groupOrderManager = null; if (orderType == OrderType.ComboMarket) { groupOrderManager = new GroupOrderManager(Portfolio.Transactions.GetIncrementGroupOrderManagerId(), positionGroup.Count, absQuantity); } return positionGroup.Positions.Select(position => { var security = Portfolio.Securities[position.Symbol]; // Always reducing, so we take the absolute quantity times the opposite sign of the position var legQuantity = absQuantity * position.UnitQuantity * -Math.Sign(position.Quantity); return new SubmitOrderRequest( orderType, security.Type, security.Symbol, legQuantity.GetOrderLegGroupQuantity(groupOrderManager), 0, 0, security.LocalTime.ConvertToUtc(security.Exchange.TimeZone), Messages.DefaultMarginCallModel.MarginCallOrderTag, DefaultOrderProperties?.Clone(), groupOrderManager); }); } /// /// Executes synchronous orders to bring the account within margin requirements. /// /// These are the margin call orders that were generated /// by individual security margin models. /// The list of orders that were actually executed public virtual List ExecuteMarginCall(IEnumerable generatedMarginCallOrders) { // if our margin used is back under the portfolio value then we can stop liquidating if (Portfolio.MarginRemaining >= 0) { return new List(); } // order by losers first var executedOrders = new List(); var ordersWithSecurities = generatedMarginCallOrders.ToDictionary(x => x, x => Portfolio[x.Symbol]); var groupManagerTemporalIds = -ordersWithSecurities.Count; var orderedByLosers = ordersWithSecurities // group orders by their group manager id so they are executed together .GroupBy(x => x.Key.GroupOrderManager?.Id ?? groupManagerTemporalIds++) .OrderBy(x => x.Sum(kvp => kvp.Value.UnrealizedProfit)) .Select(x => x.Select(kvp => kvp.Key)); foreach (var requests in orderedByLosers) { var tickets = new List(); foreach (var request in requests) { tickets.Add(Portfolio.Transactions.AddOrder(request)); } foreach (var ticket in tickets) { if (ticket.Status.IsOpen()) { Portfolio.Transactions.WaitForOrder(ticket.OrderId); } executedOrders.Add(ticket); } // if our margin used is back under the portfolio value then we can stop liquidating if (Portfolio.MarginRemaining >= 0) { break; } } return executedOrders; } } }