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