/*
* 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.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using Ionic.Zip;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Logging;
using QuantConnect.Util;
namespace QuantConnect.ToolBox
{
///
/// Data writer for saving an IEnumerable of BaseData into the LEAN data directory.
///
public class LeanDataWriter
{
private readonly Symbol _symbol;
private readonly string _market;
private readonly string _dataDirectory;
private readonly TickType _dataType;
private readonly Resolution _resolution;
private readonly SecurityType _securityType;
///
/// Create a new lean data writer to this base data directory.
///
/// Symbol string
/// Base data directory
/// Resolution of the desired output data
/// Write the data to trade files
public LeanDataWriter(Resolution resolution, Symbol symbol, string dataDirectory, TickType dataType = TickType.Trade)
{
_securityType = symbol.ID.SecurityType;
_dataDirectory = dataDirectory;
_resolution = resolution;
_symbol = symbol;
_market = symbol.ID.Market.ToLower();
_dataType = dataType;
// All fx data is quote data.
if (_securityType == SecurityType.Forex || _securityType == SecurityType.Cfd)
{
_dataType = TickType.Quote;
}
if (_securityType != SecurityType.Equity && _securityType != SecurityType.Forex && _securityType != SecurityType.Cfd && _securityType != SecurityType.Crypto && _securityType != SecurityType.Future && _securityType != SecurityType.Option)
{
throw new Exception("Sorry this security type is not yet supported by the LEAN data writer: " + _securityType);
}
}
///
/// Given the constructor parameters, write out the data in LEAN format.
///
/// IEnumerable source of the data: sorted from oldest to newest.
public void Write(IEnumerable source)
{
switch (_resolution)
{
case Resolution.Daily:
case Resolution.Hour:
WriteDailyOrHour(source);
break;
case Resolution.Minute:
case Resolution.Second:
case Resolution.Tick:
WriteMinuteOrSecondOrTick(source);
break;
}
}
///
/// Write out the data in LEAN format (minute, second or tick resolutions)
///
/// IEnumerable source of the data: sorted from oldest to newest.
/// This function overwrites existing data files
private void WriteMinuteOrSecondOrTick(IEnumerable source)
{
var sb = new StringBuilder();
var lastTime = new DateTime();
// Loop through all the data and write to file as we go
foreach (var data in source)
{
// Ensure the data is sorted
if (data.Time < lastTime) throw new Exception("The data must be pre-sorted from oldest to newest");
// Based on the security type and resolution, write the data to the zip file
if (lastTime != DateTime.MinValue && data.Time.Date > lastTime.Date)
{
// Write and clear the file contents
var outputFile = GetZipOutputFileName(_dataDirectory, lastTime);
WriteFile(outputFile, sb.ToString(), lastTime);
sb.Clear();
}
lastTime = data.Time;
// Build the line and append it to the file
sb.Append(LeanData.GenerateLine(data, _securityType, _resolution) + Environment.NewLine);
}
// Write the last file
if (sb.Length > 0)
{
var outputFile = GetZipOutputFileName(_dataDirectory, lastTime);
WriteFile(outputFile, sb.ToString(), lastTime);
}
}
///
/// Write out the data in LEAN format (daily or hour resolutions)
///
/// IEnumerable source of the data: sorted from oldest to newest.
/// This function performs a merge (insert/append/overwrite) with the existing Lean zip file
private void WriteDailyOrHour(IEnumerable source)
{
var sb = new StringBuilder();
var lastTime = new DateTime();
// Determine file path
var outputFile = GetZipOutputFileName(_dataDirectory, lastTime);
// Load new data rows into a SortedDictionary for easy merge/update
var newRows = new SortedDictionary(source.ToDictionary(x => x.Time, x => LeanData.GenerateLine(x, _securityType, _resolution)));
SortedDictionary rows;
if (File.Exists(outputFile))
{
// If file exists, we load existing data and perform merge
rows = LoadHourlyOrDailyFile(outputFile);
foreach (var kvp in newRows)
{
rows[kvp.Key] = kvp.Value;
}
}
else
{
// No existing file, just use the new data
rows = newRows;
}
// Loop through the SortedDictionary and write to file contents
foreach (var kvp in rows)
{
// Build the line and append it to the file
sb.Append(kvp.Value + Environment.NewLine);
}
// Write the file contents
if (sb.Length > 0)
{
WriteFile(outputFile, sb.ToString(), lastTime);
}
}
///
/// Loads an existing hourly or daily Lean zip file into a SortedDictionary
///
private static SortedDictionary LoadHourlyOrDailyFile(string fileName)
{
var rows = new SortedDictionary();
using (var zip = ZipFile.Read(fileName))
{
using (var stream = new MemoryStream())
{
zip[0].Extract(stream);
stream.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(stream))
{
string line;
while ((line = reader.ReadLine()) != null)
{
var time = DateTime.ParseExact(line.Substring(0, DateFormat.TwelveCharacter.Length), DateFormat.TwelveCharacter, CultureInfo.InvariantCulture);
rows[time] = line;
}
}
}
}
return rows;
}
///
/// Write this file to disk.
///
/// The full path to the new file
/// The data to write as a string
/// The date the data represents
private void WriteFile(string filePath, string data, DateTime date)
{
var tempFilePath = filePath + ".tmp";
data = data.TrimEnd();
if (File.Exists(filePath))
{
File.Delete(filePath);
Log.Trace("LeanDataWriter.Write(): Existing deleted: " + filePath);
}
// Create the directory if it doesnt exist
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
// Write out this data string to a zip file
Compression.Zip(data, tempFilePath, LeanData.GenerateZipEntryName(_symbol, date, _resolution, _dataType));
// Move temp file to the final destination with the appropriate name
File.Move(tempFilePath, filePath);
Log.Trace("LeanDataWriter.Write(): Created: " + filePath);
}
///
/// Get the output zip file
///
/// Base output directory for the zip file
/// Date/time for the data we're writing
/// The full path to the output zip file
private string GetZipOutputFileName(string baseDirectory, DateTime time)
{
return LeanData.GenerateZipFilePath(baseDirectory, _symbol, time, _resolution, _dataType);
}
}
}