/*
* 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 NodaTime;
using Python.Runtime;
using QuantConnect.Data.Market;
using QuantConnect.Scheduling;
using QuantConnect.Securities;
using System;
using System.Collections.Generic;
namespace QuantConnect.Data.UniverseSelection
{
///
/// Defines a user that is fired based on a specified and
///
public class ScheduledUniverse : Universe, ITimeTriggeredUniverse
{
private readonly IDateRule _dateRule;
private readonly ITimeRule _timeRule;
private readonly Func> _selector;
///
/// Initializes a new instance of the class
///
/// The time zone the date/time rules are in
/// Date rule defines what days the universe selection function will be invoked
/// Time rule defines what times on each day selected by date rule the universe selection function will be invoked
/// Selector function accepting the date time firing time and returning the universe selected symbols
/// Universe settings for subscriptions added via this universe, null will default to algorithm's universe settings
public ScheduledUniverse(DateTimeZone timeZone, IDateRule dateRule, ITimeRule timeRule, Func> selector, UniverseSettings settings = null)
: base(CreateConfiguration(timeZone, dateRule, timeRule))
{
_dateRule = dateRule;
_timeRule = timeRule;
_selector = selector;
UniverseSettings = settings;
}
///
/// Initializes a new instance of the class
///
/// Date rule defines what days the universe selection function will be invoked
/// Time rule defines what times on each day selected by date rule the universe selection function will be invoked
/// Selector function accepting the date time firing time and returning the universe selected symbols
/// Universe settings for subscriptions added via this universe, null will default to algorithm's universe settings
public ScheduledUniverse(IDateRule dateRule, ITimeRule timeRule, Func> selector, UniverseSettings settings = null)
: this(TimeZones.Utc, dateRule, timeRule, selector, settings)
{
}
///
/// Initializes a new instance of the class
///
/// The time zone the date/time rules are in
/// Date rule defines what days the universe selection function will be invoked
/// Time rule defines what times on each day selected by date rule the universe selection function will be invoked
/// Selector function accepting the date time firing time and returning the universe selected symbols
/// Universe settings for subscriptions added via this universe, null will default to algorithm's universe settings
public ScheduledUniverse(DateTimeZone timeZone, IDateRule dateRule, ITimeRule timeRule, PyObject selector, UniverseSettings settings = null)
: base(CreateConfiguration(timeZone, dateRule, timeRule))
{
Func func;
selector.TryConvertToDelegate(out func);
_dateRule = dateRule;
_timeRule = timeRule;
_selector = func.ConvertSelectionSymbolDelegate();
UniverseSettings = settings;
}
///
/// Initializes a new instance of the class
///
/// Date rule defines what days the universe selection function will be invoked
/// Time rule defines what times on each day selected by date rule the universe selection function will be invoked
/// Selector function accepting the date time firing time and returning the universe selected symbols
/// Universe settings for subscriptions added via this universe, null will default to algorithm's universe settings
public ScheduledUniverse(IDateRule dateRule, ITimeRule timeRule, PyObject selector, UniverseSettings settings = null)
: this(TimeZones.Utc, dateRule, timeRule, selector, settings)
{
}
///
/// Performs universe selection using the data specified
///
/// The current utc time
/// The symbols to remain in the universe
/// The data that passes the filter
public override IEnumerable SelectSymbols(DateTime utcTime, BaseDataCollection data)
{
return _selector(DateTime.SpecifyKind(utcTime, DateTimeKind.Unspecified));
}
///
/// Get an enumerator of UTC DateTimes that defines when this universe will be invoked
///
/// The start time of the range in UTC
/// The end time of the range in UTC
/// An enumerator of UTC DateTimes that defines when this universe will be invoked
public IEnumerable GetTriggerTimes(DateTime startTimeUtc, DateTime endTimeUtc, MarketHoursDatabase marketHoursDatabase)
{
var startTimeLocal = startTimeUtc.ConvertFromUtc(Configuration.ExchangeTimeZone);
var endTimeLocal = endTimeUtc.ConvertFromUtc(Configuration.ExchangeTimeZone);
// define date/time rule enumerable
var dates = _dateRule.GetDates(startTimeLocal, endTimeLocal);
var times = _timeRule.CreateUtcEventTimes(dates).GetEnumerator();
// Make sure and filter out any times before our start time
// GH #5440
do
{
if (!times.MoveNext())
{
times.Dispose();
yield break;
}
}
while (times.Current < startTimeUtc);
// Start yielding times
do
{
yield return times.Current;
}
while (times.MoveNext());
times.Dispose();
}
private static SubscriptionDataConfig CreateConfiguration(DateTimeZone timeZone, IDateRule dateRule, ITimeRule timeRule)
{
// remove forbidden characters
var ticker = $"{dateRule.Name}_{timeRule.Name}";
foreach (var c in SecurityIdentifier.InvalidSymbolCharacters)
{
ticker = ticker.Replace(c.ToStringInvariant(), "_");
}
var symbol = Symbol.Create(ticker, SecurityType.Base, QuantConnect.Market.USA);
var config = new SubscriptionDataConfig(typeof(Tick),
symbol: symbol,
resolution: Resolution.Daily,
dataTimeZone: timeZone,
exchangeTimeZone: timeZone,
fillForward: false,
extendedHours: false,
isInternalFeed: true,
isCustom: false,
tickType: null,
isFilteredSubscription: false
);
// force always open hours so we don't inadvertently mess with the scheduled firing times
MarketHoursDatabase.FromDataFolder()
.SetEntryAlwaysOpen(config.Market, config.Symbol.Value, config.SecurityType, config.ExchangeTimeZone);
return config;
}
}
}