Lean  $LEAN_TAG$
SymbolCache.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.Linq;
19 using System.Collections.Generic;
20 using System.Runtime.CompilerServices;
21 
22 namespace QuantConnect
23 {
24  /// <summary>
25  /// Provides a string->Symbol mapping to allow for user defined strings to be lifted into a Symbol
26  /// This is mainly used via the Symbol implicit operator, but also functions that create securities
27  /// should also call Set to add new mappings
28  /// </summary>
29  public static class SymbolCache
30  {
31  // we aggregate the two maps into a class so we can assign a new one as an atomic operation
32  private static readonly Dictionary<string, Symbol> Symbols = new(StringComparer.OrdinalIgnoreCase);
33  private static readonly Dictionary<Symbol, string> Tickers = new();
34 
35  /// <summary>
36  /// Adds a mapping for the specified ticker
37  /// </summary>
38  /// <param name="ticker">The string ticker symbol</param>
39  /// <param name="symbol">The symbol object that maps to the string ticker symbol</param>
40  public static void Set(string ticker, Symbol symbol)
41  {
42  lock (Symbols)
43  {
44  Symbols[ticker] = symbol;
45  Tickers[symbol] = ticker;
46 
47  var index = ticker.IndexOf('.');
48  if (index != -1)
49  {
50  var related = ticker.Substring(0, index);
51  if (Symbols.TryGetValue(related, out symbol) && symbol is null)
52  {
53  Symbols.Remove(related);
54  }
55  }
56  }
57  }
58 
59  /// <summary>
60  /// Gets the Symbol object that is mapped to the specified string ticker symbol
61  /// </summary>
62  /// <param name="ticker">The string ticker symbol</param>
63  /// <returns>The symbol object that maps to the specified string ticker symbol</returns>
64  public static Symbol GetSymbol(string ticker)
65  {
66  var result = TryGetSymbol(ticker);
67  if (!result.Item1)
68  {
69  throw result.Item3 ?? throw new InvalidOperationException(Messages.SymbolCache.UnableToLocateTicker(ticker));
70  }
71  return result.Item2;
72  }
73 
74  /// <summary>
75  /// Gets the Symbol object that is mapped to the specified string ticker symbol
76  /// </summary>
77  /// <param name="ticker">The string ticker symbol</param>
78  /// <param name="symbol">The output symbol object</param>
79  /// <returns>The symbol object that maps to the specified string ticker symbol</returns>
80  public static bool TryGetSymbol(string ticker, out Symbol symbol)
81  {
82  var result = TryGetSymbol(ticker);
83  symbol = result.Item2;
84  return result.Item1;
85  }
86 
87  /// <summary>
88  /// Gets the string ticker symbol that is mapped to the specified Symbol
89  /// </summary>
90  /// <param name="symbol">The symbol object</param>
91  /// <returns>The string ticker symbol that maps to the specified symbol object</returns>
92  public static string GetTicker(Symbol symbol)
93  {
94  lock (Symbols)
95  {
96  return Tickers.TryGetValue(symbol, out var ticker) ? ticker : symbol.ID.ToString();
97  }
98  }
99 
100  /// <summary>
101  /// Gets the string ticker symbol that is mapped to the specified Symbol
102  /// </summary>
103  /// <param name="symbol">The symbol object</param>
104  /// <param name="ticker">The output string ticker symbol</param>
105  /// <returns>The string ticker symbol that maps to the specified symbol object</returns>
106  public static bool TryGetTicker(Symbol symbol, out string ticker)
107  {
108  lock (Symbols)
109  {
110  return Tickers.TryGetValue(symbol, out ticker);
111  }
112  }
113 
114  /// <summary>
115  /// Removes the mapping for the specified symbol from the cache
116  /// </summary>
117  /// <param name="symbol">The symbol whose mappings are to be removed</param>
118  /// <returns>True if the symbol mapping were removed from the cache</returns>
119  /// <remarks>Just used for testing</remarks>
120  public static bool TryRemove(Symbol symbol)
121  {
122  lock (Symbols)
123  {
124  return Tickers.Remove(symbol, out var ticker) && Symbols.Remove(ticker, out symbol);
125  }
126  }
127 
128  /// <summary>
129  /// Removes the mapping for the specified symbol from the cache
130  /// </summary>
131  /// <param name="ticker">The ticker whose mappings are to be removed</param>
132  /// <returns>True if the symbol mapping were removed from the cache</returns>
133  /// <remarks>Just used for testing</remarks>
134  public static bool TryRemove(string ticker)
135  {
136  lock (Symbols)
137  {
138  return Symbols.Remove(ticker, out var symbol) && Tickers.Remove(symbol, out ticker);
139  }
140  }
141 
142  /// <summary>
143  /// Clears the current caches
144  /// </summary>
145  /// <remarks>Just used for testing</remarks>
146  public static void Clear()
147  {
148  lock (Symbols)
149  {
150  Symbols.Clear();
151  Tickers.Clear();
152  }
153  }
154 
155  [MethodImpl(MethodImplOptions.AggressiveInlining)]
156  private static Tuple<bool, Symbol, InvalidOperationException> TryGetSymbol(string ticker)
157  {
158  lock (Symbols)
159  {
160  if (!TryGetSymbolCached(ticker, out var symbol))
161  {
162  // fall-back full-text search as a back-shim for custom data symbols.
163  // permitting a user to use BTC to resolve to BTC.Bitcoin
164  var search = $"{ticker}.";
165  var match = Symbols.Where(kvp => kvp.Key.StartsWith(search, StringComparison.InvariantCultureIgnoreCase) && kvp.Value is not null).ToList();
166 
167  if (match.Count == 0)
168  {
169  // no matches, cache the miss! else it will get expensive
170  Symbols[ticker] = null;
171  return new(false, null, null);
172  }
173  else if (match.Count == 1)
174  {
175  // exactly one match
176  Symbols[ticker] = match[0].Value;
177  return new(true, match[0].Value, null);
178  }
179  else if (match.Count > 1)
180  {
181  // too many matches
182  return new(false, null, new InvalidOperationException(
183  Messages.SymbolCache.MultipleMatchingTickersLocated(match.Select(kvp => kvp.Key))));
184  }
185  }
186  return new(symbol is not null, symbol, null);
187  }
188  }
189 
190  /// <summary>
191  /// Attempts to resolve the ticker to a Symbol via the cache. If not found in the
192  /// cache then
193  /// </summary>
194  /// <param name="ticker">The ticker to resolver to a symbol</param>
195  /// <param name="symbol">The resolves symbol</param>
196  /// <returns>True if we successfully resolved a symbol, false otherwise</returns>
197  [MethodImpl(MethodImplOptions.AggressiveInlining)]
198  private static bool TryGetSymbolCached(string ticker, out Symbol symbol)
199  {
200  if (Symbols.TryGetValue(ticker, out symbol))
201  {
202  return true;
203  }
204  if (SecurityIdentifier.TryParse(ticker, out var sid))
205  {
206  symbol = new Symbol(sid, sid.Symbol);
207  return true;
208  }
209  return false;
210  }
211  }
212 }