/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ using System; using System.Collections; using System.Collections.Generic; using System.Linq; using QuantConnect.Data; using QuantConnect.Data.UniverseSelection; using QuantConnect.Interfaces; using QuantConnect.Logging; using QuantConnect.Util; namespace QuantConnect.Securities { /// /// Provides a means of keeping track of the different cash holdings of an algorithm /// public class CashBook : ExtendedDictionary, IDictionary, ICurrencyConverter { private string _accountCurrency; /// /// Event fired when a instance is added or removed, and when /// the is triggered for the currently hold instances /// public event EventHandler Updated; /// /// Gets the base currency used /// public string AccountCurrency { get { return _accountCurrency; } set { var amount = 0m; Cash accountCurrency; // remove previous account currency if any if (!_accountCurrency.IsNullOrEmpty() && TryGetValue(_accountCurrency, out accountCurrency)) { amount = accountCurrency.Amount; Remove(_accountCurrency); } // add new account currency using same amount as previous _accountCurrency = value.LazyToUpper(); Add(_accountCurrency, new Cash(_accountCurrency, amount, 1.0m)); } } /// /// No need for concurrent collection, they are expensive. Currencies barely change and only on the start /// by the main thread, so if they do we will just create a new collection, reference change is atomic /// private Dictionary _currencies; /// /// Gets the total value of the cash book in units of the base currency /// public decimal TotalValueInAccountCurrency { get { return this.Aggregate(0m, (d, pair) => d + pair.Value.ValueInAccountCurrency); } } /// /// Initializes a new instance of the class. /// public CashBook() { _currencies = new(); AccountCurrency = Currencies.USD; } /// /// Adds a new cash of the specified symbol and quantity /// /// The symbol used to reference the new cash /// The amount of new cash to start /// The conversion rate used to determine the initial /// portfolio value/starting capital impact caused by this currency position. /// The added cash instance public Cash Add(string symbol, decimal quantity, decimal conversionRate) { var cash = new Cash(symbol, quantity, conversionRate); // let's return the cash instance we are using return AddIternal(symbol, cash); } /// /// Checks the current subscriptions and adds necessary currency pair feeds to provide real time conversion data /// /// The SecurityManager for the algorithm /// The SubscriptionManager for the algorithm /// The market map that decides which market the new security should be in /// Will be used to consume /// Will be used to create required new /// The default resolution to use for the internal subscriptions /// Returns a list of added currency public List EnsureCurrencyDataFeeds(SecurityManager securities, SubscriptionManager subscriptions, IReadOnlyDictionary marketMap, SecurityChanges changes, ISecurityService securityService, Resolution defaultResolution = Resolution.Minute) { var addedSubscriptionDataConfigs = new List(); foreach (var kvp in _currencies) { var cash = kvp.Value; var subscriptionDataConfigs = cash.EnsureCurrencyDataFeed( securities, subscriptions, marketMap, changes, securityService, AccountCurrency, defaultResolution); if (subscriptionDataConfigs != null) { foreach (var subscriptionDataConfig in subscriptionDataConfigs) { addedSubscriptionDataConfigs.Add(subscriptionDataConfig); } } } return addedSubscriptionDataConfigs; } /// /// Converts a quantity of source currency units into the specified destination currency /// /// The quantity of source currency to be converted /// The source currency symbol /// The destination currency symbol /// The converted value public decimal Convert(decimal sourceQuantity, string sourceCurrency, string destinationCurrency) { if (sourceQuantity == 0) { return 0; } var source = this[sourceCurrency]; var destination = this[destinationCurrency]; if (source.ConversionRate == 0) { throw new ArgumentException(Messages.CashBook.ConversionRateNotFound(sourceCurrency)); } if (destination.ConversionRate == 0) { throw new ArgumentException(Messages.CashBook.ConversionRateNotFound(destinationCurrency)); } var conversionRate = source.ConversionRate / destination.ConversionRate; return sourceQuantity * conversionRate; } /// /// Converts a quantity of source currency units into the account currency /// /// The quantity of source currency to be converted /// The source currency symbol /// The converted value public decimal ConvertToAccountCurrency(decimal sourceQuantity, string sourceCurrency) { if (sourceCurrency == AccountCurrency) { return sourceQuantity; } return Convert(sourceQuantity, sourceCurrency, AccountCurrency); } /// /// Returns a string that represents the current object. /// /// /// A string that represents the current object. /// /// 2 public override string ToString() { return Messages.CashBook.ToString(this); } #region IDictionary Implementation /// /// Gets the count of Cash items in this CashBook. /// /// The count. public override int Count { get { return _currencies.Count; } } /// /// Gets a value indicating whether this instance is read only. /// /// true if this instance is read only; otherwise, false. public override bool IsReadOnly { get { return false; } } /// /// Add the specified item to this CashBook. /// /// KeyValuePair of symbol -> Cash item public void Add(KeyValuePair item) { Add(item.Key, item.Value); } /// /// Add the specified key and value. /// /// The symbol of the Cash value. /// Value. public void Add(string symbol, Cash value) { AddIternal(symbol, value); } /// /// Clear this instance of all Cash entries. /// public override void Clear() { _currencies = new(); OnUpdate(CashBookUpdateType.Removed, null); } /// /// Remove the Cash item corresponding to the specified symbol /// /// The symbolto be removed public override bool Remove(string symbol) { return Remove(symbol, calledInternally: false); } /// /// Remove the specified item. /// /// Item. public bool Remove(KeyValuePair item) { return Remove(item.Key); } /// /// Determines whether the current instance contains an entry with the specified symbol. /// /// true, if key was contained, false otherwise. /// Key. public override bool ContainsKey(string symbol) { return _currencies.ContainsKey(symbol); } /// /// Try to get the value. /// /// To be added. /// true, if get value was tryed, false otherwise. /// The symbol. /// Value. public override bool TryGetValue(string symbol, out Cash value) { return _currencies.TryGetValue(symbol, out value); } /// /// Determines whether the current collection contains the specified value. /// /// Item. public bool Contains(KeyValuePair item) { return _currencies.Contains(item); } /// /// Copies to the specified array. /// /// Array. /// Array index. public void CopyTo(KeyValuePair[] array, int arrayIndex) { ((IDictionary) _currencies).CopyTo(array, arrayIndex); } /// /// Gets or sets the with the specified symbol. /// /// Symbol. public override Cash this[string symbol] { get { if (symbol == Currencies.NullCurrency) { throw new InvalidOperationException(Messages.CashBook.UnexpectedRequestForNullCurrency); } Cash cash; if (!_currencies.TryGetValue(symbol, out cash)) { throw new KeyNotFoundException(Messages.CashBook.CashSymbolNotFound(symbol)); } return cash; } set { Add(symbol, value); } } /// /// Gets the keys. /// /// The keys. public ICollection Keys => _currencies.Keys; /// /// Gets the values. /// /// The values. public ICollection Values => _currencies.Values; /// /// Gets the keys. /// /// The keys. protected override IEnumerable GetKeys => Keys; /// /// Gets the values. /// /// The values. protected override IEnumerable GetValues => Values; /// /// Gets all the items in the dictionary /// /// All the items in the dictionary public override IEnumerable> GetItems() => _currencies; /// /// Gets the enumerator. /// /// The enumerator. public IEnumerator> GetEnumerator() { return _currencies.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return _currencies.GetEnumerator(); } #endregion #region ICurrencyConverter Implementation /// /// Converts a cash amount to the account currency /// /// The instance to convert /// A new instance denominated in the account currency public CashAmount ConvertToAccountCurrency(CashAmount cashAmount) { if (cashAmount.Currency == AccountCurrency) { return cashAmount; } var amount = Convert(cashAmount.Amount, cashAmount.Currency, AccountCurrency); return new CashAmount(amount, AccountCurrency); } #endregion private Cash AddIternal(string symbol, Cash value) { if (symbol == Currencies.NullCurrency) { return null; } if (!_currencies.TryGetValue(symbol, out var cash)) { // we link our Updated event with underlying cash instances // so interested listeners just subscribe to our event value.Updated += OnCashUpdate; var newCurrencies = new Dictionary(_currencies) { [symbol] = value }; _currencies = newCurrencies; OnUpdate(CashBookUpdateType.Added, value); return value; } else { // override the values, it will trigger an update event already // we keep the instance because it might be used by securities already cash.ConversionRate = value.ConversionRate; cash.SetAmount(value.Amount); return cash; } } private bool Remove(string symbol, bool calledInternally) { Cash cash = null; var newCurrencies = new Dictionary(_currencies); var removed = newCurrencies.Remove(symbol, out cash); _currencies = newCurrencies; if (!removed) { if (!calledInternally) { Log.Error("CashBook.Remove(): " + Messages.CashBook.FailedToRemoveRecord(symbol)); } } else { cash.Updated -= OnCashUpdate; if (!calledInternally) { OnUpdate(CashBookUpdateType.Removed, cash); } } return removed; } private void OnCashUpdate(object sender, EventArgs eventArgs) { OnUpdate(CashBookUpdateType.Updated, sender as Cash); } private void OnUpdate(CashBookUpdateType updateType, Cash cash) { Updated?.Invoke(this, new CashBookUpdatedEventArgs(updateType, cash)); } } }