18 using System.Runtime.CompilerServices;
37 private bool _securityIdentifierIsSet;
39 private int? _maxCount;
41 private IPeriodSpecification _periodSpecification;
43 private TimeSpan? _period;
45 private int _currentCount;
47 private TConsolidated _workingBar;
49 private DateTime? _lastEmit;
50 private bool _validateTimeSpan;
54 _periodSpecification = periodSpecification;
55 _period = _periodSpecification.
Period;
63 : this(new TimeSpanPeriodSpecification(period))
65 _period = _periodSpecification.Period;
73 : this(new BarCountPeriodSpecification())
84 : this(new MixedModePeriodSpecification(period))
87 _period = _periodSpecification.Period;
95 : this(new FuncPeriodSpecification(func))
105 : this(GetPeriodSpecificationFromPyObject(pyObject))
136 if (!_securityIdentifierIsSet)
138 _securityIdentifierIsSet =
true;
139 _securityIdentifier = data.Symbol.ID;
141 else if (!data.Symbol.ID.Equals(_securityIdentifier))
143 throw new InvalidOperationException($
"Consolidators can only be used with a single symbol. The previous consolidated SecurityIdentifier ({_securityIdentifier}) is not the same as in the current data ({data.Symbol.ID}).");
153 if (!_validateTimeSpan && _period.HasValue && _periodSpecification is TimeSpanPeriodSpecification)
156 _validateTimeSpan =
true;
157 var dataLength = data.EndTime - data.Time;
158 if (dataLength > _period)
160 throw new ArgumentException($
"For Symbol {data.Symbol} can not consolidate bars of period: {_period}, using data of the same or higher period: {data.EndTime - data.Time}");
165 var fireDataConsolidated =
false;
169 bool aggregateBeforeFire = _maxCount.HasValue;
171 if (_maxCount.HasValue)
175 if (_currentCount >= _maxCount.Value)
178 fireDataConsolidated =
true;
182 if (!_lastEmit.HasValue)
185 _lastEmit =
IsTimeBased ? DateTime.MinValue : data.Time;
188 if (_period.HasValue)
191 if (_workingBar !=
null && data.Time - _workingBar.Time >= _period.Value &&
GetRoundedBarTime(data) > _lastEmit)
193 fireDataConsolidated =
true;
197 if (_period.Value == TimeSpan.Zero)
199 fireDataConsolidated =
true;
200 aggregateBeforeFire =
true;
204 if (aggregateBeforeFire)
206 if (data.Time >= _lastEmit)
213 if (fireDataConsolidated)
215 var workingTradeBar = _workingBar as
TradeBar;
216 if (workingTradeBar !=
null)
219 if (_period.HasValue)
221 workingTradeBar.
Period = _period.Value;
226 workingTradeBar.Period = data.Time - _lastEmit.Value;
231 _lastEmit =
IsTimeBased && _workingBar !=
null ? _workingBar.EndTime : data.Time;
235 if (!aggregateBeforeFire)
237 if (data.Time >= _lastEmit)
248 public override void Scan(DateTime currentLocalTime)
250 if (_workingBar !=
null && _period.HasValue && _period.Value != TimeSpan.Zero
251 && currentLocalTime - _workingBar.Time >= _period.Value &&
GetRoundedBarTime(currentLocalTime) > _lastEmit)
253 _lastEmit = _workingBar.EndTime;
264 _securityIdentifier =
null;
265 _securityIdentifierIsSet =
false;
269 _validateTimeSpan =
false;
295 protected abstract void AggregateBar(ref TConsolidated workingBar, T data);
302 [MethodImpl(MethodImplOptions.AggressiveInlining)]
305 var startTime = _periodSpecification.GetRoundedBarTime(time);
308 if (_workingBar ==
null)
310 _period = _periodSpecification.Period;
321 [MethodImpl(MethodImplOptions.AggressiveInlining)]
325 if (_period.HasValue && potentialStartTime + _period < inputData.
EndTime)
338 potentialStartTime = inputData.
Time;
342 return potentialStartTime;
351 base.OnDataConsolidated(e);
362 private static IPeriodSpecification GetPeriodSpecificationFromPyObject(PyObject pyObject)
364 Func<DateTime, CalendarInfo> expiryFunc;
365 if (pyObject.TryConvertToDelegate(out expiryFunc))
367 return new FuncPeriodSpecification(expiryFunc);
372 return new TimeSpanPeriodSpecification(pyObject.As<TimeSpan>());
379 private interface IPeriodSpecification
388 private class BarCountPeriodSpecification : IPeriodSpecification
390 public TimeSpan?
Period {
get; } =
null;
398 private class MixedModePeriodSpecification : IPeriodSpecification
400 public TimeSpan?
Period {
get; }
402 public MixedModePeriodSpecification(TimeSpan period)
413 private class TimeSpanPeriodSpecification : IPeriodSpecification
415 public TimeSpan?
Period {
get; }
417 public TimeSpanPeriodSpecification(TimeSpan period)
423 Period.Value > Time.OneDay
425 : time.RoundDown(
Period.Value);
432 private class FuncPeriodSpecification : IPeriodSpecification
434 private static readonly DateTime _verificationDate =
new DateTime(2022, 01, 03, 10, 10, 10);
435 public TimeSpan?
Period {
get;
private set; }
437 public readonly Func<DateTime, CalendarInfo> _calendarInfoFunc;
439 public FuncPeriodSpecification(Func<DateTime, CalendarInfo> expiryFunc)
441 if (expiryFunc(_verificationDate).Start > _verificationDate)
443 throw new ArgumentException($
"{nameof(FuncPeriodSpecification)}: Please use a function that computes the start of the bar associated with the given date time. Should never return a time later than the one passed in.");
445 _calendarInfoFunc = expiryFunc;
450 var calendarInfo = _calendarInfoFunc(time);
451 Period = calendarInfo.Period;
452 return calendarInfo.Start;