Lean  $LEAN_TAG$
Brokerage.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.Linq;
18 using Newtonsoft.Json;
19 using System.Threading;
20 using QuantConnect.Data;
21 using QuantConnect.Orders;
22 using QuantConnect.Logging;
23 using System.Threading.Tasks;
26 using System.Collections.Generic;
27 
29 {
30  /// <summary>
31  /// Represents the base Brokerage implementation. This provides logging on brokerage events.
32  /// </summary>
33  public abstract class Brokerage : IBrokerage
34  {
35  // 7:45 AM (New York time zone)
36  private static readonly TimeSpan LiveBrokerageCashSyncTime = new TimeSpan(7, 45, 0);
37 
38  private readonly object _performCashSyncReentranceGuard = new object();
39  private bool _syncedLiveBrokerageCashToday = true;
40  private long _lastSyncTimeTicks = DateTime.UtcNow.Ticks;
41 
42  /// <summary>
43  /// Event that fires each time the brokerage order id changes
44  /// </summary>
45  public event EventHandler<BrokerageOrderIdChangedEvent> OrderIdChanged;
46 
47  /// Event that fires each time the status for a list of orders change
48  /// </summary>
49  public event EventHandler<List<OrderEvent>> OrdersStatusChanged;
50 
51  /// <summary>
52  /// Event that fires each time an order is updated in the brokerage side
53  /// </summary>
54  /// <remarks>
55  /// These are not status changes but mainly price changes, like the stop price of a trailing stop order
56  /// </remarks>
57  public event EventHandler<OrderUpdateEvent> OrderUpdated;
58 
59  /// <summary>
60  /// Event that fires each time a short option position is assigned
61  /// </summary>
62  public event EventHandler<OrderEvent> OptionPositionAssigned;
63 
64  /// <summary>
65  /// Event that fires each time an option position has changed
66  /// </summary>
67  public event EventHandler<OptionNotificationEventArgs> OptionNotification;
68 
69  /// <summary>
70  /// Event that fires each time there's a brokerage side generated order
71  /// </summary>
72  public event EventHandler<NewBrokerageOrderNotificationEventArgs> NewBrokerageOrderNotification;
73 
74  /// <summary>
75  /// Event that fires each time a delisting occurs
76  /// </summary>
77  public event EventHandler<DelistingNotificationEventArgs> DelistingNotification;
78 
79  /// <summary>
80  /// Event that fires each time a user's brokerage account is changed
81  /// </summary>
82  public event EventHandler<AccountEvent> AccountChanged;
83 
84  /// <summary>
85  /// Event that fires when an error is encountered in the brokerage
86  /// </summary>
87  public event EventHandler<BrokerageMessageEvent> Message;
88 
89  /// <summary>
90  /// Gets the name of the brokerage
91  /// </summary>
92  public string Name { get; }
93 
94  /// <summary>
95  /// Returns true if we're currently connected to the broker
96  /// </summary>
97  public abstract bool IsConnected { get; }
98 
99  /// <summary>
100  /// Creates a new Brokerage instance with the specified name
101  /// </summary>
102  /// <param name="name">The name of the brokerage</param>
103  protected Brokerage(string name)
104  {
105  Name = name;
106  }
107 
108  /// <summary>
109  /// Places a new order and assigns a new broker ID to the order
110  /// </summary>
111  /// <param name="order">The order to be placed</param>
112  /// <returns>True if the request for a new order has been placed, false otherwise</returns>
113  public abstract bool PlaceOrder(Order order);
114 
115  /// <summary>
116  /// Updates the order with the same id
117  /// </summary>
118  /// <param name="order">The new order information</param>
119  /// <returns>True if the request was made for the order to be updated, false otherwise</returns>
120  public abstract bool UpdateOrder(Order order);
121 
122  /// <summary>
123  /// Cancels the order with the specified ID
124  /// </summary>
125  /// <param name="order">The order to cancel</param>
126  /// <returns>True if the request was made for the order to be canceled, false otherwise</returns>
127  public abstract bool CancelOrder(Order order);
128 
129  /// <summary>
130  /// Connects the client to the broker's remote servers
131  /// </summary>
132  public abstract void Connect();
133 
134  /// <summary>
135  /// Disconnects the client from the broker's remote servers
136  /// </summary>
137  public abstract void Disconnect();
138 
139  /// <summary>
140  /// Dispose of the brokerage instance
141  /// </summary>
142  public virtual void Dispose()
143  {
144  // NOP
145  }
146 
147  /// <summary>
148  /// Event invocator for the OrderFilled event
149  /// </summary>
150  /// <param name="orderEvents">The list of order events</param>
151  protected virtual void OnOrderEvents(List<OrderEvent> orderEvents)
152  {
153  try
154  {
155  OrdersStatusChanged?.Invoke(this, orderEvents);
156  }
157  catch (Exception err)
158  {
159  Log.Error(err);
160  }
161  }
162 
163  /// <summary>
164  /// Event invocator for the OrderFilled event
165  /// </summary>
166  /// <param name="e">The order event</param>
167  protected virtual void OnOrderEvent(OrderEvent e)
168  {
169  OnOrderEvents(new List<OrderEvent> { e });
170  }
171 
172  /// <summary>
173  /// Event invocator for the OrderUpdated event
174  /// </summary>
175  /// <param name="e">The update event</param>
176  protected virtual void OnOrderUpdated(OrderUpdateEvent e)
177  {
178  try
179  {
180  OrderUpdated?.Invoke(this, e);
181  }
182  catch (Exception err)
183  {
184  Log.Error(err);
185  }
186  }
187 
188  /// <summary>
189  /// Event invocator for the OrderIdChanged event
190  /// </summary>
191  /// <param name="e">The BrokerageOrderIdChangedEvent</param>
193  {
194  try
195  {
196  OrderIdChanged?.Invoke(this, e);
197  }
198  catch (Exception err)
199  {
200  Log.Error(err);
201  }
202  }
203 
204  /// <summary>
205  /// Event invocator for the OptionPositionAssigned event
206  /// </summary>
207  /// <param name="e">The OrderEvent</param>
208  protected virtual void OnOptionPositionAssigned(OrderEvent e)
209  {
210  try
211  {
212  Log.Debug("Brokerage.OptionPositionAssigned(): " + e);
213 
214  OptionPositionAssigned?.Invoke(this, e);
215  }
216  catch (Exception err)
217  {
218  Log.Error(err);
219  }
220  }
221 
222  /// <summary>
223  /// Event invocator for the OptionNotification event
224  /// </summary>
225  /// <param name="e">The OptionNotification event arguments</param>
227  {
228  try
229  {
230  Log.Debug("Brokerage.OnOptionNotification(): " + e);
231 
232  OptionNotification?.Invoke(this, e);
233  }
234  catch (Exception err)
235  {
236  Log.Error(err);
237  }
238  }
239 
240  /// <summary>
241  /// Event invocator for the NewBrokerageOrderNotification event
242  /// </summary>
243  /// <param name="e">The NewBrokerageOrderNotification event arguments</param>
245  {
246  try
247  {
248  Log.Debug("Brokerage.OnNewBrokerageOrderNotification(): " + e);
249 
250  NewBrokerageOrderNotification?.Invoke(this, e);
251  }
252  catch (Exception err)
253  {
254  Log.Error(err);
255  }
256  }
257 
258  /// <summary>
259  /// Event invocator for the DelistingNotification event
260  /// </summary>
261  /// <param name="e">The DelistingNotification event arguments</param>
263  {
264  try
265  {
266  Log.Debug("Brokerage.OnDelistingNotification(): " + e);
267 
268  DelistingNotification?.Invoke(this, e);
269  }
270  catch (Exception err)
271  {
272  Log.Error(err);
273  }
274  }
275 
276  /// <summary>
277  /// Event invocator for the AccountChanged event
278  /// </summary>
279  /// <param name="e">The AccountEvent</param>
280  protected virtual void OnAccountChanged(AccountEvent e)
281  {
282  try
283  {
284  Log.Trace($"Brokerage.OnAccountChanged(): {e}");
285 
286  AccountChanged?.Invoke(this, e);
287  }
288  catch (Exception err)
289  {
290  Log.Error(err);
291  }
292  }
293 
294  /// <summary>
295  /// Event invocator for the Message event
296  /// </summary>
297  /// <param name="e">The error</param>
298  protected virtual void OnMessage(BrokerageMessageEvent e)
299  {
300  try
301  {
302  if (e.Type == BrokerageMessageType.Error)
303  {
304  Log.Error("Brokerage.OnMessage(): " + e);
305  }
306  else
307  {
308  Log.Trace("Brokerage.OnMessage(): " + e);
309  }
310 
311  Message?.Invoke(this, e);
312  }
313  catch (Exception err)
314  {
315  Log.Error(err);
316  }
317  }
318 
319  /// <summary>
320  /// Helper method that will try to get the live holdings from the provided brokerage data collection else will default to the algorithm state
321  /// </summary>
322  /// <remarks>Holdings will removed from the provided collection on the first call, since this method is expected to be called only
323  /// once on initialize, after which the algorithm should use Lean accounting</remarks>
324  protected virtual List<Holding> GetAccountHoldings(Dictionary<string, string> brokerageData, IEnumerable<Security> securities)
325  {
326  if (Log.DebuggingEnabled)
327  {
328  Log.Debug("Brokerage.GetAccountHoldings(): starting...");
329  }
330 
331  if (brokerageData != null && brokerageData.Remove("live-holdings", out var value) && !string.IsNullOrEmpty(value))
332  {
333  // remove the key, we really only want to return the cached value on the first request
334  var result = JsonConvert.DeserializeObject<List<Holding>>(value);
335  if (result == null)
336  {
337  return new List<Holding>();
338  }
339  Log.Trace($"Brokerage.GetAccountHoldings(): sourcing holdings from provided brokerage data, found {result.Count} entries");
340  return result;
341  }
342 
343  return securities?.Where(security => security.Holdings.AbsoluteQuantity > 0)
344  .OrderBy(security => security.Symbol)
345  .Select(security => new Holding(security)).ToList() ?? new List<Holding>();
346  }
347 
348  /// <summary>
349  /// Helper method that will try to get the live cash balance from the provided brokerage data collection else will default to the algorithm state
350  /// </summary>
351  /// <remarks>Cash balance will removed from the provided collection on the first call, since this method is expected to be called only
352  /// once on initialize, after which the algorithm should use Lean accounting</remarks>
353  protected virtual List<CashAmount> GetCashBalance(Dictionary<string, string> brokerageData, CashBook cashBook)
354  {
355  if (Log.DebuggingEnabled)
356  {
357  Log.Debug("Brokerage.GetCashBalance(): starting...");
358  }
359 
360  if (brokerageData != null && brokerageData.Remove("live-cash-balance", out var value) && !string.IsNullOrEmpty(value))
361  {
362  // remove the key, we really only want to return the cached value on the first request
363  var result = JsonConvert.DeserializeObject<List<CashAmount>>(value);
364  if (result == null)
365  {
366  return new List<CashAmount>();
367  }
368  Log.Trace($"Brokerage.GetCashBalance(): sourcing cash balance from provided brokerage data, found {result.Count} entries");
369  return result;
370  }
371 
372  return cashBook?.Select(x => new CashAmount(x.Value.Amount, x.Value.Symbol)).ToList() ?? new List<CashAmount>();
373  }
374 
375  /// <summary>
376  /// Gets all open orders on the account.
377  /// NOTE: The order objects returned do not have QC order IDs.
378  /// </summary>
379  /// <returns>The open orders returned from IB</returns>
380  public abstract List<Order> GetOpenOrders();
381 
382  /// <summary>
383  /// Gets all holdings for the account
384  /// </summary>
385  /// <returns>The current holdings from the account</returns>
386  public abstract List<Holding> GetAccountHoldings();
387 
388  /// <summary>
389  /// Gets the current cash balance for each currency held in the brokerage account
390  /// </summary>
391  /// <returns>The current cash balance for each currency available for trading</returns>
392  public abstract List<CashAmount> GetCashBalance();
393 
394  /// <summary>
395  /// Specifies whether the brokerage will instantly update account balances
396  /// </summary>
397  public virtual bool AccountInstantlyUpdated => false;
398 
399  /// <summary>
400  /// Returns the brokerage account's base currency
401  /// </summary>
402  public virtual string AccountBaseCurrency { get; protected set; }
403 
404  /// <summary>
405  /// Gets the history for the requested security
406  /// </summary>
407  /// <param name="request">The historical data request</param>
408  /// <returns>An enumerable of bars covering the span specified in the request</returns>
409  public virtual IEnumerable<BaseData> GetHistory(HistoryRequest request)
410  {
411  return Enumerable.Empty<BaseData>();
412  }
413 
414  /// <summary>
415  /// Gets the position that might result given the specified order direction and the current holdings quantity.
416  /// This is useful for brokerages that require more specific direction information than provided by the OrderDirection enum
417  /// (e.g. Tradier differentiates Buy/Sell and BuyToOpen/BuyToCover/SellShort/SellToClose)
418  /// </summary>
419  /// <param name="orderDirection">The order direction</param>
420  /// <param name="holdingsQuantity">The current holdings quantity</param>
421  /// <returns>The order position</returns>
422  protected static OrderPosition GetOrderPosition(OrderDirection orderDirection, decimal holdingsQuantity)
423  {
424  return orderDirection switch
425  {
426  OrderDirection.Buy => holdingsQuantity >= 0 ? OrderPosition.BuyToOpen : OrderPosition.BuyToClose,
427  OrderDirection.Sell => holdingsQuantity <= 0 ? OrderPosition.SellToOpen : OrderPosition.SellToClose,
428  _ => throw new ArgumentOutOfRangeException(nameof(orderDirection), orderDirection, "Invalid order direction")
429  };
430  }
431 
432  #region IBrokerageCashSynchronizer implementation
433 
434  /// <summary>
435  /// Gets the date of the last sync (New York time zone)
436  /// </summary>
437  protected DateTime LastSyncDate => LastSyncDateTimeUtc.ConvertFromUtc(TimeZones.NewYork).Date;
438 
439  /// <summary>
440  /// Gets the datetime of the last sync (UTC)
441  /// </summary>
442  public DateTime LastSyncDateTimeUtc => new DateTime(Interlocked.Read(ref _lastSyncTimeTicks));
443 
444  /// <summary>
445  /// Returns whether the brokerage should perform the cash synchronization
446  /// </summary>
447  /// <param name="currentTimeUtc">The current time (UTC)</param>
448  /// <returns>True if the cash sync should be performed</returns>
449  public virtual bool ShouldPerformCashSync(DateTime currentTimeUtc)
450  {
451  // every morning flip this switch back
452  var currentTimeNewYork = currentTimeUtc.ConvertFromUtc(TimeZones.NewYork);
453  if (_syncedLiveBrokerageCashToday && currentTimeNewYork.Date != LastSyncDate)
454  {
455  _syncedLiveBrokerageCashToday = false;
456  }
457 
458  return !_syncedLiveBrokerageCashToday && currentTimeNewYork.TimeOfDay >= LiveBrokerageCashSyncTime;
459  }
460 
461  /// <summary>
462  /// Synchronizes the cashbook with the brokerage account
463  /// </summary>
464  /// <param name="algorithm">The algorithm instance</param>
465  /// <param name="currentTimeUtc">The current time (UTC)</param>
466  /// <param name="getTimeSinceLastFill">A function which returns the time elapsed since the last fill</param>
467  /// <returns>True if the cash sync was performed successfully</returns>
468  public virtual bool PerformCashSync(IAlgorithm algorithm, DateTime currentTimeUtc, Func<TimeSpan> getTimeSinceLastFill)
469  {
470  try
471  {
472  // prevent reentrance in this method
473  if (!Monitor.TryEnter(_performCashSyncReentranceGuard))
474  {
475  Log.Trace("Brokerage.PerformCashSync(): Reentrant call, cash sync not performed");
476  return false;
477  }
478 
479  Log.Trace("Brokerage.PerformCashSync(): Sync cash balance");
480 
481  List<CashAmount> balances = null;
482  try
483  {
484  balances = GetCashBalance();
485  }
486  catch (Exception err)
487  {
488  Log.Error(err, "Error in GetCashBalance:");
489  }
490 
491  // empty cash balance is valid, if there was No error/exception
492  if (balances == null)
493  {
494  Log.Trace("Brokerage.PerformCashSync(): No cash balances available, cash sync not performed");
495  return false;
496  }
497 
498  // Adds currency to the cashbook that the user might have deposited
499  foreach (var balance in balances)
500  {
501  if (!algorithm.Portfolio.CashBook.ContainsKey(balance.Currency))
502  {
503  Log.Trace($"Brokerage.PerformCashSync(): Unexpected cash found {balance.Currency} {balance.Amount}", true);
504  algorithm.Portfolio.SetCash(balance.Currency, balance.Amount, 0);
505  }
506  }
507 
508  // if we were returned our balances, update everything and flip our flag as having performed sync today
509  foreach (var kvp in algorithm.Portfolio.CashBook)
510  {
511  var cash = kvp.Value;
512 
513  //update the cash if the entry if found in the balances
514  var balanceCash = balances.Find(balance => balance.Currency == cash.Symbol);
515  if (balanceCash != default(CashAmount))
516  {
517  // compare in account currency
518  var delta = cash.Amount - balanceCash.Amount;
519  if (Math.Abs(algorithm.Portfolio.CashBook.ConvertToAccountCurrency(delta, cash.Symbol)) > 5)
520  {
521  // log the delta between
522  Log.Trace($"Brokerage.PerformCashSync(): {balanceCash.Currency} Delta: {delta:0.00}", true);
523  }
524  algorithm.Portfolio.CashBook[cash.Symbol].SetAmount(balanceCash.Amount);
525  }
526  else
527  {
528  //Set the cash amount to zero if cash entry not found in the balances
529  Log.Trace($"Brokerage.PerformCashSync(): {cash.Symbol} was not found in brokerage cash balance, setting the amount to 0", true);
530  algorithm.Portfolio.CashBook[cash.Symbol].SetAmount(0);
531  }
532  }
533  _syncedLiveBrokerageCashToday = true;
534  _lastSyncTimeTicks = currentTimeUtc.Ticks;
535  }
536  finally
537  {
538  Monitor.Exit(_performCashSyncReentranceGuard);
539  }
540 
541  // fire off this task to check if we've had recent fills, if we have then we'll invalidate the cash sync
542  // and do it again until we're confident in it
543  Task.Delay(TimeSpan.FromSeconds(10)).ContinueWith(_ =>
544  {
545  // we want to make sure this is a good value, so check for any recent fills
546  if (getTimeSinceLastFill() <= TimeSpan.FromSeconds(20))
547  {
548  // this will cause us to come back in and reset cash again until we
549  // haven't processed a fill for +- 10 seconds of the set cash time
550  _syncedLiveBrokerageCashToday = false;
551  //_failedCashSyncAttempts = 0;
552  Log.Trace("Brokerage.PerformCashSync(): Unverified cash sync - resync required.");
553  }
554  else
555  {
556  Log.Trace("Brokerage.PerformCashSync(): Verified cash sync.");
557 
558  algorithm.Portfolio.LogMarginInformation();
559  }
560  });
561 
562  return true;
563  }
564 
565  #endregion
566 
567  }
568 }