Lean  $LEAN_TAG$
DefaultMarginCallModel.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  *
15 */
16 
17 using System;
18 using System.Collections.Generic;
19 using System.Linq;
21 using QuantConnect.Orders;
23 
25 {
26  /// <summary>
27  /// Represents the model responsible for picking which orders should be executed during a margin call
28  /// </summary>
29  /// <remarks>
30  /// This is a default implementation that orders the generated margin call orders by the unrealized
31  /// profit (losers first) and executes each order synchronously until we're within the margin requirements
32  /// </remarks>
34  {
35  /// <summary>
36  /// The percent margin buffer to use when checking whether the total margin used is
37  /// above the total portfolio value to generate margin call orders
38  /// </summary>
39  private readonly decimal _marginBuffer;
40 
41  /// <summary>
42  /// Gets the portfolio that margin calls will be transacted against
43  /// </summary>
45 
46  /// <summary>
47  /// Gets the default order properties to be used in margin call orders
48  /// </summary>
50 
51  /// <summary>
52  /// Initializes a new instance of the <see cref="DefaultMarginCallModel"/> class
53  /// </summary>
54  /// <param name="portfolio">The portfolio object to receive margin calls</param>
55  /// <param name="defaultOrderProperties">The default order properties to be used in margin call orders</param>
56  /// <param name="marginBuffer">
57  /// The percent margin buffer to use when checking whether the total margin used is
58  /// above the total portfolio value to generate margin call orders
59  /// </param>
60  public DefaultMarginCallModel(SecurityPortfolioManager portfolio, IOrderProperties defaultOrderProperties, decimal marginBuffer = 0.10m)
61  {
62  Portfolio = portfolio;
63  DefaultOrderProperties = defaultOrderProperties;
64  _marginBuffer = marginBuffer;
65  }
66 
67  /// <summary>
68  /// Scan the portfolio and the updated data for a potential margin call situation which may get the holdings below zero!
69  /// If there is a margin call, liquidate the portfolio immediately before the portfolio gets sub zero.
70  /// </summary>
71  /// <param name="issueMarginCallWarning">Set to true if a warning should be issued to the algorithm</param>
72  /// <returns>True for a margin call on the holdings.</returns>
73  public List<SubmitOrderRequest> GetMarginCallOrders(out bool issueMarginCallWarning)
74  {
75  issueMarginCallWarning = false;
76 
77  var totalMarginUsed = Portfolio.TotalMarginUsed;
78 
79  // don't issue a margin call if we're not using margin
80  if (totalMarginUsed <= 0)
81  {
82  return new List<SubmitOrderRequest>();
83  }
84 
85  var totalPortfolioValue = Portfolio.TotalPortfolioValue;
86  var marginRemaining = Portfolio.GetMarginRemaining(totalPortfolioValue);
87 
88  // issue a margin warning when we're down to 5% margin remaining
89  if (marginRemaining <= totalPortfolioValue * 0.05m)
90  {
91  issueMarginCallWarning = true;
92  }
93 
94  // generate a listing of margin call orders
95  var marginCallOrders = new List<SubmitOrderRequest>();
96 
97  // if we still have margin remaining then there's no need for a margin call
98  if (marginRemaining <= 0)
99  {
100  if (totalMarginUsed > totalPortfolioValue * (1 + _marginBuffer))
101  {
102  foreach (var positionGroup in Portfolio.Positions.Groups)
103  {
104  var positionMarginCallOrders = GenerateMarginCallOrders(
105  new MarginCallOrdersParameters(positionGroup, totalPortfolioValue, totalMarginUsed)).ToList();
106  if (positionMarginCallOrders.Count > 0 && positionMarginCallOrders.All(x => x.Quantity != 0))
107  {
108  marginCallOrders.AddRange(positionMarginCallOrders);
109  }
110  }
111  }
112 
113  issueMarginCallWarning = marginCallOrders.Count > 0;
114  }
115 
116  return marginCallOrders;
117  }
118 
119  /// <summary>
120  /// Generates a new order for the specified security taking into account the total margin
121  /// used by the account. Returns null when no margin call is to be issued.
122  /// </summary>
123  /// <param name="parameters">The set of parameters required to generate the margin call orders</param>
124  /// <returns>An order object representing a liquidation order to be executed to bring the account within margin requirements</returns>
125  protected virtual IEnumerable<SubmitOrderRequest> GenerateMarginCallOrders(MarginCallOrdersParameters parameters)
126  {
127  var positionGroup = parameters.PositionGroup;
128  if (positionGroup.Positions.Any(position => Portfolio.Securities[position.Symbol].QuoteCurrency.ConversionRate == 0))
129  {
130  // check for div 0 - there's no conv rate, so we can't place an order
131  return Enumerable.Empty<SubmitOrderRequest>();
132  }
133 
134  // compute the amount of quote currency we need to liquidate in order to get within margin requirements
135  var deltaAccountCurrency = parameters.TotalUsedMargin - parameters.TotalPortfolioValue;
136 
137  var currentlyUsedBuyingPower = positionGroup.BuyingPowerModel.GetReservedBuyingPowerForPositionGroup(Portfolio, positionGroup);
138 
139  // if currentlyUsedBuyingPower > deltaAccountCurrency, means we can keep using the diff in buying power
140  var buyingPowerToKeep = Math.Max(0, currentlyUsedBuyingPower - deltaAccountCurrency);
141 
142  // we want a reduction so we send the inverse side of our position
143  var deltaBuyingPower = (currentlyUsedBuyingPower - buyingPowerToKeep) * -Math.Sign(positionGroup.Quantity);
144 
145  var result = positionGroup.BuyingPowerModel.GetMaximumLotsForDeltaBuyingPower(new GetMaximumLotsForDeltaBuyingPowerParameters(
146  Portfolio, positionGroup, deltaBuyingPower,
147  // margin is negative, we need to reduce positions, no minimum
148  minimumOrderMarginPortfolioPercentage: 0
149  ));
150 
151  var absQuantity = Math.Abs(result.NumberOfLots);
152  var orderType = positionGroup.Count > 1 ? OrderType.ComboMarket : OrderType.Market;
153 
154  GroupOrderManager groupOrderManager = null;
155  if (orderType == OrderType.ComboMarket)
156  {
157  groupOrderManager = new GroupOrderManager(Portfolio.Transactions.GetIncrementGroupOrderManagerId(), positionGroup.Count,
158  absQuantity);
159  }
160 
161  return positionGroup.Positions.Select(position =>
162  {
163  var security = Portfolio.Securities[position.Symbol];
164  // Always reducing, so we take the absolute quantity times the opposite sign of the position
165  var legQuantity = absQuantity * position.UnitQuantity * -Math.Sign(position.Quantity);
166 
167  return new SubmitOrderRequest(
168  orderType,
169  security.Type,
170  security.Symbol,
171  legQuantity.GetOrderLegGroupQuantity(groupOrderManager),
172  0,
173  0,
174  security.LocalTime.ConvertToUtc(security.Exchange.TimeZone),
175  Messages.DefaultMarginCallModel.MarginCallOrderTag,
177  groupOrderManager);
178  });
179  }
180 
181  /// <summary>
182  /// Executes synchronous orders to bring the account within margin requirements.
183  /// </summary>
184  /// <param name="generatedMarginCallOrders">These are the margin call orders that were generated
185  /// by individual security margin models.</param>
186  /// <returns>The list of orders that were actually executed</returns>
187  public virtual List<OrderTicket> ExecuteMarginCall(IEnumerable<SubmitOrderRequest> generatedMarginCallOrders)
188  {
189  // if our margin used is back under the portfolio value then we can stop liquidating
190  if (Portfolio.MarginRemaining >= 0)
191  {
192  return new List<OrderTicket>();
193  }
194 
195  // order by losers first
196  var executedOrders = new List<OrderTicket>();
197  var ordersWithSecurities = generatedMarginCallOrders.ToDictionary(x => x, x => Portfolio[x.Symbol]);
198  var groupManagerTemporalIds = -ordersWithSecurities.Count;
199  var orderedByLosers = ordersWithSecurities
200  // group orders by their group manager id so they are executed together
201  .GroupBy(x => x.Key.GroupOrderManager?.Id ?? groupManagerTemporalIds++)
202  .OrderBy(x => x.Sum(kvp => kvp.Value.UnrealizedProfit))
203  .Select(x => x.Select(kvp => kvp.Key));
204  foreach (var requests in orderedByLosers)
205  {
206  var tickets = new List<OrderTicket>();
207  foreach (var request in requests)
208  {
209  tickets.Add(Portfolio.Transactions.AddOrder(request));
210  }
211 
212  foreach (var ticket in tickets)
213  {
214  if (ticket.Status.IsOpen())
215  {
216  Portfolio.Transactions.WaitForOrder(ticket.OrderId);
217  }
218  executedOrders.Add(ticket);
219  }
220 
221  // if our margin used is back under the portfolio value then we can stop liquidating
222  if (Portfolio.MarginRemaining >= 0)
223  {
224  break;
225  }
226  }
227  return executedOrders;
228  }
229  }
230 }