Lean  $LEAN_TAG$
CashBuyingPowerModel.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 System.Collections.Generic;
18 using QuantConnect.Orders;
20 
22 {
23  /// <summary>
24  /// Represents a buying power model for cash accounts
25  /// </summary>
27  {
28  /// <summary>
29  /// Initializes a new instance of the <see cref="CashBuyingPowerModel"/> class
30  /// </summary>
32  : base(1m, 0m, 0m)
33  {
34  }
35 
36  /// <summary>
37  /// Gets the current leverage of the security
38  /// </summary>
39  /// <param name="security">The security to get leverage for</param>
40  /// <returns>The current leverage in the security</returns>
41  public override decimal GetLeverage(Security security)
42  {
43  // Always returns 1. Cash accounts have no leverage.
44  return 1m;
45  }
46 
47  /// <summary>
48  /// Sets the leverage for the applicable securities, i.e, equities
49  /// </summary>
50  /// <remarks>
51  /// This is added to maintain backwards compatibility with the old margin/leverage system
52  /// </remarks>
53  /// <param name="security">The security to set leverage for</param>
54  /// <param name="leverage">The new leverage</param>
55  public override void SetLeverage(Security security, decimal leverage)
56  {
57  if (leverage != 1)
58  {
59  throw new InvalidOperationException(Messages.CashBuyingPowerModel.UnsupportedLeverage);
60  }
61  }
62 
63  /// <summary>
64  /// The margin that must be held in order to increase the position by the provided quantity
65  /// </summary>
66  /// <param name="parameters">An object containing the security and quantity of shares</param>
68  {
69  var security = parameters.Security;
70  var quantity = parameters.Quantity;
71  return security.QuoteCurrency.ConversionRate
72  * security.SymbolProperties.ContractMultiplier
73  * security.Price
74  * quantity;
75  }
76 
77  /// <summary>
78  /// Check if there is sufficient buying power to execute this order.
79  /// </summary>
80  /// <param name="parameters">An object containing the portfolio, the security and the order</param>
81  /// <returns>Returns buying power information for an order</returns>
83  {
84  var baseCurrency = parameters.Security as IBaseCurrencySymbol;
85  if (baseCurrency == null)
86  {
87  return parameters.Insufficient(Messages.CashBuyingPowerModel.UnsupportedSecurity(parameters.Security));
88  }
89 
90  decimal totalQuantity;
91  decimal orderQuantity;
92  if (parameters.Order.Direction == OrderDirection.Buy)
93  {
94  // quantity available for buying in quote currency
95  totalQuantity = parameters.Security.QuoteCurrency.Amount;
96  orderQuantity = parameters.Order.AbsoluteQuantity * GetOrderPrice(parameters.Security, parameters.Order);
97  }
98  else
99  {
100  // quantity available for selling in base currency
101  totalQuantity = baseCurrency.BaseCurrency.Amount;
102  orderQuantity = parameters.Order.AbsoluteQuantity;
103  }
104 
105  // calculate reserved quantity for open orders (in quote or base currency depending on direction)
106  var openOrdersReservedQuantity = GetOpenOrdersReservedQuantity(parameters.Portfolio, parameters.Security, parameters.Order);
107 
108  if (parameters.Order.Direction == OrderDirection.Sell)
109  {
110  // can sell available and non-reserved quantities
111  if (orderQuantity <= totalQuantity - openOrdersReservedQuantity)
112  {
113  return parameters.Sufficient();
114  }
115 
116  return parameters.Insufficient(Messages.CashBuyingPowerModel.SellOrderShortHoldingsNotSupported(totalQuantity,
117  openOrdersReservedQuantity, orderQuantity, baseCurrency));
118  }
119 
120  var maximumQuantity = 0m;
121  if (parameters.Order.Type == OrderType.Market)
122  {
123  // include existing holdings (in quote currency)
124  var holdingsValue =
125  parameters.Portfolio.CashBook.Convert(baseCurrency.BaseCurrency.Amount, baseCurrency.BaseCurrency.Symbol, parameters.Security.QuoteCurrency.Symbol);
126 
127  // find a target value in account currency for buy market orders
128  var targetValue =
129  parameters.Portfolio.CashBook.ConvertToAccountCurrency(totalQuantity - openOrdersReservedQuantity + holdingsValue,
130  parameters.Security.QuoteCurrency.Symbol);
131 
132  // convert the target into a percent in relation to TPV
133  var targetPercent = parameters.Portfolio.TotalPortfolioValue == 0 ? 0 : targetValue / parameters.Portfolio.TotalPortfolioValue;
134 
135  // maximum quantity that can be bought (in quote currency)
136  maximumQuantity =
138  new GetMaximumOrderQuantityForTargetBuyingPowerParameters(parameters.Portfolio, parameters.Security, targetPercent, 0)).Quantity * GetOrderPrice(parameters.Security, parameters.Order);
139 
140  if (orderQuantity <= Math.Abs(maximumQuantity))
141  {
142  return parameters.Sufficient();
143  }
144 
145  return parameters.Insufficient(Messages.CashBuyingPowerModel.BuyOrderQuantityGreaterThanMaxForBuyingPower(totalQuantity,
146  maximumQuantity, openOrdersReservedQuantity, orderQuantity, baseCurrency, parameters.Security, parameters.Order));
147  }
148 
149  // for limit orders, add fees to the order cost
150  var orderFee = 0m;
151  if (parameters.Order.Type == OrderType.Limit)
152  {
153  var fee = parameters.Security.FeeModel.GetOrderFee(
154  new OrderFeeParameters(parameters.Security,
155  parameters.Order)).Value;
156  orderFee = parameters.Portfolio.CashBook.Convert(
157  fee.Amount,
158  fee.Currency,
159  parameters.Security.QuoteCurrency.Symbol);
160  }
161 
162  maximumQuantity = totalQuantity - openOrdersReservedQuantity - orderFee;
163  if (orderQuantity <= maximumQuantity)
164  {
165  return parameters.Sufficient();
166  }
167 
168  return parameters.Insufficient(Messages.CashBuyingPowerModel.BuyOrderQuantityGreaterThanMaxForBuyingPower(totalQuantity,
169  maximumQuantity, openOrdersReservedQuantity, orderQuantity, baseCurrency, parameters.Security, parameters.Order));
170  }
171 
172  /// <summary>
173  /// Get the maximum market order quantity to obtain a delta in the buying power used by a security.
174  /// The deltas sign defines the position side to apply it to, positive long, negative short.
175  /// </summary>
176  /// <param name="parameters">An object containing the portfolio, the security and the delta buying power</param>
177  /// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns>
178  /// <remarks>Used by the margin call model to reduce the position by a delta percent.</remarks>
181  {
182  throw new NotImplementedException(Messages.CashBuyingPowerModel.GetMaximumOrderQuantityForDeltaBuyingPowerNotImplemented);
183  }
184 
185  /// <summary>
186  /// Get the maximum market order quantity to obtain a position with a given buying power percentage.
187  /// Will not take into account free buying power.
188  /// </summary>
189  /// <param name="parameters">An object containing the portfolio, the security and the target signed buying power percentage</param>
190  /// <returns>Returns the maximum allowed market order quantity and if zero, also the reason</returns>
192  {
193  var targetPortfolioValue = parameters.TargetBuyingPower * parameters.Portfolio.TotalPortfolioValue;
194  // no shorting allowed
195  if (targetPortfolioValue < 0)
196  {
197  return new GetMaximumOrderQuantityResult(0, Messages.CashBuyingPowerModel.ShortingNotSupported);
198  }
199 
200  var baseCurrency = parameters.Security as IBaseCurrencySymbol;
201  if (baseCurrency == null)
202  {
203  return new GetMaximumOrderQuantityResult(0, Messages.CashBuyingPowerModel.InvalidSecurity);
204  }
205 
206  // if target value is zero, return amount of base currency available to sell
207  if (targetPortfolioValue == 0)
208  {
209  return new GetMaximumOrderQuantityResult(-baseCurrency.BaseCurrency.Amount);
210  }
211 
212  // convert base currency cash to account currency
213  var baseCurrencyPosition = parameters.Portfolio.CashBook.ConvertToAccountCurrency(
214  baseCurrency.BaseCurrency.Amount,
215  baseCurrency.BaseCurrency.Symbol);
216 
217  // remove directionality, we'll work in the land of absolutes
218  var targetOrderValue = Math.Abs(targetPortfolioValue - baseCurrencyPosition);
219  var direction = targetPortfolioValue > baseCurrencyPosition ? OrderDirection.Buy : OrderDirection.Sell;
220 
221  // determine the unit price in terms of the account currency
222  var unitPrice = direction == OrderDirection.Buy ? parameters.Security.AskPrice : parameters.Security.BidPrice;
224 
225  if (unitPrice == 0)
226  {
227  if (parameters.Security.QuoteCurrency.ConversionRate == 0)
228  {
229  return new GetMaximumOrderQuantityResult(0,
230  Messages.CashBuyingPowerModel.NoDataInInternalCashFeedYet(parameters.Security, parameters.Portfolio));
231  }
232 
233  if (parameters.Security.SymbolProperties.ContractMultiplier == 0)
234  {
235  return new GetMaximumOrderQuantityResult(0, Messages.CashBuyingPowerModel.ZeroContractMultiplier(parameters.Security));
236  }
237 
238  // security.Price == 0
239  return new GetMaximumOrderQuantityResult(0, parameters.Security.Symbol.GetZeroPriceMessage());
240  }
241 
242  // continue iterating while we do not have enough cash for the order
243  decimal orderFees = 0;
244  decimal currentOrderValue = 0;
245  // compute the initial order quantity
246  var orderQuantity = targetOrderValue / unitPrice;
247 
248  // rounding off Order Quantity to the nearest multiple of Lot Size
249  orderQuantity -= orderQuantity % parameters.Security.SymbolProperties.LotSize;
250  if (orderQuantity == 0)
251  {
252  string reason = null;
253  if (!parameters.SilenceNonErrorReasons)
254  {
255  reason = Messages.CashBuyingPowerModel.OrderQuantityLessThanLotSize(parameters.Security);
256  }
257  return new GetMaximumOrderQuantityResult(0, reason, false);
258  }
259 
260  // Just in case...
261  var lastOrderQuantity = 0m;
262  var utcTime = parameters.Security.LocalTime.ConvertToUtc(parameters.Security.Exchange.TimeZone);
263  do
264  {
265  // Each loop will reduce the order quantity based on the difference between
266  // (cashRequired + orderFees) and targetOrderValue
267  if (currentOrderValue > targetOrderValue)
268  {
269  var currentOrderValuePerUnit = currentOrderValue / orderQuantity;
270  var amountOfOrdersToRemove = (currentOrderValue - targetOrderValue) / currentOrderValuePerUnit;
271  if (amountOfOrdersToRemove < parameters.Security.SymbolProperties.LotSize)
272  {
273  // we will always substract at leat 1 LotSize
274  amountOfOrdersToRemove = parameters.Security.SymbolProperties.LotSize;
275  }
276  orderQuantity -= amountOfOrdersToRemove;
277  }
278 
279  // rounding off Order Quantity to the nearest multiple of Lot Size
280  orderQuantity -= orderQuantity % parameters.Security.SymbolProperties.LotSize;
281  if (orderQuantity <= 0)
282  {
283  return new GetMaximumOrderQuantityResult(0,
284  Messages.CashBuyingPowerModel.OrderQuantityLessThanLotSize(parameters.Security) +
285  Messages.CashBuyingPowerModel.OrderQuantityLessThanLotSizeOrderDetails(targetOrderValue, orderQuantity, orderFees)
286  );
287  }
288 
289  if (lastOrderQuantity == orderQuantity)
290  {
291  throw new ArgumentException(Messages.CashBuyingPowerModel.FailedToConvergeOnTargetOrderValue(targetOrderValue, currentOrderValue,
292  orderQuantity, orderFees, parameters.Security));
293  }
294  lastOrderQuantity = orderQuantity;
295 
296  // generate the order
297  var order = new MarketOrder(parameters.Security.Symbol, orderQuantity, utcTime);
298  var orderValue = orderQuantity * unitPrice;
299 
300  var fees = parameters.Security.FeeModel.GetOrderFee(
301  new OrderFeeParameters(parameters.Security,
302  order)).Value;
303  orderFees = parameters.Portfolio.CashBook.ConvertToAccountCurrency(fees).Amount;
304 
305  currentOrderValue = orderValue + orderFees;
306  } while (currentOrderValue > targetOrderValue);
307 
308  // add directionality back in
309  return new GetMaximumOrderQuantityResult((direction == OrderDirection.Sell ? -1 : 1) * orderQuantity);
310  }
311 
312  /// <summary>
313  /// Gets the amount of buying power reserved to maintain the specified position
314  /// </summary>
315  /// <param name="parameters">A parameters object containing the security</param>
316  /// <returns>The reserved buying power in account currency</returns>
318  {
319  // Always returns 0. Since we're purchasing currencies outright, the position doesn't consume buying power
320  return parameters.ResultInAccountCurrency(0m);
321  }
322 
323  /// <summary>
324  /// Gets the buying power available for a trade
325  /// </summary>
326  /// <param name="parameters">A parameters object containing the algorithm's portfolio, security, and order direction</param>
327  /// <returns>The buying power available for the trade</returns>
329  {
330  var security = parameters.Security;
331  var portfolio = parameters.Portfolio;
332  var direction = parameters.Direction;
333 
334  var baseCurrency = security as IBaseCurrencySymbol;
335  if (baseCurrency == null)
336  {
337  return parameters.ResultInAccountCurrency(0m);
338  }
339 
340  var baseCurrencyPosition = baseCurrency.BaseCurrency.Amount;
341  var quoteCurrencyPosition = portfolio.CashBook[security.QuoteCurrency.Symbol].Amount;
342 
343  // determine the unit price in terms of the quote currency
344  var utcTime = parameters.Security.LocalTime.ConvertToUtc(parameters.Security.Exchange.TimeZone);
345  var unitPrice = new MarketOrder(security.Symbol, 1, utcTime).GetValue(security) / security.QuoteCurrency.ConversionRate;
346  if (unitPrice == 0)
347  {
348  return parameters.ResultInAccountCurrency(0m);
349  }
350 
351  // NOTE: This is returning in units of the BASE currency
352  if (direction == OrderDirection.Buy)
353  {
354  // invert units for math, 6500USD per BTC, currency pairs aren't real fractions
355  // (USD)/(BTC/USD) => 10kUSD/ (6500 USD/BTC) => 10kUSD * (1BTC/6500USD) => ~ 1.5BTC
356  return parameters.Result(quoteCurrencyPosition / unitPrice, baseCurrency.BaseCurrency.Symbol);
357  }
358 
359  if (direction == OrderDirection.Sell)
360  {
361  return parameters.Result(baseCurrencyPosition, baseCurrency.BaseCurrency.Symbol);
362  }
363 
364  return parameters.ResultInAccountCurrency(0m);
365  }
366 
367  private static decimal GetOrderPrice(Security security, Order order)
368  {
369  var orderPrice = 0m;
370  switch (order.Type)
371  {
372  case OrderType.Market:
373  orderPrice = security.Price;
374  break;
375 
376  case OrderType.Limit:
377  orderPrice = ((LimitOrder)order).LimitPrice;
378  break;
379 
380  case OrderType.StopMarket:
381  orderPrice = ((StopMarketOrder)order).StopPrice;
382  break;
383 
384  case OrderType.StopLimit:
385  orderPrice = ((StopLimitOrder)order).LimitPrice;
386  break;
387 
388  case OrderType.LimitIfTouched:
389  orderPrice = ((LimitIfTouchedOrder)order).LimitPrice;
390  break;
391 
392  case OrderType.TrailingStop:
393  orderPrice = ((TrailingStopOrder)order).StopPrice;
394  break;
395  }
396 
397  return orderPrice;
398  }
399 
400  private static decimal GetOpenOrdersReservedQuantity(SecurityPortfolioManager portfolio, Security security, Order order)
401  {
402  var baseCurrency = security as IBaseCurrencySymbol;
403  if (baseCurrency == null) return 0;
404 
405  // find the target currency for the requested direction and the securities potentially involved
406  var targetCurrency = order.Direction == OrderDirection.Buy
407  ? security.QuoteCurrency.Symbol
408  : baseCurrency.BaseCurrency.Symbol;
409 
410  var symbolDirectionPairs = new Dictionary<Symbol, OrderDirection>();
411  foreach (var portfolioSecurity in portfolio.Securities.Values)
412  {
413  var basePortfolioSecurity = portfolioSecurity as IBaseCurrencySymbol;
414  if (basePortfolioSecurity == null) continue;
415 
416  if (basePortfolioSecurity.BaseCurrency.Symbol == targetCurrency)
417  {
418  symbolDirectionPairs.Add(portfolioSecurity.Symbol, OrderDirection.Sell);
419  }
420  else if (portfolioSecurity.QuoteCurrency.Symbol == targetCurrency)
421  {
422  symbolDirectionPairs.Add(portfolioSecurity.Symbol, OrderDirection.Buy);
423  }
424  }
425 
426  // fetch open orders with matching symbol/side
427  var openOrders = portfolio.Transactions.GetOpenOrders(x =>
428  {
429  OrderDirection dir;
430  return symbolDirectionPairs.TryGetValue(x.Symbol, out dir) &&
431  // same direction of our order
432  dir == x.Direction &&
433  // don't count our current order
434  x.Id != order.Id &&
435  // only count working orders
436  (x.Type == OrderType.Limit || x.Type == OrderType.StopMarket);
437  }
438  );
439 
440  // calculate reserved quantity for selected orders
441  var openOrdersReservedQuantity = 0m;
442  foreach (var openOrder in openOrders)
443  {
444  var orderSecurity = portfolio.Securities[openOrder.Symbol];
445  var orderBaseCurrency = orderSecurity as IBaseCurrencySymbol;
446 
447  if (orderBaseCurrency != null)
448  {
449  // convert order value to target currency
450  var quantityInTargetCurrency = openOrder.AbsoluteQuantity;
451  if (orderSecurity.QuoteCurrency.Symbol == targetCurrency)
452  {
453  quantityInTargetCurrency *= GetOrderPrice(security, openOrder);
454  }
455 
456  openOrdersReservedQuantity += quantityInTargetCurrency;
457  }
458  }
459 
460  return openOrdersReservedQuantity;
461  }
462  }
463 }