/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using QuantConnect.Util; using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; namespace QuantConnect.Securities { /// /// Provides access to specific properties for various symbols /// public class SymbolPropertiesDatabase : BaseSecurityDatabase { private IReadOnlyDictionary _keyBySecurityType; /// /// Initialize a new instance of using the given file /// /// File to read from protected SymbolPropertiesDatabase(string file) : base(null, FromDataFolder, (entry, newEntry) => entry.Update(newEntry)) { var allEntries = new Dictionary(); var entriesBySecurityType = new Dictionary(); foreach (var keyValuePair in FromCsvFile(file)) { if (allEntries.ContainsKey(keyValuePair.Key)) { throw new DuplicateNameException(Messages.SymbolPropertiesDatabase.DuplicateKeyInFile(file, keyValuePair.Key)); } // we wildcard the market, so per security type and symbol we will keep the *first* instance // this allows us to fetch deterministically, in O(1), an entry without knowing the market, see 'TryGetMarket()' var key = new SecurityDatabaseKey(SecurityDatabaseKey.Wildcard, keyValuePair.Key.Symbol, keyValuePair.Key.SecurityType); if (!entriesBySecurityType.ContainsKey(key)) { entriesBySecurityType[key] = keyValuePair.Key; } allEntries[keyValuePair.Key] = keyValuePair.Value; } Entries = allEntries; _keyBySecurityType = entriesBySecurityType; } /// /// Tries to get the market for the provided symbol/security type /// /// The particular symbol being traded /// The security type of the symbol /// The market the exchange resides in /// True if market was retrieved, false otherwise public bool TryGetMarket(string symbol, SecurityType securityType, out string market) { SecurityDatabaseKey result; var key = new SecurityDatabaseKey(SecurityDatabaseKey.Wildcard, symbol, securityType); if (_keyBySecurityType.TryGetValue(key, out result)) { market = result.Market; return true; } market = null; return false; } /// /// Gets the symbol properties for the specified market/symbol/security-type /// /// The market the exchange resides in, i.e, 'usa', 'fxcm', ect... /// The particular symbol being traded (Symbol class) /// The security type of the symbol /// Specifies the quote currency to be used when returning a default instance of an entry is not found in the database /// The symbol properties matching the specified market/symbol/security-type or null if not found /// 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 public SymbolProperties GetSymbolProperties(string market, Symbol symbol, SecurityType securityType, string defaultQuoteCurrency) { SymbolProperties symbolProperties; var lookupTicker = MarketHoursDatabase.GetDatabaseSymbolKey(symbol); var key = new SecurityDatabaseKey(market, lookupTicker, securityType); if (!Entries.TryGetValue(key, out symbolProperties)) { if (symbol != null && symbol.SecurityType == SecurityType.FutureOption) { // Default to looking up the underlying symbol's properties and using those instead if there's // no existing entry for the future option. lookupTicker = MarketHoursDatabase.GetDatabaseSymbolKey(symbol.Underlying); key = new SecurityDatabaseKey(market, lookupTicker, symbol.Underlying.SecurityType); if (Entries.TryGetValue(key, out symbolProperties)) { return symbolProperties; } } // now check with null symbol key if (!Entries.TryGetValue(new SecurityDatabaseKey(market, null, securityType), out symbolProperties)) { // no properties found, return object with default property values return SymbolProperties.GetDefault(defaultQuoteCurrency); } } return symbolProperties; } /// /// Gets a list of symbol properties for the specified market/security-type /// /// The market the exchange resides in, i.e, 'usa', 'fxcm', ect... /// The security type of the symbol /// An IEnumerable of symbol properties matching the specified market/security-type public IEnumerable> GetSymbolPropertiesList(string market, SecurityType securityType) { foreach (var entry in Entries) { var key = entry.Key; var symbolProperties = entry.Value; if (key.Market == market && key.SecurityType == securityType) { yield return new KeyValuePair(key, symbolProperties); } } } /// /// Gets a list of symbol properties for the specified market /// /// The market the exchange resides in, i.e, 'usa', 'fxcm', ect... /// An IEnumerable of symbol properties matching the specified market public IEnumerable> GetSymbolPropertiesList(string market) { foreach (var entry in Entries) { var key = entry.Key; var symbolProperties = entry.Value; if (key.Market == market) { yield return new KeyValuePair(key, symbolProperties); } } } /// /// Set SymbolProperties entry for a particular market, symbol and security type. /// /// Market of the entry /// Symbol of the entry /// Type of security for the entry /// The new symbol properties to store /// True if successful public bool SetEntry(string market, string symbol, SecurityType securityType, SymbolProperties properties) { var key = new SecurityDatabaseKey(market, symbol, securityType); lock (DataFolderDatabaseLock) { Entries[key] = properties; CustomEntries.Add(key); } return true; } /// /// Gets the instance of the class produced by reading in the symbol properties /// data found in /Data/symbol-properties/ /// /// A class that represents the data in the symbol-properties folder public static SymbolPropertiesDatabase FromDataFolder() { if (DataFolderDatabase == null) { lock (DataFolderDatabaseLock) { if (DataFolderDatabase == null) { var path = Path.Combine(Globals.GetDataFolderPath("symbol-properties"), "symbol-properties-database.csv"); DataFolderDatabase = new SymbolPropertiesDatabase(path); } } } return DataFolderDatabase; } /// /// Creates a new instance of the class by reading the specified csv file /// /// The csv file to be read /// A new instance of the class representing the data in the specified file private static IEnumerable> FromCsvFile(string file) { if (!File.Exists(file)) { throw new FileNotFoundException(Messages.SymbolPropertiesDatabase.DatabaseFileNotFound(file)); } // skip the first header line, also skip #'s as these are comment lines foreach (var line in File.ReadLines(file).Where(x => !x.StartsWith("#") && !string.IsNullOrWhiteSpace(x)).Skip(1)) { SecurityDatabaseKey key; var entry = FromCsvLine(line, out key); if (key == null || entry == null) { continue; } yield return new KeyValuePair(key, entry); } } /// /// Creates a new instance of from the specified csv line /// /// The csv line to be parsed /// The key used to uniquely identify this security /// A new for the specified csv line protected static SymbolProperties FromCsvLine(string line, out SecurityDatabaseKey key) { var csv = line.Split(','); SecurityType securityType; if (!csv[2].TryParseSecurityType(out securityType)) { key = null; return null; } key = new SecurityDatabaseKey( market: csv[0], symbol: csv[1], securityType: securityType); return new SymbolProperties( description: csv[3], quoteCurrency: csv[4], contractMultiplier: csv[5].ToDecimal(), minimumPriceVariation: csv[6].ToDecimalAllowExponent(), lotSize: csv[7].ToDecimal(), marketTicker: HasValidValue(csv, 8) ? csv[8] : string.Empty, minimumOrderSize: HasValidValue(csv, 9) ? csv[9].ToDecimal() : null, priceMagnifier: HasValidValue(csv, 10) ? csv[10].ToDecimal() : 1, strikeMultiplier: HasValidValue(csv, 11) ? csv[11].ToDecimal() : 1); } private static bool HasValidValue(string[] array, uint position) { return array.Length > position && !string.IsNullOrEmpty(array[position]); } internal override void Merge(SymbolPropertiesDatabase newDatabase, bool resetCustomEntries) { base.Merge(newDatabase, resetCustomEntries); _keyBySecurityType = newDatabase._keyBySecurityType.ToReadOnlyDictionary(); } } }