/*
* 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.Threading;
using System.Collections.Concurrent;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Logging;
namespace QuantConnect.Lean.Engine
{
/********************************************************
* CLASS DEFINITIONS
*********************************************************/
///
/// StreamStore manages the creation of data objects for live streams; including managing
/// a fillforward data stream request.
///
///
/// Streams data is pushed into update where it is appended to the data object to be consolidated. Once required time has lapsed for the bar the
/// data is piped into a queue.
///
public class StreamStore
{
/********************************************************
* CLASS PRIVATE VARIABLES
*********************************************************/
//Internal lock object
private BaseData _data = null;
private BaseData _previousData = null;
private Type _type;
private SubscriptionDataConfig _config = new SubscriptionDataConfig();
private TimeSpan _increment = new TimeSpan(0, 0, 1);
private object _lock = new Object();
private ConcurrentQueue _queue = new ConcurrentQueue();
/********************************************************
* CLASS PUBLIC PROPERTIES:
*********************************************************/
///
/// Public access to the data object we're dynamically generating.
///
public BaseData Data
{
get
{
return _data;
}
}
///
/// Timespan increment for this resolution data:
///
public TimeSpan Increment
{
get
{
return _increment;
}
}
///
/// Start time of the bar.
///
public DateTime StartTime
{
get
{
return DateTime.Now.RoundDown(_increment);
}
}
///
/// Queue for temporary storage for generated data.
///
public ConcurrentQueue Queue
{
get
{
return _queue;
}
set
{
_queue = value;
}
}
///
/// Symbol for the given stream.
///
public string Symbol
{
get
{
return _config.Symbol;
}
}
/********************************************************
* CLASS CONSTRUCTOR
*********************************************************/
///
/// Create a new self updating, thread safe data updater.
///
/// Configuration for subscription
public StreamStore(SubscriptionDataConfig config)
{
_type = config.Type;
_data = null;
_lock = new object();
_config = config;
_increment = config.Increment;
_queue = new ConcurrentQueue();
}
/********************************************************
* CLASS METHODS
*********************************************************/
///
/// For custom data streams just manually set the data, it doesn't need to be compiled over time into a bar.
///
/// New data
public void Update(BaseData data)
{
//If the second has ticked over, and we have data not processed yet, wait for it to be stored:
while (_data != null && _data.Time < StartTime)
{ Thread.Sleep(1); }
_data = data;
}
///
/// Trade causing an update to the current tradebar object.
///
/// Trade value
/// This Trade size
/// Asking price
/// Current bid price
/// We build bars from the trade data, or if its a tick stream just pass the trade through as a tick.
public void Update(decimal lastTrade, decimal lastTradeSize = 0, decimal bidPrice = 0, decimal askPrice = 0)
{
//If the second has ticked over, and we have data not processed yet, wait for it to be stored:
while(_data != null && _data.Time < StartTime)
{ Thread.Sleep(1); }
lock (_lock)
{
switch (_type.Name)
{
case "TradeBar":
if (_data == null)
{
_data = new TradeBar(StartTime, _config.Symbol, lastTrade, lastTrade, lastTrade, lastTrade, (long)lastTradeSize);
}
else
{
//Update the bar:
_data.Update(lastTrade, lastTradeSize, bidPrice, askPrice);
}
break;
//Each tick is a new data obj.
case "Tick":
var tick = new Tick(StartTime, _config.Symbol, lastTrade, bidPrice, askPrice);
_queue.Enqueue(tick);
break;
}
} // End of Lock
} // End of Update
///
/// A time period has lapsed, trigger a save/queue of the current value of data.
///
/// Data stream is a fillforward type
/// The data stream is a QCManaged stream
public void TriggerArchive(bool fillForward, bool isQCData)
{
lock (_lock)
{
try
{
Console.Write(".");
//When there's nothing to do:
if (_data == null && !fillForward)
{
Log.Debug("StreamStore.TriggerArchive(): No data to store, and not fill forward: " + Symbol);
}
if (_data != null && _data.Time < StartTime)
{
//Create clone and reset original
Log.Debug("StreamStore.TriggerArchive(): Enqueued new data: S:" + _data.Symbol + " V:" + _data.Value);
_previousData = _data.Clone();
_queue.Enqueue(_data.Clone());
_data = null;
}
else if (fillForward && _data == null && _previousData != null || (!isQCData && _data == null && _previousData != null))
{
//There was no other data in this timer period, and this is a fillforward subscription:
Log.Debug("StreamStore.TriggerArchive(): Fillforward, Previous Enqueued: S:" + _previousData.Symbol + " V:" + _previousData.Value);
var cloneForward = _previousData.Clone();
cloneForward.Time = StartTime.Subtract(_config.Increment);
_queue.Enqueue(cloneForward);
}
}
catch (Exception err)
{
Log.Error("StreamStore.TriggerAchive(fillforward): Failed to archive: " + err.Message);
}
}
}
} // End of Class
} // End of Namespace