Lean  $LEAN_TAG$
InteractiveBrokersBrokerageModel.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 System.Linq;
19 using QuantConnect.Util;
21 using QuantConnect.Orders;
27 
29 {
30  /// <summary>
31  /// Provides properties specific to interactive brokers
32  /// </summary>
34  {
35  /// <summary>
36  /// The default markets for the IB brokerage
37  /// </summary>
38  public new static readonly IReadOnlyDictionary<SecurityType, string> DefaultMarketMap = new Dictionary<SecurityType, string>
39  {
40  {SecurityType.Base, Market.USA},
41  {SecurityType.Equity, Market.USA},
42  {SecurityType.Index, Market.USA},
43  {SecurityType.Option, Market.USA},
44  {SecurityType.IndexOption, Market.USA},
45  {SecurityType.Future, Market.CME},
46  {SecurityType.FutureOption, Market.CME},
47  {SecurityType.Forex, Market.Oanda},
49  }.ToReadOnlyDictionary();
50 
51  private readonly Type[] _supportedTimeInForces =
52  {
54  typeof(DayTimeInForce),
56  };
57 
58  private readonly HashSet<OrderType> _supportedOrderTypes = new HashSet<OrderType>
59  {
60  OrderType.Market,
61  OrderType.MarketOnOpen,
62  OrderType.MarketOnClose,
63  OrderType.Limit,
64  OrderType.StopMarket,
65  OrderType.StopLimit,
66  OrderType.TrailingStop,
67  OrderType.LimitIfTouched,
68  OrderType.ComboMarket,
69  OrderType.ComboLimit,
70  OrderType.ComboLegLimit,
71  OrderType.OptionExercise
72  };
73 
74  /// <summary>
75  /// Initializes a new instance of the <see cref="InteractiveBrokersBrokerageModel"/> class
76  /// </summary>
77  /// <param name="accountType">The type of account to be modelled, defaults to
78  /// <see cref="AccountType.Margin"/></param>
80  : base(accountType)
81  {
82  }
83 
84  /// <summary>
85  /// Gets a map of the default markets to be used for each security type
86  /// </summary>
87  public override IReadOnlyDictionary<SecurityType, string> DefaultMarkets => DefaultMarketMap;
88 
89  /// <summary>
90  /// Get the benchmark for this model
91  /// </summary>
92  /// <param name="securities">SecurityService to create the security with if needed</param>
93  /// <returns>The benchmark for this brokerage</returns>
94  public override IBenchmark GetBenchmark(SecurityManager securities)
95  {
96  // Equivalent to no benchmark
97  return new FuncBenchmark(x => 0);
98  }
99 
100  /// <summary>
101  /// Gets a new fee model that represents this brokerage's fee structure
102  /// </summary>
103  /// <param name="security">The security to get a fee model for</param>
104  /// <returns>The new fee model for this brokerage</returns>
105  public override IFeeModel GetFeeModel(Security security)
106  {
107  return new InteractiveBrokersFeeModel();
108  }
109 
110  /// <summary>
111  /// Gets the brokerage's leverage for the specified security
112  /// </summary>
113  /// <param name="security">The security's whose leverage we seek</param>
114  /// <returns>The leverage for the specified security</returns>
115  public override decimal GetLeverage(Security security)
116  {
117  if (AccountType == AccountType.Cash)
118  {
119  return 1m;
120  }
121 
122  return security.Type == SecurityType.Cfd ? 10m : base.GetLeverage(security);
123  }
124 
125  /// <summary>
126  /// Returns true if the brokerage could accept this order. This takes into account
127  /// order type, security type, and order size limits.
128  /// </summary>
129  /// <remarks>
130  /// For example, a brokerage may have no connectivity at certain times, or an order rate/size limit
131  /// </remarks>
132  /// <param name="security">The security being ordered</param>
133  /// <param name="order">The order to be processed</param>
134  /// <param name="message">If this function returns false, a brokerage message detailing why the order may not be submitted</param>
135  /// <returns>True if the brokerage could process the order, false otherwise</returns>
136  public override bool CanSubmitOrder(Security security, Order order, out BrokerageMessageEvent message)
137  {
138  message = null;
139 
140  // validate order type
141  if (!_supportedOrderTypes.Contains(order.Type))
142  {
143  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
144  Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, _supportedOrderTypes));
145 
146  return false;
147  }
148  else if (order.Type == OrderType.MarketOnClose && security.Type != SecurityType.Future && security.Type != SecurityType.Equity)
149  {
150  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, $"Unsupported order type for {security.Type} security type",
151  "InteractiveBrokers does not support Market-on-Close orders for other security types different than Future and Equity.");
152  return false;
153  }
154  else if (order.Type == OrderType.MarketOnOpen && security.Type != SecurityType.Equity && !security.Type.IsOption())
155  {
156  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, $"Unsupported order type for {security.Type} security type",
157  "InteractiveBrokers does not support Market-on-Open orders for other security types different than Option and Equity.");
158  return false;
159  }
160 
161  // validate security type
162  if (security.Type != SecurityType.Equity &&
163  security.Type != SecurityType.Forex &&
164  security.Type != SecurityType.Option &&
165  security.Type != SecurityType.Future &&
166  security.Type != SecurityType.FutureOption &&
167  security.Type != SecurityType.Index &&
168  security.Type != SecurityType.IndexOption &&
169  security.Type != SecurityType.Cfd)
170  {
171  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
173 
174  return false;
175  }
176 
177  // validate order quantity
178  //https://www.interactivebrokers.com/en/?f=%2Fen%2Ftrading%2FforexOrderSize.php
179  if (security.Type == SecurityType.Forex &&
180  !IsForexWithinOrderSizeLimits(order.Symbol.Value, order.Quantity, out message))
181  {
182  return false;
183  }
184 
185  // validate time in force
186  if (!_supportedTimeInForces.Contains(order.TimeInForce.GetType()))
187  {
188  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
190 
191  return false;
192  }
193 
194  // IB doesn't support index options and cash-settled options exercise
195  if (order.Type == OrderType.OptionExercise &&
196  (security.Type == SecurityType.IndexOption ||
197  (security.Type == SecurityType.Option && (security as Option).ExerciseSettlement == SettlementType.Cash)))
198  {
199  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
201 
202  return false;
203  }
204 
205  return true;
206  }
207 
208  /// <summary>
209  /// Returns true if the brokerage would allow updating the order as specified by the request
210  /// </summary>
211  /// <param name="security">The security of the order</param>
212  /// <param name="order">The order to be updated</param>
213  /// <param name="request">The requested update to be made to the order</param>
214  /// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
215  /// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
216  public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
217  {
218  message = null;
219 
220  if (order.SecurityType == SecurityType.Forex && request.Quantity != null)
221  {
222  return IsForexWithinOrderSizeLimits(order.Symbol.Value, request.Quantity.Value, out message);
223  }
224 
225  return true;
226  }
227 
228  /// <summary>
229  /// Returns true if the brokerage would be able to execute this order at this time assuming
230  /// market prices are sufficient for the fill to take place. This is used to emulate the
231  /// brokerage fills in backtesting and paper trading. For example some brokerages may not perform
232  /// executions during extended market hours. This is not intended to be checking whether or not
233  /// the exchange is open, that is handled in the Security.Exchange property.
234  /// </summary>
235  /// <param name="security"></param>
236  /// <param name="order">The order to test for execution</param>
237  /// <returns>True if the brokerage would be able to perform the execution, false otherwise</returns>
238  public override bool CanExecuteOrder(Security security, Order order)
239  {
240  return order.SecurityType != SecurityType.Base;
241  }
242 
243  /// <summary>
244  /// Returns true if the specified order is within IB's order size limits
245  /// </summary>
246  private bool IsForexWithinOrderSizeLimits(string currencyPair, decimal quantity, out BrokerageMessageEvent message)
247  {
248  /* https://www.interactivebrokers.com/en/trading/forexOrderSize.php
249  Currency Currency Description Minimum Order Size Maximum Order Size
250  USD US Dollar 25,000 7,000,000
251  AUD Australian Dollar 25,000 6,000,000
252  CAD Canadian Dollar 25,000 6,000,000
253  CHF Swiss Franc 25,000 6,000,000
254  CNH China Renminbi (offshore) 150,000 40,000,000
255  CZK Czech Koruna USD 25,000(1) USD 7,000,000(1)
256  DKK Danish Krone 150,000 35,000,000
257  EUR Euro 20,000 6,000,000
258  GBP British Pound Sterling 20,000 5,000,000
259  HKD Hong Kong Dollar 200,000 50,000,000
260  HUF Hungarian Forint USD 25,000(1) USD 7,000,000(1)
261  ILS Israeli Shekel USD 25,000(1) USD 7,000,000(1)
262  KRW Korean Won 0 200,000,000
263  JPY Japanese Yen 2,500,000 550,000,000
264  MXN Mexican Peso 300,000 70,000,000
265  NOK Norwegian Krone 150,000 35,000,000
266  NZD New Zealand Dollar 35,000 8,000,000
267  PLN Polish Zloty USD 25,000(1) USD 7,000,000(1)
268  RUB Russian Ruble 750,000 30,000,000
269  SEK Swedish Krona 175,000 40,000,000
270  SGD Singapore Dollar 35,000 8,000,000
271  ZAR South African Rand 350,000 100,000,000
272  */
273 
274  message = null;
275 
276  // switch on the currency being bought
277  Forex.DecomposeCurrencyPair(currencyPair, out var baseCurrency, out _);
278 
279  ForexCurrencyLimits.TryGetValue(baseCurrency, out var limits);
280  var min = limits?.Item1 ?? 0m;
281  var max = limits?.Item2 ?? 0m;
282 
283  var absoluteQuantity = Math.Abs(quantity);
284  var orderIsWithinForexSizeLimits = ((min == 0 && absoluteQuantity > min) || (min > 0 && absoluteQuantity >= min)) && absoluteQuantity <= max;
285  if (!orderIsWithinForexSizeLimits)
286  {
287  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "OrderSizeLimit",
289  }
290  return orderIsWithinForexSizeLimits;
291  }
292 
293  // currency -> (min, max)
294  private static readonly IReadOnlyDictionary<string, Tuple<decimal, decimal>> ForexCurrencyLimits =
295  new Dictionary<string, Tuple<decimal, decimal>>()
296  {
297  {"USD", Tuple.Create(25000m, 7000000m)},
298  {"AUD", Tuple.Create(25000m, 6000000m)},
299  {"CAD", Tuple.Create(25000m, 6000000m)},
300  {"CHF", Tuple.Create(25000m, 6000000m)},
301  {"CNH", Tuple.Create(150000m, 40000000m)},
302  {"CZK", Tuple.Create(0m, 0m)}, // need market price in USD or EUR -- do later when we support
303  {"DKK", Tuple.Create(150000m, 35000000m)},
304  {"EUR", Tuple.Create(20000m, 6000000m)},
305  {"GBP", Tuple.Create(20000m, 5000000m)},
306  {"HKD", Tuple.Create(200000m, 50000000m)},
307  {"HUF", Tuple.Create(0m, 0m)}, // need market price in USD or EUR -- do later when we support
308  {"ILS", Tuple.Create(0m, 0m)}, // need market price in USD or EUR -- do later when we support
309  {"KRW", Tuple.Create(0m, 200000000m)},
310  {"JPY", Tuple.Create(2500000m, 550000000m)},
311  {"MXN", Tuple.Create(300000m, 70000000m)},
312  {"NOK", Tuple.Create(150000m, 35000000m)},
313  {"NZD", Tuple.Create(35000m, 8000000m)},
314  {"PLN", Tuple.Create(0m, 0m)}, // need market price in USD or EUR -- do later when we support
315  {"RUB", Tuple.Create(750000m, 30000000m)},
316  {"SEK", Tuple.Create(175000m, 40000000m)},
317  {"SGD", Tuple.Create(35000m, 8000000m)},
318  {"ZAR", Tuple.Create(350000m, 100000000m)}
319  };
320  }
321 }