Lean  $LEAN_TAG$
DefaultOrderBook.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 
21 {
22  /// <summary>
23  /// Represents a full order book for a security.
24  /// It contains prices and order sizes for each bid and ask level.
25  /// The best bid and ask prices are also kept up to date.
26  /// </summary>
27  public class DefaultOrderBook : IOrderBookUpdater<decimal, decimal>
28  {
29  private decimal _bestBidPrice;
30  private decimal _bestBidSize;
31  private decimal _bestAskPrice;
32  private decimal _bestAskSize;
33  private readonly object _locker = new object();
34 
35  /// <summary>
36  /// Represents bid prices and sizes
37  /// </summary>
38  protected SortedDictionary<decimal, decimal> Bids { get; init; } = new SortedDictionary<decimal, decimal>();
39 
40  /// <summary>
41  /// Represents ask prices and sizes
42  /// </summary>
43  protected SortedDictionary<decimal, decimal> Asks { get; init; } = new SortedDictionary<decimal, decimal>();
44 
45  /// <summary>
46  /// Represents a unique security identifier of current Order Book
47  /// </summary>
48  public Symbol Symbol { get; }
49 
50  /// <summary>
51  /// Event fired each time <see cref="BestBidPrice"/> or <see cref="BestAskPrice"/> are changed
52  /// </summary>
53  public event EventHandler<BestBidAskUpdatedEventArgs> BestBidAskUpdated;
54 
55  /// <summary>
56  /// The best bid price
57  /// </summary>
58  public decimal BestBidPrice
59  {
60  get
61  {
62  lock (_locker)
63  {
64  return _bestBidPrice;
65  }
66  }
67  }
68 
69  /// <summary>
70  /// The best bid size
71  /// </summary>
72  public decimal BestBidSize
73  {
74  get
75  {
76  lock (_locker)
77  {
78  return _bestBidSize;
79  }
80  }
81  }
82 
83  /// <summary>
84  /// The best ask price
85  /// </summary>
86  public decimal BestAskPrice
87  {
88  get
89  {
90  lock (_locker)
91  {
92  return _bestAskPrice;
93  }
94  }
95  }
96 
97  /// <summary>
98  /// The best ask size
99  /// </summary>
100  public decimal BestAskSize
101  {
102  get
103  {
104  lock (_locker)
105  {
106  return _bestAskSize;
107  }
108  }
109  }
110 
111  /// <summary>
112  /// Initializes a new instance of the <see cref="DefaultOrderBook"/> class
113  /// </summary>
114  /// <param name="symbol">The symbol for the order book</param>
115  public DefaultOrderBook(Symbol symbol)
116  {
117  Symbol = symbol;
118  }
119 
120  /// <summary>
121  /// Clears all bid/ask levels and prices.
122  /// </summary>
123  public void Clear()
124  {
125  lock (_locker)
126  {
127  _bestBidPrice = 0;
128  _bestBidSize = 0;
129  _bestAskPrice = 0;
130  _bestAskSize = 0;
131 
132  Bids.Clear();
133  Asks.Clear();
134  }
135  }
136 
137  /// <summary>
138  /// Updates or inserts a bid price level in the order book
139  /// </summary>
140  /// <param name="price">The bid price level to be inserted or updated</param>
141  /// <param name="size">The new size at the bid price level</param>
142  public void UpdateBidRow(decimal price, decimal size)
143  {
144  lock (_locker)
145  {
146  Bids[price] = size;
147 
148  if (_bestBidPrice == 0 || price >= _bestBidPrice)
149  {
150  _bestBidPrice = price;
151  _bestBidSize = size;
152 
153  BestBidAskUpdated?.Invoke(this, new BestBidAskUpdatedEventArgs(Symbol, _bestBidPrice, _bestBidSize, _bestAskPrice, _bestAskSize));
154  }
155  }
156  }
157 
158  /// <summary>
159  /// Updates or inserts an ask price level in the order book
160  /// </summary>
161  /// <param name="price">The ask price level to be inserted or updated</param>
162  /// <param name="size">The new size at the ask price level</param>
163  public void UpdateAskRow(decimal price, decimal size)
164  {
165  lock (_locker)
166  {
167  Asks[price] = size;
168 
169  if (_bestAskPrice == 0 || price <= _bestAskPrice)
170  {
171  _bestAskPrice = price;
172  _bestAskSize = size;
173 
174  BestBidAskUpdated?.Invoke(this, new BestBidAskUpdatedEventArgs(Symbol, _bestBidPrice, _bestBidSize, _bestAskPrice, _bestAskSize));
175  }
176  }
177  }
178 
179  /// <summary>
180  /// Removes a bid price level from the order book
181  /// </summary>
182  /// <param name="price">The bid price level to be removed</param>
183  public void RemoveBidRow(decimal price)
184  {
185  lock (_locker)
186  {
187  Bids.Remove(price);
188 
189  if (price == _bestBidPrice)
190  {
191  var priceLevel = Bids.LastOrDefault();
192  _bestBidPrice = priceLevel.Key;
193  _bestBidSize = priceLevel.Value;
194 
195  BestBidAskUpdated?.Invoke(this, new BestBidAskUpdatedEventArgs(Symbol, _bestBidPrice, _bestBidSize, _bestAskPrice, _bestAskSize));
196  }
197  }
198  }
199 
200  /// <summary>
201  /// Removes an ask price level from the order book
202  /// </summary>
203  /// <param name="price">The ask price level to be removed</param>
204  public void RemoveAskRow(decimal price)
205  {
206  lock (_locker)
207  {
208  Asks.Remove(price);
209 
210  if (price == _bestAskPrice)
211  {
212  var priceLevel = Asks.FirstOrDefault();
213  _bestAskPrice = priceLevel.Key;
214  _bestAskSize = priceLevel.Value;
215 
216  BestBidAskUpdated?.Invoke(this, new BestBidAskUpdatedEventArgs(Symbol, _bestBidPrice, _bestBidSize, _bestAskPrice, _bestAskSize));
217  }
218  }
219  }
220 
221  /// <summary>
222  /// Common price level removal method
223  /// </summary>
224  /// <param name="priceLevel"></param>
225  public void RemovePriceLevel(decimal priceLevel)
226  {
227  lock (_locker)
228  {
229  if (Asks.ContainsKey(priceLevel))
230  {
231  RemoveAskRow(priceLevel);
232  }
233  else if (Bids.ContainsKey(priceLevel))
234  {
235  RemoveBidRow(priceLevel);
236  }
237  }
238  }
239  }
240 }