/*
* 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 NodaTime;
using System.Linq;
using QuantConnect.Securities;
using System.Collections.Generic;
using static QuantConnect.StringExtensions;
namespace QuantConnect.Scheduling
{
///
/// Helper class used to provide better syntax when defining time rules
///
public class TimeRules : BaseScheduleRules
{
///
/// Initializes a new instance of the helper class
///
/// The security manager
/// The algorithm's default time zone
/// The market hours database instance to use
public TimeRules(SecurityManager securities, DateTimeZone timeZone, MarketHoursDatabase marketHoursDatabase)
: base(securities, timeZone, marketHoursDatabase)
{
}
///
/// Sets the default time zone
///
/// The time zone to use for helper methods that can't resolve a time zone
public void SetDefaultTimeZone(DateTimeZone timeZone)
{
TimeZone = timeZone;
}
///
/// Specifies an event should fire at the current time
///
public ITimeRule Now => new FuncTimeRule("Now", dates => {
return dates.Select(date =>
{
// we ignore the given date and just use the current time, why? if the algorithm used 'DateRules.Today'
// we get the algorithms first 'Date', which during warmup might not be a complete date, depending on the warmup period
// and since Today returns dates we might get a time in the past which get's ignored. See 'WarmupTrainRegressionAlgorithm'
// which reproduces GH issue #6410
return Securities.UtcTime;
});
});
///
/// Convenience property for running a scheduled event at midnight in the algorithm time zone
///
public ITimeRule Midnight => new FuncTimeRule("Midnight", dates => dates.Select(date => date.ConvertToUtc(TimeZone)));
///
/// Convenience property for running a scheduled event at noon in the algorithm time zone
///
public ITimeRule Noon => new FuncTimeRule("Noon", dates => dates.Select(date => date.ConvertToUtc(TimeZone).AddHours(12)));
///
/// Specifies an event should fire at the specified time of day in the algorithm's time zone
///
/// The time of day in the algorithm's time zone the event should fire
/// A time rule that fires at the specified time in the algorithm's time zone
public ITimeRule At(TimeSpan timeOfDay)
{
return At(timeOfDay, TimeZone);
}
///
/// Specifies an event should fire at the specified time of day in the algorithm's time zone
///
/// The hour
/// The minute
/// The second
/// A time rule that fires at the specified time in the algorithm's time zone
public ITimeRule At(int hour, int minute, int second = 0)
{
return At(new TimeSpan(hour, minute, second), TimeZone);
}
///
/// Specifies an event should fire at the specified time of day in the specified time zone
///
/// The hour
/// The minute
/// The time zone the event time is represented in
/// A time rule that fires at the specified time in the algorithm's time zone
public ITimeRule At(int hour, int minute, DateTimeZone timeZone)
{
return At(new TimeSpan(hour, minute, 0), timeZone);
}
///
/// Specifies an event should fire at the specified time of day in the specified time zone
///
/// The hour
/// The minute
/// The second
/// The time zone the event time is represented in
/// A time rule that fires at the specified time in the algorithm's time zone
public ITimeRule At(int hour, int minute, int second, DateTimeZone timeZone)
{
return At(new TimeSpan(hour, minute, second), timeZone);
}
///
/// Specifies an event should fire at the specified time of day in the specified time zone
///
/// The time of day in the algorithm's time zone the event should fire
/// The time zone the date time is expressed in
/// A time rule that fires at the specified time in the algorithm's time zone
public ITimeRule At(TimeSpan timeOfDay, DateTimeZone timeZone)
{
var name = string.Join(",", timeOfDay.TotalHours.ToStringInvariant("0.##"));
Func, IEnumerable> applicator = dates =>
from date in dates
let localEventTime = date + timeOfDay
let utcEventTime = localEventTime.ConvertToUtc(timeZone)
select utcEventTime;
return new FuncTimeRule(name, applicator);
}
///
/// Specifies an event should fire periodically on the requested interval
///
/// The frequency with which the event should fire, can not be zero or less
/// A time rule that fires after each interval passes
public ITimeRule Every(TimeSpan interval)
{
if (interval <= TimeSpan.Zero)
{
throw new ArgumentException("TimeRules.Every(): time span interval can not be zero or less");
}
var name = Invariant($"Every {interval.TotalMinutes:0.##} min");
Func, IEnumerable> applicator = dates => EveryIntervalIterator(dates, interval, TimeZone);
return new FuncTimeRule(name, applicator);
}
///
/// Specifies an event should fire at market open +-
///
/// The symbol whose market open we want an event for
/// The minutes before market open that the event should fire
/// True to use extended market open, false to use regular market open
/// A time rule that fires the specified number of minutes before the symbol's market open
public ITimeRule BeforeMarketOpen(Symbol symbol, double minutesBeforeOpen = 0, bool extendedMarketOpen = false)
{
return AfterMarketOpen(symbol, minutesBeforeOpen * (-1), extendedMarketOpen);
}
///
/// Specifies an event should fire at market open +-
///
/// The symbol whose market open we want an event for
/// The minutes after market open that the event should fire
/// True to use extended market open, false to use regular market open
/// A time rule that fires the specified number of minutes after the symbol's market open
public ITimeRule AfterMarketOpen(Symbol symbol, double minutesAfterOpen = 0, bool extendedMarketOpen = false)
{
var type = extendedMarketOpen ? "ExtendedMarketOpen" : "MarketOpen";
var afterOrBefore = minutesAfterOpen > 0 ? "after" : "before";
var name = Invariant($"{symbol}: {Math.Abs(minutesAfterOpen):0.##} min {afterOrBefore} {type}");
var exchangeHours = GetSecurityExchangeHours(symbol);
var timeAfterOpen = TimeSpan.FromMinutes(minutesAfterOpen);
Func, IEnumerable> applicator = dates =>
from date in dates
let marketOpen = exchangeHours.GetFirstDailyMarketOpen((date + Time.OneDay).AddTicks(-1), extendedMarketOpen)
// make sure the market open is of this date
where exchangeHours.IsDateOpen(date, extendedMarketOpen) && marketOpen.Date == date.Date
let localEventTime = marketOpen + timeAfterOpen
let utcEventTime = localEventTime.ConvertToUtc(exchangeHours.TimeZone)
select utcEventTime;
return new FuncTimeRule(name, applicator);
}
///
/// Specifies an event should fire at the market close +-
///
/// The symbol whose market close we want an event for
/// The time after market close that the event should fire
/// True to use extended market close, false to use regular market close
/// A time rule that fires the specified number of minutes after the symbol's market close
public ITimeRule AfterMarketClose(Symbol symbol, double minutesAfterClose = 0, bool extendedMarketClose = false)
{
return BeforeMarketClose(symbol, minutesAfterClose * (-1), extendedMarketClose);
}
///
/// Specifies an event should fire at the market close +-
///
/// The symbol whose market close we want an event for
/// The time before market close that the event should fire
/// True to use extended market close, false to use regular market close
/// A time rule that fires the specified number of minutes before the symbol's market close
public ITimeRule BeforeMarketClose(Symbol symbol, double minutesBeforeClose = 0, bool extendedMarketClose = false)
{
var type = extendedMarketClose ? "ExtendedMarketClose" : "MarketClose";
var afterOrBefore = minutesBeforeClose > 0 ? "before" : "after";
var name = Invariant($"{symbol}: {Math.Abs(minutesBeforeClose):0.##} min {afterOrBefore} {type}");
var exchangeHours = GetSecurityExchangeHours(symbol);
var timeBeforeClose = TimeSpan.FromMinutes(minutesBeforeClose);
Func, IEnumerable> applicator = dates =>
from date in dates
let marketClose = exchangeHours.GetLastDailyMarketClose(date, extendedMarketClose)
// make sure the market open is of this date
where exchangeHours.IsDateOpen(date, extendedMarketClose)
let localEventTime = marketClose - timeBeforeClose
let utcEventTime = localEventTime.ConvertToUtc(exchangeHours.TimeZone)
select utcEventTime;
return new FuncTimeRule(name, applicator);
}
///
/// For each provided date will yield all the time intervals based on the supplied time span
///
/// The dates for which we want to create the different intervals
/// The interval value to use, can not be zero or less
/// The time zone the date time is expressed in
private static IEnumerable EveryIntervalIterator(IEnumerable dates, TimeSpan interval, DateTimeZone timeZone)
{
if (interval <= TimeSpan.Zero)
{
throw new ArgumentException("TimeRules.EveryIntervalIterator(): time span interval can not be zero or less");
}
foreach (var date in dates)
{
for (var time = TimeSpan.Zero; time < Time.OneDay; time += interval)
{
yield return (date + time).ConvertToUtc(timeZone);
}
}
}
}
}