487 lines
21 KiB
C#
487 lines
21 KiB
C#
/*
|
|
* 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 NAMESPACES
|
|
**********************************************************/
|
|
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using QuantConnect.Data;
|
|
using QuantConnect.Data.Market;
|
|
using QuantConnect.Interfaces;
|
|
using QuantConnect.Logging;
|
|
using QuantConnect.Packets;
|
|
|
|
namespace QuantConnect.Lean.Engine.DataFeeds
|
|
{
|
|
/********************************************************
|
|
* CLASS DEFINITIONS
|
|
*********************************************************/
|
|
/// <summary>
|
|
/// Historical datafeed stream reader for processing files on a local disk.
|
|
/// </summary>
|
|
/// <remarks>Filesystem datafeeds are incredibly fast</remarks>
|
|
public class FileSystemDataFeed : IDataFeed
|
|
{
|
|
/********************************************************
|
|
* CLASS VARIABLES
|
|
*********************************************************/
|
|
// Set types in public area to speed up:
|
|
private IAlgorithm _algorithm;
|
|
private BacktestNodePacket _job;
|
|
private Type _ticksType = typeof(Ticks);
|
|
private Type _tradeBarsType = typeof(TradeBars);
|
|
private bool _endOfStreams = false;
|
|
private int _subscriptions = 0;
|
|
private int _bridgeMax = 500000;
|
|
private bool _exitTriggered = false;
|
|
|
|
/********************************************************
|
|
* CLASS PROPERTIES
|
|
*********************************************************/
|
|
/// Subscriptions for this datafeed. What data would we like to pull:
|
|
public List<SubscriptionDataConfig> Subscriptions { get; private set; }
|
|
|
|
/// IDataStream Thread Safe Data Bridge
|
|
public ConcurrentQueue<List<BaseData>>[] Bridge { get; set; }
|
|
|
|
/// Stream created from the configuration settings.
|
|
public SubscriptionDataReader[] SubscriptionReaderManagers { get; set; }
|
|
|
|
/// Thread State Control
|
|
public ThreadState ThreadControl { get; set; }
|
|
|
|
/// Set the source of data we're requesting
|
|
public DataFeedEndpoint DataFeed { get; set; }
|
|
|
|
/// Flag indicating the hander thread is completely finished and ready to dispose.
|
|
public bool IsActive { get; private set; }
|
|
|
|
/// Signifying no more data
|
|
public bool EndOfBridges {
|
|
get {
|
|
for (var i = 0; i < Bridge.Length; i++)
|
|
{
|
|
if (Bridge[i].Count != 0 || EndOfBridge[i] != true || _endOfStreams != true)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// End of Stream for One Bridge:
|
|
public bool[] EndOfBridge { get; set; }
|
|
|
|
/// Frontiers for each fill forward high water mark
|
|
public DateTime[] FillForwardFrontiers;
|
|
|
|
|
|
/********************************************************
|
|
* CLASS CONSTRUCTOR
|
|
*********************************************************/
|
|
/// <summary>
|
|
/// Create a new backtesting data feed.
|
|
/// </summary>
|
|
/// <param name="algorithm">Instance of the algorithm</param>
|
|
/// <param name="job">Algorithm work task</param>
|
|
public FileSystemDataFeed(IAlgorithm algorithm, BacktestNodePacket job)
|
|
{
|
|
Subscriptions = algorithm.SubscriptionManager.Subscriptions;
|
|
_subscriptions = Subscriptions.Count;
|
|
|
|
//Public Properties:
|
|
DataFeed = DataFeedEndpoint.FileSystem;
|
|
IsActive = true;
|
|
Bridge = new ConcurrentQueue<List<BaseData>>[_subscriptions];
|
|
EndOfBridge = new bool[_subscriptions];
|
|
SubscriptionReaderManagers = new SubscriptionDataReader[_subscriptions];
|
|
FillForwardFrontiers = new DateTime[_subscriptions];
|
|
|
|
//Class Privates:
|
|
_job = job;
|
|
_algorithm = algorithm;
|
|
_endOfStreams = false;
|
|
_bridgeMax = _bridgeMax / _subscriptions; //Set the bridge maximum count:
|
|
}
|
|
|
|
/********************************************************
|
|
* CLASS METHODS
|
|
*********************************************************/
|
|
/// <summary>
|
|
/// Initialize activators to invoke types in the algorithm
|
|
/// </summary>
|
|
private void ResetActivators()
|
|
{
|
|
for (var i = 0; i < _subscriptions; i++)
|
|
{
|
|
//Create a new instance in the dictionary:
|
|
Bridge[i] = new ConcurrentQueue<List<BaseData>>();
|
|
EndOfBridge[i] = false;
|
|
SubscriptionReaderManagers[i] = new SubscriptionDataReader(Subscriptions[i], _algorithm.Securities[Subscriptions[i].Symbol], DataFeed, _job.PeriodStart, _job.PeriodFinish);
|
|
FillForwardFrontiers[i] = new DateTime();
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Get the number of active streams still EndOfBridge array.
|
|
/// </summary>
|
|
/// <returns>Count of the number of streams with data</returns>
|
|
private int GetActiveStreams()
|
|
{
|
|
//Get the number of active streams:
|
|
var activeStreams = (from stream in EndOfBridge
|
|
where stream == false
|
|
select stream).Count();
|
|
return activeStreams;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Calculate the minimum increment to scan for data based on the data requested.
|
|
/// </summary>
|
|
/// <param name="includeTick">When true the subscriptions include a tick data source, meaning there is almost no increment.</param>
|
|
/// <returns>Timespan to jump the data source so it efficiently orders the results</returns>
|
|
private TimeSpan CalculateIncrement(bool includeTick)
|
|
{
|
|
var increment = TimeSpan.FromDays(1);
|
|
foreach (var config in Subscriptions)
|
|
{
|
|
switch (config.Resolution)
|
|
{
|
|
//Hourly TradeBars:
|
|
case Resolution.Hour:
|
|
if (increment > TimeSpan.FromHours(1))
|
|
{
|
|
increment = TimeSpan.FromHours(1);
|
|
}
|
|
break;
|
|
|
|
//Minutely TradeBars:
|
|
case Resolution.Minute:
|
|
if (increment > TimeSpan.FromMinutes(1))
|
|
{
|
|
increment = TimeSpan.FromMinutes(1);
|
|
}
|
|
break;
|
|
|
|
//Secondly Bars:
|
|
case Resolution.Second:
|
|
if (increment > TimeSpan.FromSeconds(1))
|
|
{
|
|
increment = TimeSpan.FromSeconds(1);
|
|
}
|
|
break;
|
|
|
|
//Ticks: No increment; just fire each data piece in as they happen.
|
|
case Resolution.Tick:
|
|
if (increment > TimeSpan.FromMilliseconds(1) && includeTick)
|
|
{
|
|
increment = new TimeSpan(0, 0, 0, 0, 1);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return increment;
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Main routine for datafeed analysis.
|
|
/// </summary>
|
|
/// <remarks>This is a hot-thread and should be kept extremely lean. Modify with caution.</remarks>
|
|
public void Run()
|
|
{
|
|
//Initialize Parameters:
|
|
long earlyBirdTicks = 0;
|
|
var subscriptions = SubscriptionReaderManagers.Length;
|
|
var frontier = new DateTime();
|
|
var tradeBarIncrements = TimeSpan.FromDays(1);
|
|
var increment = TimeSpan.FromDays(1);
|
|
var activeStreams = subscriptions;
|
|
|
|
//Initialize Activators:
|
|
ResetActivators();
|
|
|
|
//Calculate the increment based on the subscriptions:
|
|
tradeBarIncrements = CalculateIncrement(includeTick: false); //TradeBars get fillforward, larger increments.
|
|
increment = CalculateIncrement(includeTick: true); //Include ticks for small increment for frontier calcs.
|
|
|
|
//Loop over each date in the job
|
|
foreach (var date in Time.EachTradeableDay(_algorithm.Securities, _job.PeriodStart, _job.PeriodFinish))
|
|
{
|
|
//Update the source-URL from the BaseData, reset the frontier to today. Update the source URL once per day.
|
|
frontier = date.Add(increment); //Frontier is in the future and looks back to all the data produced so far.
|
|
activeStreams = subscriptions;
|
|
//Log.Debug("FileSystemDataFeed.Run(): Date Changed: " + date.ToShortDateString());
|
|
|
|
//Initialize the feeds to this date:
|
|
for (var i = 0; i < subscriptions; i++)
|
|
{
|
|
//Don't refresh source when we know the market is closed for this security:
|
|
//if (SubscriptionReaderManagers[i].MarketOpen(date)) { }
|
|
var success = SubscriptionReaderManagers[i].RefreshSource(date);
|
|
|
|
//If we know the market is closed for security then can declare bridge closed.
|
|
if (success) {
|
|
EndOfBridge[i] = false;
|
|
} else {
|
|
EndOfBridge[i] = true;
|
|
}
|
|
}
|
|
|
|
//Pause the DataFeed
|
|
var bridgeFullCount = 1;
|
|
var bridgeZeroCount = 0;
|
|
var active = GetActiveStreams();
|
|
//Log.Debug("FileSystemDataFeed.Run(): Active Streams: " + active);
|
|
|
|
//Pause here while bridges are full and the
|
|
while (bridgeFullCount > 0 && ((subscriptions - active) == bridgeZeroCount) && !_exitTriggered)
|
|
{
|
|
bridgeFullCount = (from bridge in Bridge where bridge.Count >= _bridgeMax select bridge).Count();
|
|
bridgeZeroCount = (from bridge in Bridge where bridge.Count == 0 select bridge).Count();
|
|
Thread.Sleep(5);
|
|
}
|
|
|
|
if (_exitTriggered) break;
|
|
|
|
while ((frontier.Date == date.Date || frontier == date.Date.AddDays(1)) && !_exitTriggered)
|
|
{
|
|
var cache = new List<BaseData>[subscriptions];
|
|
try
|
|
{
|
|
//Reset Loop:
|
|
earlyBirdTicks = 0;
|
|
|
|
//Go over all the subscriptions, one by one add a minute of data to the bridge.
|
|
for (var i = 0; i < subscriptions; i++)
|
|
{
|
|
//Get the reader manager:
|
|
var manager = SubscriptionReaderManagers[i];
|
|
|
|
//End of the manager stream set flag to end bridge: also if the EOB flag set, from the refresh source method above
|
|
if (manager.EndOfStream || EndOfBridge[i])
|
|
{
|
|
EndOfBridge[i] = true;
|
|
activeStreams = GetActiveStreams();
|
|
if (activeStreams == 0)
|
|
{
|
|
frontier = frontier.Date + TimeSpan.FromDays(1);
|
|
//if (frontier.ToString("yyyy-MMM-dd") == "2013-May-03") System.Diagnostics.Debugger.Break();
|
|
//Log.Debug("FileSystemDataFeed.Run(): No Active Streams; moving to next day.");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
//Initialize data store:
|
|
cache[i] = new List<BaseData>();
|
|
|
|
//Add the last iteration to the new list: only if it falls into this time category
|
|
//Log.Debug("FileSystemDataFeed.Run(): Entering cache builder: current: " + manager.Current.Time.ToLongTimeString());
|
|
while (manager.Current.Time < frontier) //.RoundUp(increment)
|
|
{
|
|
cache[i].Add(manager.Current);
|
|
//Log.Debug("FileSystemDataFeed.Run(): Adding feed.Current to cache: frontier: " + frontier.ToLongTimeString());
|
|
if (!manager.MoveNext()) break;
|
|
}
|
|
|
|
//Save the next earliest time from the bridges: only if we're not filling forward.
|
|
if (manager.Current != null)
|
|
{
|
|
if (earlyBirdTicks == 0 || manager.Current.Time.Ticks < earlyBirdTicks)
|
|
{
|
|
earlyBirdTicks = manager.Current.Time.Ticks;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (activeStreams == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
|
|
//Now lock the bridge, add all the lists to the bridge, release the bridge:
|
|
lock (Bridge)
|
|
{
|
|
for (var i = 0; i < subscriptions; i++)
|
|
{
|
|
if (cache[i] != null && cache[i].Count > 0)
|
|
{
|
|
FillForwardFrontiers[i] = cache[i][0].Time;
|
|
Bridge[i].Enqueue(cache[i]);
|
|
}
|
|
ProcessFillForward(SubscriptionReaderManagers[i], i, tradeBarIncrements);
|
|
}
|
|
}
|
|
|
|
if (earlyBirdTicks > 0 && earlyBirdTicks > frontier.Ticks) {
|
|
//Jump increment to the nearest second, in the future: Round down, add increment
|
|
frontier = (new DateTime(earlyBirdTicks)).RoundDown(increment) + increment;
|
|
}
|
|
else
|
|
{
|
|
//Otherwise step one forward.
|
|
frontier += increment;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
// End Using Cache: Fix memory leak
|
|
cache = null;
|
|
}
|
|
} // End of This Day.
|
|
|
|
if (_exitTriggered) break;
|
|
|
|
} // End of All Days:
|
|
|
|
Log.Trace(DataFeed + ".Run(): Data Feed Completed.");
|
|
|
|
//Make sure all bridges empty before declaring "end of bridge":
|
|
while (!EndOfBridges && !_exitTriggered)
|
|
{
|
|
for (var i = 0; i < subscriptions; i++)
|
|
{
|
|
if (Bridge[i].Count == 0 && SubscriptionReaderManagers[i].EndOfStream)
|
|
{
|
|
EndOfBridge[i] = true;
|
|
}
|
|
//Log.Trace("FileSystemDataFeed.Run(): Bridge: " + i + " Count: " + Bridge[i].Count + " EOS: " + SubscriptionReaderManagers[i].EndOfStream.ToString() + " EOB:" + EndOfBridge[i].ToString());
|
|
}
|
|
if (GetActiveStreams() == 0) _endOfStreams = true;
|
|
Thread.Sleep(100);
|
|
}
|
|
|
|
//Close up all streams:
|
|
for (var i = 0; i < Subscriptions.Count; i++)
|
|
{
|
|
SubscriptionReaderManagers[i].Dispose();
|
|
}
|
|
|
|
Log.Trace(DataFeed + ".Run(): Ending Thread... ");
|
|
IsActive = false;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// If this is a fillforward subscription, look at the previous time, and current time, and add new
|
|
/// objects to queue until current time to fill up the gaps.
|
|
/// </summary>
|
|
/// <param name="manager">Subscription to process</param>
|
|
/// <param name="i">Subscription position in the bridge ( which queue are we pushing data to )</param>
|
|
/// <param name="increment">Timespan increment to jump the fillforward results</param>
|
|
void ProcessFillForward(SubscriptionDataReader manager, int i, TimeSpan increment)
|
|
{
|
|
//Fillforward only applies to QC TradeBars:
|
|
// If previous == null cannot fill forward nothing there to move forward (e.g. cases where file not found on first file).
|
|
if (!manager.IsQCTradeBar || !Subscriptions[i].FillDataForward || manager.Previous == null) return;
|
|
|
|
//Last tradebar and the current one we're about to add to queue:
|
|
var previous = manager.Previous;
|
|
var current = manager.Current;
|
|
|
|
//Initialize the frontier:
|
|
if (FillForwardFrontiers[i].Ticks == 0) FillForwardFrontiers[i] = previous.Time;
|
|
|
|
//Data ended before the market closed: premature ending flag - continue filling forward until market close.
|
|
if (manager.EndOfStream && manager.MarketOpen(current.Time))
|
|
{
|
|
//Premature end of stream: fill manually until market closed.
|
|
for (var date = FillForwardFrontiers[i] + increment; manager.MarketOpen(date); date = date + increment)
|
|
{
|
|
var cache = new List<BaseData>(1);
|
|
var fillforward = current.Clone();
|
|
fillforward.Time = date;
|
|
FillForwardFrontiers[i] = date;
|
|
cache.Add(fillforward);
|
|
Bridge[i].Enqueue(cache);
|
|
}
|
|
return;
|
|
}
|
|
|
|
//Once per increment, add a new cache to the Bridge:
|
|
//If the current.Time is before market close, (e.g. suspended trading at 2pm) the date is always greater than currentTime and fillforward never runs.
|
|
//In this circumstance we need to keep looping till market/extended hours close even if no data.
|
|
for (var date = FillForwardFrontiers[i] + increment; (date < current.Time); date = date + increment)
|
|
{
|
|
//If we don't want aftermarket data, rewind it backwards until the market closes.
|
|
if (!Subscriptions[i].ExtendedMarketHours)
|
|
{
|
|
if (!manager.MarketOpen(date))
|
|
{
|
|
// Move fill forward so we don't waste time in this tight loop.
|
|
//Question is where to shuffle the date?
|
|
// --> If BEFORE market open, shuffle forward.
|
|
// --> If AFTER market close, and current.Time after market close, quit loop.
|
|
date = current.Time;
|
|
do
|
|
{
|
|
date = date - increment;
|
|
} while (manager.MarketOpen(date));
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//If we've asked for extended hours, and the security is no longer inside extended market hours, skip:
|
|
if (!manager.ExtendedMarketOpen(date))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
var cache = new List<BaseData>(1);
|
|
var fillforward = previous.Clone();
|
|
fillforward.Time = date;
|
|
FillForwardFrontiers[i] = date;
|
|
cache.Add(fillforward);
|
|
Bridge[i].Enqueue(cache);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Send an exit signal to the thread.
|
|
/// </summary>
|
|
public void Exit()
|
|
{
|
|
_exitTriggered = true;
|
|
PurgeData();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Loop over all the queues and clear them to fast-quit this thread and return to main.
|
|
/// </summary>
|
|
public void PurgeData()
|
|
{
|
|
foreach (var t in Bridge)
|
|
{
|
|
t.Clear();
|
|
}
|
|
}
|
|
} // End FileSystem Local Feed Class:
|
|
} // End Namespace
|