18 using System.Collections;
19 using System.Collections.Generic;
37 private DateTime? _delistedTime;
40 private bool _isFillingForward;
42 private readonly
bool _useStrictEndTime;
43 private readonly TimeSpan _dataResolution;
44 private readonly DateTimeZone _dataTimeZone;
45 private readonly
bool _isExtendedMarketHours;
46 private readonly DateTime _subscriptionEndTime;
47 private readonly
CalendarInfo _subscriptionEndDataCalendar;
48 private readonly IEnumerator<BaseData> _enumerator;
50 private readonly
bool _strictEndTimeIntraDayFillForward;
75 bool isExtendedMarketHours,
76 DateTime subscriptionEndTime,
77 TimeSpan dataResolution,
78 DateTimeZone dataTimeZone,
79 bool dailyStrictEndTimeEnabled,
83 _subscriptionEndTime = subscriptionEndTime;
85 _enumerator = enumerator;
86 _dataResolution = dataResolution;
87 _dataTimeZone = dataTimeZone;
88 _fillForwardResolution = fillForwardResolution;
89 _isExtendedMarketHours = isExtendedMarketHours;
90 _useStrictEndTime = dailyStrictEndTimeEnabled;
94 _strictEndTimeIntraDayFillForward = dailyStrictEndTimeEnabled && dataType !=
null && dataType == typeof(
OpenInterest);
100 if (_useStrictEndTime && !_strictEndTimeIntraDayFillForward)
102 var lastDayCalendar = GetDailyCalendar(_subscriptionEndTime);
103 while (lastDayCalendar.End > _subscriptionEndTime)
105 lastDayCalendar = GetDailyCalendar(lastDayCalendar.Start.AddDays(-1));
107 _subscriptionEndDataCalendar = lastDayCalendar;
111 _subscriptionEndDataCalendar =
new (RoundDown(_subscriptionEndTime, _dataResolution), _dataResolution);
134 object IEnumerator.Current =>
Current;
145 if (_delistedTime.HasValue)
148 if (_previous ==
null || _previous.
EndTime >= _delistedTime.Value)
163 if (!_isFillingForward)
166 if (!_enumerator.MoveNext())
169 if (_delistedTime.HasValue)
176 if (_previous ==
null || _previous.
EndTime >= _subscriptionEndTime)
183 var endOfSubscription = (
Current ?? _previous).Clone(
true);
184 endOfSubscription.
Time = _subscriptionEndDataCalendar.Start;
185 endOfSubscription.EndTime = endOfSubscription.Time + _subscriptionEndDataCalendar.Period;
195 if (!
Exchange.IsOpenDuringBar(endOfSubscription.Time, endOfSubscription.EndTime, _isExtendedMarketHours))
215 else if (_enumerator.Current ==
null && !_ended)
217 _ended = _enumerator.MoveNext();
220 var underlyingCurrent = _enumerator.Current;
221 if (underlyingCurrent !=
null && underlyingCurrent.DataType ==
MarketDataType.Auxiliary)
223 var delisting = underlyingCurrent as
Delisting;
226 _delistedTime = delisting.
EndTime;
230 if (_previous ==
null)
239 if (_previous.
EndTime >= _subscriptionEndTime)
245 _isFillingForward =
true;
250 _isFillingForward =
false;
261 _enumerator.Dispose();
284 var nextCalculatedEndTimeUtc = DateTime.MaxValue;
288 var previousTimeUtc = previous.
Time.ConvertToUtc(
Exchange.TimeZone);
289 var nextTimeUtc = next.
Time.ConvertToUtc(
Exchange.TimeZone);
292 if (nextEndTimeUtc < previousTimeUtc)
294 Log.
Error(
"FillForwardEnumerator received data out of order. Symbol: " + previous.
Symbol.
ID);
300 var nextPreviousTimeUtcDelta = nextTimeUtc - previousTimeUtc;
301 if (nextPreviousTimeUtcDelta <= fillForwardResolution &&
302 nextPreviousTimeUtcDelta <= _dataResolution &&
304 !_strictEndTimeIntraDayFillForward)
310 var period = _dataResolution;
311 if (_useStrictEndTime)
320 period = TimeSpan.Zero;
322 nextCalculatedEndTimeUtc = nextTimeUtc + period;
336 foreach (var item
in GetSortedReferenceDateIntervals(previous, fillForwardResolution, _dataResolution))
370 var startTime = (_useStrictEndTime && item.Period >
Time.
OneHour) ? item.Start : RoundDown(item.Start, item.Period);
371 var potentialBarEndTime = startTime.ConvertToUtc(
Exchange.TimeZone) + item.Period;
376 if (potentialBarEndTime < nextCalculatedEndTimeUtc
379 || next !=
null && next.DataType ==
MarketDataType.Auxiliary && potentialBarEndTime == nextCalculatedEndTimeUtc)
383 var potentialBarEndTimeInExchangeTZ =
384 potentialBarEndTime.ConvertFromUtc(
Exchange.TimeZone);
385 var nextFillForwardBarStartTime = potentialBarEndTimeInExchangeTZ - item.Period;
387 if (
Exchange.IsOpenDuringBar(nextFillForwardBarStartTime, potentialBarEndTimeInExchangeTZ, _isExtendedMarketHours))
389 fillForward = previous.Clone(
true);
392 var expectedPeriod = _dataResolution;
393 if (_useStrictEndTime)
404 var marketHoursDateTime = potentialBarEndTimeInExchangeTZ -
Exchange.Hours.RegularMarketDuration;
406 if (marketHoursDateTime < item.Start)
408 marketHoursDateTime = item.Start;
410 var marketHours =
Exchange.Hours.GetMarketHours(marketHoursDateTime);
411 expectedPeriod = marketHours.MarketDuration;
413 fillForward.Time = (potentialBarEndTime - expectedPeriod).ConvertFromUtc(
Exchange.TimeZone);
414 fillForward.EndTime = potentialBarEndTimeInExchangeTZ;
429 private IEnumerable<CalendarInfo> GetSortedReferenceDateIntervals(
BaseData previous, TimeSpan fillForwardResolution, TimeSpan dataResolution)
431 if (fillForwardResolution < dataResolution)
433 return GetReferenceDateIntervals(previous.
EndTime, fillForwardResolution, dataResolution);
436 if (fillForwardResolution > dataResolution)
438 return GetReferenceDateIntervals(previous.
EndTime, dataResolution, fillForwardResolution);
441 return GetReferenceDateIntervals(previous.
EndTime, fillForwardResolution);
448 private IEnumerable<CalendarInfo> GetReferenceDateIntervals(DateTime previousEndTime, TimeSpan resolution)
451 if (!_useStrictEndTime &&
Exchange.IsOpenDuringBar(previousEndTime, previousEndTime + resolution, _isExtendedMarketHours))
454 yield
return new (previousEndTime, resolution);
457 if (_useStrictEndTime)
464 if (_strictEndTimeIntraDayFillForward)
466 var firtMarketOpen =
Exchange.Hours.GetNextMarketOpen(previousEndTime.Date, _isExtendedMarketHours);
469 if (firstCalendar.End > previousEndTime)
471 yield
return firstCalendar;
477 yield
return GetDailyCalendar(marketOpen);
483 yield
return new(marketOpen, resolution);
490 private IEnumerable<CalendarInfo> GetReferenceDateIntervals(DateTime previousEndTime, TimeSpan smallerResolution, TimeSpan largerResolution)
492 List<CalendarInfo> result =
null;
495 if (_useStrictEndTime)
500 new(previousEndTime, smallerResolution)
507 yield
return new (previousEndTime, smallerResolution);
510 result ??=
new List<CalendarInfo>(4);
513 if (_useStrictEndTime)
517 var dailyCalendar = GetDailyCalendar(previousEndTime);
518 if (previousEndTime < (dailyCalendar.Start + dailyCalendar.Period))
520 result.Add(
new(dailyCalendar.Start, dailyCalendar.Period));
525 var start = RoundDown(previousEndTime, largerResolution);
528 result.Add(
new(start, largerResolution));
536 result.Add(
new (marketOpen, smallerResolution));
537 if (_useStrictEndTime)
543 foreach (var referenceDateInterval
in result.OrderBy(interval => interval.Start + interval.Period))
545 yield
return referenceDateInterval;
555 private DateTime RoundDown(DateTime value, TimeSpan interval)
557 return value.RoundDownInTimeZone(interval,
Exchange.
TimeZone, _dataTimeZone);
560 private CalendarInfo GetDailyCalendar(DateTime localReferenceTime)