Skip to content

Commit 6b6ca54

Browse files
committed
Move the backup universe file fallback logic into the live custom data enumerator factory
1 parent 2c75daf commit 6b6ca54

3 files changed

Lines changed: 148 additions & 80 deletions

File tree

Engine/DataFeeds/Enumerators/Factories/LiveCustomDataSubscriptionEnumeratorFactory.cs

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818
using System.Collections.Generic;
1919
using System.Linq;
2020
using Python.Runtime;
21+
using QuantConnect.Configuration;
2122
using QuantConnect.Data;
2223
using QuantConnect.Data.UniverseSelection;
2324
using QuantConnect.Interfaces;
25+
using QuantConnect.Logging;
2426
using QuantConnect.Util;
2527

2628
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators.Factories
@@ -30,10 +32,15 @@ namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators.Factories
3032
/// </summary>
3133
public class LiveCustomDataSubscriptionEnumeratorFactory : ISubscriptionEnumeratorFactory
3234
{
35+
// when the expected universe file is not available yet, we fall back to the backup universe file ("*.backup"),
36+
// if any, as a last resort, when the market is open or within this time span before the next market open
37+
private static readonly TimeSpan UniverseFileBackupFallbackWindow =
38+
TimeSpan.FromMinutes(Config.GetInt("universe-file-backup-fallback-minutes", 30));
39+
3340
private readonly TimeSpan _minimumIntervalCheck;
3441
private readonly ITimeProvider _timeProvider;
3542
private readonly Func<DateTime, DateTime> _dateAdjustment;
36-
private readonly Func<SubscriptionDataSource, DateTime, SubscriptionDataSource> _sourceAdjustment;
43+
private readonly bool _fallBackToBackupUniverseFiles;
3744
private readonly IObjectStore _objectStore;
3845

3946
/// <summary>
@@ -43,17 +50,17 @@ public class LiveCustomDataSubscriptionEnumeratorFactory : ISubscriptionEnumerat
4350
/// <param name="objectStore">The object store to use</param>
4451
/// <param name="dateAdjustment">Func that allows adjusting the datetime to use</param>
4552
/// <param name="minimumIntervalCheck">Allows specifying the minimum interval between each enumerator refresh and data check, default is 30 minutes</param>
46-
/// <param name="sourceAdjustment">Optional func that allows adjusting the data source to read from, given the source and the current utc time,
47-
/// e.g. to fall back to an alternative source when the expected one is not available.
48-
/// It is evaluated at the same cadence as the enumerator refreshes</param>
53+
/// <param name="fallBackToBackupUniverseFiles">Whether to fall back to the backup universe file ("*.backup"), if any, as a last resort
54+
/// when the expected universe file is not available and the market is open or close to opening.
55+
/// Only meaningful for universe subscriptions backed by local files</param>
4956
public LiveCustomDataSubscriptionEnumeratorFactory(ITimeProvider timeProvider, IObjectStore objectStore,
5057
Func<DateTime, DateTime> dateAdjustment = null, TimeSpan? minimumIntervalCheck = null,
51-
Func<SubscriptionDataSource, DateTime, SubscriptionDataSource> sourceAdjustment = null)
58+
bool fallBackToBackupUniverseFiles = false)
5259
{
5360
_timeProvider = timeProvider;
5461
_dateAdjustment = dateAdjustment;
5562
_minimumIntervalCheck = minimumIntervalCheck ?? TimeSpan.FromMinutes(30);
56-
_sourceAdjustment = sourceAdjustment;
63+
_fallBackToBackupUniverseFiles = fallBackToBackupUniverseFiles;
5764
_objectStore = objectStore;
5865
}
5966

@@ -72,6 +79,7 @@ public IEnumerator<BaseData> CreateEnumerator(SubscriptionRequest request, IData
7279
var frontier = Ref.Create(_dateAdjustment?.Invoke(request.StartTimeLocal) ?? request.StartTimeLocal);
7380
var lastSourceRefreshTime = DateTime.MinValue;
7481
var sourceFactory = config.GetBaseDataInstance();
82+
var sourceAdjustment = _fallBackToBackupUniverseFiles ? GetUniverseFileBackupSourceAdjustment(request, dataProvider) : null;
7583

7684
// this is refreshing the enumerator stack for each new source
7785
var refresher = new RefreshEnumerator<BaseData>(() =>
@@ -87,9 +95,9 @@ public IEnumerator<BaseData> CreateEnumerator(SubscriptionRequest request, IData
8795
lastSourceRefreshTime = utcNow;
8896
var localDate = _dateAdjustment?.Invoke(utcNow.ConvertFromUtc(config.ExchangeTimeZone).Date) ?? utcNow.ConvertFromUtc(config.ExchangeTimeZone).Date;
8997
var source = sourceFactory.GetSource(config, localDate, true);
90-
if (_sourceAdjustment != null)
98+
if (sourceAdjustment != null)
9199
{
92-
source = _sourceAdjustment(source, utcNow);
100+
source = sourceAdjustment(source, utcNow);
93101
}
94102

95103
// fetch the new source and enumerate the data source reader
@@ -207,6 +215,55 @@ IDataProvider dataProvider
207215
return SubscriptionDataSourceReader.ForSource(source, dataCacheProvider, config, date, true, baseDataInstance, dataProvider, _objectStore);
208216
}
209217

218+
/// <summary>
219+
/// Gets a source adjustment for universe files as a safety net for when the expected universe file
220+
/// is not available yet: when the market is open or close to opening (within <see cref="UniverseFileBackupFallbackWindow"/>
221+
/// of the next market open), it falls back to the backup universe file ("*.backup") if present, as a last resort.
222+
/// It is evaluated at the same cadence as the enumerator refreshes
223+
/// </summary>
224+
private static Func<SubscriptionDataSource, DateTime, SubscriptionDataSource> GetUniverseFileBackupSourceAdjustment(
225+
SubscriptionRequest request, IDataProvider dataProvider)
226+
{
227+
var exchangeHours = request.Security.Exchange.Hours;
228+
return (source, utcNow) =>
229+
{
230+
if (source.TransportMedium != SubscriptionTransportMedium.LocalFile)
231+
{
232+
return source;
233+
}
234+
235+
var localTime = utcNow.ConvertFromUtc(exchangeHours.TimeZone);
236+
// only fall back when the market is open or close to opening, when the expected universe file should already be available
237+
if (!exchangeHours.IsOpen(localTime, extendedMarketHours: false)
238+
// if the market is closed, GetNextMarketOpen returns the next day open
239+
&& exchangeHours.GetNextMarketOpen(localTime, extendedMarketHours: false) - localTime > UniverseFileBackupFallbackWindow)
240+
{
241+
return source;
242+
}
243+
244+
if (CanFetchDataSource(dataProvider, source))
245+
{
246+
return source;
247+
}
248+
249+
var backupSource = new SubscriptionDataSource(source.Source + ".backup", source.TransportMedium, source.Format);
250+
if (CanFetchDataSource(dataProvider, backupSource))
251+
{
252+
Log.Trace($"LiveCustomDataSubscriptionEnumeratorFactory.GetUniverseFileBackupSourceAdjustment(): universe file '{source.Source}' is not available, " +
253+
$"falling back to backup universe file '{backupSource.Source}'");
254+
return backupSource;
255+
}
256+
257+
return source;
258+
};
259+
}
260+
261+
private static bool CanFetchDataSource(IDataProvider dataProvider, SubscriptionDataSource source)
262+
{
263+
using var stream = dataProvider.Fetch(source.Source);
264+
return stream != null;
265+
}
266+
210267
private bool SourceRequiresFastForward(SubscriptionDataSource source)
211268
{
212269
return source.TransportMedium == SubscriptionTransportMedium.LocalFile

Engine/DataFeeds/LiveTradingDataFeed.cs

Lines changed: 2 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,6 @@ public class LiveTradingDataFeed : FileSystemDataFeed
4242
{
4343
private static readonly int MaximumWarmupHistoryDaysLookBack = Config.GetInt("maximum-warmup-history-days-look-back", 5);
4444

45-
// when the expected universe file is not available yet, we fall back to the backup universe file ("*.backup"),
46-
// if any, as a last resort, when the market is open or within this time span before the next market open
47-
private static readonly TimeSpan UniverseFileBackupFallbackWindow =
48-
TimeSpan.FromMinutes(Config.GetInt("universe-file-backup-fallback-minutes", 30));
49-
5045
private LiveNodePacket _job;
5146

5247
// used to get current time
@@ -362,7 +357,8 @@ request.Universe is OptionChainUniverse ||
362357
// we adjust time to the previous tradable date
363358
time => Time.GetStartTimeForTradeBars(request.Security.Exchange.Hours, time, Time.OneDay, 1, false, config.DataTimeZone, _algorithm.Settings.DailyPreciseEndTime),
364359
TimeSpan.FromMinutes(10),
365-
sourceAdjustment: GetUniverseFileBackupSourceAdjustment(request)
360+
// when the expected universe file is not available yet, fall back to the backup universe file as a last resort
361+
fallBackToBackupUniverseFiles: true
366362
);
367363
var enumeratorStack = factory.CreateEnumerator(request, _dataProvider);
368364

@@ -421,53 +417,6 @@ public static TimeSpan GetScheduledUniverseUtcTimeShift(DateTime currentUtcDateT
421417
return _scheduledUniverseUtcTimeShift.Value;
422418
}
423419

424-
/// <summary>
425-
/// Gets a source adjustment for universe files as a safety net for when the expected universe file
426-
/// is not available yet: when the market is open or close to opening (within <see cref="UniverseFileBackupFallbackWindow"/>
427-
/// of the next market open), it falls back to the backup universe file ("*.backup") if present, as a last resort
428-
/// </summary>
429-
private Func<SubscriptionDataSource, DateTime, SubscriptionDataSource> GetUniverseFileBackupSourceAdjustment(SubscriptionRequest request)
430-
{
431-
var exchangeHours = request.Security.Exchange.Hours;
432-
return (source, utcNow) =>
433-
{
434-
if (source.TransportMedium != SubscriptionTransportMedium.LocalFile)
435-
{
436-
return source;
437-
}
438-
439-
var localTime = utcNow.ConvertFromUtc(exchangeHours.TimeZone);
440-
// only fall back when the market is open or close to opening, when the expected universe file should already be available
441-
if (!exchangeHours.IsOpen(localTime, extendedMarketHours: false)
442-
// if the market is closed, GetNextMarketOpen returns the next day open
443-
&& exchangeHours.GetNextMarketOpen(localTime, extendedMarketHours: false) - localTime > UniverseFileBackupFallbackWindow)
444-
{
445-
return source;
446-
}
447-
448-
if (CanFetchDataSource(source))
449-
{
450-
return source;
451-
}
452-
453-
var backupSource = new SubscriptionDataSource(source.Source + ".backup", source.TransportMedium, source.Format);
454-
if (CanFetchDataSource(backupSource))
455-
{
456-
Log.Trace($"LiveTradingDataFeed.GetUniverseFileBackupSourceAdjustment(): universe file '{source.Source}' is not available, " +
457-
$"falling back to backup universe file '{backupSource.Source}'");
458-
return backupSource;
459-
}
460-
461-
return source;
462-
};
463-
}
464-
465-
private bool CanFetchDataSource(SubscriptionDataSource source)
466-
{
467-
using var stream = _dataProvider.Fetch(source.Source);
468-
return stream != null;
469-
}
470-
471420
/// <summary>
472421
/// Build and apply the warmup enumerators when required
473422
/// </summary>

0 commit comments

Comments
 (0)