Lean  $LEAN_TAG$
MapFile.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 
17 using System;
18 using System.Collections;
19 using System.Collections.Generic;
20 using System.IO;
21 using System.Linq;
22 using System.Threading.Tasks;
24 using QuantConnect.Logging;
25 using static QuantConnect.StringExtensions;
26 
28 {
29  /// <summary>
30  /// Represents an entire map file for a specified symbol
31  /// </summary>
32  public class MapFile : IEnumerable<MapFileRow>
33  {
34  private readonly List<MapFileRow> _data;
35 
36  /// <summary>
37  /// Gets the entity's unique symbol, i.e OIH.1
38  /// </summary>
39  public string Permtick { get; }
40 
41  /// <summary>
42  /// Gets the last date in the map file which is indicative of a delisting event
43  /// </summary>
44  public DateTime DelistingDate { get; }
45 
46  /// <summary>
47  /// Gets the first date in this map file
48  /// </summary>
49  public DateTime FirstDate { get; }
50 
51  /// <summary>
52  /// Gets the first ticker for the security represented by this map file
53  /// </summary>
54  public string FirstTicker { get; }
55 
56  /// <summary>
57  /// Initializes a new instance of the <see cref="MapFile"/> class.
58  /// </summary>
59  public MapFile(string permtick, IEnumerable<MapFileRow> data)
60  {
61  if (string.IsNullOrEmpty(permtick))
62  {
63  throw new ArgumentNullException(nameof(permtick), "Provided ticker is null or empty");
64  }
65 
66  Permtick = permtick.LazyToUpper();
67  _data = data.Distinct().OrderBy(row => row.Date).ToList();
68 
69  // for performance we set first and last date on ctr
70  if (_data.Count == 0)
71  {
74  }
75  else
76  {
77  FirstDate = _data[0].Date;
78  DelistingDate = _data[_data.Count - 1].Date;
79  }
80 
81  var firstTicker = GetMappedSymbol(FirstDate, Permtick);
82  if (char.IsDigit(firstTicker.Last()))
83  {
84  var dotIndex = firstTicker.LastIndexOf(".", StringComparison.Ordinal);
85  if (dotIndex > 0)
86  {
87  int value;
88  var number = firstTicker.AsSpan(dotIndex + 1);
89  if (int.TryParse(number, out value))
90  {
91  firstTicker = firstTicker.Substring(0, dotIndex);
92  }
93  }
94  }
95 
96  FirstTicker = firstTicker;
97  }
98 
99  /// <summary>
100  /// Memory overload search method for finding the mapped symbol for this date.
101  /// </summary>
102  /// <param name="searchDate">date for symbol we need to find.</param>
103  /// <param name="defaultReturnValue">Default return value if search was got no result.</param>
104  /// <param name="dataMappingMode">The mapping mode to use if any.</param>
105  /// <returns>Symbol on this date.</returns>
106  public string GetMappedSymbol(DateTime searchDate, string defaultReturnValue = "", DataMappingMode? dataMappingMode = null)
107  {
108  var mappedSymbol = defaultReturnValue;
109  //Iterate backwards to find the most recent factor:
110  for (var i = 0; i < _data.Count; i++)
111  {
112  var row = _data[i];
113  if (row.Date < searchDate || row.DataMappingMode.HasValue && row.DataMappingMode != dataMappingMode)
114  {
115  continue;
116  }
117  mappedSymbol = row.MappedSymbol;
118  break;
119  }
120  return mappedSymbol;
121  }
122 
123  /// <summary>
124  /// Determines if there's data for the requested date
125  /// </summary>
126  public bool HasData(DateTime date)
127  {
128  // handle the case where we don't have any data
129  if (_data.Count == 0)
130  {
131  return true;
132  }
133 
134  if (date < FirstDate || date > DelistingDate)
135  {
136  // don't even bother checking the disk if the map files state we don't have the data
137  return false;
138  }
139  return true;
140  }
141 
142  /// <summary>
143  /// Reads and writes each <see cref="MapFileRow"/>
144  /// </summary>
145  /// <returns>Enumerable of csv lines</returns>
146  public IEnumerable<string> ToCsvLines()
147  {
148  return _data.Select(mapRow => mapRow.ToCsv());
149  }
150 
151  /// <summary>
152  /// Writes the map file to a CSV file
153  /// </summary>
154  /// <param name="market">The market to save the MapFile to</param>
155  /// <param name="securityType">The map file security type</param>
156  public void WriteToCsv(string market, SecurityType securityType)
157  {
158  var filePath = Path.Combine(Globals.DataFolder, GetRelativeMapFilePath(market, securityType), Permtick.ToLowerInvariant() + ".csv");
159  var fileDir = Path.GetDirectoryName(filePath);
160 
161  if (!Directory.Exists(fileDir))
162  {
163  Directory.CreateDirectory(fileDir);
164  Log.Trace($"Created directory for map file: {fileDir}");
165  }
166 
167  File.WriteAllLines(filePath, ToCsvLines());
168  }
169 
170  /// <summary>
171  /// Constructs the map file path for the specified market and symbol
172  /// </summary>
173  /// <param name="market">The market this symbol belongs to</param>
174  /// <param name="securityType">The map file security type</param>
175  /// <returns>The file path to the requested map file</returns>
176  public static string GetRelativeMapFilePath(string market, SecurityType securityType)
177  {
178  return Invariant($"{securityType.SecurityTypeToLower()}/{market}/map_files");
179  }
180 
181  #region Implementation of IEnumerable
182 
183  /// <summary>
184  /// Returns an enumerator that iterates through the collection.
185  /// </summary>
186  /// <returns>
187  /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
188  /// </returns>
189  /// <filterpriority>1</filterpriority>
190  public IEnumerator<MapFileRow> GetEnumerator()
191  {
192  return _data.GetEnumerator();
193  }
194 
195  /// <summary>
196  /// Returns an enumerator that iterates through a collection.
197  /// </summary>
198  /// <returns>
199  /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
200  /// </returns>
201  /// <filterpriority>2</filterpriority>
202  IEnumerator IEnumerable.GetEnumerator()
203  {
204  return GetEnumerator();
205  }
206 
207  #endregion
208 
209  /// <summary>
210  /// Reads all the map files in the specified directory
211  /// </summary>
212  /// <param name="mapFileDirectory">The map file directory path</param>
213  /// <param name="market">The map file market</param>
214  /// <param name="securityType">The map file security type</param>
215  /// <param name="dataProvider">The data provider instance to use</param>
216  /// <returns>An enumerable of all map files</returns>
217  public static IEnumerable<MapFile> GetMapFiles(string mapFileDirectory, string market, SecurityType securityType, IDataProvider dataProvider)
218  {
219  var mapFiles = new List<MapFile>();
220  Parallel.ForEach(Directory.EnumerateFiles(mapFileDirectory), file =>
221  {
222  if (file.EndsWith(".csv"))
223  {
224  var permtick = Path.GetFileNameWithoutExtension(file);
225  var fileRead = SafeMapFileRowRead(file, market, securityType, dataProvider);
226  var mapFile = new MapFile(permtick, fileRead);
227  lock (mapFiles)
228  {
229  // 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
230  mapFiles.Add(mapFile);
231  }
232  }
233  });
234  return mapFiles;
235  }
236 
237  /// <summary>
238  /// Reads in the map file at the specified path, returning null if any exceptions are encountered
239  /// </summary>
240  private static List<MapFileRow> SafeMapFileRowRead(string file, string market, SecurityType securityType, IDataProvider dataProvider)
241  {
242  try
243  {
244  return MapFileRow.Read(file, market, securityType, dataProvider).ToList();
245  }
246  catch (Exception err)
247  {
248  Log.Error(err, $"File: {file}");
249  return new List<MapFileRow>();
250  }
251  }
252  }
253 }