Lean  $LEAN_TAG$
SymbolPropertiesDatabase.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 QuantConnect.Util;
17 using System.Collections.Generic;
18 using System.Data;
19 using System.IO;
20 using System.Linq;
21 
23 {
24  /// <summary>
25  /// Provides access to specific properties for various symbols
26  /// </summary>
28  {
29  private static SymbolPropertiesDatabase _dataFolderSymbolPropertiesDatabase;
30  private static readonly object DataFolderSymbolPropertiesDatabaseLock = new object();
31 
32  private Dictionary<SecurityDatabaseKey, SymbolProperties> _entries;
33  private readonly Dictionary<SecurityDatabaseKey, SymbolProperties> _customEntries;
34  private IReadOnlyDictionary<SecurityDatabaseKey, SecurityDatabaseKey> _keyBySecurityType;
35 
36  /// <summary>
37  /// Initialize a new instance of <see cref="SymbolPropertiesDatabase"/> using the given file
38  /// </summary>
39  /// <param name="file">File to read from</param>
40  protected SymbolPropertiesDatabase(string file)
41  {
42  var allEntries = new Dictionary<SecurityDatabaseKey, SymbolProperties>();
43  var entriesBySecurityType = new Dictionary<SecurityDatabaseKey, SecurityDatabaseKey>();
44 
45  foreach (var keyValuePair in FromCsvFile(file))
46  {
47  if (allEntries.ContainsKey(keyValuePair.Key))
48  {
49  throw new DuplicateNameException(Messages.SymbolPropertiesDatabase.DuplicateKeyInFile(file, keyValuePair.Key));
50  }
51  // we wildcard the market, so per security type and symbol we will keep the *first* instance
52  // this allows us to fetch deterministically, in O(1), an entry without knowing the market, see 'TryGetMarket()'
53  var key = new SecurityDatabaseKey(SecurityDatabaseKey.Wildcard, keyValuePair.Key.Symbol, keyValuePair.Key.SecurityType);
54  if (!entriesBySecurityType.ContainsKey(key))
55  {
56  entriesBySecurityType[key] = keyValuePair.Key;
57  }
58  allEntries[keyValuePair.Key] = keyValuePair.Value;
59  }
60 
61  _entries = allEntries;
62  _customEntries = new();
63  _keyBySecurityType = entriesBySecurityType;
64  }
65 
66  /// <summary>
67  /// Check whether symbol properties exists for the specified market/symbol/security-type
68  /// </summary>
69  /// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
70  /// <param name="symbol">The particular symbol being traded</param>
71  /// <param name="securityType">The security type of the symbol</param>
72  public bool ContainsKey(string market, string symbol, SecurityType securityType)
73  {
74  var key = new SecurityDatabaseKey(market, symbol, securityType);
75  return _entries.ContainsKey(key);
76  }
77 
78  /// <summary>
79  /// Check whether symbol properties exists for the specified market/symbol/security-type
80  /// </summary>
81  /// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
82  /// <param name="symbol">The particular symbol being traded (Symbol class)</param>
83  /// <param name="securityType">The security type of the symbol</param>
84  public bool ContainsKey(string market, Symbol symbol, SecurityType securityType)
85  {
86  return ContainsKey(
87  market,
89  securityType);
90  }
91 
92  /// <summary>
93  /// Tries to get the market for the provided symbol/security type
94  /// </summary>
95  /// <param name="symbol">The particular symbol being traded</param>
96  /// <param name="securityType">The security type of the symbol</param>
97  /// <param name="market">The market the exchange resides in <see cref="Market"/></param>
98  /// <returns>True if market was retrieved, false otherwise</returns>
99  public bool TryGetMarket(string symbol, SecurityType securityType, out string market)
100  {
101  SecurityDatabaseKey result;
102  var key = new SecurityDatabaseKey(SecurityDatabaseKey.Wildcard, symbol, securityType);
103  if (_keyBySecurityType.TryGetValue(key, out result))
104  {
105  market = result.Market;
106  return true;
107  }
108 
109  market = null;
110  return false;
111  }
112 
113  /// <summary>
114  /// Gets the symbol properties for the specified market/symbol/security-type
115  /// </summary>
116  /// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
117  /// <param name="symbol">The particular symbol being traded (Symbol class)</param>
118  /// <param name="securityType">The security type of the symbol</param>
119  /// <param name="defaultQuoteCurrency">Specifies the quote currency to be used when returning a default instance of an entry is not found in the database</param>
120  /// <returns>The symbol properties matching the specified market/symbol/security-type or null if not found</returns>
121  /// <remarks>For any derivative options asset that is not for equities, we default to the underlying symbol's properties if no entry is found in the database</remarks>
122  public SymbolProperties GetSymbolProperties(string market, Symbol symbol, SecurityType securityType, string defaultQuoteCurrency)
123  {
124  SymbolProperties symbolProperties;
125  var lookupTicker = MarketHoursDatabase.GetDatabaseSymbolKey(symbol);
126  var key = new SecurityDatabaseKey(market, lookupTicker, securityType);
127 
128  if (!_entries.TryGetValue(key, out symbolProperties))
129  {
130  if (symbol != null && symbol.SecurityType == SecurityType.FutureOption)
131  {
132  // Default to looking up the underlying symbol's properties and using those instead if there's
133  // no existing entry for the future option.
134  lookupTicker = MarketHoursDatabase.GetDatabaseSymbolKey(symbol.Underlying);
135  key = new SecurityDatabaseKey(market, lookupTicker, symbol.Underlying.SecurityType);
136 
137  if (_entries.TryGetValue(key, out symbolProperties))
138  {
139  return symbolProperties;
140  }
141  }
142 
143  // now check with null symbol key
144  if (!_entries.TryGetValue(new SecurityDatabaseKey(market, null, securityType), out symbolProperties))
145  {
146  // no properties found, return object with default property values
147  return SymbolProperties.GetDefault(defaultQuoteCurrency);
148  }
149  }
150 
151  return symbolProperties;
152  }
153 
154  /// <summary>
155  /// Gets a list of symbol properties for the specified market/security-type
156  /// </summary>
157  /// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
158  /// <param name="securityType">The security type of the symbol</param>
159  /// <returns>An IEnumerable of symbol properties matching the specified market/security-type</returns>
160  public IEnumerable<KeyValuePair<SecurityDatabaseKey, SymbolProperties>> GetSymbolPropertiesList(string market, SecurityType securityType)
161  {
162  foreach (var entry in _entries)
163  {
164  var key = entry.Key;
165  var symbolProperties = entry.Value;
166 
167  if (key.Market == market && key.SecurityType == securityType)
168  {
169  yield return new KeyValuePair<SecurityDatabaseKey, SymbolProperties>(key, symbolProperties);
170  }
171  }
172  }
173 
174  /// <summary>
175  /// Gets a list of symbol properties for the specified market
176  /// </summary>
177  /// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
178  /// <returns>An IEnumerable of symbol properties matching the specified market</returns>
179  public IEnumerable<KeyValuePair<SecurityDatabaseKey, SymbolProperties>> GetSymbolPropertiesList(string market)
180  {
181  foreach (var entry in _entries)
182  {
183  var key = entry.Key;
184  var symbolProperties = entry.Value;
185 
186  if (key.Market == market)
187  {
188  yield return new KeyValuePair<SecurityDatabaseKey, SymbolProperties>(key, symbolProperties);
189  }
190  }
191  }
192 
193  /// <summary>
194  /// Set SymbolProperties entry for a particular market, symbol and security type.
195  /// </summary>
196  /// <param name="market">Market of the entry</param>
197  /// <param name="symbol">Symbol of the entry</param>
198  /// <param name="securityType">Type of security for the entry</param>
199  /// <param name="properties">The new symbol properties to store</param>
200  /// <returns>True if successful</returns>
201  public bool SetEntry(string market, string symbol, SecurityType securityType, SymbolProperties properties)
202  {
203  var key = new SecurityDatabaseKey(market, symbol, securityType);
204  lock (DataFolderSymbolPropertiesDatabaseLock)
205  {
206  _entries[key] = properties;
207  _customEntries[key] = properties;
208  }
209  return true;
210  }
211 
212  /// <summary>
213  /// Gets the instance of the <see cref="SymbolPropertiesDatabase"/> class produced by reading in the symbol properties
214  /// data found in /Data/symbol-properties/
215  /// </summary>
216  /// <returns>A <see cref="SymbolPropertiesDatabase"/> class that represents the data in the symbol-properties folder</returns>
218  {
219  lock (DataFolderSymbolPropertiesDatabaseLock)
220  {
221  if (_dataFolderSymbolPropertiesDatabase == null)
222  {
223  _dataFolderSymbolPropertiesDatabase = new SymbolPropertiesDatabase(Path.Combine(Globals.GetDataFolderPath("symbol-properties"), "symbol-properties-database.csv"));
224  }
225  }
226  return _dataFolderSymbolPropertiesDatabase;
227  }
228 
229  /// <summary>
230  /// Creates a new instance of the <see cref="SymbolPropertiesDatabase"/> class by reading the specified csv file
231  /// </summary>
232  /// <param name="file">The csv file to be read</param>
233  /// <returns>A new instance of the <see cref="SymbolPropertiesDatabase"/> class representing the data in the specified file</returns>
234  private static IEnumerable<KeyValuePair<SecurityDatabaseKey, SymbolProperties>> FromCsvFile(string file)
235  {
236  if (!File.Exists(file))
237  {
238  throw new FileNotFoundException(Messages.SymbolPropertiesDatabase.DatabaseFileNotFound(file));
239  }
240 
241  // skip the first header line, also skip #'s as these are comment lines
242  foreach (var line in File.ReadLines(file).Where(x => !x.StartsWith("#") && !string.IsNullOrWhiteSpace(x)).Skip(1))
243  {
244  SecurityDatabaseKey key;
245  var entry = FromCsvLine(line, out key);
246  if (key == null || entry == null)
247  {
248  continue;
249  }
250 
251  yield return new KeyValuePair<SecurityDatabaseKey, SymbolProperties>(key, entry);
252  }
253  }
254 
255  /// <summary>
256  /// Creates a new instance of <see cref="SymbolProperties"/> from the specified csv line
257  /// </summary>
258  /// <param name="line">The csv line to be parsed</param>
259  /// <param name="key">The key used to uniquely identify this security</param>
260  /// <returns>A new <see cref="SymbolProperties"/> for the specified csv line</returns>
261  protected static SymbolProperties FromCsvLine(string line, out SecurityDatabaseKey key)
262  {
263  var csv = line.Split(',');
264 
265  SecurityType securityType;
266  if (!csv[2].TryParseSecurityType(out securityType))
267  {
268  key = null;
269  return null;
270  }
271 
272  key = new SecurityDatabaseKey(
273  market: csv[0],
274  symbol: csv[1],
275  securityType: securityType);
276 
277  return new SymbolProperties(
278  description: csv[3],
279  quoteCurrency: csv[4],
280  contractMultiplier: csv[5].ToDecimal(),
281  minimumPriceVariation: csv[6].ToDecimalAllowExponent(),
282  lotSize: csv[7].ToDecimal(),
283  marketTicker: HasValidValue(csv, 8) ? csv[8] : string.Empty,
284  minimumOrderSize: HasValidValue(csv, 9) ? csv[9].ToDecimal() : null,
285  priceMagnifier: HasValidValue(csv, 10) ? csv[10].ToDecimal() : 1,
286  strikeMultiplier: HasValidValue(csv, 11) ? csv[11].ToDecimal() : 1);
287  }
288 
289  private static bool HasValidValue(string[] array, uint position)
290  {
291  return array.Length > position && !string.IsNullOrEmpty(array[position]);
292  }
293 
294  /// <summary>
295  /// Reload entries dictionary from SPDB file and merge them with previous custom ones
296  /// </summary>
297  internal void ReloadEntries()
298  {
299  lock (DataFolderSymbolPropertiesDatabaseLock)
300  {
301  _dataFolderSymbolPropertiesDatabase = null;
302  var newInstance = FromDataFolder();
303  var fileEntries = newInstance._entries.Where(x => !_customEntries.ContainsKey(x.Key));
304  var newEntries = fileEntries.Concat(_customEntries).ToDictionary();
305  _entries = newEntries;
306  _keyBySecurityType = newInstance._keyBySecurityType.ToReadOnlyDictionary();
307  }
308  }
309  }
310 }