Lean  $LEAN_TAG$
CashBook.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 
17 using System;
18 using System.Collections;
19 using System.Collections.Generic;
20 using System.Linq;
21 using QuantConnect.Data;
24 using QuantConnect.Logging;
25 using QuantConnect.Util;
26 
28 {
29  /// <summary>
30  /// Provides a means of keeping track of the different cash holdings of an algorithm
31  /// </summary>
32  public class CashBook : IDictionary<string, Cash>, ICurrencyConverter
33  {
34  private string _accountCurrency;
35 
36  /// <summary>
37  /// Event fired when a <see cref="Cash"/> instance is added or removed, and when
38  /// the <see cref="Cash.Updated"/> is triggered for the currently hold instances
39  /// </summary>
40  public event EventHandler<CashBookUpdatedEventArgs> Updated;
41 
42  /// <summary>
43  /// Gets the base currency used
44  /// </summary>
45  public string AccountCurrency
46  {
47  get { return _accountCurrency; }
48  set
49  {
50  var amount = 0m;
51  Cash accountCurrency;
52  // remove previous account currency if any
53  if (!_accountCurrency.IsNullOrEmpty()
54  && TryGetValue(_accountCurrency, out accountCurrency))
55  {
56  amount = accountCurrency.Amount;
57  Remove(_accountCurrency);
58  }
59 
60  // add new account currency using same amount as previous
61  _accountCurrency = value.LazyToUpper();
62  Add(_accountCurrency, new Cash(_accountCurrency, amount, 1.0m));
63  }
64  }
65 
66  /// <summary>
67  /// No need for concurrent collection, they are expensive. Currencies barely change and only on the start
68  /// by the main thread, so if they do we will just create a new collection, reference change is atomic
69  /// </summary>
70  private Dictionary<string, Cash> _currencies;
71 
72  /// <summary>
73  /// Gets the total value of the cash book in units of the base currency
74  /// </summary>
75  public decimal TotalValueInAccountCurrency
76  {
77  get
78  {
79  return this.Aggregate(0m, (d, pair) => d + pair.Value.ValueInAccountCurrency);
80  }
81  }
82 
83  /// <summary>
84  /// Initializes a new instance of the <see cref="CashBook"/> class.
85  /// </summary>
86  public CashBook()
87  {
88  _currencies = new();
90  }
91 
92  /// <summary>
93  /// Adds a new cash of the specified symbol and quantity
94  /// </summary>
95  /// <param name="symbol">The symbol used to reference the new cash</param>
96  /// <param name="quantity">The amount of new cash to start</param>
97  /// <param name="conversionRate">The conversion rate used to determine the initial
98  /// portfolio value/starting capital impact caused by this currency position.</param>
99  /// <returns>The added cash instance</returns>
100  public Cash Add(string symbol, decimal quantity, decimal conversionRate)
101  {
102  var cash = new Cash(symbol, quantity, conversionRate);
103  // let's return the cash instance we are using
104  return AddIternal(symbol, cash);
105  }
106 
107  /// <summary>
108  /// Checks the current subscriptions and adds necessary currency pair feeds to provide real time conversion data
109  /// </summary>
110  /// <param name="securities">The SecurityManager for the algorithm</param>
111  /// <param name="subscriptions">The SubscriptionManager for the algorithm</param>
112  /// <param name="marketMap">The market map that decides which market the new security should be in</param>
113  /// <param name="changes">Will be used to consume <see cref="SecurityChanges.AddedSecurities"/></param>
114  /// <param name="securityService">Will be used to create required new <see cref="Security"/></param>
115  /// <param name="defaultResolution">The default resolution to use for the internal subscriptions</param>
116  /// <returns>Returns a list of added currency <see cref="SubscriptionDataConfig"/></returns>
117  public List<SubscriptionDataConfig> EnsureCurrencyDataFeeds(SecurityManager securities,
118  SubscriptionManager subscriptions,
119  IReadOnlyDictionary<SecurityType, string> marketMap,
120  SecurityChanges changes,
121  ISecurityService securityService,
122  Resolution defaultResolution = Resolution.Minute)
123  {
124  var addedSubscriptionDataConfigs = new List<SubscriptionDataConfig>();
125  foreach (var kvp in _currencies)
126  {
127  var cash = kvp.Value;
128 
129  var subscriptionDataConfigs = cash.EnsureCurrencyDataFeed(
130  securities,
131  subscriptions,
132  marketMap,
133  changes,
134  securityService,
136  defaultResolution);
137  if (subscriptionDataConfigs != null)
138  {
139  foreach (var subscriptionDataConfig in subscriptionDataConfigs)
140  {
141  addedSubscriptionDataConfigs.Add(subscriptionDataConfig);
142  }
143  }
144  }
145  return addedSubscriptionDataConfigs;
146  }
147 
148  /// <summary>
149  /// Converts a quantity of source currency units into the specified destination currency
150  /// </summary>
151  /// <param name="sourceQuantity">The quantity of source currency to be converted</param>
152  /// <param name="sourceCurrency">The source currency symbol</param>
153  /// <param name="destinationCurrency">The destination currency symbol</param>
154  /// <returns>The converted value</returns>
155  public decimal Convert(decimal sourceQuantity, string sourceCurrency, string destinationCurrency)
156  {
157  if (sourceQuantity == 0)
158  {
159  return 0;
160  }
161 
162  var source = this[sourceCurrency];
163  var destination = this[destinationCurrency];
164 
165  if (source.ConversionRate == 0)
166  {
167  throw new ArgumentException(Messages.CashBook.ConversionRateNotFound(sourceCurrency));
168  }
169 
170  if (destination.ConversionRate == 0)
171  {
172  throw new ArgumentException(Messages.CashBook.ConversionRateNotFound(destinationCurrency));
173  }
174 
175  var conversionRate = source.ConversionRate / destination.ConversionRate;
176  return sourceQuantity * conversionRate;
177  }
178 
179  /// <summary>
180  /// Converts a quantity of source currency units into the account currency
181  /// </summary>
182  /// <param name="sourceQuantity">The quantity of source currency to be converted</param>
183  /// <param name="sourceCurrency">The source currency symbol</param>
184  /// <returns>The converted value</returns>
185  public decimal ConvertToAccountCurrency(decimal sourceQuantity, string sourceCurrency)
186  {
187  if (sourceCurrency == AccountCurrency)
188  {
189  return sourceQuantity;
190  }
191  return Convert(sourceQuantity, sourceCurrency, AccountCurrency);
192  }
193 
194  /// <summary>
195  /// Returns a string that represents the current object.
196  /// </summary>
197  /// <returns>
198  /// A string that represents the current object.
199  /// </returns>
200  /// <filterpriority>2</filterpriority>
201  public override string ToString()
202  {
203  return Messages.CashBook.ToString(this);
204  }
205 
206  #region IDictionary Implementation
207 
208  /// <summary>
209  /// Gets the count of Cash items in this CashBook.
210  /// </summary>
211  /// <value>The count.</value>
212  public int Count
213  {
214  get
215  {
216  return _currencies.Count;
217  }
218  }
219 
220  /// <summary>
221  /// Gets a value indicating whether this instance is read only.
222  /// </summary>
223  /// <value><c>true</c> if this instance is read only; otherwise, <c>false</c>.</value>
224  public bool IsReadOnly
225  {
226  get { return false; }
227  }
228 
229  /// <summary>
230  /// Add the specified item to this CashBook.
231  /// </summary>
232  /// <param name="item">KeyValuePair of symbol -> Cash item</param>
233  public void Add(KeyValuePair<string, Cash> item)
234  {
235  Add(item.Key, item.Value);
236  }
237 
238  /// <summary>
239  /// Add the specified key and value.
240  /// </summary>
241  /// <param name="symbol">The symbol of the Cash value.</param>
242  /// <param name="value">Value.</param>
243  public void Add(string symbol, Cash value)
244  {
245  AddIternal(symbol, value);
246  }
247 
248  /// <summary>
249  /// Clear this instance of all Cash entries.
250  /// </summary>
251  public void Clear()
252  {
253  _currencies = new();
254  OnUpdate(CashBookUpdateType.Removed, null);
255  }
256 
257  /// <summary>
258  /// Remove the Cash item corresponding to the specified symbol
259  /// </summary>
260  /// <param name="symbol">The symbolto be removed</param>
261  public bool Remove(string symbol)
262  {
263  return Remove(symbol, calledInternally: false);
264  }
265 
266  /// <summary>
267  /// Remove the specified item.
268  /// </summary>
269  /// <param name="item">Item.</param>
270  public bool Remove(KeyValuePair<string, Cash> item)
271  {
272  return Remove(item.Key);
273  }
274 
275  /// <summary>
276  /// Determines whether the current instance contains an entry with the specified symbol.
277  /// </summary>
278  /// <returns><c>true</c>, if key was contained, <c>false</c> otherwise.</returns>
279  /// <param name="symbol">Key.</param>
280  public bool ContainsKey(string symbol)
281  {
282  return _currencies.ContainsKey(symbol);
283  }
284 
285  /// <summary>
286  /// Try to get the value.
287  /// </summary>
288  /// <remarks>To be added.</remarks>
289  /// <returns><c>true</c>, if get value was tryed, <c>false</c> otherwise.</returns>
290  /// <param name="symbol">The symbol.</param>
291  /// <param name="value">Value.</param>
292  public bool TryGetValue(string symbol, out Cash value)
293  {
294  return _currencies.TryGetValue(symbol, out value);
295  }
296 
297  /// <summary>
298  /// Determines whether the current collection contains the specified value.
299  /// </summary>
300  /// <param name="item">Item.</param>
301  public bool Contains(KeyValuePair<string, Cash> item)
302  {
303  return _currencies.Contains(item);
304  }
305 
306  /// <summary>
307  /// Copies to the specified array.
308  /// </summary>
309  /// <param name="array">Array.</param>
310  /// <param name="arrayIndex">Array index.</param>
311  public void CopyTo(KeyValuePair<string, Cash>[] array, int arrayIndex)
312  {
313  ((IDictionary<string, Cash>) _currencies).CopyTo(array, arrayIndex);
314  }
315 
316  /// <summary>
317  /// Gets or sets the <see cref="QuantConnect.Securities.Cash"/> with the specified symbol.
318  /// </summary>
319  /// <param name="symbol">Symbol.</param>
320  public Cash this[string symbol]
321  {
322  get
323  {
324  if (symbol == Currencies.NullCurrency)
325  {
326  throw new InvalidOperationException(Messages.CashBook.UnexpectedRequestForNullCurrency);
327  }
328  Cash cash;
329  if (!_currencies.TryGetValue(symbol, out cash))
330  {
331  throw new KeyNotFoundException(Messages.CashBook.CashSymbolNotFound(symbol));
332  }
333  return cash;
334  }
335  set
336  {
337  Add(symbol, value);
338  }
339  }
340 
341  /// <summary>
342  /// Gets the keys.
343  /// </summary>
344  /// <value>The keys.</value>
345  public ICollection<string> Keys => _currencies.Keys;
346 
347  /// <summary>
348  /// Gets the values.
349  /// </summary>
350  /// <value>The values.</value>
351  public ICollection<Cash> Values => _currencies.Values;
352 
353  /// <summary>
354  /// Gets the enumerator.
355  /// </summary>
356  /// <returns>The enumerator.</returns>
357  public IEnumerator<KeyValuePair<string, Cash>> GetEnumerator()
358  {
359  return _currencies.GetEnumerator();
360  }
361 
362  IEnumerator IEnumerable.GetEnumerator()
363  {
364  return _currencies.GetEnumerator();
365  }
366 
367  #endregion
368 
369  #region ICurrencyConverter Implementation
370 
371  /// <summary>
372  /// Converts a cash amount to the account currency
373  /// </summary>
374  /// <param name="cashAmount">The <see cref="CashAmount"/> instance to convert</param>
375  /// <returns>A new <see cref="CashAmount"/> instance denominated in the account currency</returns>
377  {
378  if (cashAmount.Currency == AccountCurrency)
379  {
380  return cashAmount;
381  }
382 
383  var amount = Convert(cashAmount.Amount, cashAmount.Currency, AccountCurrency);
384  return new CashAmount(amount, AccountCurrency);
385  }
386 
387  #endregion
388 
389  private Cash AddIternal(string symbol, Cash value)
390  {
391  if (symbol == Currencies.NullCurrency)
392  {
393  return null;
394  }
395 
396  if (!_currencies.TryGetValue(symbol, out var cash))
397  {
398  // we link our Updated event with underlying cash instances
399  // so interested listeners just subscribe to our event
400  value.Updated += OnCashUpdate;
401  var newCurrencies = new Dictionary<string, Cash>(_currencies)
402  {
403  [symbol] = value
404  };
405  _currencies = newCurrencies;
406 
407  OnUpdate(CashBookUpdateType.Added, value);
408 
409  return value;
410  }
411  else
412  {
413  // override the values, it will trigger an update event already
414  // we keep the instance because it might be used by securities already
415  cash.ConversionRate = value.ConversionRate;
416  cash.SetAmount(value.Amount);
417 
418  return cash;
419  }
420  }
421 
422  private bool Remove(string symbol, bool calledInternally)
423  {
424  Cash cash = null;
425  var newCurrencies = new Dictionary<string, Cash>(_currencies);
426  var removed = newCurrencies.Remove(symbol, out cash);
427  _currencies = newCurrencies;
428  if (!removed)
429  {
430  if (!calledInternally)
431  {
432  Log.Error("CashBook.Remove(): " + Messages.CashBook.FailedToRemoveRecord(symbol));
433  }
434  }
435  else
436  {
437  cash.Updated -= OnCashUpdate;
438  if (!calledInternally)
439  {
440  OnUpdate(CashBookUpdateType.Removed, cash);
441  }
442  }
443  return removed;
444  }
445 
446  private void OnCashUpdate(object sender, EventArgs eventArgs)
447  {
448  OnUpdate(CashBookUpdateType.Updated, sender as Cash);
449  }
450 
451  private void OnUpdate(CashBookUpdateType updateType, Cash cash)
452  {
453  Updated?.Invoke(this, new CashBookUpdatedEventArgs(updateType, cash));
454  }
455  }
456 }