20 using System.Collections.Generic;
22 using System.Runtime.CompilerServices;
31 internal class DateChangeTimeKeeper : TimeKeeper, IDisposable
33 private IEnumerator<DateTime> _tradableDatesInDataTimeZone;
36 private DateTime _delistingDate;
38 private DateTime _previousNewExchangeDate;
40 private bool _needsMoveNext;
41 private bool _initialized;
43 private DateTime _exchangeTime;
44 private DateTime _dataTime;
45 private bool _exchangeTimeNeedsUpdate;
46 private bool _dataTimeNeedsUpdate;
51 public DateTime DataTime
55 if (_dataTimeNeedsUpdate)
58 _dataTimeNeedsUpdate =
false;
67 public DateTime ExchangeTime
71 if (_exchangeTimeNeedsUpdate)
74 _exchangeTimeNeedsUpdate =
false;
83 public event EventHandler<DateTime> NewExchangeDate;
92 public DateChangeTimeKeeper(IEnumerable<DateTime> tradableDatesInDataTimeZone,
SubscriptionDataConfig config,
96 _tradableDatesInDataTimeZone = tradableDatesInDataTimeZone.GetEnumerator();
98 _exchangeHours = exchangeHours;
99 _delistingDate = delistingDate;
100 _exchangeTimeNeedsUpdate =
true;
101 _dataTimeNeedsUpdate =
true;
102 _needsMoveNext =
true;
108 public void Dispose()
110 _tradableDatesInDataTimeZone.DisposeSafely();
117 public override void SetUtcDateTime(DateTime utcDateTime)
119 base.SetUtcDateTime(utcDateTime);
120 _exchangeTimeNeedsUpdate =
true;
121 _dataTimeNeedsUpdate =
true;
129 public void AdvanceTowardsExchangeTime(DateTime targetExchangeTime)
133 throw new InvalidOperationException($
"The time keeper has not been initialized. " +
134 $
"{nameof(TryAdvanceUntilNextDataDate)} needs to be called at least once to flush the first date before advancing.");
137 var currentExchangeTime = ExchangeTime;
139 if (targetExchangeTime.Date == currentExchangeTime.Date)
141 SetExchangeTime(targetExchangeTime);
145 while (currentExchangeTime < targetExchangeTime)
147 var newExchangeTime = currentExchangeTime + Time.OneDay;
148 if (newExchangeTime > targetExchangeTime)
150 newExchangeTime = targetExchangeTime;
153 var newExchangeDate = newExchangeTime.Date;
156 if (newExchangeDate != currentExchangeTime.Date &&
160 SetExchangeTime(newExchangeDate);
161 EmitNewExchangeDate(newExchangeDate);
165 currentExchangeTime = newExchangeTime;
169 SetExchangeTime(targetExchangeTime);
175 public bool TryAdvanceUntilNextDataDate()
179 return EmitFirstExchangeDate();
183 if (TryEmitPassedExchangeDate())
188 if (!_needsMoveNext || _tradableDatesInDataTimeZone.MoveNext())
190 var nextDataDate = _tradableDatesInDataTimeZone.Current;
192 var nextExchangeDate = nextExchangeTime.Date;
194 if (nextExchangeDate > _delistingDate)
197 TryEmitPassedExchangeDate();
198 _needsMoveNext =
false;
206 if (!IsExchangeBehindData(nextExchangeTime, nextDataDate) && nextExchangeDate > _previousNewExchangeDate)
208 EmitNewExchangeDate(nextExchangeDate);
209 SetExchangeTime(nextExchangeDate);
211 _needsMoveNext = nextExchangeDate == DataTime;
215 _needsMoveNext =
true;
216 SetDataTime(nextDataDate);
220 _needsMoveNext =
false;
228 private bool EmitFirstExchangeDate()
235 if (!_tradableDatesInDataTimeZone.MoveNext())
241 var firstDataDate = _tradableDatesInDataTimeZone.Current;
243 var firstExchangeDate = firstExchangeTime.Date;
245 DateTime exchangeDateToEmit;
249 exchangeDateToEmit = firstExchangeDate;
250 SetExchangeTime(exchangeDateToEmit);
252 _needsMoveNext =
false;
258 exchangeDateToEmit = firstDataDate;
259 SetDataTime(firstDataDate);
260 _needsMoveNext =
true;
263 EmitNewExchangeDate(exchangeDateToEmit);
272 public bool IsExchangeBehindData()
274 return IsExchangeBehindData(ExchangeTime, DataTime);
280 private static bool IsExchangeBehindData(DateTime exchangeTime, DateTime dataTime)
282 return dataTime > exchangeTime;
285 [MethodImpl(MethodImplOptions.AggressiveInlining)]
286 private void SetExchangeTime(DateTime exchangeTime)
288 SetUtcDateTime(exchangeTime.ConvertToUtc(_exchangeHours.
TimeZone));
291 [MethodImpl(MethodImplOptions.AggressiveInlining)]
292 private void SetDataTime(DateTime dataTime)
294 SetUtcDateTime(dataTime.ConvertToUtc(_config.
DataTimeZone));
303 private bool TryEmitPassedExchangeDate()
305 if (_needsMoveNext && _tradableDatesInDataTimeZone.Current !=
default)
309 var currentDataDate = _tradableDatesInDataTimeZone.Current;
310 if (_previousNewExchangeDate < currentDataDate &&
313 var nextExchangeDate = currentDataDate;
314 SetExchangeTime(nextExchangeDate);
315 EmitNewExchangeDate(nextExchangeDate);
326 private void EmitNewExchangeDate(DateTime newExchangeDate)
328 NewExchangeDate?.Invoke(
this, newExchangeDate);
329 _previousNewExchangeDate = newExchangeDate;