from app.bars.aggregator import Aggregator from app.bars.models import Bar, Timeframe def minute(t: int, price: float = 100, volume: int = 1) -> Bar: return Bar(Timeframe.M1, t, price, price + 1, price - 1, price + 0.5, volume, True, "ES=F", "replay") def test_aggregates_all_timeframes_and_closes_on_later_bucket(): aggregator = Aggregator() first = aggregator.update(minute(0, 100, 2)) second = aggregator.update(minute(60, 102, 3)) boundary = aggregator.update(minute(900, 105, 4)) assert {bar.tf for bar in first} == set(Timeframe) forming_15m = [bar for bar in second if bar.tf is Timeframe.M15][-1] assert (forming_15m.o, forming_15m.h, forming_15m.l, forming_15m.c, forming_15m.v) == ( 100, 103, 99, 102.5, 5, ) bars_15m = [bar for bar in boundary if bar.tf is Timeframe.M15] assert [(bar.t, bar.closed) for bar in bars_15m] == [(0, True), (900, False)] def test_gap_closes_previous_bucket_without_synthesizing_empty_bars(): aggregator = Aggregator([Timeframe.M5]) aggregator.update(minute(0)) output = aggregator.update(minute(1800)) assert [(bar.t, bar.closed) for bar in output] == [(0, True), (1800, False)] def test_replay_is_deterministic(): tape = [minute(t, 100 + index) for index, t in enumerate(range(0, 3600, 60))] def run(): aggregator = Aggregator() return [bar.to_dict() for source in tape for bar in aggregator.update(source)] assert run() == run()