/*
* 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();
}
}
}
}