/* * 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 System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using QuantConnect.Interfaces; using QuantConnect.Logging; using static QuantConnect.StringExtensions; namespace QuantConnect.Data.Auxiliary { /// /// Represents an entire map file for a specified symbol /// public class MapFile : IEnumerable { private readonly List _data; /// /// Gets the entity's unique symbol, i.e OIH.1 /// public string Permtick { get; } /// /// Gets the last date in the map file which is indicative of a delisting event /// public DateTime DelistingDate { get; } /// /// Gets the first date in this map file /// public DateTime FirstDate { get; } /// /// Gets the first ticker for the security represented by this map file /// public string FirstTicker { get; } /// /// Initializes a new instance of the class. /// public MapFile(string permtick, IEnumerable data) { if (string.IsNullOrEmpty(permtick)) { throw new ArgumentNullException(nameof(permtick), "Provided ticker is null or empty"); } Permtick = permtick.LazyToUpper(); _data = data.Distinct().OrderBy(row => row.Date).ToList(); // for performance we set first and last date on ctr if (_data.Count == 0) { FirstDate = Time.BeginningOfTime; DelistingDate = Time.EndOfTime; } else { FirstDate = _data[0].Date; DelistingDate = _data[_data.Count - 1].Date; } var firstTicker = GetMappedSymbol(FirstDate, Permtick); if (char.IsDigit(firstTicker.Last())) { var dotIndex = firstTicker.LastIndexOf(".", StringComparison.Ordinal); if (dotIndex > 0) { int value; var number = firstTicker.AsSpan(dotIndex + 1); if (int.TryParse(number, out value)) { firstTicker = firstTicker.Substring(0, dotIndex); } } } FirstTicker = firstTicker; } /// /// Memory overload search method for finding the mapped symbol for this date. /// /// date for symbol we need to find. /// Default return value if search was got no result. /// The mapping mode to use if any. /// Symbol on this date. public string GetMappedSymbol(DateTime searchDate, string defaultReturnValue = "", DataMappingMode? dataMappingMode = null) { var mappedSymbol = defaultReturnValue; //Iterate backwards to find the most recent factor: for (var i = 0; i < _data.Count; i++) { var row = _data[i]; if (row.Date < searchDate || row.DataMappingMode.HasValue && row.DataMappingMode != dataMappingMode) { continue; } mappedSymbol = row.MappedSymbol; break; } return mappedSymbol; } /// /// Determines if there's data for the requested date /// public bool HasData(DateTime date) { // handle the case where we don't have any data if (_data.Count == 0) { return true; } if (date < FirstDate || date > DelistingDate) { // don't even bother checking the disk if the map files state we don't have the data return false; } return true; } /// /// Reads and writes each /// /// Enumerable of csv lines public IEnumerable ToCsvLines() { return _data.Select(mapRow => mapRow.ToCsv()); } /// /// Writes the map file to a CSV file /// /// The market to save the MapFile to /// The map file security type public void WriteToCsv(string market, SecurityType securityType) { var filePath = Path.Combine(Globals.DataFolder, GetRelativeMapFilePath(market, securityType), Permtick.ToLowerInvariant() + ".csv"); var fileDir = Path.GetDirectoryName(filePath); if (!Directory.Exists(fileDir)) { Directory.CreateDirectory(fileDir); Log.Trace($"Created directory for map file: {fileDir}"); } File.WriteAllLines(filePath, ToCsvLines()); } /// /// Constructs the map file path for the specified market and symbol /// /// The market this symbol belongs to /// The map file security type /// The file path to the requested map file public static string GetRelativeMapFilePath(string market, SecurityType securityType) { return Invariant($"{securityType.SecurityTypeToLower()}/{market}/map_files"); } #region Implementation of IEnumerable /// /// Returns an enumerator that iterates through the collection. /// /// /// A that can be used to iterate through the collection. /// /// 1 public IEnumerator GetEnumerator() { return _data.GetEnumerator(); } /// /// Returns an enumerator that iterates through a collection. /// /// /// An object that can be used to iterate through the collection. /// /// 2 IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion /// /// Reads all the map files in the specified directory /// /// The map file directory path /// The map file market /// The map file security type /// The data provider instance to use /// An enumerable of all map files public static IEnumerable GetMapFiles(string mapFileDirectory, string market, SecurityType securityType, IDataProvider dataProvider) { var mapFiles = new List(); Parallel.ForEach(Directory.EnumerateFiles(mapFileDirectory), file => { if (file.EndsWith(".csv")) { var permtick = Path.GetFileNameWithoutExtension(file); var fileRead = SafeMapFileRowRead(file, market, securityType, dataProvider); var mapFile = new MapFile(permtick, fileRead); lock (mapFiles) { // just use a list + lock, not concurrent bag, avoid garbage it creates for features we don't need here. See https://github.com/dotnet/runtime/issues/23103 mapFiles.Add(mapFile); } } }); return mapFiles; } /// /// Reads in the map file at the specified path, returning null if any exceptions are encountered /// private static List SafeMapFileRowRead(string file, string market, SecurityType securityType, IDataProvider dataProvider) { try { return MapFileRow.Read(file, market, securityType, dataProvider).ToList(); } catch (Exception err) { Log.Error(err, $"File: {file}"); return new List(); } } } }