/* * 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); } } }