Lean  $LEAN_TAG$
BuyingPowerModel.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 using System;
17 using QuantConnect.Orders;
19 using System.Diagnostics.CodeAnalysis;
21 
23 {
24  /// <summary>
25  /// Provides a base class for all buying power models
26  /// </summary>
28  {
29  /// <summary>
30  /// Gets an implementation of <see cref="IBuyingPowerModel"/> that
31  /// does not check for sufficient buying power
32  /// </summary>
33  public static readonly IBuyingPowerModel Null = new NullBuyingPowerModel();
34 
35  private decimal _initialMarginRequirement;
36  private decimal _maintenanceMarginRequirement;
37 
38  /// <summary>
39  /// The percentage used to determine the required unused buying power for the account.
40  /// </summary>
41  protected decimal RequiredFreeBuyingPowerPercent;
42 
43  /// <summary>
44  /// Initializes a new instance of the <see cref="BuyingPowerModel"/> with no leverage (1x)
45  /// </summary>
47  : this(1m)
48  {
49  }
50 
51  /// <summary>
52  /// Initializes a new instance of the <see cref="BuyingPowerModel"/>
53  /// </summary>
54  /// <param name="initialMarginRequirement">The percentage of an order's absolute cost
55  /// that must be held in free cash in order to place the order</param>
56  /// <param name="maintenanceMarginRequirement">The percentage of the holding's absolute
57  /// cost that must be held in free cash in order to avoid a margin call</param>
58  /// <param name="requiredFreeBuyingPowerPercent">The percentage used to determine the required
59  /// unused buying power for the account.</param>
61  decimal initialMarginRequirement,
62  decimal maintenanceMarginRequirement,
63  decimal requiredFreeBuyingPowerPercent
64  )
65  {
66  if (initialMarginRequirement < 0 || initialMarginRequirement > 1)
67  {
68  throw new ArgumentException(Messages.BuyingPowerModel.InvalidInitialMarginRequirement);
69  }
70 
71  if (maintenanceMarginRequirement < 0 || maintenanceMarginRequirement > 1)
72  {
73  throw new ArgumentException(Messages.BuyingPowerModel.InvalidMaintenanceMarginRequirement);
74  }
75 
76  if (requiredFreeBuyingPowerPercent < 0 || requiredFreeBuyingPowerPercent > 1)
77  {
78  throw new ArgumentException(Messages.BuyingPowerModel.InvalidFreeBuyingPowerPercentRequirement);
79  }
80 
81  _initialMarginRequirement = initialMarginRequirement;
82  _maintenanceMarginRequirement = maintenanceMarginRequirement;
83  RequiredFreeBuyingPowerPercent = requiredFreeBuyingPowerPercent;
84  }
85 
86  /// <summary>
87  /// Initializes a new instance of the <see cref="BuyingPowerModel"/>
88  /// </summary>
89  /// <param name="leverage">The leverage</param>
90  /// <param name="requiredFreeBuyingPowerPercent">The percentage used to determine the required
91  /// unused buying power for the account.</param>
92  public BuyingPowerModel(decimal leverage, decimal requiredFreeBuyingPowerPercent = 0)
93  {
94  if (leverage < 1)
95  {
96  throw new ArgumentException(Messages.BuyingPowerModel.InvalidLeverage);
97  }
98 
99  if (requiredFreeBuyingPowerPercent < 0 || requiredFreeBuyingPowerPercent > 1)
100  {
101  throw new ArgumentException(Messages.BuyingPowerModel.InvalidFreeBuyingPowerPercentRequirement);
102  }
103 
104  _initialMarginRequirement = 1 / leverage;
105  _maintenanceMarginRequirement = 1 / leverage;
106  RequiredFreeBuyingPowerPercent = requiredFreeBuyingPowerPercent;
107  }
108 
109  /// <summary>
110  /// Gets the current leverage of the security
111  /// </summary>
112  /// <param name="security">The security to get leverage for</param>
113  /// <returns>The current leverage in the security</returns>
114  public virtual decimal GetLeverage(Security security)
115  {
116  return 1 / _initialMarginRequirement;
117  }
118 
119  /// <summary>
120  /// Sets the leverage for the applicable securities, i.e, equities
121  /// </summary>
122  /// <remarks>
123  /// This is added to maintain backwards compatibility with the old margin/leverage system
124  /// </remarks>
125  /// <param name="security"></param>
126  /// <param name="leverage">The new leverage</param>
127  public virtual void SetLeverage(Security security, decimal leverage)
128  {
129  if (leverage < 1)
130  {
131  throw new ArgumentException(Messages.BuyingPowerModel.InvalidLeverage);
132  }
133 
134  var margin = 1 / leverage;
135  _initialMarginRequirement = margin;
136  _maintenanceMarginRequirement = margin;
137  }
138 
139  /// <summary>
140  /// Gets the total margin required to execute the specified order in units of the account currency including fees
141  /// </summary>
142  /// <param name="parameters">An object containing the portfolio, the security and the order</param>
143  /// <returns>The total margin in terms of the currency quoted in the order</returns>
146  )
147  {
148  //Get the order value from the non-abstract order classes (MarketOrder, LimitOrder, StopMarketOrder)
149  //Market order is approximated from the current security price and set in the MarketOrder Method in QCAlgorithm.
150 
151  var fees = parameters.Security.FeeModel.GetOrderFee(
152  new OrderFeeParameters(parameters.Security,
153  parameters.Order)).Value;
154  var feesInAccountCurrency = parameters.CurrencyConverter.
155  ConvertToAccountCurrency(fees).Amount;
156 
157  var orderMargin = this.GetInitialMarginRequirement(parameters.Security, parameters.Order.Quantity);
158 
159  return orderMargin + Math.Sign(orderMargin) * feesInAccountCurrency;
160  }
161 
162  /// <summary>
163  /// Gets the margin currently allocated to the specified holding
164  /// </summary>
165  /// <param name="parameters">An object containing the security and holdings quantity/cost/value</param>
166  /// <returns>The maintenance margin required for the provided holdings quantity/cost/value</returns>
168  {
169  return parameters.AbsoluteHoldingsValue * _maintenanceMarginRequirement;
170  }
171 
172  /// <summary>
173  /// Gets the margin cash available for a trade
174  /// </summary>
175  /// <param name="portfolio">The algorithm's portfolio</param>
176  /// <param name="security">The security to be traded</param>
177  /// <param name="direction">The direction of the trade</param>
178  /// <returns>The margin available for the trade</returns>
179  protected virtual decimal GetMarginRemaining(
180  SecurityPortfolioManager portfolio,
181  Security security,
182  OrderDirection direction
183  )
184  {
185  var totalPortfolioValue = portfolio.TotalPortfolioValue;
186  var result = portfolio.GetMarginRemaining(totalPortfolioValue);
187 
188  if (direction != OrderDirection.Hold)
189  {
190  var holdings = security.Holdings;
191  //If the order is in the same direction as holdings, our remaining cash is our cash
192  //In the opposite direction, our remaining cash is 2 x current value of assets + our cash
193  if (holdings.IsLong)
194  {
195  switch (direction)
196  {
197  case OrderDirection.Sell:
198  result +=
199  // portion of margin to close the existing position
200  this.GetMaintenanceMargin(security) +
201  // portion of margin to open the new position
202  this.GetInitialMarginRequirement(security, security.Holdings.AbsoluteQuantity);
203  break;
204  }
205  }
206  else if (holdings.IsShort)
207  {
208  switch (direction)
209  {
210  case OrderDirection.Buy:
211  result +=
212  // portion of margin to close the existing position
213  this.GetMaintenanceMargin(security) +
214  // portion of margin to open the new position
215  this.GetInitialMarginRequirement(security, security.Holdings.AbsoluteQuantity);
216  break;
217  }
218  }
219  }
220 
221  result -= totalPortfolioValue * RequiredFreeBuyingPowerPercent;
222  return result < 0 ? 0 : result;
223  }
224 
225  /// <summary>
226  /// The margin that must be held in order to increase the position by the provided quantity
227  /// </summary>
228  /// <param name="parameters">An object containing the security and quantity of shares</param>
229  /// <returns>The initial margin required for the provided security and quantity</returns>
231  {
232  var security = parameters.Security;
233  var quantity = parameters.Quantity;
234  return security.QuoteCurrency.ConversionRate
235  * security.SymbolProperties.ContractMultiplier
236  * security.Price
237  * quantity
238  * _initialMarginRequirement;
239  }
240 
241  /// <summary>
242  /// Check if there is sufficient buying power to execute this order.
243  /// </summary>
244  /// <param name="parameters">An object containing the portfolio, the security and the order</param>
245  /// <returns>Returns buying power information for an order</returns>
247  {
248  // short circuit the div 0 case
249  if (parameters.Order.Quantity == 0)
250  {
251  return parameters.Sufficient();
252  }
253 
254  var ticket = parameters.Portfolio.Transactions.GetOrderTicket(parameters.Order.Id);
255  if (ticket == null)
256  {
257  return parameters.Insufficient(Messages.BuyingPowerModel.InsufficientBuyingPowerDueToNullOrderTicket(parameters.Order));
258  }
259 
260  if (parameters.Order.Type == OrderType.OptionExercise)
261  {
262  // for option assignment and exercise orders we look into the requirements to process the underlying security transaction
263  var option = (Option.Option) parameters.Security;
264  var underlying = option.Underlying;
265 
266  if (option.IsAutoExercised(underlying.Close) && underlying.IsTradable)
267  {
268  var quantity = option.GetExerciseQuantity(parameters.Order.Quantity);
269 
270  var newOrder = new LimitOrder
271  {
272  Id = parameters.Order.Id,
273  Time = parameters.Order.Time,
274  LimitPrice = option.StrikePrice,
275  Symbol = underlying.Symbol,
276  Quantity = quantity
277  };
278 
279  // we continue with this call for underlying
280  var parametersForUnderlying = parameters.ForUnderlying(newOrder);
281 
282  var freeMargin = underlying.BuyingPowerModel.GetBuyingPower(parametersForUnderlying.Portfolio, parametersForUnderlying.Security, parametersForUnderlying.Order.Direction);
283  // we add the margin used by the option itself
285 
286  var initialMarginRequired = underlying.BuyingPowerModel.GetInitialMarginRequiredForOrder(
287  new InitialMarginRequiredForOrderParameters(parameters.Portfolio.CashBook, underlying, newOrder));
288 
289  return HasSufficientBuyingPowerForOrder(parametersForUnderlying, ticket, freeMargin, initialMarginRequired);
290  }
291 
292  return parameters.Sufficient();
293  }
294 
295  return HasSufficientBuyingPowerForOrder(parameters, ticket);
296  }
297 
299  decimal? freeMarginToUse = null, decimal? initialMarginRequired = null)
300  {
301  // When order only reduces or closes a security position, capital is always sufficient
302  if (parameters.Security.Holdings.Quantity * parameters.Order.Quantity < 0 && Math.Abs(parameters.Security.Holdings.Quantity) >= Math.Abs(parameters.Order.Quantity))
303  {
304  return parameters.Sufficient();
305  }
306 
307  var freeMargin = freeMarginToUse ?? GetMarginRemaining(parameters.Portfolio, parameters.Security, parameters.Order.Direction);
308  var initialMarginRequiredForOrder = initialMarginRequired ?? GetInitialMarginRequiredForOrder(
309  new InitialMarginRequiredForOrderParameters(
310  parameters.Portfolio.CashBook, parameters.Security, parameters.Order
311  ));
312 
313  // pro-rate the initial margin required for order based on how much has already been filled
314  var percentUnfilled = (Math.Abs(parameters.Order.Quantity) - Math.Abs(ticket.QuantityFilled)) / Math.Abs(parameters.Order.Quantity);
315  var initialMarginRequiredForRemainderOfOrder = percentUnfilled * initialMarginRequiredForOrder;
316 
317  if (Math.Abs(initialMarginRequiredForRemainderOfOrder) > freeMargin)
318  {
319  return parameters.Insufficient(Messages.BuyingPowerModel.InsufficientBuyingPowerDueToUnsufficientMargin(parameters.Order,
320  initialMarginRequiredForRemainderOfOrder, freeMargin));
321  }
322 
323  return parameters.Sufficient();
324  }
325 
326  /// <summary>
327  /// Get the maximum market order quantity to obtain a delta in the buying power used by a security.
328  /// The deltas sign defines the position side to apply it to, positive long, negative short.
329  /// </summary>
330  /// <param name="parameters">An object containing the portfolio, the security and the delta buying power</param>
331  /// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns>
332  /// <remarks>Used by the margin call model to reduce the position by a delta percent.</remarks>
335  {
336  var usedBuyingPower = parameters.Security.BuyingPowerModel.GetReservedBuyingPowerForPosition(
338 
339  var signedUsedBuyingPower = usedBuyingPower * (parameters.Security.Holdings.IsLong ? 1 : -1);
340 
341  var targetBuyingPower = signedUsedBuyingPower + parameters.DeltaBuyingPower;
342 
343  var target = 0m;
344  if (parameters.Portfolio.TotalPortfolioValue != 0)
345  {
346  target = targetBuyingPower / parameters.Portfolio.TotalPortfolioValue;
347  }
348 
351  parameters.Security,
352  target,
354  parameters.SilenceNonErrorReasons));
355  }
356 
357  /// <summary>
358  /// Get the maximum market order quantity to obtain a position with a given buying power percentage.
359  /// Will not take into account free buying power.
360  /// </summary>
361  /// <param name="parameters">An object containing the portfolio, the security and the target signed buying power percentage</param>
362  /// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns>
363  /// <remarks>This implementation ensures that our resulting holdings is less than the target, but it does not necessarily
364  /// maximize the holdings to meet the target. To do that we need a minimizing algorithm that reduces the difference between
365  /// the target final margin value and the target holdings margin.</remarks>
367  {
368  // this is expensive so lets fetch it once
369  var totalPortfolioValue = parameters.Portfolio.TotalPortfolioValue;
370 
371  // adjust target buying power to comply with required Free Buying Power Percent
372  var signedTargetFinalMarginValue =
373  parameters.TargetBuyingPower * (totalPortfolioValue - totalPortfolioValue * RequiredFreeBuyingPowerPercent);
374 
375  // if targeting zero, simply return the negative of the quantity
376  if (signedTargetFinalMarginValue == 0)
377  {
378  return new GetMaximumOrderQuantityResult(-parameters.Security.Holdings.Quantity, string.Empty, false);
379  }
380 
381  // we use initial margin requirement here to avoid the duplicate PortfolioTarget.Percent situation:
382  // PortfolioTarget.Percent(1) -> fills -> PortfolioTarget.Percent(1) _could_ detect free buying power if we use Maintenance requirement here
383  var signedCurrentUsedMargin = this.GetInitialMarginRequirement(parameters.Security, parameters.Security.Holdings.Quantity);
384 
385  // determine the unit price in terms of the account currency
386  var utcTime = parameters.Security.LocalTime.ConvertToUtc(parameters.Security.Exchange.TimeZone);
387 
388  // determine the margin required for 1 unit
389  var absUnitMargin = this.GetInitialMarginRequirement(parameters.Security, 1);
390  if (absUnitMargin == 0)
391  {
392  return new GetMaximumOrderQuantityResult(0, parameters.Security.Symbol.GetZeroPriceMessage());
393  }
394 
395  // Check that the change of margin is above our models minimum percentage change
396  var absDifferenceOfMargin = Math.Abs(signedTargetFinalMarginValue - signedCurrentUsedMargin);
398  parameters.MinimumOrderMarginPortfolioPercentage, absDifferenceOfMargin))
399  {
400  string reason = null;
401  if (!parameters.SilenceNonErrorReasons)
402  {
403  var minimumValue = totalPortfolioValue * parameters.MinimumOrderMarginPortfolioPercentage;
404  reason = Messages.BuyingPowerModel.TargetOrderMarginNotAboveMinimum(absDifferenceOfMargin, minimumValue);
405  }
406 
408  {
409  // will trigger the warning if it has not already been sent
411  }
412  return new GetMaximumOrderQuantityResult(0, reason, false);
413  }
414 
415  // Use the following loop to converge on a value that places us under our target allocation when adjusted for fees
416  var lastOrderQuantity = 0m; // For safety check
417  decimal orderFees = 0m;
418  decimal signedTargetHoldingsMargin;
419  decimal orderQuantity;
420 
421  do
422  {
423  // Calculate our order quantity
424  orderQuantity = GetAmountToOrder(parameters.Security, signedTargetFinalMarginValue, absUnitMargin, out signedTargetHoldingsMargin);
425  if (orderQuantity == 0)
426  {
427  string reason = null;
428  if (!parameters.SilenceNonErrorReasons)
429  {
430  reason = Messages.BuyingPowerModel.OrderQuantityLessThanLotSize(parameters.Security,
431  signedTargetFinalMarginValue - signedCurrentUsedMargin);
432  }
433 
434  return new GetMaximumOrderQuantityResult(0, reason, false);
435  }
436 
437  // generate the order
438  var order = new MarketOrder(parameters.Security.Symbol, orderQuantity, utcTime);
439  var fees = parameters.Security.FeeModel.GetOrderFee(
440  new OrderFeeParameters(parameters.Security,
441  order)).Value;
442  orderFees = parameters.Portfolio.CashBook.ConvertToAccountCurrency(fees).Amount;
443 
444  // Update our target portfolio margin allocated when considering fees, then calculate the new FinalOrderMargin
445  signedTargetFinalMarginValue = (totalPortfolioValue - orderFees - totalPortfolioValue * RequiredFreeBuyingPowerPercent) * parameters.TargetBuyingPower;
446 
447  // Start safe check after first loop, stops endless recursion
448  if (lastOrderQuantity == orderQuantity)
449  {
450  var message = Messages.BuyingPowerModel.FailedToConvergeOnTheTargetMargin(parameters, signedTargetFinalMarginValue, orderFees);
451 
452  // Need to add underlying value to message to reproduce with options
453  if (parameters.Security is Option.Option option && option.Underlying != null)
454  {
455  var underlying = option.Underlying;
456  message += " " + Messages.BuyingPowerModel.FailedToConvergeOnTheTargetMarginUnderlyingSecurityInfo(underlying);
457  }
458 
459  throw new ArgumentException(message);
460  }
461  lastOrderQuantity = orderQuantity;
462 
463  }
464  // Ensure that our target holdings margin will be less than or equal to our target allocated margin
465  while (Math.Abs(signedTargetHoldingsMargin) > Math.Abs(signedTargetFinalMarginValue));
466 
467  // add directionality back in
468  return new GetMaximumOrderQuantityResult(orderQuantity);
469  }
470 
471  /// <summary>
472  /// Helper function that determines the amount to order to get to a given target safely.
473  /// Meaning it will either be at or just below target always.
474  /// </summary>
475  /// <param name="security">Security we are to determine order size for</param>
476  /// <param name="targetMargin">Target margin allocated</param>
477  /// <param name="marginForOneUnit">Margin requirement for one unit; used in our initial order guess</param>
478  /// <param name="finalMargin">Output the final margin allocated to this security</param>
479  /// <returns>The size of the order to get safely to our target</returns>
480  public decimal GetAmountToOrder([NotNull]Security security, decimal targetMargin, decimal marginForOneUnit, out decimal finalMargin)
481  {
482  var lotSize = security.SymbolProperties.LotSize;
483 
484  // Start with order size that puts us back to 0, in theory this means current margin is 0
485  // so we can calculate holdings to get to the new target margin directly. This is very helpful for
486  // odd cases where margin requirements aren't linear.
487  var orderSize = -security.Holdings.Quantity;
488 
489  // Use the margin for one unit to make our initial guess.
490  orderSize += targetMargin / marginForOneUnit;
491 
492  // Determine the rounding mode for this order size
493  var roundingMode = targetMargin < 0
494  // Ending in short position; orders need to be rounded towards positive so we end up under our target
495  ? MidpointRounding.ToPositiveInfinity
496  // Ending in long position; orders need to be rounded towards negative so we end up under our target
497  : MidpointRounding.ToNegativeInfinity;
498 
499  // Round this order size appropriately
500  orderSize = orderSize.DiscretelyRoundBy(lotSize, roundingMode);
501 
502  // Use our model to calculate this final margin as a final check
503  finalMargin = this.GetInitialMarginRequirement(security,
504  orderSize + security.Holdings.Quantity);
505 
506  // Until our absolute final margin is equal to or below target we need to adjust; ensures we don't overshoot target
507  // This isn't usually the case, but for non-linear margin per unit cases this may be necessary.
508  // For example https://www.quantconnect.com/forum/discussion/12470, (covered in OptionMarginBuyingPowerModelTests)
509  var marginDifference = finalMargin - targetMargin;
510  while ((targetMargin < 0 && marginDifference < 0) || (targetMargin > 0 && marginDifference > 0))
511  {
512  // TODO: Can this be smarter about its adjustment, instead of just stepping by lotsize?
513  // We adjust according to the target margin being a short or long
514  orderSize += targetMargin < 0 ? lotSize : -lotSize;
515 
516  // Recalculate final margin with this adjusted orderSize
517  finalMargin = this.GetInitialMarginRequirement(security,
518  orderSize + security.Holdings.Quantity);
519 
520  // Safety check, does not occur in any of our testing, but to be sure we don't enter a endless loop
521  // have this guy check that the difference between the two is not growing.
522  var newDifference = finalMargin - targetMargin;
523  if (Math.Abs(newDifference) > Math.Abs(marginDifference) && Math.Sign(newDifference) == Math.Sign(marginDifference))
524  {
525  // We have a problem and are correcting in the wrong direction
526  var errorMessage = "BuyingPowerModel().GetAmountToOrder(): " +
527  Messages.BuyingPowerModel.MarginBeingAdjustedInTheWrongDirection(targetMargin, marginForOneUnit, security);
528 
529  // Need to add underlying value to message to reproduce with options
530  if (security is Option.Option option && option.Underlying != null)
531  {
532  errorMessage += " " + Messages.BuyingPowerModel.MarginBeingAdjustedInTheWrongDirectionUnderlyingSecurityInfo(option.Underlying);
533  }
534 
535  throw new ArgumentException(errorMessage);
536  }
537 
538  marginDifference = newDifference;
539  }
540 
541  return orderSize;
542  }
543 
544  /// <summary>
545  /// Gets the amount of buying power reserved to maintain the specified position
546  /// </summary>
547  /// <param name="parameters">A parameters object containing the security</param>
548  /// <returns>The reserved buying power in account currency</returns>
550  {
551  var maintenanceMargin = this.GetMaintenanceMargin(parameters.Security);
552  return parameters.ResultInAccountCurrency(maintenanceMargin);
553  }
554 
555  /// <summary>
556  /// Gets the buying power available for a trade
557  /// </summary>
558  /// <param name="parameters">A parameters object containing the algorithm's portfolio, security, and order direction</param>
559  /// <returns>The buying power available for the trade</returns>
561  {
562  var marginRemaining = GetMarginRemaining(parameters.Portfolio, parameters.Security, parameters.Direction);
563  return parameters.ResultInAccountCurrency(marginRemaining);
564  }
565  }
566 }