18 using System.Runtime.CompilerServices;
36 private bool _securityIdentifierIsSet;
38 private int? _maxCount;
40 private IPeriodSpecification _periodSpecification;
42 private TimeSpan? _period;
44 private int _currentCount;
46 private TConsolidated _workingBar;
48 private DateTime? _lastEmit;
49 private bool _validateTimeSpan;
53 _periodSpecification = periodSpecification;
54 _period = _periodSpecification.
Period;
62 : this(new TimeSpanPeriodSpecification(period))
64 _period = _periodSpecification.Period;
72 : this(new BarCountPeriodSpecification())
83 : this(new MixedModePeriodSpecification(period))
86 _period = _periodSpecification.Period;
94 : this(new FuncPeriodSpecification(func))
104 : this(GetPeriodSpecificationFromPyObject(pyObject))
135 if (!_securityIdentifierIsSet)
137 _securityIdentifierIsSet =
true;
138 _securityIdentifier = data.Symbol.ID;
140 else if (!data.Symbol.ID.Equals(_securityIdentifier))
142 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}).");
152 if (!_validateTimeSpan && _period.HasValue && _periodSpecification is TimeSpanPeriodSpecification)
155 _validateTimeSpan =
true;
156 var dataLength = data.EndTime - data.Time;
157 if (dataLength > _period)
159 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}");
164 var fireDataConsolidated =
false;
168 bool aggregateBeforeFire = _maxCount.HasValue;
170 if (_maxCount.HasValue)
174 if (_currentCount >= _maxCount.Value)
177 fireDataConsolidated =
true;
181 if (!_lastEmit.HasValue)
184 _lastEmit =
IsTimeBased ? DateTime.MinValue : data.Time;
187 if (_period.HasValue)
190 if (_workingBar !=
null && data.Time - _workingBar.Time >= _period.Value &&
GetRoundedBarTime(data) > _lastEmit)
192 fireDataConsolidated =
true;
196 if (_period.Value == TimeSpan.Zero)
198 fireDataConsolidated =
true;
199 aggregateBeforeFire =
true;
203 if (aggregateBeforeFire)
205 if (data.Time >= _lastEmit)
212 if (fireDataConsolidated)
214 var workingTradeBar = _workingBar as
TradeBar;
215 if (workingTradeBar !=
null)
218 if (_period.HasValue)
220 workingTradeBar.
Period = _period.Value;
225 workingTradeBar.Period = data.Time - _lastEmit.Value;
230 _lastEmit =
IsTimeBased && _workingBar !=
null ? _workingBar.EndTime : data.Time;
234 if (!aggregateBeforeFire)
236 if (data.Time >= _lastEmit)
247 public override void Scan(DateTime currentLocalTime)
249 if (_workingBar !=
null && _period.HasValue && _period.Value != TimeSpan.Zero
250 && currentLocalTime - _workingBar.Time >= _period.Value &&
GetRoundedBarTime(currentLocalTime) > _lastEmit)
252 _lastEmit = _workingBar.EndTime;
280 protected abstract void AggregateBar(ref TConsolidated workingBar, T data);
287 [MethodImpl(MethodImplOptions.AggressiveInlining)]
290 var startTime = _periodSpecification.GetRoundedBarTime(time);
293 if (_workingBar ==
null)
295 _period = _periodSpecification.Period;
306 [MethodImpl(MethodImplOptions.AggressiveInlining)]
310 if(_period.HasValue && potentialStartTime + _period < inputData.
EndTime)
313 potentialStartTime = inputData.
Time;
316 return potentialStartTime;
325 base.OnDataConsolidated(e);
336 private static IPeriodSpecification GetPeriodSpecificationFromPyObject(PyObject pyObject)
338 Func<DateTime, CalendarInfo> expiryFunc;
339 if (pyObject.TryConvertToDelegate(out expiryFunc))
341 return new FuncPeriodSpecification(expiryFunc);
346 return new TimeSpanPeriodSpecification(pyObject.As<TimeSpan>());
353 private interface IPeriodSpecification
362 private class BarCountPeriodSpecification : IPeriodSpecification
364 public TimeSpan?
Period {
get; } =
null;
372 private class MixedModePeriodSpecification : IPeriodSpecification
374 public TimeSpan?
Period {
get; }
376 public MixedModePeriodSpecification(TimeSpan period)
387 private class TimeSpanPeriodSpecification : IPeriodSpecification
389 public TimeSpan?
Period {
get; }
391 public TimeSpanPeriodSpecification(TimeSpan period)
397 Period.Value > Time.OneDay
399 : time.RoundDown(
Period.Value);
406 private class FuncPeriodSpecification : IPeriodSpecification
408 private static readonly DateTime _verificationDate =
new DateTime(2022, 01, 03, 10, 10, 10);
409 public TimeSpan?
Period {
get;
private set; }
411 public readonly Func<DateTime, CalendarInfo> _calendarInfoFunc;
413 public FuncPeriodSpecification(Func<DateTime, CalendarInfo> expiryFunc)
415 if (expiryFunc(_verificationDate).Start > _verificationDate)
417 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.");
419 _calendarInfoFunc = expiryFunc;
424 var calendarInfo = _calendarInfoFunc(time);
425 Period = calendarInfo.Period;
426 return calendarInfo.Start;