namespace QuantConnect.Algorithm.CSharp
{
public class ConsolidatorTest : QCAlgorithm
{
private Symbol _symbol = QuantConnect.Symbol.Create("EURUSD", SecurityType.Forex, Market.Oanda);
// private Symbol _symbol = QuantConnect.Symbol.Create("EURUSD", SecurityType.Forex, Market.FXCM);
public override void Initialize()
{
SetStartDate(2019, 01, 01); //Set Start Date
SetEndDate(2019, 02, 03); //Set End Date
SetCash(100000); //Set Strategy Cash
AddForex(_symbol, Resolution.Minute);
var dailyCloseTime = new TimeSpan(17,0,0);
var nyCloseConsolidator = new TimeOfDayQuoteBarConsolidator(dailyCloseTime);
Log("dailyCloseTime = " + dailyCloseTime.ToString());
nyCloseConsolidator.DataConsolidated += OnNYClose;
SubscriptionManager.AddConsolidator(_symbol, nyCloseConsolidator);
}
public override void OnData(Slice data)
{
}
public void OnNYClose(object sender, QuoteBar bar)
{
Log("OnNYClose(): bar.Time = " + bar.Time.DayOfWeek.ToString() + " " + bar.Time.ToString()
+ ", OHLC = " + bar.Open.ToString("0.00000")
+ ", " + bar.High.ToString("0.00000")
+ ", " + bar.Low.ToString("0.00000")
+ ", " + bar.Close.ToString("0.00000"));
}
}
}
namespace QuantConnect
{
/// <summary>
/// Provides a base class for consolidators that emit data based on the passing
/// of a specified time of day (in the data time zone.
/// </summary>
/// <typeparam name="T">The input type of the consolidator</typeparam>
/// <typeparam name="TConsolidated">The output type of the consolidator</typeparam>
public abstract class TimeOfDayConsolidatorBase<T, TConsolidated> : DataConsolidator<T>
where T : IBaseData
where TConsolidated : BaseData
{
// The time of day to emit the consolidated bar.
private readonly TimeSpan _dailyOpenTime;
// The working bar used for aggregating the data.
private TConsolidated _workingBar;
// The last time we emitted a consolidated bar.
private DateTime _lastEmit;
/// <summary>
/// Creates a consolidator to produce a new <typeparamref name="TConsolidated"/> instance
/// representing a day starting at the specified time.
/// </summary>
/// <param name="dailyOpenTime">The time of day to emit a consolidated bar.</param>
protected TimeOfDayConsolidatorBase(TimeSpan dailyOpenTime)
{
_dailyOpenTime = dailyOpenTime;
_lastEmit = DateTime.MinValue;
}
/// <summary>
/// Gets the type produced by this consolidator.
/// </summary>
public override Type OutputType
{
get { return typeof(TConsolidated); }
}
/// <summary>
/// Gets a clone of the data being currently consolidated.
/// </summary>
public override IBaseData WorkingData
{
get { return _workingBar != null ? _workingBar.Clone() : null; }
}
/// <summary>
/// Event handler that fires when a new piece of data is produced. We define this as a 'new'
/// event so we can expose it as a <typeparamref name="TConsolidated"/> instead of a <see cref="BaseData"/> instance.
/// </summary>
public new event EventHandler<TConsolidated> DataConsolidated;
/// <summary>
/// Updates this consolidator with the specified data. This method is
/// responsible for raising the DataConsolidated event.
/// The bar range is closed on the left and open on the right: [dailyOpenTime, dailyOpenTime+1day).
/// For example, if time of day is 17:00, we have [17:00, next day 17:00): so
/// data at 17:00 next day is not included in the bar.
/// </summary>
/// <param name="data">The new data for the consolidator.</param>
public override void Update(T data)
{
if (!ShouldProcess(data))
{
// First allow the base class a chance to filter out data it doesn't want
// before we start incrementing counts and what not.
return;
}
//Fire the event
if (_workingBar != null && GetRoundedBarTime(data.Time) > _workingBar.Time)
{
// *** I don't understand why this is needed. Refer class PeriodCountConsolidatorBase<>.
var workingTradeBar = _workingBar as TradeBar;
if (workingTradeBar != null)
{
// we kind of are cheating here...
// if (_period.HasValue)
// {
// workingTradeBar.Period = _period.Value;
workingTradeBar.Period = new TimeSpan(1, 0, 0, 0);
// }
// since trade bar has period it aggregates this properly
// else if (!(data is TradeBar))
// {
// workingTradeBar.Period = data.Time - _lastEmit.Value;
// }
}
OnDataConsolidated(_workingBar);
_lastEmit = _workingBar != null ? _workingBar.Time.AddDays(1) : data.Time;
_workingBar = null;
}
if (data.Time >= _lastEmit)
{
AggregateBar(ref _workingBar, data);
}
}
/// <summary>
/// Scans this consolidator to see if it should emit a bar due to time passing.
/// </summary>
/// <param name="currentLocalTime">The current time in the local time zone (same as <see cref="BaseData.Time"/>).</param>
public override void Scan(DateTime currentLocalTime)
{
if (_workingBar != null)
{
currentLocalTime = GetRoundedBarTime(currentLocalTime);
if (currentLocalTime > _workingBar.Time)
{
OnDataConsolidated(_workingBar);
_lastEmit = currentLocalTime;
_workingBar = null;
}
}
}
/// <summary>
/// Determines whether or not the specified data should be processed.
/// </summary>
/// <param name="data">The data to check.</param>
/// <returns>True if the consolidator should process this data, false otherwise.</returns>
protected virtual bool ShouldProcess(T data)
{
return true;
}
/// <summary>
/// Aggregates the new 'data' into the 'workingBar'. The 'workingBar' will be
/// null following the event firing.
/// </summary>
/// <param name="workingBar">The bar we're building, null if the event was just fired and we're starting a new consolidated bar.</param>
/// <param name="data">The new data.</param>
protected abstract void AggregateBar(ref TConsolidated workingBar, T data);
/// <summary>
/// Gets a bar time rounded-down to the prior daily close time. Called by AggregateBar in derived classes.
/// </summary>
/// <param name="time">The bar time to be rounded down.</param>
/// <returns>The rounded bar time.</returns>
protected DateTime GetRoundedBarTime(DateTime time)
{
DateTime roundedBarTime = time.Date.Add(_dailyOpenTime);
return time.TimeOfDay >= _dailyOpenTime ? roundedBarTime : roundedBarTime.AddDays(-1);
}
/// <summary>
/// Event invocator for the <see cref="DataConsolidated"/> event.
/// </summary>
/// <param name="e">The consolidated data.</param>
protected virtual void OnDataConsolidated(TConsolidated e)
{
base.OnDataConsolidated(e);
var handler = DataConsolidated;
if (handler != null) handler(this, e);
}
}
}
namespace QuantConnect
{
/// <summary>
/// Consolidates quotebars into larger quotebars at the specified time of day (in the QuoteBar time zone).
/// </summary>
public class TimeOfDayQuoteBarConsolidator : TimeOfDayConsolidatorBase<QuoteBar, QuoteBar>
{
/// <summary>
/// Initializes a new instance of the <see cref="TickQuoteBarConsolidator"/> class.
/// </summary>
/// <param name="dailyOpenTime">The time of day to emit a consolidated bar.</param>
public TimeOfDayQuoteBarConsolidator(TimeSpan dailyOpenTime)
: base(dailyOpenTime)
{
}
/// <summary>
/// Aggregates the new 'data' into the 'workingBar'. The 'workingBar' will be
/// null following the event firing.
/// </summary>
/// <param name="workingBar">The bar we're building, null if the event was just fired and we're starting a new consolidated bar.</param>
/// <param name="data">The new data.</param>
protected override void AggregateBar(ref QuoteBar workingBar, QuoteBar data)
{
var bid = data.Bid;
var ask = data.Ask;
if (workingBar == null)
{
workingBar = new QuoteBar
{
Symbol = data.Symbol,
Time = GetRoundedBarTime(data.Time),
Bid = bid == null ? null : bid.Clone(),
Ask = ask == null ? null : ask.Clone(),
Period = data.Period
};
}
// update the bid and ask
if (bid != null)
{
workingBar.LastBidSize = data.LastBidSize;
if (workingBar.Bid == null)
{
workingBar.Bid = new Bar(bid.Open, bid.High, bid.Low, bid.Close);
}
else
{
workingBar.Bid.Close = bid.Close;
if (workingBar.Bid.High < bid.High) workingBar.Bid.High = bid.High;
if (workingBar.Bid.Low > bid.Low) workingBar.Bid.Low = bid.Low;
}
}
if (ask != null)
{
workingBar.LastAskSize = data.LastAskSize;
if (workingBar.Ask == null)
{
workingBar.Ask = new Bar(ask.Open, ask.High, ask.Low, ask.Close);
}
else
{
workingBar.Ask.Close = ask.Close;
if (workingBar.Ask.High < ask.High) workingBar.Ask.High = ask.High;
if (workingBar.Ask.Low > ask.Low) workingBar.Ask.Low = ask.Low;
}
}
workingBar.Value = data.Value;
workingBar.Period += data.Period;
}
}
}