@@ -189,6 +189,7 @@ cdef class BarBuilder:
189189 self ._open = None
190190 self ._high = None
191191 self ._low = None
192+ self ._close = None
192193
193194 self .volume = Quantity.zero_c(precision = self .size_precision)
194195 self .count = 0
@@ -1406,6 +1407,7 @@ cdef class TimeBarAggregator(BarAggregator):
14061407 self.interval_ns = self ._get_interval_ns()
14071408 self.stored_open_ns = 0
14081409 self.next_close_ns = 0
1410+ self.first_close_ns = 0
14091411 self.historical_mode = False
14101412 self._historical_events = []
14111413
@@ -1436,46 +1438,55 @@ cdef class TimeBarAggregator(BarAggregator):
14361438 # Closing a partial bar at the transition from historical to backtest data
14371439 cdef bint fire_immediately = (start_time == now)
14381440
1439- self ._skip_first_non_full_bar = self ._skip_first_non_full_bar and now > start_time
1440-
1441- if self .bar_type.spec.aggregation not in (BarAggregation.MONTH, BarAggregation.YEAR):
1442- self ._clock.set_timer(
1443- name = self ._timer_name,
1444- interval = self .interval,
1445- start_time = start_time,
1446- stop_time = None ,
1447- callback = self ._build_bar,
1448- allow_past = True ,
1449- fire_immediately = fire_immediately,
1450- )
1441+ # Calculate the next close time based on aggregation type
1442+ cdef datetime close_time
1443+ if fire_immediately:
1444+ close_time = start_time
1445+ elif self .bar_type.spec.aggregation == BarAggregation.MONTH:
1446+ close_time = start_time + pd.DateOffset(months = self .bar_type.spec.step)
1447+ elif self .bar_type.spec.aggregation == BarAggregation.YEAR:
1448+ close_time = start_time + pd.DateOffset(years = self .bar_type.spec.step)
1449+ else :
1450+ close_time = start_time + self .interval
14511451
1452- if fire_immediately:
1453- self .next_close_ns = dt_to_unix_nanos(start_time)
1454- else :
1455- self .next_close_ns = dt_to_unix_nanos(start_time + self .interval)
1452+ self .next_close_ns = dt_to_unix_nanos(close_time)
14561453
1457- self .stored_open_ns = self .next_close_ns - self .interval_ns
1454+ # The stored open time needs to be defined as a subtraction with respect to the first closing time
1455+ if self .bar_type.spec.aggregation == BarAggregation.MONTH:
1456+ self .stored_open_ns = dt_to_unix_nanos(close_time - pd.DateOffset(months = self .bar_type.spec.step))
1457+ elif self .bar_type.spec.aggregation == BarAggregation.YEAR:
1458+ self .stored_open_ns = dt_to_unix_nanos(close_time - pd.DateOffset(years = self .bar_type.spec.step))
14581459 else :
1459- # The monthly/yearly alert time is defined iteratively at each alert time as there is no regular interval
1460- if self .bar_type.spec.aggregation == BarAggregation.MONTH:
1461- alert_time = start_time + (pd.DateOffset(months = self .bar_type.spec.step) if not fire_immediately else pd.Timedelta(0 ))
1462- elif self .bar_type.spec.aggregation == BarAggregation.YEAR:
1463- alert_time = start_time + (pd.DateOffset(years = self .bar_type.spec.step) if not fire_immediately else pd.Timedelta(0 ))
1464- else :
1465- alert_time = start_time
1460+ self .stored_open_ns = self .next_close_ns - self .interval_ns
1461+
1462+ if self ._skip_first_non_full_bar:
1463+ self .first_close_ns = self .next_close_ns
14661464
1465+ if self .bar_type.spec.aggregation in (BarAggregation.MONTH, BarAggregation.YEAR):
1466+ # The monthly/yearly alert time is defined iteratively at each alert time as there is no regular interval
14671467 self ._clock.set_time_alert(
14681468 name = self ._timer_name,
1469- alert_time = alert_time ,
1469+ alert_time = close_time ,
14701470 callback = self ._build_bar,
14711471 override = True ,
14721472 allow_past = True ,
14731473 )
1474- self .next_close_ns = alert_time.value
1475- self .stored_open_ns = start_time.value
1474+ else :
1475+ self ._clock.set_timer(
1476+ name = self ._timer_name,
1477+ interval = self .interval,
1478+ start_time = start_time,
1479+ stop_time = None ,
1480+ callback = self ._build_bar,
1481+ allow_past = True ,
1482+ fire_immediately = fire_immediately,
1483+ )
14761484
1477- self ._log.debug(f" Started timer {self._timer_name}, {start_time=}, {self.historical_mode=}, "
1478- f" {fire_immediately=}, {start_time=}, {now=}, {self._bar_build_delay=}" )
1485+ self ._log.debug(f" [start_timer] fire_immediately={fire_immediately}, "
1486+ f" _skip_first_non_full_bar={self._skip_first_non_full_bar}, "
1487+ f" now={now}, start_time={start_time}, "
1488+ f" first_close_ns={unix_nanos_to_dt(self.first_close_ns)}, "
1489+ f" next_close_ns={unix_nanos_to_dt(self.next_close_ns)}" )
14791490
14801491 cpdef void stop_timer(self ):
14811492 cdef str timer_name = str (self .bar_type)
@@ -1631,10 +1642,11 @@ cdef class TimeBarAggregator(BarAggregator):
16311642 self .next_close_ns = self ._clock.next_time_ns(self ._timer_name)
16321643
16331644 cdef void _build_and_send(self , uint64_t ts_event, uint64_t ts_init):
1634- if self ._skip_first_non_full_bar:
1645+ if self ._skip_first_non_full_bar and ts_init <= self .first_close_ns :
16351646 self ._builder.reset()
1636- self ._skip_first_non_full_bar = False
16371647 else :
1648+ # We set set _skip_first_non_full_bar to False for the transition from historical to live data
1649+ self ._skip_first_non_full_bar = False
16381650 BarAggregator._build_and_send(self , ts_event, ts_init)
16391651
16401652
0 commit comments