Overall Statistics
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;
        }
    }
}