Files
quantconnect--lean/Engine/DataStream.cs
T
snugs 3636124c5e Misc edits from code review
Removed unused BaseDataFeed and IQFeedDataFeed
Add catch for NullReferenceException in stream store
ConsoleSetupHandler now throws on live jobs, use BrokerageSetupHandler
Renamed/moved FixedSizedQueue to Util\FixedSizeQueue
2015-06-18 17:19:36 -04:00

247 lines
11 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 System;
using System.Collections.Generic;
using System.Threading;
using System.Linq;
using QuantConnect.Data;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Logging;
namespace QuantConnect.Lean.Engine
{
/// <summary>
/// Data stream class takes a datafeed hander and converts it into a synchronized enumerable data format for looping
/// in the primary algorithm thread.
/// </summary>
public class DataStream
{
private readonly IDataFeed _feed;
//Count of bridges and subscriptions.
private int _subscriptions;
private readonly bool _liveMode;
/// <summary>
/// The frontier time of the data stream
/// </summary>
public DateTime AlgorithmTime { get; private set; }
/// <summary>
/// Initializes a new <see cref="DataStream"/> for the specified data feed instance
/// </summary>
/// <param name="feed">The data feed to be streamed</param>
/// <param name="liveMode"></param>
public DataStream(IDataFeed feed, bool liveMode)
{
_feed = feed;
_liveMode = liveMode;
}
/// <summary>
/// Process over the datafeed cross thread bridges to generate an enumerable sorted collection of the data, ready for a consumer
/// to use and already synchronized in time.
/// </summary>
/// <param name="frontierOrigin">Starting date for the data feed</param>
/// <returns></returns>
public IEnumerable<Dictionary<int, List<BaseData>>> GetData(DateTime frontierOrigin)
{
//Initialize:
_subscriptions = _feed.Subscriptions.Count;
AlgorithmTime = frontierOrigin;
long algorithmTime = AlgorithmTime.Ticks;
var frontier = frontierOrigin;
var nextEmitTime = DateTime.MinValue;
var periods = _feed.Subscriptions.Select(x => x.Resolution.ToTimeSpan()).ToArray();
//Wait for datafeeds to be ready, wait for first data to arrive:
while (_feed.Bridge.Length != _subscriptions) Thread.Sleep(100);
// clear data first when in live mode, start with fresh data
if (_liveMode)
{
_feed.PurgeData();
}
//Get all data in queues: return as a sorted dictionary:
while (!_feed.EndOfBridges)
{
//Reset items which are not fill forward:
long earlyBirdTicks = 0;
var newData = new Dictionary<int, List<BaseData>>();
// spin wait until the feed catches up to our frontier
WaitForDataOrEndOfBridges(frontier);
for (var i = 0; i < _subscriptions; i++)
{
//If there's data on the bridge, check to see if it's time to pull it off, if it's in the future
// we'll record the time as 'earlyBirdTicks' so we can fast forward the frontier time
while (!_feed.Bridge[i].IsEmpty)
{
//Look at first item on list, leave it there until time passes this item.
List<BaseData> result;
if (!_feed.Bridge[i].TryPeek(out result))
{
// if there's no item skip to the next subscription
break;
}
DateTime endTime = result[0].EndTime;
if (result.Count > 0 && endTime > frontier)
{
// we have at least one item, check to see if its in ahead of the frontier,
// if so, keep track of how many ticks in the future it is
if (earlyBirdTicks == 0 || earlyBirdTicks > endTime.Ticks)
{
earlyBirdTicks = endTime.Ticks;
}
break;
}
if (result.Count > 0)
{
// we have at least one item, check to see if its in ahead of the frontier,
// if so, keep track of how many ticks in the future it is
if (earlyBirdTicks == 0 || earlyBirdTicks > endTime.Ticks)
{
earlyBirdTicks = endTime.Ticks;
}
}
//Pull a grouped time list out of the bridge
List<BaseData> dataPoints;
if (_feed.Bridge[i].TryDequeue(out dataPoints))
{
// round the time down based on the requested resolution for fill forward data
// this is a temporary fix, long term fill forward logic should be moved into this class
foreach (var point in dataPoints)
{
if (algorithmTime < point.EndTime.Ticks)
{
// set this to most advanced end point in time, pre rounding
// min 10:02 10:02:01(FF) 10:02:01.01(FF)
// sec 10:02 10:02:01 10:02:01.01(FF)
// tic 10:02 10:02:01 10:02:01.01
// the algorithm time should always be the 'frontier' the furthest
// time within this data slice
algorithmTime = point.EndTime.Ticks;
}
if (point.IsFillForward)
{
point.Time = point.Time.RoundDown(periods[i]);
}
}
// add the list to the collection to be yielded
List<BaseData> dp;
if (!newData.TryGetValue(i, out dp))
{
dp = new List<BaseData>();
newData[i] = dp;
}
dp.AddRange(dataPoints);
}
else
{
//Should never fail:
Log.Error("DataStream.GetData(): Failed to dequeue bridge item");
}
}
}
if (newData.Count > 0)
{
AlgorithmTime = new DateTime(algorithmTime);
yield return newData;
}
//Update the frontier and start again.
if (earlyBirdTicks > 0)
{
//Seek forward in time to next data event from stream: there's nothing here for us to do now: why loop over empty seconds
frontier = new DateTime(earlyBirdTicks);
}
else if (_feed.EndOfBridges)
{
// we're out of data or quit
break;
}
//Allow loop pass through emits every second to allow event handling (liquidate/stop/ect...)
if (_liveMode && DateTime.Now > nextEmitTime)
{
AlgorithmTime = DateTime.Now.RoundDown(periods.Min());
nextEmitTime = DateTime.Now + TimeSpan.FromSeconds(1);
yield return new Dictionary<int, List<BaseData>>();
}
}
Log.Trace("DataStream.GetData(): All Streams Completed.");
}
/// <summary>
/// Waits until the data feed is ready for the data stream to pull data from it.
/// </summary>
/// <param name="dataStreamFrontier">The frontier of the data stream</param>
private void WaitForDataOrEndOfBridges(DateTime dataStreamFrontier)
{
//Make sure all bridges have data to to peek sync properly.
// timeout to prevent infinite looping here -- 50ms for live and 30sec for non-live
var timeout = DateTime.UtcNow + (_liveMode ? new TimeSpan(0, 0, 0, 0, 50) : new TimeSpan(0, 0, 30));
if (_liveMode)
{
// give some time to the other threads in live mode
Thread.Sleep(1);
}
//Waiting for data in the bridges:
while (!AllBridgesHaveData() && DateTime.UtcNow < timeout)
{
Thread.Sleep(1);
}
//we want to verify that our data stream is never ahead of our data feed.
//this acts as a virtual lock around the bridge so we can wait for the feed
//to be ahead of us
// if we're out of data then the feed will never update (it will stay here forever if there's no more data, so use a timeout!!)
while (dataStreamFrontier > _feed.LoadedDataFrontier && (!_feed.EndOfBridges && !_feed.LoadingComplete) && DateTime.UtcNow < timeout)
{
Thread.Sleep(1);
}
}
/// <summary>
/// Check if all the bridges have data or are dead before starting the analysis
///
/// This determines whether or not the data stream can pull data from the data feed.
/// </summary>
/// <returns>Boolean true more data to download</returns>
private bool AllBridgesHaveData()
{
//Lock on the bridge to scan if it has data:
for (var i = 0; i < _subscriptions; i++)
{
if (_feed.EndOfBridge[i]) continue;
if (_feed.Bridge[i].IsEmpty)
{
return false;
}
}
return true;
}
}
}