/*
* 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.Linq;
using QuantConnect.Logging;
namespace QuantConnect.Scheduling
{
///
/// Real time self scheduling event
///
public class ScheduledEvent : IDisposable
{
///
/// Gets the default time before market close end of trading day events will fire
///
public static readonly TimeSpan SecurityEndOfDayDelta = TimeSpan.FromMinutes(10);
///
/// Gets the default time before midnight end of day events will fire
///
public static readonly TimeSpan AlgorithmEndOfDayDelta = TimeSpan.FromMinutes(2);
private bool _needsMoveNext;
private bool _endOfScheduledEvents;
private readonly Action _callback;
private readonly IEnumerator _orderedEventUtcTimes;
///
/// Event that fires each time this scheduled event happens
///
public event Action EventFired;
///
/// Gets or sets whether this event is enabled
///
public bool Enabled
{
get; set;
}
///
/// Gets or sets whether this event will log each time it fires
///
internal bool IsLoggingEnabled
{
get; set;
}
///
/// Gets the next time this scheduled event will fire in UTC
///
public DateTime NextEventUtcTime
{
get
{
if (_endOfScheduledEvents)
{
return DateTime.MaxValue;
}
if (_needsMoveNext)
{
_needsMoveNext = false;
_endOfScheduledEvents = !_orderedEventUtcTimes.MoveNext();
return NextEventUtcTime;
}
return _orderedEventUtcTimes.Current;
}
}
///
/// Gets an identifier for this event
///
public string Name { get; }
///
/// Initializes a new instance of the class
///
/// An identifier for this event
/// The date time the event should fire
/// Delegate to be called when the event time passes
public ScheduledEvent(string name, DateTime eventUtcTime, Action callback = null)
: this(name, new[] { eventUtcTime }.AsEnumerable().GetEnumerator(), callback)
{
}
///
/// Initializes a new instance of the class
///
/// An identifier for this event
/// An enumerable that emits event times
/// Delegate to be called each time an event passes
public ScheduledEvent(string name, IEnumerable orderedEventUtcTimes, Action callback = null)
: this(name, orderedEventUtcTimes.GetEnumerator(), callback)
{
}
///
/// Initializes a new instance of the class
///
/// An identifier for this event
/// An enumerator that emits event times
/// Delegate to be called each time an event passes
public ScheduledEvent(string name, IEnumerator orderedEventUtcTimes, Action callback = null)
{
Name = name;
Enabled = true;
_callback = callback;
// we don't move next until we are requested, this allows the algorithm to support the warmup period correctly
_needsMoveNext = true;
_orderedEventUtcTimes = orderedEventUtcTimes;
}
/// Serves as the default hash function.
/// A hash code for the current object.
/// 2
public override int GetHashCode()
{
return Name.GetHashCode();
}
/// Determines whether the specified object is equal to the current object.
/// true if the specified object is equal to the current object; otherwise, false.
/// The object to compare with the current object.
/// 2
public override bool Equals(object obj)
{
return !ReferenceEquals(null, obj) && ReferenceEquals(this, obj);
}
///
/// Scans this event and fires the callback if an event happened
///
/// The current time in UTC
internal void Scan(DateTime utcTime)
{
if (_endOfScheduledEvents)
{
return;
}
do
{
if (_needsMoveNext)
{
// if we've passed an event or are just priming the pump, we need to move next
if (!_orderedEventUtcTimes.MoveNext())
{
if (IsLoggingEnabled)
{
Log.Trace($"ScheduledEvent.{Name}: Completed scheduled events.");
}
_endOfScheduledEvents = true;
return;
}
if (IsLoggingEnabled)
{
Log.Trace($"ScheduledEvent.{Name}: Next event: {_orderedEventUtcTimes.Current.ToStringInvariant(DateFormat.UI)} UTC");
}
}
// if time has passed our event
if (utcTime >= _orderedEventUtcTimes.Current)
{
if (IsLoggingEnabled)
{
Log.Trace($"ScheduledEvent.{Name}: Firing at {utcTime.ToStringInvariant(DateFormat.UI)} UTC " +
$"Scheduled at {_orderedEventUtcTimes.Current.ToStringInvariant(DateFormat.UI)} UTC"
);
}
// fire the event
OnEventFired(_orderedEventUtcTimes.Current);
_needsMoveNext = true;
}
else
{
// we haven't passed the event time yet, so keep waiting on this Current
_needsMoveNext = false;
}
}
// keep checking events until we pass the current time, this will fire
// all 'skipped' events back to back in order, perhaps this should be handled
// in the real time handler
while (_needsMoveNext);
}
///
/// Fast forwards this schedule to the specified time without invoking the events
///
/// Frontier time
internal void SkipEventsUntil(DateTime utcTime)
{
do
{
// zoom through the enumerator until we get to the desired time
if (utcTime <= NextEventUtcTime)
{
if (IsLoggingEnabled)
{
Log.Trace($"ScheduledEvent.{Name}: Skipped events before {utcTime.ToStringInvariant(DateFormat.UI)}. " +
$"Next event: {_orderedEventUtcTimes.Current.ToStringInvariant(DateFormat.UI)}"
);
}
return;
}
}
while (_orderedEventUtcTimes.MoveNext());
if (IsLoggingEnabled)
{
Log.Trace($"ScheduledEvent.{Name}: Exhausted event stream during skip until {utcTime.ToStringInvariant(DateFormat.UI)}");
}
_endOfScheduledEvents = true;
}
///
/// Will return the ScheduledEvents name
///
public override string ToString()
{
return $"{Name}";
}
///
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
///
/// 2
void IDisposable.Dispose()
{
_orderedEventUtcTimes.Dispose();
}
///
/// Event invocator for the event
///
/// The event's time in UTC
protected void OnEventFired(DateTime triggerTime)
{
// don't fire the event if we're turned off
if (!Enabled) return;
_callback?.Invoke(Name, _orderedEventUtcTimes.Current);
EventFired?.Invoke(Name, triggerTime);
}
}
}