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.IsOption())
149  {
150  message = new BrokerageMessageEvent(BrokerageMessageType.Warning,
151  "InteractiveBrokers does not support Market-on-Close orders for Options",
152  Messages.DefaultBrokerageModel.UnsupportedOrderType(this, order, _supportedOrderTypes.Where(x => x != OrderType.MarketOnClose)));
153  return false;
154  }
155 
156  // validate security type
157  if (security.Type != SecurityType.Equity &&
158  security.Type != SecurityType.Forex &&
159  security.Type != SecurityType.Option &&
160  security.Type != SecurityType.Future &&
161  security.Type != SecurityType.FutureOption &&
162  security.Type != SecurityType.Index &&
163  security.Type != SecurityType.IndexOption &&
164  security.Type != SecurityType.Cfd)
165  {
166  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
167  Messages.DefaultBrokerageModel.UnsupportedSecurityType(this, security));
168 
169  return false;
170  }
171 
172  // validate order quantity
173  //https://www.interactivebrokers.com/en/?f=%2Fen%2Ftrading%2FforexOrderSize.php
174  if (security.Type == SecurityType.Forex &&
175  !IsForexWithinOrderSizeLimits(order.Symbol.Value, order.Quantity, out message))
176  {
177  return false;
178  }
179 
180  // validate time in force
181  if (!_supportedTimeInForces.Contains(order.TimeInForce.GetType()))
182  {
183  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
184  Messages.DefaultBrokerageModel.UnsupportedTimeInForce(this, order));
185 
186  return false;
187  }
188 
189  // IB doesn't support index options and cash-settled options exercise
190  if (order.Type == OrderType.OptionExercise &&
191  (security.Type == SecurityType.IndexOption ||
192  (security.Type == SecurityType.Option && (security as Option).ExerciseSettlement == SettlementType.Cash)))
193  {
194  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "NotSupported",
195  Messages.InteractiveBrokersBrokerageModel.UnsupportedExerciseForIndexAndCashSettledOptions(this, order));
196 
197  return false;
198  }
199 
200  return true;
201  }
202 
203  /// <summary>
204  /// Returns true if the brokerage would allow updating the order as specified by the request
205  /// </summary>
206  /// <param name="security">The security of the order</param>
207  /// <param name="order">The order to be updated</param>
208  /// <param name="request">The requested update to be made to the order</param>
209  /// <param name="message">If this function returns false, a brokerage message detailing why the order may not be updated</param>
210  /// <returns>True if the brokerage would allow updating the order, false otherwise</returns>
211  public override bool CanUpdateOrder(Security security, Order order, UpdateOrderRequest request, out BrokerageMessageEvent message)
212  {
213  message = null;
214 
215  if (order.SecurityType == SecurityType.Forex && request.Quantity != null)
216  {
217  return IsForexWithinOrderSizeLimits(order.Symbol.Value, request.Quantity.Value, out message);
218  }
219 
220  return true;
221  }
222 
223  /// <summary>
224  /// Returns true if the brokerage would be able to execute this order at this time assuming
225  /// market prices are sufficient for the fill to take place. This is used to emulate the
226  /// brokerage fills in backtesting and paper trading. For example some brokerages may not perform
227  /// executions during extended market hours. This is not intended to be checking whether or not
228  /// the exchange is open, that is handled in the Security.Exchange property.
229  /// </summary>
230  /// <param name="security"></param>
231  /// <param name="order">The order to test for execution</param>
232  /// <returns>True if the brokerage would be able to perform the execution, false otherwise</returns>
233  public override bool CanExecuteOrder(Security security, Order order)
234  {
235  return order.SecurityType != SecurityType.Base;
236  }
237 
238  /// <summary>
239  /// Returns true if the specified order is within IB's order size limits
240  /// </summary>
241  private bool IsForexWithinOrderSizeLimits(string currencyPair, decimal quantity, out BrokerageMessageEvent message)
242  {
243  /* https://www.interactivebrokers.com/en/trading/forexOrderSize.php
244  Currency Currency Description Minimum Order Size Maximum Order Size
245  USD US Dollar 25,000 7,000,000
246  AUD Australian Dollar 25,000 6,000,000
247  CAD Canadian Dollar 25,000 6,000,000
248  CHF Swiss Franc 25,000 6,000,000
249  CNH China Renminbi (offshore) 150,000 40,000,000
250  CZK Czech Koruna USD 25,000(1) USD 7,000,000(1)
251  DKK Danish Krone 150,000 35,000,000
252  EUR Euro 20,000 6,000,000
253  GBP British Pound Sterling 20,000 5,000,000
254  HKD Hong Kong Dollar 200,000 50,000,000
255  HUF Hungarian Forint USD 25,000(1) USD 7,000,000(1)
256  ILS Israeli Shekel USD 25,000(1) USD 7,000,000(1)
257  KRW Korean Won 0 200,000,000
258  JPY Japanese Yen 2,500,000 550,000,000
259  MXN Mexican Peso 300,000 70,000,000
260  NOK Norwegian Krone 150,000 35,000,000
261  NZD New Zealand Dollar 35,000 8,000,000
262  PLN Polish Zloty USD 25,000(1) USD 7,000,000(1)
263  RUB Russian Ruble 750,000 30,000,000
264  SEK Swedish Krona 175,000 40,000,000
265  SGD Singapore Dollar 35,000 8,000,000
266  ZAR South African Rand 350,000 100,000,000
267  */
268 
269  message = null;
270 
271  // switch on the currency being bought
272  Forex.DecomposeCurrencyPair(currencyPair, out var baseCurrency, out _);
273 
274  ForexCurrencyLimits.TryGetValue(baseCurrency, out var limits);
275  var min = limits?.Item1 ?? 0m;
276  var max = limits?.Item2 ?? 0m;
277 
278  var absoluteQuantity = Math.Abs(quantity);
279  var orderIsWithinForexSizeLimits = ((min == 0 && absoluteQuantity > min) || (min > 0 && absoluteQuantity >= min)) && absoluteQuantity <= max;
280  if (!orderIsWithinForexSizeLimits)
281  {
282  message = new BrokerageMessageEvent(BrokerageMessageType.Warning, "OrderSizeLimit",
283  Messages.InteractiveBrokersBrokerageModel.InvalidForexOrderSize(min, max, baseCurrency));
284  }
285  return orderIsWithinForexSizeLimits;
286  }
287 
288  // currency -> (min, max)
289  private static readonly IReadOnlyDictionary<string, Tuple<decimal, decimal>> ForexCurrencyLimits =
290  new Dictionary<string, Tuple<decimal, decimal>>()
291  {
292  {"USD", Tuple.Create(25000m, 7000000m)},
293  {"AUD", Tuple.Create(25000m, 6000000m)},
294  {"CAD", Tuple.Create(25000m, 6000000m)},
295  {"CHF", Tuple.Create(25000m, 6000000m)},
296  {"CNH", Tuple.Create(150000m, 40000000m)},
297  {"CZK", Tuple.Create(0m, 0m)}, // need market price in USD or EUR -- do later when we support
298  {"DKK", Tuple.Create(150000m, 35000000m)},
299  {"EUR", Tuple.Create(20000m, 6000000m)},
300  {"GBP", Tuple.Create(20000m, 5000000m)},
301  {"HKD", Tuple.Create(200000m, 50000000m)},
302  {"HUF", Tuple.Create(0m, 0m)}, // need market price in USD or EUR -- do later when we support
303  {"ILS", Tuple.Create(0m, 0m)}, // need market price in USD or EUR -- do later when we support
304  {"KRW", Tuple.Create(0m, 200000000m)},
305  {"JPY", Tuple.Create(2500000m, 550000000m)},
306  {"MXN", Tuple.Create(300000m, 70000000m)},
307  {"NOK", Tuple.Create(150000m, 35000000m)},
308  {"NZD", Tuple.Create(35000m, 8000000m)},
309  {"PLN", Tuple.Create(0m, 0m)}, // need market price in USD or EUR -- do later when we support
310  {"RUB", Tuple.Create(750000m, 30000000m)},
311  {"SEK", Tuple.Create(175000m, 40000000m)},
312  {"SGD", Tuple.Create(35000m, 8000000m)},
313  {"ZAR", Tuple.Create(350000m, 100000000m)}
314  };
315  }
316 }