def test_bar_spec_equality(self): # Arrange bar_spec1 = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) bar_spec2 = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) bar_spec3 = BarSpecification(1, BarAggregation.MINUTE, PriceType.ASK) # Act, Assert assert bar_spec1 == bar_spec1 assert bar_spec1 == bar_spec2 assert bar_spec1 != bar_spec3
def test_bar_spec_comparison(self): # Arrange bar_spec1 = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) bar_spec2 = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) bar_spec3 = BarSpecification(1, BarAggregation.MINUTE, PriceType.ASK) # Act, Assert assert bar_spec1 <= bar_spec2 assert bar_spec3 < bar_spec1 assert bar_spec1 > bar_spec3 assert bar_spec1 >= bar_spec3
def test_handle_trade_tick_when_volume_below_threshold_updates(self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument = AUDUSD_SIM bar_spec = BarSpecification(10000, BarAggregation.VOLUME, PriceType.LAST) bar_type = BarType(instrument.id, bar_spec) aggregator = VolumeBarAggregator( instrument, bar_type, handler, Logger(TestClock()), ) tick1 = TradeTick( instrument_id=instrument.id, price=Price.from_str("1.00001"), size=Quantity.from_int(1), aggressor_side=AggressorSide.BUY, trade_id="123456", ts_event=0, ts_init=0, ) # Act aggregator.handle_trade_tick(tick1) # Assert assert len(bar_store.get_store()) == 0
def test_run_quote_ticks_through_aggregator_results_in_expected_bars(self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument = AUDUSD_SIM bar_spec = BarSpecification(1000, BarAggregation.VOLUME, PriceType.MID) bar_type = BarType(instrument.id, bar_spec) aggregator = VolumeBarAggregator( instrument, bar_type, handler, Logger(TestClock()), ) # Setup data wrangler = QuoteTickDataWrangler(instrument) provider = TestDataProvider() ticks = wrangler.process( data=provider.read_csv_ticks("truefx-audusd-ticks.csv")[:10000], default_volume=1, ) # Act for tick in ticks: aggregator.handle_quote_tick(tick) # Assert last_bar = bar_store.get_store()[-1] assert len(bar_store.get_store()) == 10 assert last_bar.open == Price.from_str("0.670635") assert last_bar.high == Price.from_str("0.670705") assert last_bar.low == Price.from_str("0.670370") assert last_bar.close == Price.from_str("0.670655") assert last_bar.volume == Quantity.from_int(1000)
def test_run_trade_ticks_through_aggregator_results_in_expected_bars(self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument = ETHUSDT_BINANCE bar_spec = BarSpecification(1000, BarAggregation.VOLUME, PriceType.LAST) bar_type = BarType(instrument.id, bar_spec) aggregator = VolumeBarAggregator( instrument, bar_type, handler, Logger(TestClock()), ) wrangler = TradeTickDataWrangler(instrument=ETHUSDT_BINANCE) provider = TestDataProvider() ticks = wrangler.process( provider.read_csv_ticks("binance-ethusdt-trades.csv")[:10000]) # Act for tick in ticks: aggregator.handle_trade_tick(tick) # Assert last_bar = bar_store.get_store()[-1] assert len(bar_store.get_store()) == 26 assert last_bar.open == Price.from_str("425.17") assert last_bar.high == Price.from_str("425.24") assert last_bar.low == Price.from_str("424.69") assert last_bar.close == Price.from_str("425.14") assert last_bar.volume == Quantity.from_int(1000)
def test_handle_quote_tick_when_value_below_threshold_updates(self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument_id = TestStubs.audusd_id() bar_spec = BarSpecification(100000, BarAggregation.VALUE, PriceType.BID) bar_type = BarType(instrument_id, bar_spec) aggregator = ValueBarAggregator( AUDUSD_SIM, bar_type, handler, Logger(TestClock()), ) tick1 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00001"), ask=Price.from_str("1.00004"), bid_size=Quantity.from_int(3000), ask_size=Quantity.from_int(2000), ts_event=0, ts_init=0, ) # Act aggregator.handle_quote_tick(tick1) # Assert assert len(bar_store.get_store()) == 0 assert aggregator.get_cumulative_value() == Decimal("3000.03000")
def test_handle_trade_tick_when_value_below_threshold_updates(self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument_id = TestStubs.audusd_id() bar_spec = BarSpecification(100000, BarAggregation.VALUE, PriceType.LAST) bar_type = BarType(instrument_id, bar_spec) aggregator = ValueBarAggregator( AUDUSD_SIM, bar_type, handler, Logger(TestClock()), ) tick1 = TradeTick( instrument_id=AUDUSD_SIM.id, price=Price.from_str("15000.00"), size=Quantity.from_str("3.5"), aggressor_side=AggressorSide.BUY, trade_id="123456", ts_event=0, ts_init=0, ) # Act aggregator.handle_trade_tick(tick1) # Assert assert len(bar_store.get_store()) == 0 assert aggregator.get_cumulative_value() == Decimal("52500.000")
def test_handle_quote_tick_when_volume_below_threshold_updates(self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument = AUDUSD_SIM bar_spec = BarSpecification(10000, BarAggregation.VOLUME, PriceType.BID) bar_type = BarType(instrument.id, bar_spec) aggregator = VolumeBarAggregator( instrument, bar_type, handler, Logger(TestClock()), ) tick1 = QuoteTick( instrument_id=instrument.id, bid=Price.from_str("1.00001"), ask=Price.from_str("1.00004"), bid_size=Quantity.from_int(3000), ask_size=Quantity.from_int(2000), ts_event=0, ts_init=0, ) # Act aggregator.handle_quote_tick(tick1) # Assert assert len(bar_store.get_store()) == 0
def test_add_bars_adds_to_engine(self, capsys): # Arrange engine = BacktestEngine() bar_spec = BarSpecification( step=1, aggregation=BarAggregation.MINUTE, price_type=PriceType.BID, ) bar_type = BarType( instrument_id=USDJPY_SIM.id, bar_spec=bar_spec, aggregation_source=AggregationSource.EXTERNAL, # <-- important ) wrangler = BarDataWrangler( bar_type=bar_type, instrument=USDJPY_SIM, ) provider = TestDataProvider() bars = wrangler.process( provider.read_csv_bars("fxcm-usdjpy-m1-bid-2013.csv")[:2000]) # Act engine.add_instrument(USDJPY_SIM) engine.add_bars(data=bars) # Assert log = "".join(capsys.readouterr()) assert "Added USD/JPY.SIM Instrument." in log assert "Added 2,000 USD/JPY.SIM-1-MINUTE-BID-EXTERNAL Bar elements." in log
def test_update_timed_with_test_clock_sends_single_bar_to_handler(self): # Arrange clock = TestClock() bar_store = ObjectStorer() handler = bar_store.store instrument_id = TestStubs.audusd_id() bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.MID) bar_type = BarType(instrument_id, bar_spec) aggregator = TimeBarAggregator( AUDUSD_SIM, bar_type, handler, TestClock(), Logger(clock), ) tick1 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00001"), ask=Price.from_str("1.00004"), bid_size=Quantity.from_int(1), ask_size=Quantity.from_int(1), ts_event=0, ts_init=0, ) tick2 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00002"), ask=Price.from_str("1.00005"), bid_size=Quantity.from_int(1), ask_size=Quantity.from_int(1), ts_event=0, ts_init=0, ) tick3 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00000"), ask=Price.from_str("1.00003"), bid_size=Quantity.from_int(1), ask_size=Quantity.from_int(1), ts_event=2 * 60 * 1_000_000_000, # 2 minutes in nanoseconds ts_init=2 * 60 * 1_000_000_000, # 2 minutes in nanoseconds ) # Act aggregator.handle_quote_tick(tick1) aggregator.handle_quote_tick(tick2) aggregator.handle_quote_tick(tick3) # Assert assert len(bar_store.get_store()) == 1 assert Price.from_str("1.000025") == bar_store.get_store()[0].open assert Price.from_str("1.000035") == bar_store.get_store()[0].high assert Price.from_str("1.000025") == bar_store.get_store()[0].low assert Price.from_str("1.000035") == bar_store.get_store()[0].close assert Quantity.from_int(2) == bar_store.get_store()[0].volume assert 60_000_000_000 == bar_store.get_store()[0].ts_init
def test_handle_quote_tick_when_value_beyond_threshold_sends_bar_to_handler( self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument_id = TestStubs.audusd_id() bar_spec = BarSpecification(100000, BarAggregation.VALUE, PriceType.BID) bar_type = BarType(instrument_id, bar_spec) aggregator = ValueBarAggregator( AUDUSD_SIM, bar_type, handler, Logger(TestClock()), ) tick1 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00001"), ask=Price.from_str("1.00004"), bid_size=Quantity.from_int(20000), ask_size=Quantity.from_int(20000), ts_event=0, ts_init=0, ) tick2 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00002"), ask=Price.from_str("1.00005"), bid_size=Quantity.from_int(60000), ask_size=Quantity.from_int(20000), ts_event=0, ts_init=0, ) tick3 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00000"), ask=Price.from_str("1.00003"), bid_size=Quantity.from_int(30500), ask_size=Quantity.from_int(20000), ts_event=0, ts_init=0, ) # Act aggregator.handle_quote_tick(tick1) aggregator.handle_quote_tick(tick2) aggregator.handle_quote_tick(tick3) # Assert assert len(bar_store.get_store()) == 1 assert bar_store.get_store()[0].open == Price.from_str("1.00001") assert bar_store.get_store()[0].high == Price.from_str("1.00002") assert bar_store.get_store()[0].low == Price.from_str("1.00000") assert bar_store.get_store()[0].close == Price.from_str("1.00000") assert bar_store.get_store()[0].volume == Quantity.from_str("99999") assert aggregator.get_cumulative_value() == Decimal("10501.400")
def test_bar_spec_hash_str_and_repr(self): # Arrange bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) # Act, Assert assert isinstance(hash(bar_spec), int) assert str(bar_spec) == "1-MINUTE-BID" assert repr(bar_spec) == "BarSpecification(1-MINUTE-BID)"
def test_handle_quote_tick_when_volume_at_threshold_sends_bar_to_handler( self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument = AUDUSD_SIM bar_spec = BarSpecification(10000, BarAggregation.VOLUME, PriceType.BID) bar_type = BarType(instrument.id, bar_spec) aggregator = VolumeBarAggregator( instrument, bar_type, handler, Logger(TestClock()), ) tick1 = QuoteTick( instrument_id=instrument.id, bid=Price.from_str("1.00001"), ask=Price.from_str("1.00004"), bid_size=Quantity.from_int(3000), ask_size=Quantity.from_int(2000), ts_event=0, ts_init=0, ) tick2 = QuoteTick( instrument_id=instrument.id, bid=Price.from_str("1.00002"), ask=Price.from_str("1.00005"), bid_size=Quantity.from_int(4000), ask_size=Quantity.from_int(2000), ts_event=0, ts_init=0, ) tick3 = QuoteTick( instrument_id=instrument.id, bid=Price.from_str("1.00000"), ask=Price.from_str("1.00003"), bid_size=Quantity.from_int(3000), ask_size=Quantity.from_int(2000), ts_event=0, ts_init=0, ) # Act aggregator.handle_quote_tick(tick1) aggregator.handle_quote_tick(tick2) aggregator.handle_quote_tick(tick3) # Assert assert len(bar_store.get_store()) == 1 assert bar_store.get_store()[0].open == Price.from_str("1.00001") assert bar_store.get_store()[0].high == Price.from_str("1.00002") assert bar_store.get_store()[0].low == Price.from_str("1.00000") assert bar_store.get_store()[0].close == Price.from_str("1.00000") assert bar_store.get_store()[0].volume == Quantity.from_int(10000)
def test_handle_trade_tick_when_volume_at_threshold_sends_bar_to_handler( self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument = AUDUSD_SIM bar_spec = BarSpecification(10000, BarAggregation.VOLUME, PriceType.LAST) bar_type = BarType(instrument.id, bar_spec) aggregator = VolumeBarAggregator( instrument, bar_type, handler, Logger(TestClock()), ) tick1 = TradeTick( instrument_id=instrument.id, price=Price.from_str("1.00001"), size=Quantity.from_int(3000), aggressor_side=AggressorSide.BUY, trade_id="123456", ts_event=0, ts_init=0, ) tick2 = TradeTick( instrument_id=instrument.id, price=Price.from_str("1.00002"), size=Quantity.from_int(4000), aggressor_side=AggressorSide.BUY, trade_id="123457", ts_event=0, ts_init=0, ) tick3 = TradeTick( instrument_id=instrument.id, price=Price.from_str("1.00000"), size=Quantity.from_int(3000), aggressor_side=AggressorSide.BUY, trade_id="123458", ts_event=0, ts_init=0, ) # Act aggregator.handle_trade_tick(tick1) aggregator.handle_trade_tick(tick2) aggregator.handle_trade_tick(tick3) # Assert assert len(bar_store.get_store()) == 1 assert bar_store.get_store()[0].open == Price.from_str("1.00001") assert bar_store.get_store()[0].high == Price.from_str("1.00002") assert bar_store.get_store()[0].low == Price.from_str("1.00000") assert bar_store.get_store()[0].close == Price.from_str("1.00000") assert bar_store.get_store()[0].volume == Quantity.from_int(10000)
def test_bar_type_hash_str_and_repr(self): # Arrange instrument_id = InstrumentId(Symbol("AUD/USD"), Venue("SIM")) bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) bar_type = BarType(instrument_id, bar_spec) # Act, Assert assert isinstance(hash(bar_type), int) assert str(bar_type) == "AUD/USD.SIM-1-MINUTE-BID-EXTERNAL" assert repr(bar_type) == "BarType(AUD/USD.SIM-1-MINUTE-BID-EXTERNAL)"
def test_bar_type_equality(self): # Arrange instrument_id1 = InstrumentId(Symbol("AUD/USD"), Venue("SIM")) instrument_id2 = InstrumentId(Symbol("GBP/USD"), Venue("SIM")) bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) bar_type1 = BarType(instrument_id1, bar_spec) bar_type2 = BarType(instrument_id1, bar_spec) bar_type3 = BarType(instrument_id2, bar_spec) # Act, Assert assert bar_type1 == bar_type1 assert bar_type1 == bar_type2 assert bar_type1 != bar_type3
def test_instantiate_given_invalid_bar_spec_raises_value_error(self): # Arrange clock = TestClock() bar_store = ObjectStorer() handler = bar_store.store instrument = AUDUSD_SIM bar_spec = BarSpecification(100, BarAggregation.TICK, PriceType.MID) bar_type = BarType(instrument.id, bar_spec) # Act, Assert with pytest.raises(ValueError): TimeBarAggregator( instrument, bar_type, handler, clock, Logger(clock), )
def parse_bar_ws( instrument_id: InstrumentId, kline: Dict, ts_event: int, ts_init: int, ) -> BinanceBar: interval = kline["i"] resolution = interval[1] if resolution == "m": aggregation = BarAggregation.MINUTE elif resolution == "h": aggregation = BarAggregation.HOUR elif resolution == "d": aggregation = BarAggregation.DAY else: # pragma: no cover (design-time error) raise RuntimeError( f"unsupported time aggregation resolution, was {resolution}") bar_spec = BarSpecification( step=int(interval[0]), aggregation=aggregation, price_type=PriceType.LAST, ) bar_type = BarType( instrument_id=instrument_id, bar_spec=bar_spec, aggregation_source=AggregationSource.EXTERNAL, ) return BinanceBar( bar_type=bar_type, open=Price.from_str(kline["o"]), high=Price.from_str(kline["h"]), low=Price.from_str(kline["l"]), close=Price.from_str(kline["c"]), volume=Quantity.from_str(kline["v"]), quote_volume=Quantity.from_str(kline["q"]), count=kline["n"], taker_buy_base_volume=Quantity.from_str(kline["V"]), taker_buy_quote_volume=Quantity.from_str(kline["Q"]), ts_event=ts_event, ts_init=ts_init, )
def parse_bar_ws( instrument_id: InstrumentId, data: BinanceCandlestick, ts_init: int, ) -> BinanceBar: resolution = data.i[1] if resolution == "m": aggregation = BarAggregation.MINUTE elif resolution == "h": aggregation = BarAggregation.HOUR elif resolution == "d": aggregation = BarAggregation.DAY else: # pragma: no cover (design-time error) raise RuntimeError( f"unsupported time aggregation resolution, was {resolution}") bar_spec = BarSpecification( step=int(data.i[0]), aggregation=aggregation, price_type=PriceType.LAST, ) bar_type = BarType( instrument_id=instrument_id, bar_spec=bar_spec, aggregation_source=AggregationSource.EXTERNAL, ) return BinanceBar( bar_type=bar_type, open=Price.from_str(data.o), high=Price.from_str(data.h), low=Price.from_str(data.l), close=Price.from_str(data.c), volume=Quantity.from_str(data.v), quote_volume=Quantity.from_str(data.q), count=data.n, taker_buy_base_volume=Quantity.from_str(data.V), taker_buy_quote_volume=Quantity.from_str(data.Q), ts_event=millis_to_nanos(data.T), ts_init=ts_init, )
def test_given_list_of_ticks_aggregates_tick_bars(self): # Arrange instrument = TestInstrumentProvider.default_fx_ccy("USD/JPY") wrangler = QuoteTickDataWrangler(instrument) provider = TestDataProvider() ticks = wrangler.process( provider.read_csv_ticks("truefx-usdjpy-ticks.csv")) bar_store = ObjectStorer() handler = bar_store.store instrument_id = TestStubs.usdjpy_id() bar_spec = BarSpecification(3, BarAggregation.TICK, PriceType.MID) bar_type = BarType(instrument_id, bar_spec) clock = TestClock() logger = Logger(clock) builder = BulkTickBarBuilder(instrument, bar_type, logger, handler) # Act builder.receive(ticks) # Assert assert len(bar_store.get_store()[0]) == 333
def bar_spec_1min_mid() -> BarSpecification: return BarSpecification(1, BarAggregation.MINUTE, PriceType.MID)
def bar_spec_1sec_mid() -> BarSpecification: return BarSpecification(1, BarAggregation.SECOND, PriceType.MID)
class TestBarSpecification: def test_bar_spec_equality(self): # Arrange bar_spec1 = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) bar_spec2 = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) bar_spec3 = BarSpecification(1, BarAggregation.MINUTE, PriceType.ASK) # Act, Assert assert bar_spec1 == bar_spec1 assert bar_spec1 == bar_spec2 assert bar_spec1 != bar_spec3 def test_bar_spec_comparison(self): # Arrange bar_spec1 = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) bar_spec2 = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) bar_spec3 = BarSpecification(1, BarAggregation.MINUTE, PriceType.ASK) # Act, Assert assert bar_spec1 <= bar_spec2 assert bar_spec3 < bar_spec1 assert bar_spec1 > bar_spec3 assert bar_spec1 >= bar_spec3 def test_bar_spec_hash_str_and_repr(self): # Arrange bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) # Act, Assert assert isinstance(hash(bar_spec), int) assert str(bar_spec) == "1-MINUTE-BID" assert repr(bar_spec) == "BarSpecification(1-MINUTE-BID)" @pytest.mark.parametrize( "value", ["", "1", "-1-TICK-MID", "1-TICK_MID"], ) def test_from_str_given_various_invalid_strings_raises_value_error(self, value): # Arrange, Act, Assert with pytest.raises(ValueError): BarSpecification.from_str(value) @pytest.mark.parametrize( "value, expected", [ ["1-MINUTE-BID", BarSpecification(1, BarAggregation.MINUTE, PriceType.BID)], [ "15-MINUTE-MID", BarSpecification(15, BarAggregation.MINUTE, PriceType.MID), ], [ "100-TICK-LAST", BarSpecification(100, BarAggregation.TICK, PriceType.LAST), ], [ "10000-VALUE_IMBALANCE-MID", BarSpecification(10000, BarAggregation.VALUE_IMBALANCE, PriceType.MID), ], ], ) def test_from_str_given_various_valid_string_returns_expected_specification( self, value, expected ): # Arrange, Act spec = BarSpecification.from_str(value) # Assert assert spec == expected @pytest.mark.parametrize( "bar_spec, is_time_aggregated, is_threshold_aggregated, is_information_aggregated", [ [ BarSpecification(1, BarAggregation.SECOND, PriceType.BID), True, False, False, ], [ BarSpecification(1, BarAggregation.MINUTE, PriceType.BID), True, False, False, ], [ BarSpecification(1000, BarAggregation.TICK, PriceType.MID), False, True, False, ], [ BarSpecification(10000, BarAggregation.VALUE_RUNS, PriceType.MID), False, False, True, ], ], ) def test_aggregation_queries( self, bar_spec, is_time_aggregated, is_threshold_aggregated, is_information_aggregated, ): # Arrange, Act, Assert assert bar_spec.is_time_aggregated() == is_time_aggregated assert bar_spec.is_threshold_aggregated() == is_threshold_aggregated assert bar_spec.is_information_aggregated() == is_information_aggregated assert BarSpecification.check_time_aggregated(bar_spec.aggregation) == is_time_aggregated assert ( BarSpecification.check_threshold_aggregated(bar_spec.aggregation) == is_threshold_aggregated ) assert ( BarSpecification.check_information_aggregated(bar_spec.aggregation) == is_information_aggregated )
def bar_spec_1min_last() -> BarSpecification: return BarSpecification(1, BarAggregation.MINUTE, PriceType.LAST)
class TestTimeBarAggregator: def test_instantiate_given_invalid_bar_spec_raises_value_error(self): # Arrange clock = TestClock() bar_store = ObjectStorer() handler = bar_store.store instrument = AUDUSD_SIM bar_spec = BarSpecification(100, BarAggregation.TICK, PriceType.MID) bar_type = BarType(instrument.id, bar_spec) # Act, Assert with pytest.raises(ValueError): TimeBarAggregator( instrument, bar_type, handler, clock, Logger(clock), ) @pytest.mark.parametrize( "bar_spec, expected", [ [ BarSpecification(10, BarAggregation.SECOND, PriceType.MID), int(pd.Timestamp(1970, 1, 1, 0, 0, 10).to_datetime64()), ], [ BarSpecification(1, BarAggregation.MINUTE, PriceType.MID), int(pd.Timestamp(1970, 1, 1, 0, 1).to_datetime64()), ], [ BarSpecification(1, BarAggregation.HOUR, PriceType.MID), int(pd.Timestamp(1970, 1, 1, 1, 0).to_datetime64()), ], [ BarSpecification(1, BarAggregation.DAY, PriceType.MID), int(pd.Timestamp(1970, 1, 2, 0, 0).to_datetime64()), ], ], ) def test_instantiate_with_various_bar_specs(self, bar_spec, expected): # Arrange clock = TestClock() bar_store = ObjectStorer() handler = bar_store.store instrument_id = TestStubs.audusd_id() bar_type = BarType(instrument_id, bar_spec) # Act aggregator = TimeBarAggregator( AUDUSD_SIM, bar_type, handler, clock, Logger(clock), ) # Assert assert aggregator.next_close_ns == expected def test_update_timed_with_test_clock_sends_single_bar_to_handler(self): # Arrange clock = TestClock() bar_store = ObjectStorer() handler = bar_store.store instrument_id = TestStubs.audusd_id() bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.MID) bar_type = BarType(instrument_id, bar_spec) aggregator = TimeBarAggregator( AUDUSD_SIM, bar_type, handler, TestClock(), Logger(clock), ) tick1 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00001"), ask=Price.from_str("1.00004"), bid_size=Quantity.from_int(1), ask_size=Quantity.from_int(1), ts_event=0, ts_init=0, ) tick2 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00002"), ask=Price.from_str("1.00005"), bid_size=Quantity.from_int(1), ask_size=Quantity.from_int(1), ts_event=0, ts_init=0, ) tick3 = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("1.00000"), ask=Price.from_str("1.00003"), bid_size=Quantity.from_int(1), ask_size=Quantity.from_int(1), ts_event=2 * 60 * 1_000_000_000, # 2 minutes in nanoseconds ts_init=2 * 60 * 1_000_000_000, # 2 minutes in nanoseconds ) # Act aggregator.handle_quote_tick(tick1) aggregator.handle_quote_tick(tick2) aggregator.handle_quote_tick(tick3) # Assert assert len(bar_store.get_store()) == 1 assert Price.from_str("1.000025") == bar_store.get_store()[0].open assert Price.from_str("1.000035") == bar_store.get_store()[0].high assert Price.from_str("1.000025") == bar_store.get_store()[0].low assert Price.from_str("1.000035") == bar_store.get_store()[0].close assert Quantity.from_int(2) == bar_store.get_store()[0].volume assert 60_000_000_000 == bar_store.get_store()[0].ts_init
class TestBarType: def test_bar_type_equality(self): # Arrange instrument_id1 = InstrumentId(Symbol("AUD/USD"), Venue("SIM")) instrument_id2 = InstrumentId(Symbol("GBP/USD"), Venue("SIM")) bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) bar_type1 = BarType(instrument_id1, bar_spec) bar_type2 = BarType(instrument_id1, bar_spec) bar_type3 = BarType(instrument_id2, bar_spec) # Act, Assert assert bar_type1 == bar_type1 assert bar_type1 == bar_type2 assert bar_type1 != bar_type3 def test_bar_type_comparison(self): # Arrange instrument_id1 = InstrumentId(Symbol("AUD/USD"), Venue("SIM")) instrument_id2 = InstrumentId(Symbol("GBP/USD"), Venue("SIM")) bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) bar_type1 = BarType(instrument_id1, bar_spec) bar_type2 = BarType(instrument_id1, bar_spec) bar_type3 = BarType(instrument_id2, bar_spec) # Act, Assert assert bar_type1 <= bar_type2 assert bar_type1 < bar_type3 assert bar_type3 > bar_type1 assert bar_type3 >= bar_type1 def test_bar_type_hash_str_and_repr(self): # Arrange instrument_id = InstrumentId(Symbol("AUD/USD"), Venue("SIM")) bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) bar_type = BarType(instrument_id, bar_spec) # Act, Assert assert isinstance(hash(bar_type), int) assert str(bar_type) == "AUD/USD.SIM-1-MINUTE-BID-EXTERNAL" assert repr(bar_type) == "BarType(AUD/USD.SIM-1-MINUTE-BID-EXTERNAL)" @pytest.mark.parametrize( "value", ["", "AUD/USD", "AUD/USD.IDEALPRO-1-MILLISECOND-BID"], ) def test_from_str_given_various_invalid_strings_raises_value_error(self, value): # Arrange, Act, Assert with pytest.raises(ValueError): BarType.from_str(value) @pytest.mark.parametrize( "value, expected", [ [ "AUD/USD.IDEALPRO-1-MINUTE-BID-EXTERNAL", BarType( InstrumentId(Symbol("AUD/USD"), Venue("IDEALPRO")), BarSpecification(1, BarAggregation.MINUTE, PriceType.BID), ), ], [ "GBP/USD.SIM-1000-TICK-MID-INTERNAL", BarType( InstrumentId(Symbol("GBP/USD"), Venue("SIM")), BarSpecification(1000, BarAggregation.TICK, PriceType.MID), AggregationSource.INTERNAL, ), ], [ "AAPL.NYSE-1-HOUR-MID-INTERNAL", BarType( InstrumentId(Symbol("AAPL"), Venue("NYSE")), BarSpecification(1, BarAggregation.HOUR, PriceType.MID), AggregationSource.INTERNAL, ), ], [ "BTCUSDT.BINANCE-100-TICK-LAST-INTERNAL", BarType( InstrumentId(Symbol("BTCUSDT"), Venue("BINANCE")), BarSpecification(100, BarAggregation.TICK, PriceType.LAST), AggregationSource.INTERNAL, ), ], [ "ETH-PERP.FTX-100-TICK-LAST-INTERNAL", BarType( InstrumentId(Symbol("ETH-PERP"), Venue("FTX")), BarSpecification(100, BarAggregation.TICK, PriceType.LAST), AggregationSource.INTERNAL, ), ], ], ) def test_from_str_given_various_valid_string_returns_expected_specification( self, value, expected ): # Arrange, Act bar_type = BarType.from_str(value) # Assert assert expected == bar_type
from nautilus_trader.model.data.bar import BarType from nautilus_trader.model.enums import AggregationSource from nautilus_trader.model.enums import BarAggregation from nautilus_trader.model.enums import PriceType from nautilus_trader.model.identifiers import InstrumentId from nautilus_trader.model.identifiers import Symbol from nautilus_trader.model.identifiers import Venue from nautilus_trader.model.objects import Price from nautilus_trader.model.objects import Quantity from tests.test_kit.stubs.data import TestDataStubs from tests.test_kit.stubs.identifiers import TestIdStubs AUDUSD_SIM = TestIdStubs.audusd_id() GBPUSD_SIM = TestIdStubs.gbpusd_id() ONE_MIN_BID = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) AUDUSD_1_MIN_BID = BarType(AUDUSD_SIM, ONE_MIN_BID) GBPUSD_1_MIN_BID = BarType(GBPUSD_SIM, ONE_MIN_BID) class TestBarSpecification: def test_bar_spec_equality(self): # Arrange bar_spec1 = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) bar_spec2 = BarSpecification(1, BarAggregation.MINUTE, PriceType.BID) bar_spec3 = BarSpecification(1, BarAggregation.MINUTE, PriceType.ASK) # Act, Assert assert bar_spec1 == bar_spec1 assert bar_spec1 == bar_spec2 assert bar_spec1 != bar_spec3
def test_handle_trade_tick_when_volume_beyond_threshold_sends_bars_to_handler( self): # Arrange bar_store = ObjectStorer() handler = bar_store.store instrument_id = TestStubs.audusd_id() bar_spec = BarSpecification(100000, BarAggregation.VALUE, PriceType.LAST) bar_type = BarType(instrument_id, bar_spec) aggregator = ValueBarAggregator( AUDUSD_SIM, bar_type, handler, Logger(TestClock()), ) tick1 = TradeTick( instrument_id=AUDUSD_SIM.id, price=Price.from_str("20.00001"), size=Quantity.from_str("3000.00"), aggressor_side=AggressorSide.BUY, trade_id="123456", ts_event=0, ts_init=0, ) tick2 = TradeTick( instrument_id=AUDUSD_SIM.id, price=Price.from_str("20.00002"), size=Quantity.from_str("4000.00"), aggressor_side=AggressorSide.BUY, trade_id="123457", ts_event=0, ts_init=0, ) tick3 = TradeTick( instrument_id=AUDUSD_SIM.id, price=Price.from_str("20.00000"), size=Quantity.from_str("5000.00"), aggressor_side=AggressorSide.BUY, trade_id="123458", ts_event=0, ts_init=0, ) # Act aggregator.handle_trade_tick(tick1) aggregator.handle_trade_tick(tick2) aggregator.handle_trade_tick(tick3) # Assert assert len(bar_store.get_store()) == 2 assert bar_store.get_store()[0].open == Price.from_str("20.00001") assert bar_store.get_store()[0].high == Price.from_str("20.00002") assert bar_store.get_store()[0].low == Price.from_str("20.00001") assert bar_store.get_store()[0].close == Price.from_str("20.00002") assert bar_store.get_store()[0].volume == Quantity.from_str("5000.00") assert bar_store.get_store()[1].open == Price.from_str("20.00002") assert bar_store.get_store()[1].high == Price.from_str("20.00002") assert bar_store.get_store()[1].low == Price.from_str("20.00000") assert bar_store.get_store()[1].close == Price.from_str("20.00000") assert bar_store.get_store()[1].volume == Quantity.from_str("5000.00") assert aggregator.get_cumulative_value() == Decimal("40000.11000")
def bar_spec_100tick_last() -> BarSpecification: return BarSpecification(100, BarAggregation.TICK, PriceType.LAST)