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