Lean  $LEAN_TAG$
Cash.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 Newtonsoft.Json;
20 using ProtoBuf;
21 using QuantConnect.Data;
24 using QuantConnect.Logging;
26 
28 {
29  /// <summary>
30  /// Represents a holding of a currency in cash.
31  /// </summary>
32  [ProtoContract(SkipConstructor = true)]
33  public class Cash
34  {
35  private ICurrencyConversion _currencyConversion;
36 
37  private readonly object _locker = new object();
38 
39  /// <summary>
40  /// Event fired when this instance is updated
41  /// <see cref="AddAmount"/>, <see cref="SetAmount"/>, <see cref="Update"/>
42  /// </summary>
43  public event EventHandler Updated;
44 
45  /// <summary>
46  /// Event fired when this instance's <see cref="CurrencyConversion"/> is set/updated
47  /// </summary>
48  public event EventHandler CurrencyConversionUpdated;
49 
50  /// <summary>
51  /// Gets the symbols of the securities required to provide conversion rates.
52  /// If this cash represents the account currency, then an empty enumerable is returned.
53  /// </summary>
54  public IEnumerable<Symbol> SecuritySymbols => CurrencyConversion.ConversionRateSecurities.Any()
55  ? CurrencyConversion.ConversionRateSecurities.Select(x => x.Symbol)
56  // we do this only because Newtonsoft.Json complains about empty enumerables
57  : new List<Symbol>(0);
58 
59  /// <summary>
60  /// Gets the object that calculates the conversion rate to account currency
61  /// </summary>
62  [JsonIgnore]
64  {
65  get
66  {
67  return _currencyConversion;
68  }
69  internal set
70  {
71 
72  var lastConversionRate = 0m;
73  if (_currencyConversion != null)
74  {
75  lastConversionRate = _currencyConversion.ConversionRate;
76  _currencyConversion.ConversionRateUpdated -= OnConversionRateUpdated;
77  }
78 
79  _currencyConversion = value;
80  if (_currencyConversion != null)
81  {
82  if (lastConversionRate != 0m)
83  {
84  // If a user adds cash with an initial conversion rate and then this is overriden to a SecurityCurrencyConversion,
85  // we want to keep the previous rate until the new one is updated.
86  _currencyConversion.ConversionRate = lastConversionRate;
87  }
88  _currencyConversion.ConversionRateUpdated += OnConversionRateUpdated;
89  }
90  CurrencyConversionUpdated?.Invoke(this, EventArgs.Empty);
91  }
92  }
93 
94  private void OnConversionRateUpdated(object sender, decimal e)
95  {
96  OnUpdate();
97  }
98 
99  /// <summary>
100  /// Gets the symbol used to represent this cash
101  /// </summary>
102  [ProtoMember(1)]
103  public string Symbol { get; }
104 
105  /// <summary>
106  /// Gets or sets the amount of cash held
107  /// </summary>
108  [ProtoMember(2)]
109  public decimal Amount { get; private set; }
110 
111  /// <summary>
112  /// Gets the conversion rate into account currency
113  /// </summary>
114  [ProtoMember(3)]
115  public decimal ConversionRate
116  {
117  get
118  {
119  return _currencyConversion.ConversionRate;
120  }
121  internal set
122  {
123  if (_currencyConversion == null)
124  {
126  }
127 
128  _currencyConversion.ConversionRate = value;
129  }
130  }
131 
132  /// <summary>
133  /// The symbol of the currency, such as $
134  /// </summary>
135  [ProtoMember(4)]
136  public string CurrencySymbol { get; }
137 
138  /// <summary>
139  /// Gets the value of this cash in the account currency
140  /// </summary>
142 
143  /// <summary>
144  /// Initializes a new instance of the <see cref="Cash"/> class
145  /// </summary>
146  /// <param name="symbol">The symbol used to represent this cash</param>
147  /// <param name="amount">The amount of this currency held</param>
148  /// <param name="conversionRate">The initial conversion rate of this currency into the <see cref="CashBook.AccountCurrency"/></param>
149  public Cash(string symbol, decimal amount, decimal conversionRate)
150  {
151  if (string.IsNullOrEmpty(symbol))
152  {
153  throw new ArgumentException(Messages.Cash.NullOrEmptyCashSymbol);
154  }
155  Amount = amount;
156  Symbol = symbol.LazyToUpper();
158  CurrencyConversion = new ConstantCurrencyConversion(Symbol, null, conversionRate);
159  }
160 
161  /// <summary>
162  /// Marks this cash object's conversion rate as being potentially outdated
163  /// </summary>
164  public void Update()
165  {
166  _currencyConversion.Update();
167  }
168 
169  /// <summary>
170  /// Adds the specified amount of currency to this Cash instance and returns the new total.
171  /// This operation is thread-safe
172  /// </summary>
173  /// <param name="amount">The amount of currency to be added</param>
174  /// <returns>The amount of currency directly after the addition</returns>
175  public decimal AddAmount(decimal amount)
176  {
177  lock (_locker)
178  {
179  Amount += amount;
180  }
181  OnUpdate();
182  return Amount;
183  }
184 
185  /// <summary>
186  /// Sets the Quantity to the specified amount
187  /// </summary>
188  /// <param name="amount">The amount to set the quantity to</param>
189  public void SetAmount(decimal amount)
190  {
191  var updated = false;
192  // lock can be null when proto deserializing this instance
193  lock (_locker ?? new object())
194  {
195  if (Amount != amount)
196  {
197  Amount = amount;
198  // only update if there was actually one
199  updated = true;
200  }
201  }
202 
203  if (updated)
204  {
205  OnUpdate();
206  }
207  }
208 
209  /// <summary>
210  /// Ensures that we have a data feed to convert this currency into the base currency.
211  /// This will add a <see cref="SubscriptionDataConfig"/> and create a <see cref="Security"/> at the lowest resolution if one is not found.
212  /// </summary>
213  /// <param name="securities">The security manager</param>
214  /// <param name="subscriptions">The subscription manager used for searching and adding subscriptions</param>
215  /// <param name="marketMap">The market map that decides which market the new security should be in</param>
216  /// <param name="changes">Will be used to consume <see cref="SecurityChanges.AddedSecurities"/></param>
217  /// <param name="securityService">Will be used to create required new <see cref="Security"/></param>
218  /// <param name="accountCurrency">The account currency</param>
219  /// <param name="defaultResolution">The default resolution to use for the internal subscriptions</param>
220  /// <returns>Returns the added <see cref="SubscriptionDataConfig"/>, otherwise null</returns>
221  public List<SubscriptionDataConfig> EnsureCurrencyDataFeed(SecurityManager securities,
222  SubscriptionManager subscriptions,
223  IReadOnlyDictionary<SecurityType, string> marketMap,
224  SecurityChanges changes,
225  ISecurityService securityService,
226  string accountCurrency,
227  Resolution defaultResolution = Resolution.Minute
228  )
229  {
230  // this gets called every time we add securities using universe selection,
231  // so must of the time we've already resolved the value and don't need to again
233  {
234  return null;
235  }
236 
237  if (Symbol == accountCurrency)
238  {
240  return null;
241  }
242 
243  // existing securities
244  var securitiesToSearch = securities.Select(kvp => kvp.Value)
245  .Concat(changes.AddedSecurities)
246  .Where(s => ProvidesConversionRate(s.Type));
247 
248  // Create a SecurityType to Market mapping with the markets from SecurityManager members
249  var markets = securities.Select(x => x.Key)
250  .GroupBy(x => x.SecurityType)
251  .ToDictionary(x => x.Key, y => y.Select(symbol => symbol.ID.Market).ToHashSet());
252  if (markets.ContainsKey(SecurityType.Cfd) && !markets.ContainsKey(SecurityType.Forex))
253  {
254  markets.Add(SecurityType.Forex, markets[SecurityType.Cfd]);
255  }
256  if (markets.ContainsKey(SecurityType.Forex) && !markets.ContainsKey(SecurityType.Cfd))
257  {
258  markets.Add(SecurityType.Cfd, markets[SecurityType.Forex]);
259  }
260 
261  var forexEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.Forex, marketMap, markets);
262  var cfdEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.Cfd, marketMap, markets);
263  var cryptoEntries = GetAvailableSymbolPropertiesDatabaseEntries(SecurityType.Crypto, marketMap, markets);
264 
265  var potentialEntries = forexEntries
266  .Concat(cfdEntries)
267  .Concat(cryptoEntries)
268  .ToList();
269 
270  if (!potentialEntries.Any(x =>
271  Symbol == x.Key.Symbol.Substring(0, x.Key.Symbol.Length - x.Value.QuoteCurrency.Length) ||
272  Symbol == x.Value.QuoteCurrency))
273  {
274  // currency not found in any tradeable pair
275  Log.Error(Messages.Cash.NoTradablePairFoundForCurrencyConversion(Symbol, accountCurrency, marketMap.Where(kvp => ProvidesConversionRate(kvp.Key))));
277  return null;
278  }
279 
280  // Special case for crypto markets without direct pairs (They wont be found by the above)
281  // This allows us to add cash for "StableCoins" that are 1-1 with our account currency without needing a conversion security.
282  // Check out the StableCoinsWithoutPairs static var for those that are missing their 1-1 conversion pairs
283  if (marketMap.TryGetValue(SecurityType.Crypto, out var market)
284  &&
285  (Currencies.IsStableCoinWithoutPair(Symbol + accountCurrency, market)
286  || Currencies.IsStableCoinWithoutPair(accountCurrency + Symbol, market)))
287  {
289  return null;
290  }
291 
292  var requiredSecurities = new List<SubscriptionDataConfig>();
293 
294  var potentials = potentialEntries
295  .Select(x => QuantConnect.Symbol.Create(x.Key.Symbol, x.Key.SecurityType, x.Key.Market));
296 
297  var minimumResolution = subscriptions.Subscriptions.Select(x => x.Resolution).DefaultIfEmpty(defaultResolution).Min();
298 
299  var makeNewSecurity = new Func<Symbol, Security>(symbol =>
300  {
301  var securityType = symbol.ID.SecurityType;
302 
303  // use the first subscription defined in the subscription manager
304  var type = subscriptions.LookupSubscriptionConfigDataTypes(securityType, minimumResolution, false).First();
305  var objectType = type.Item1;
306  var tickType = type.Item2;
307 
308  // set this as an internal feed so that the data doesn't get sent into the algorithm's OnData events
309  var config = subscriptions.SubscriptionDataConfigService.Add(symbol,
310  minimumResolution,
311  fillForward: true,
312  extendedMarketHours: false,
313  isInternalFeed: true,
314  subscriptionDataTypes: new List<Tuple<Type, TickType>>
315  {new Tuple<Type, TickType>(objectType, tickType)}).First();
316 
317  var newSecurity = securityService.CreateSecurity(symbol,
318  config,
319  addToSymbolCache: false);
320 
321  Log.Trace("Cash.EnsureCurrencyDataFeed(): " + Messages.Cash.AddingSecuritySymbolForCashCurrencyFeed(symbol, Symbol));
322 
323  securities.Add(symbol, newSecurity);
324  requiredSecurities.Add(config);
325 
326  return newSecurity;
327  });
328 
330  accountCurrency,
331  securitiesToSearch.ToList(),
332  potentials,
333  makeNewSecurity);
334 
335  return requiredSecurities;
336  }
337 
338  /// <summary>
339  /// Returns a <see cref="string"/> that represents the current <see cref="Cash"/>.
340  /// </summary>
341  /// <returns>A <see cref="string"/> that represents the current <see cref="Cash"/>.</returns>
342  public override string ToString()
343  {
344  return ToString(Currencies.USD);
345  }
346 
347  /// <summary>
348  /// Returns a <see cref="string"/> that represents the current <see cref="Cash"/>.
349  /// </summary>
350  /// <returns>A <see cref="string"/> that represents the current <see cref="Cash"/>.</returns>
351  public string ToString(string accountCurrency)
352  {
353  return Messages.Cash.ToString(this, accountCurrency);
354  }
355 
356  private static IEnumerable<KeyValuePair<SecurityDatabaseKey, SymbolProperties>> GetAvailableSymbolPropertiesDatabaseEntries(
357  SecurityType securityType,
358  IReadOnlyDictionary<SecurityType, string> marketMap,
359  IReadOnlyDictionary<SecurityType, HashSet<string>> markets
360  )
361  {
362  var marketJoin = new HashSet<string>();
363  {
364  string market;
365  if (marketMap.TryGetValue(securityType, out market))
366  {
367  marketJoin.Add(market);
368  }
369  HashSet<string> existingMarkets;
370  if (markets.TryGetValue(securityType, out existingMarkets))
371  {
372  foreach (var existingMarket in existingMarkets)
373  {
374  marketJoin.Add(existingMarket);
375  }
376  }
377  }
378 
379  return marketJoin.SelectMany(market => SymbolPropertiesDatabase.FromDataFolder()
380  .GetSymbolPropertiesList(market, securityType));
381  }
382 
383  private static bool ProvidesConversionRate(SecurityType securityType)
384  {
385  return securityType == SecurityType.Forex || securityType == SecurityType.Crypto || securityType == SecurityType.Cfd;
386  }
387 
388  private void OnUpdate()
389  {
390  Updated?.Invoke(this, EventArgs.Empty);
391  }
392  }
393 }