class BitmexExchangeTests(unittest.TestCase): def setUp(self): # Fixture Setup self.strategies = [ MockStrategy(TestStubs.bartype_btcusdt_binance_1min_bid()) ] self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = TestLogger(self.clock) self.portfolio = Portfolio( clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, clock=self.clock, logger=self.logger, config={'use_previous_close': False}, # To correctly reproduce historical data bars ) self.data_engine.cache.add_instrument(XBTUSD_BITMEX) self.portfolio.register_cache(self.data_engine.cache) self.analyzer = PerformanceAnalyzer() self.trader_id = TraderId("TESTER", "000") self.account_id = AccountId("BITMEX", "001") exec_db = BypassExecutionDatabase( trader_id=self.trader_id, logger=self.logger, ) self.exec_engine = ExecutionEngine( database=exec_db, portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) self.exchange = SimulatedExchange( venue=Venue("BITMEX"), oms_type=OMSType.HEDGING, generate_position_ids=True, is_frozen_account=False, starting_balances=[Money(1_000_000, USD)], exec_cache=self.exec_engine.cache, instruments=[XBTUSD_BITMEX], modules=[], fill_model=FillModel(), clock=self.clock, logger=self.logger, ) self.exec_client = BacktestExecClient( exchange=self.exchange, account_id=self.account_id, engine=self.exec_engine, clock=self.clock, logger=self.logger, ) self.exec_engine.register_client(self.exec_client) self.exchange.register_client(self.exec_client) self.strategy = MockStrategy( bar_type=TestStubs.bartype_btcusdt_binance_1min_bid()) self.strategy.register_trader( self.trader_id, self.clock, self.logger, ) self.data_engine.register_strategy(self.strategy) self.exec_engine.register_strategy(self.strategy) self.data_engine.start() self.exec_engine.start() self.strategy.start() def test_commission_maker_taker_order(self): # Arrange # Prepare market quote1 = QuoteTick( XBTUSD_BITMEX.symbol, Price("11493.70"), Price("11493.75"), Quantity(1500000), Quantity(1500000), UNIX_EPOCH, ) self.data_engine.process(quote1) self.exchange.process_tick(quote1) order_market = self.strategy.order_factory.market( XBTUSD_BITMEX.symbol, OrderSide.BUY, Quantity(100000), ) order_limit = self.strategy.order_factory.limit( XBTUSD_BITMEX.symbol, OrderSide.BUY, Quantity(100000), Price("11493.65"), ) # Act self.strategy.submit_order(order_market) self.strategy.submit_order(order_limit) quote2 = QuoteTick( XBTUSD_BITMEX.symbol, Price("11493.60"), Price("11493.64"), Quantity(1500000), Quantity(1500000), UNIX_EPOCH, ) self.exchange.process_tick(quote2) # Fill the limit order self.portfolio.update_tick(quote2) # Assert self.assertEqual( LiquiditySide.TAKER, self.strategy.object_storer.get_store()[2].liquidity_side) self.assertEqual( LiquiditySide.MAKER, self.strategy.object_storer.get_store()[6].liquidity_side) self.assertEqual(Money("0.00652529", BTC), self.strategy.object_storer.get_store()[2].commission) self.assertEqual(Money("-0.00217511", BTC), self.strategy.object_storer.get_store()[6].commission)
class ExecutionEngineTests(unittest.TestCase): def setUp(self): # Fixture Setup self.clock = LiveClock() self.uuid_factory = UUIDFactory() self.logger = TestLogger(self.clock) self.trader_id = TraderId("TESTER", "000") self.account_id = TestStubs.account_id() self.order_factory = OrderFactory( trader_id=self.trader_id, strategy_id=StrategyId("S", "001"), clock=self.clock, ) self.portfolio = Portfolio( clock=self.clock, logger=self.logger, ) self.portfolio.register_cache(DataCache(self.logger)) self.analyzer = PerformanceAnalyzer() # Fresh isolated loop testing pattern self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self.database = BypassExecutionDatabase(trader_id=self.trader_id, logger=self.logger) self.exec_engine = LiveExecutionEngine( loop=self.loop, database=self.database, portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) def tearDown(self): self.exec_engine.dispose() self.loop.stop() self.loop.close() def test_message_qsize_at_max_blocks_on_put_command(self): self.exec_engine = LiveExecutionEngine(loop=self.loop, database=self.database, portfolio=self.portfolio, clock=self.clock, logger=self.logger, config={"qsize": 1}) strategy = TradingStrategy(order_id_tag="001") strategy.register_trader( TraderId("TESTER", "000"), self.clock, self.logger, ) self.exec_engine.register_strategy(strategy) order = strategy.order_factory.market( AUDUSD_SIM.symbol, OrderSide.BUY, Quantity(100000), ) submit_order = SubmitOrder( Venue("SIM"), self.trader_id, self.account_id, strategy.id, PositionId.null(), order, self.uuid_factory.generate(), self.clock.utc_now(), ) # Act self.exec_engine.execute(submit_order) self.exec_engine.execute(submit_order) # Assert self.assertEqual(1, self.exec_engine.qsize()) self.assertEqual(0, self.exec_engine.command_count) def test_message_qsize_at_max_blocks_on_put_event(self): self.exec_engine = LiveExecutionEngine(loop=self.loop, database=self.database, portfolio=self.portfolio, clock=self.clock, logger=self.logger, config={"qsize": 1}) strategy = TradingStrategy(order_id_tag="001") strategy.register_trader( TraderId("TESTER", "000"), self.clock, self.logger, ) self.exec_engine.register_strategy(strategy) order = strategy.order_factory.market( AUDUSD_SIM.symbol, OrderSide.BUY, Quantity(100000), ) submit_order = SubmitOrder( Venue("SIM"), self.trader_id, self.account_id, strategy.id, PositionId.null(), order, self.uuid_factory.generate(), self.clock.utc_now(), ) event = TestStubs.event_order_submitted(order) # Act self.exec_engine.execute(submit_order) self.exec_engine.process(event) # Assert self.assertEqual(1, self.exec_engine.qsize()) self.assertEqual(0, self.exec_engine.command_count) def test_get_event_loop_returns_expected_loop(self): # Arrange # Act loop = self.exec_engine.get_event_loop() # Assert self.assertEqual(self.loop, loop) def test_start(self): async def run_test(): # Arrange # Act self.exec_engine.start() await asyncio.sleep(0.1) # Assert self.assertEqual(ComponentState.RUNNING, self.exec_engine.state) # Tear Down self.exec_engine.stop() self.loop.run_until_complete(run_test()) def test_kill(self): async def run_test(): # Arrange # Act self.exec_engine.start() await asyncio.sleep(0) self.exec_engine.kill() # Assert self.assertEqual(ComponentState.STOPPED, self.exec_engine.state) self.loop.run_until_complete(run_test()) def test_execute_command_places_command_on_queue(self): async def run_test(): # Arrange self.exec_engine.start() strategy = TradingStrategy(order_id_tag="001") strategy.register_trader( TraderId("TESTER", "000"), self.clock, self.logger, ) self.exec_engine.register_strategy(strategy) order = strategy.order_factory.market( AUDUSD_SIM.symbol, OrderSide.BUY, Quantity(100000), ) submit_order = SubmitOrder( Venue("SIM"), self.trader_id, self.account_id, strategy.id, PositionId.null(), order, self.uuid_factory.generate(), self.clock.utc_now(), ) # Act self.exec_engine.execute(submit_order) await asyncio.sleep(0.1) # Assert self.assertEqual(0, self.exec_engine.qsize()) self.assertEqual(1, self.exec_engine.command_count) # Tear Down self.exec_engine.stop() self.loop.run_until_complete(run_test()) def test_handle_position_opening_with_position_id_none(self): async def run_test(): # Arrange self.exec_engine.start() strategy = TradingStrategy(order_id_tag="001") strategy.register_trader( TraderId("TESTER", "000"), self.clock, self.logger, ) self.exec_engine.register_strategy(strategy) order = strategy.order_factory.market( AUDUSD_SIM.symbol, OrderSide.BUY, Quantity(100000), ) event = TestStubs.event_order_submitted(order) # Act self.exec_engine.process(event) await asyncio.sleep(0.1) # Assert self.assertEqual(0, self.exec_engine.qsize()) self.assertEqual(1, self.exec_engine.event_count) # Tear Down self.exec_engine.stop() self.loop.run_until_complete(run_test())
class DataEngineTests(unittest.TestCase): def setUp(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = Logger(self.clock) self.portfolio = Portfolio( clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) self.portfolio.register_cache(self.data_engine.cache) self.binance_client = BacktestMarketDataClient( instruments=[BTCUSDT_BINANCE, ETHUSDT_BINANCE], name=BINANCE.value, engine=self.data_engine, clock=self.clock, logger=self.logger, ) self.bitmex_client = BacktestMarketDataClient( instruments=[XBTUSD_BITMEX], name=BITMEX.value, engine=self.data_engine, clock=self.clock, logger=self.logger, ) self.quandl = MockMarketDataClient( name="QUANDL", engine=self.data_engine, clock=self.clock, logger=self.logger, ) def test_registered_venues(self): # Arrange # Act # Assert self.assertEqual([], self.data_engine.registered_clients) def test_subscribed_instruments_when_nothing_subscribed_returns_empty_list(self): # Arrange # Act # Assert self.assertEqual([], self.data_engine.subscribed_instruments) def test_subscribed_quote_ticks_when_nothing_subscribed_returns_empty_list(self): # Arrange # Act # Assert self.assertEqual([], self.data_engine.subscribed_quote_ticks) def test_subscribed_trade_ticks_when_nothing_subscribed_returns_empty_list(self): # Arrange # Act # Assert self.assertEqual([], self.data_engine.subscribed_trade_ticks) def test_subscribed_bars_when_nothing_subscribed_returns_empty_list(self): # Arrange # Act # Assert self.assertEqual([], self.data_engine.subscribed_bars) def test_register_client_successfully_adds_client(self): # Arrange # Act self.data_engine.register_client(self.binance_client) # Assert self.assertIn(BINANCE.value, self.data_engine.registered_clients) def test_deregister_client_successfully_removes_client(self): # Arrange self.data_engine.register_client(self.binance_client) # Act self.data_engine.deregister_client(self.binance_client) # Assert self.assertNotIn(BINANCE.value, self.data_engine.registered_clients) def test_register_strategy_successfully_registered_with_strategy(self): # Arrange strategy = TradingStrategy("000") # Act strategy.register_data_engine(self.data_engine) # Assert self.assertEqual(self.data_engine.cache, strategy.data) def test_reset(self): # Arrange # Act self.data_engine.reset() # Assert self.assertEqual(0, self.data_engine.command_count) self.assertEqual(0, self.data_engine.data_count) self.assertEqual(0, self.data_engine.request_count) self.assertEqual(0, self.data_engine.response_count) def test_stop_and_resume(self): # Arrange self.data_engine.start() # Act self.data_engine.stop() self.data_engine.resume() self.data_engine.stop() self.data_engine.reset() # Assert self.assertEqual(0, self.data_engine.command_count) self.assertEqual(0, self.data_engine.data_count) self.assertEqual(0, self.data_engine.request_count) self.assertEqual(0, self.data_engine.response_count) def test_dispose(self): # Arrange self.data_engine.reset() # Act self.data_engine.dispose() # Assert self.assertEqual(0, self.data_engine.command_count) self.assertEqual(0, self.data_engine.data_count) self.assertEqual(0, self.data_engine.request_count) self.assertEqual(0, self.data_engine.response_count) def test_check_connected_when_client_disconnected_returns_false(self): # Arrange self.data_engine.register_client(self.binance_client) self.data_engine.register_client(self.bitmex_client) self.binance_client.disconnect() self.bitmex_client.disconnect() # Act result = self.data_engine.check_connected() # Assert self.assertFalse(result) def test_check_connected_when_client_connected_returns_true(self): # Arrange self.data_engine.register_client(self.binance_client) self.data_engine.register_client(self.bitmex_client) self.binance_client.connect() self.bitmex_client.connect() # Act result = self.data_engine.check_connected() # Assert self.assertTrue(result) def test_check_disconnected_when_client_disconnected_returns_true(self): # Arrange self.data_engine.register_client(self.binance_client) self.data_engine.register_client(self.bitmex_client) # Act result = self.data_engine.check_disconnected() # Assert self.assertTrue(result) def test_check_disconnected_when_client_connected_returns_false(self): # Arrange self.data_engine.register_client(self.binance_client) self.data_engine.register_client(self.bitmex_client) self.binance_client.connect() self.bitmex_client.connect() # Act result = self.data_engine.check_disconnected() # Assert self.assertFalse(result) def test_reset_when_already_disposed_raises_invalid_state_trigger(self): # Arrange self.data_engine.dispose() # Act # Assert self.assertRaises(InvalidStateTrigger, self.data_engine.reset) def test_dispose_when_already_disposed_raises_invalid_state_trigger(self): # Arrange self.data_engine.dispose() # Act # Assert self.assertRaises(InvalidStateTrigger, self.data_engine.dispose) def test_execute_unrecognized_message_logs_and_does_nothing(self): # Arrange self.data_engine.register_client(self.binance_client) # Bogus message command = DataCommand( client_name=BINANCE.value, data_type=DataType(str), handler=[].append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.execute(command) # Assert self.assertEqual(1, self.data_engine.command_count) def test_send_request_when_no_data_clients_registered_does_nothing(self): # Arrange handler = [] request = DataRequest( client_name="RANDOM", data_type=DataType( QuoteTick, metadata={ "InstrumentId": InstrumentId(Symbol("SOMETHING"), Venue("RANDOM")), "FromDateTime": None, "ToDateTime": None, "Limit": 1000, }, ), callback=handler.append, request_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.send(request) # Assert self.assertEqual(1, self.data_engine.request_count) def test_send_data_request_when_data_type_unrecognized_logs_and_does_nothing(self): # Arrange self.data_engine.register_client(self.binance_client) handler = [] request = DataRequest( client_name=BINANCE.value, data_type=DataType( str, metadata={ # str data type is invalid "InstrumentId": InstrumentId(Symbol("SOMETHING"), Venue("RANDOM")), "FromDateTime": None, "ToDateTime": None, "Limit": 1000, }, ), callback=handler.append, request_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.send(request) # Assert self.assertEqual(1, self.data_engine.request_count) def test_send_data_request_with_duplicate_ids_logs_and_does_not_handle_second(self): # Arrange self.data_engine.register_client(self.binance_client) self.data_engine.start() handler = [] uuid = self.uuid_factory.generate() # We'll use this as a duplicate request1 = DataRequest( client_name=BINANCE.value, data_type=DataType( QuoteTick, metadata={ # str data type is invalid "InstrumentId": InstrumentId(Symbol("SOMETHING"), Venue("RANDOM")), "FromDateTime": None, "ToDateTime": None, "Limit": 1000, }, ), callback=handler.append, request_id=uuid, # Duplicate timestamp_ns=self.clock.timestamp_ns(), ) request2 = DataRequest( client_name=BINANCE.value, data_type=DataType( QuoteTick, metadata={ # str data type is invalid "InstrumentId": InstrumentId(Symbol("SOMETHING"), Venue("RANDOM")), "FromDateTime": None, "ToDateTime": None, "Limit": 1000, }, ), callback=handler.append, request_id=uuid, # Duplicate timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.send(request1) self.data_engine.send(request2) # Assert self.assertEqual(2, self.data_engine.request_count) def test_execute_subscribe_when_data_type_unrecognized_logs_and_does_nothing(self): # Arrange self.data_engine.register_client(self.binance_client) subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType(str), # str data type is invalid handler=[].append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual(1, self.data_engine.command_count) def test_execute_subscribe_when_already_subscribed_does_not_add_and_logs(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType( QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=[].append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.execute(subscribe) self.data_engine.execute(subscribe) # Assert self.assertEqual(2, self.data_engine.command_count) def test_execute_subscribe_custom_data(self): # Arrange self.data_engine.register_client(self.binance_client) self.data_engine.register_client(self.quandl) self.binance_client.connect() subscribe = Subscribe( client_name="QUANDL", data_type=DataType(str, metadata={"Type": "news"}), handler=[].append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual(1, self.data_engine.command_count) self.assertEqual(["subscribe"], self.quandl.calls) def test_execute_unsubscribe_custom_data(self): # Arrange self.data_engine.register_client(self.binance_client) self.data_engine.register_client(self.quandl) self.binance_client.connect() handler = [] subscribe = Subscribe( client_name="QUANDL", data_type=DataType(str, metadata={"Type": "news"}), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( client_name="QUANDL", data_type=DataType(str, metadata={"Type": "news"}), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual(2, self.data_engine.command_count) self.assertEqual(["subscribe", "unsubscribe"], self.quandl.calls) def test_execute_unsubscribe_when_data_type_unrecognized_logs_and_does_nothing( self, ): # Arrange self.data_engine.register_client(self.binance_client) handler = [] unsubscribe = Unsubscribe( client_name=BINANCE.value, data_type=DataType(type(str)), # str data type is invalid handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual(1, self.data_engine.command_count) def test_execute_unsubscribe_when_not_subscribed_logs_and_does_nothing(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] unsubscribe = Unsubscribe( client_name=BINANCE.value, data_type=DataType( type(QuoteTick), metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual(1, self.data_engine.command_count) def test_receive_response_when_no_data_clients_registered_does_nothing(self): # Arrange response = DataResponse( client_name=BINANCE.value, data_type=DataType(QuoteTick), data=[], correlation_id=self.uuid_factory.generate(), response_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.receive(response) # Assert self.assertEqual(1, self.data_engine.response_count) def test_process_unrecognized_data_type_logs_and_does_nothing(self): # Arrange data = Data(0) # Act self.data_engine.process(data) # Invalid # Assert self.assertEqual(1, self.data_engine.data_count) def test_process_data_places_data_on_queue(self): # Arrange tick = TestStubs.trade_tick_5decimal() # Act self.data_engine.process(tick) # Assert self.assertEqual(1, self.data_engine.data_count) def test_execute_subscribe_instrument_then_adds_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType( Instrument, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=[].append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_instruments) def test_execute_unsubscribe_instrument_then_removes_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType( Instrument, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( client_name=BINANCE.value, data_type=DataType( Instrument, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual([], self.data_engine.subscribed_instruments) def test_process_instrument_when_subscriber_then_sends_to_registered_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType( Instrument, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe) # Act self.data_engine.process(ETHUSDT_BINANCE) # Assert self.assertEqual([ETHUSDT_BINANCE], handler) def test_process_instrument_when_subscribers_then_sends_to_registered_handlers( self, ): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler1 = [] subscribe1 = Subscribe( client_name=BINANCE.value, data_type=DataType( Instrument, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler1.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) handler2 = [] subscribe2 = Subscribe( client_name=BINANCE.value, data_type=DataType( Instrument, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler2.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe1) self.data_engine.execute(subscribe2) # Act self.data_engine.process(ETHUSDT_BINANCE) # Assert self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_instruments) self.assertEqual([ETHUSDT_BINANCE], handler1) self.assertEqual([ETHUSDT_BINANCE], handler2) def test_execute_subscribe_order_book_stream_then_adds_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType( OrderBook, metadata={ "InstrumentId": ETHUSDT_BINANCE.id, "Level": 2, "Depth": 10, "Interval": 0, }, ), handler=[].append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_order_books) def test_execute_subscribe_order_book_intervals_then_adds_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType( OrderBook, metadata={ "InstrumentId": ETHUSDT_BINANCE.id, "Level": 2, "Depth": 25, "Interval": 10, }, ), handler=[].append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_order_books) def test_execute_unsubscribe_order_book_stream_then_removes_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType( OrderBook, metadata={ "InstrumentId": ETHUSDT_BINANCE.id, "Level": 2, "Depth": 25, "Interval": 0, }, ), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( client_name=BINANCE.value, data_type=DataType( OrderBook, metadata={ "InstrumentId": ETHUSDT_BINANCE.id, "Interval": 0, }, ), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual([], self.data_engine.subscribed_order_books) def test_execute_unsubscribe_order_book_interval_then_removes_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType( OrderBook, metadata={ "InstrumentId": ETHUSDT_BINANCE.id, "Level": 2, "Depth": 25, "Interval": 10, }, ), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( client_name=BINANCE.value, data_type=DataType( OrderBook, metadata={ "InstrumentId": ETHUSDT_BINANCE.id, "Interval": 10, }, ), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual([], self.data_engine.subscribed_order_books) def test_process_order_book_snapshot_when_one_subscriber_then_sends_to_registered_handler( self, ): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType( OrderBook, { "InstrumentId": ETHUSDT_BINANCE.id, "Level": OrderBookLevel.L2, "Depth": 25, "Interval": 0, # Streaming }, ), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe) snapshot = OrderBookSnapshot( instrument_id=ETHUSDT_BINANCE.id, level=OrderBookLevel.L2, bids=[[1000, 1]], asks=[[1001, 1]], timestamp_ns=0, ) # Act self.data_engine.process(snapshot) # Assert assert self.data_engine.subscribed_order_books == [ETHUSDT_BINANCE.id] assert handler[0].instrument_id == ETHUSDT_BINANCE.id assert type(handler[0]) == L2OrderBook def test_process_order_book_ops_then_sends_to_registered_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType( OrderBook, { "InstrumentId": ETHUSDT_BINANCE.id, "Level": OrderBookLevel.L2, "Depth": 25, "Interval": 0, # Streaming }, ), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe) ops = OrderBookOperations( instrument_id=ETHUSDT_BINANCE.id, level=OrderBookLevel.L2, ops=[], timestamp_ns=0, ) # Act self.data_engine.process(ops) # Assert assert self.data_engine.subscribed_order_books == [ETHUSDT_BINANCE.id] assert handler[0].instrument_id == ETHUSDT_BINANCE.id assert type(handler[0]) == L2OrderBook def test_process_order_book_when_multiple_subscribers_then_sends_to_registered_handlers( self, ): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler1 = [] subscribe1 = Subscribe( client_name=BINANCE.value, data_type=DataType( OrderBook, { "InstrumentId": ETHUSDT_BINANCE.id, "Level": OrderBookLevel.L2, "Depth": 25, "Interval": 0, # Streaming }, ), handler=handler1.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) handler2 = [] subscribe2 = Subscribe( client_name=BINANCE.value, data_type=DataType( OrderBook, { "InstrumentId": ETHUSDT_BINANCE.id, "Level": OrderBookLevel.L2, "Depth": 25, "Interval": 0, # Streaming }, ), handler=handler2.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe1) self.data_engine.execute(subscribe2) snapshot = OrderBookSnapshot( instrument_id=ETHUSDT_BINANCE.id, level=OrderBookLevel.L2, bids=[[1000, 1]], asks=[[1001, 1]], timestamp_ns=0, ) # Act self.data_engine.process(snapshot) # Assert cached_book = self.data_engine.cache.order_book(ETHUSDT_BINANCE.id) assert self.data_engine.subscribed_order_books == [ETHUSDT_BINANCE.id] assert type(cached_book) == L2OrderBook assert cached_book.instrument_id == ETHUSDT_BINANCE.id assert handler1[0] == cached_book assert handler2[0] == cached_book def test_execute_subscribe_for_quote_ticks(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType( QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe) # Assert self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_quote_ticks) def test_execute_unsubscribe_for_quote_ticks(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType( QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( client_name=BINANCE.value, data_type=DataType( QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual([], self.data_engine.subscribed_quote_ticks) def test_process_quote_tick_when_subscriber_then_sends_to_registered_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType( QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe) tick = QuoteTick( ETHUSDT_BINANCE.id, Price("100.003"), Price("100.003"), Quantity(1), Quantity(1), 0, ) # Act self.data_engine.process(tick) # Assert self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_quote_ticks) self.assertEqual([tick], handler) def test_process_quote_tick_when_subscribers_then_sends_to_registered_handlers( self, ): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler1 = [] subscribe1 = Subscribe( client_name=BINANCE.value, data_type=DataType( QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler1.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) handler2 = [] subscribe2 = Subscribe( client_name=BINANCE.value, data_type=DataType( QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler2.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe1) self.data_engine.execute(subscribe2) tick = QuoteTick( ETHUSDT_BINANCE.id, Price("100.003"), Price("100.003"), Quantity(1), Quantity(1), 0, ) # Act self.data_engine.process(tick) # Assert self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_quote_ticks) self.assertEqual([tick], handler1) self.assertEqual([tick], handler2) def test_subscribe_trade_tick_then_subscribes(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType( TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_trade_ticks) def test_unsubscribe_trade_tick_then_unsubscribes(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType( TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( client_name=BINANCE.value, data_type=DataType( TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual([], self.data_engine.subscribed_trade_ticks) def test_process_trade_tick_when_subscriber_then_sends_to_registered_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType( TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe) tick = TradeTick( ETHUSDT_BINANCE.id, Price("1050.00000"), Quantity(100), OrderSide.BUY, TradeMatchId("123456789"), 0, ) # Act self.data_engine.process(tick) # Assert self.assertEqual([tick], handler) def test_process_trade_tick_when_subscribers_then_sends_to_registered_handlers( self, ): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler1 = [] subscribe1 = Subscribe( client_name=BINANCE.value, data_type=DataType( TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler1.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) handler2 = [] subscribe2 = Subscribe( client_name=BINANCE.value, data_type=DataType( TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id} ), handler=handler2.append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe1) self.data_engine.execute(subscribe2) tick = TradeTick( ETHUSDT_BINANCE.id, Price("1050.00000"), Quantity(100), OrderSide.BUY, TradeMatchId("123456789"), 0, ) # Act self.data_engine.process(tick) # Assert self.assertEqual([tick], handler1) self.assertEqual([tick], handler2) def test_subscribe_bar_type_then_subscribes(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID) bar_type = BarType(ETHUSDT_BINANCE.id, bar_spec, internal_aggregation=True) handler = ObjectStorer() subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType(Bar, metadata={"BarType": bar_type}), handler=handler.store_2, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual([bar_type], self.data_engine.subscribed_bars) def test_unsubscribe_bar_type_then_unsubscribes(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID) bar_type = BarType(ETHUSDT_BINANCE.id, bar_spec, internal_aggregation=True) handler = ObjectStorer() subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType(Bar, metadata={"BarType": bar_type}), handler=handler.store_2, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( client_name=BINANCE.value, data_type=DataType(Bar, metadata={"BarType": bar_type}), handler=handler.store_2, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual([], self.data_engine.subscribed_bars) def test_process_bar_when_subscriber_then_sends_to_registered_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID) bar_type = BarType(ETHUSDT_BINANCE.id, bar_spec, internal_aggregation=True) handler = ObjectStorer() subscribe = Subscribe( client_name=BINANCE.value, data_type=DataType(Bar, metadata={"BarType": bar_type}), handler=handler.store, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe) bar = Bar( bar_type, Price("1051.00000"), Price("1055.00000"), Price("1050.00000"), Price("1052.00000"), Quantity(100), 0, ) # Act self.data_engine.process(bar) # Assert self.assertEqual([bar], handler.get_store()) def test_process_bar_when_subscribers_then_sends_to_registered_handlers(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID) bar_type = BarType(ETHUSDT_BINANCE.id, bar_spec, internal_aggregation=True) handler1 = ObjectStorer() subscribe1 = Subscribe( client_name=BINANCE.value, data_type=DataType(Bar, metadata={"BarType": bar_type}), handler=handler1.store, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) handler2 = ObjectStorer() subscribe2 = Subscribe( client_name=BINANCE.value, data_type=DataType(Bar, metadata={"BarType": bar_type}), handler=handler2.store, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) self.data_engine.execute(subscribe1) self.data_engine.execute(subscribe2) bar = Bar( bar_type, Price("1051.00000"), Price("1055.00000"), Price("1050.00000"), Price("1052.00000"), Quantity(100), 0, ) # Act self.data_engine.process(bar) # Assert self.assertEqual([bar], handler1.get_store()) self.assertEqual([bar], handler2.get_store())
class TraderTests(unittest.TestCase): def setUp(self): # Fixture Setup clock = TestClock() logger = Logger(clock) trader_id = TraderId("TESTER", "000") account_id = TestStubs.account_id() self.portfolio = Portfolio( clock=clock, logger=logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, clock=clock, logger=logger, config={"use_previous_close": False}, ) self.portfolio.register_cache(self.data_engine.cache) self.analyzer = PerformanceAnalyzer() self.exec_db = BypassExecutionDatabase( trader_id=trader_id, logger=logger, ) self.exec_engine = ExecutionEngine( database=self.exec_db, portfolio=self.portfolio, clock=clock, logger=logger, ) self.exchange = SimulatedExchange( venue=Venue("SIM"), oms_type=OMSType.HEDGING, is_frozen_account=False, starting_balances=[Money(1_000_000, USD)], exec_cache=self.exec_engine.cache, instruments=[USDJPY_SIM], modules=[], fill_model=FillModel(), clock=clock, logger=logger, ) self.data_client = BacktestMarketDataClient( instruments=[USDJPY_SIM], name="SIM", engine=self.data_engine, clock=clock, logger=logger, ) self.data_engine.register_client(self.data_client) self.exec_client = BacktestExecClient( exchange=self.exchange, account_id=account_id, engine=self.exec_engine, clock=clock, logger=logger, ) self.risk_engine = RiskEngine( exec_engine=self.exec_engine, portfolio=self.portfolio, clock=clock, logger=logger, ) self.exec_engine.register_risk_engine(self.risk_engine) self.exec_engine.register_client(self.exec_client) strategies = [ TradingStrategy("001"), TradingStrategy("002"), ] self.trader = Trader( trader_id=trader_id, strategies=strategies, portfolio=self.portfolio, data_engine=self.data_engine, exec_engine=self.exec_engine, risk_engine=self.risk_engine, clock=clock, logger=logger, ) def test_initialize_trader(self): # Arrange # Act trader_id = self.trader.id # Assert self.assertEqual(TraderId("TESTER", "000"), trader_id) self.assertEqual(IdTag("000"), trader_id.tag) self.assertEqual(ComponentState.INITIALIZED, self.trader.state) self.assertEqual(2, len(self.trader.strategy_states())) def test_get_strategy_states(self): # Arrange # Act status = self.trader.strategy_states() # Assert self.assertTrue(StrategyId("TradingStrategy", "001") in status) self.assertTrue(StrategyId("TradingStrategy", "002") in status) self.assertEqual("INITIALIZED", status[StrategyId("TradingStrategy", "001")]) self.assertEqual("INITIALIZED", status[StrategyId("TradingStrategy", "002")]) self.assertEqual(2, len(status)) def test_change_strategies(self): # Arrange strategies = [ TradingStrategy("003"), TradingStrategy("004"), ] # Act self.trader.initialize_strategies(strategies, warn_no_strategies=True) # Assert self.assertTrue(strategies[0].id in self.trader.strategy_states()) self.assertTrue(strategies[1].id in self.trader.strategy_states()) self.assertEqual(2, len(self.trader.strategy_states())) def test_trader_detects_duplicate_identifiers(self): # Arrange strategies = [ TradingStrategy("000"), TradingStrategy("000"), ] # Act self.assertRaises( ValueError, self.trader.initialize_strategies, strategies, True, ) def test_start_a_trader(self): # Arrange # Act self.trader.start() strategy_states = self.trader.strategy_states() # Assert self.assertEqual(ComponentState.RUNNING, self.trader.state) self.assertEqual( "RUNNING", strategy_states[StrategyId("TradingStrategy", "001")] ) self.assertEqual( "RUNNING", strategy_states[StrategyId("TradingStrategy", "002")] ) def test_stop_a_running_trader(self): # Arrange self.trader.start() # Act self.trader.stop() strategy_states = self.trader.strategy_states() # Assert self.assertEqual(ComponentState.STOPPED, self.trader.state) self.assertEqual( "STOPPED", strategy_states[StrategyId("TradingStrategy", "001")] ) self.assertEqual( "STOPPED", strategy_states[StrategyId("TradingStrategy", "002")] )
class DataEngineTests(unittest.TestCase): def setUp(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = TestLogger(self.clock) self.portfolio = Portfolio( clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) self.portfolio.register_cache(self.data_engine.cache) self.binance_client = BacktestDataClient( instruments=[BTCUSDT_BINANCE, ETHUSDT_BINANCE], venue=BINANCE, engine=self.data_engine, clock=self.clock, logger=self.logger, ) self.bitmex_client = BacktestDataClient( instruments=[XBTUSD_BITMEX], venue=BITMEX, engine=self.data_engine, clock=self.clock, logger=self.logger, ) def test_registered_venues(self): # Arrange # Act # Assert self.assertEqual([], self.data_engine.registered_venues) def test_subscribed_instruments_when_nothing_subscribed_returns_empty_list( self): # Arrange # Act # Assert self.assertEqual([], self.data_engine.subscribed_instruments) def test_subscribed_quote_ticks_when_nothing_subscribed_returns_empty_list( self): # Arrange # Act # Assert self.assertEqual([], self.data_engine.subscribed_quote_ticks) def test_subscribed_trade_ticks_when_nothing_subscribed_returns_empty_list( self): # Arrange # Act # Assert self.assertEqual([], self.data_engine.subscribed_trade_ticks) def test_subscribed_bars_when_nothing_subscribed_returns_empty_list(self): # Arrange # Act # Assert self.assertEqual([], self.data_engine.subscribed_bars) def test_register_client_successfully_adds_client(self): # Arrange # Act self.data_engine.register_client(self.binance_client) # Assert self.assertIn(BINANCE, self.data_engine.registered_venues) def test_deregister_client_successfully_removes_client(self): # Arrange self.data_engine.register_client(self.binance_client) # Act self.data_engine.deregister_client(self.binance_client) # Assert self.assertNotIn(BINANCE, self.data_engine.registered_venues) def test_register_strategy_successfully_registered_with_strategy(self): # Arrange strategy = TradingStrategy("000") # Act strategy.register_data_engine(self.data_engine) # Assert self.assertEqual(self.data_engine.cache, strategy.data) def test_reset(self): # Arrange # Act self.data_engine.reset() # Assert self.assertEqual(0, self.data_engine.command_count) self.assertEqual(0, self.data_engine.data_count) self.assertEqual(0, self.data_engine.request_count) self.assertEqual(0, self.data_engine.response_count) def test_stop_and_resume(self): # Arrange self.data_engine.start() # Act self.data_engine.stop() self.data_engine.resume() self.data_engine.stop() self.data_engine.reset() # Assert self.assertEqual(0, self.data_engine.command_count) self.assertEqual(0, self.data_engine.data_count) self.assertEqual(0, self.data_engine.request_count) self.assertEqual(0, self.data_engine.response_count) def test_dispose(self): # Arrange self.data_engine.reset() # Act self.data_engine.dispose() # Assert self.assertEqual(0, self.data_engine.command_count) self.assertEqual(0, self.data_engine.data_count) self.assertEqual(0, self.data_engine.request_count) self.assertEqual(0, self.data_engine.response_count) def test_check_connected_when_client_disconnected_returns_false(self): # Arrange self.data_engine.register_client(self.binance_client) self.data_engine.register_client(self.bitmex_client) self.binance_client.disconnect() self.bitmex_client.disconnect() # Act result = self.data_engine.check_connected() # Assert self.assertFalse(result) def test_check_connected_when_client_connected_returns_true(self): # Arrange self.data_engine.register_client(self.binance_client) self.data_engine.register_client(self.bitmex_client) self.binance_client.connect() self.bitmex_client.connect() # Act result = self.data_engine.check_connected() # Assert self.assertTrue(result) def test_check_disconnected_when_client_disconnected_returns_true(self): # Arrange self.data_engine.register_client(self.binance_client) self.data_engine.register_client(self.bitmex_client) # Act result = self.data_engine.check_disconnected() # Assert self.assertTrue(result) def test_check_disconnected_when_client_connected_returns_false(self): # Arrange self.data_engine.register_client(self.binance_client) self.data_engine.register_client(self.bitmex_client) self.binance_client.connect() self.bitmex_client.connect() # Act result = self.data_engine.check_disconnected() # Assert self.assertFalse(result) def test_reset_when_already_disposed_raises_invalid_state_trigger(self): # Arrange self.data_engine.dispose() # Act # Assert self.assertRaises(InvalidStateTrigger, self.data_engine.reset) def test_dispose_when_already_disposed_raises_invalid_state_trigger(self): # Arrange self.data_engine.dispose() # Act # Assert self.assertRaises(InvalidStateTrigger, self.data_engine.dispose) def test_execute_unrecognized_message_logs_and_does_nothing(self): # Arrange self.data_engine.register_client(self.binance_client) # Bogus message command = DataCommand( venue=BINANCE, data_type=QuoteTick, metadata={}, handler=[].append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(command) # Assert self.assertEqual(1, self.data_engine.command_count) def test_send_request_when_no_data_clients_registered_does_nothing(self): # Arrange handler = [] request = DataRequest( venue=Venue("RANDOM"), data_type=QuoteTick, metadata={ "Symbol": Symbol("SOMETHING", Venue("RANDOM")), "FromDateTime": None, "ToDateTime": None, "Limit": 1000, }, callback=handler.append, request_id=self.uuid_factory.generate(), request_timestamp=self.clock.utc_now(), ) # Act self.data_engine.send(request) # Assert self.assertEqual(1, self.data_engine.request_count) def test_send_data_request_when_data_type_unrecognized_logs_and_does_nothing( self): # Arrange self.data_engine.register_client(self.binance_client) handler = [] request = DataRequest( venue=BINANCE, data_type=str, # str data type is invalid metadata={ "Symbol": Symbol("SOMETHING", Venue("RANDOM")), "FromDateTime": None, "ToDateTime": None, "Limit": 1000, }, callback=handler.append, request_id=self.uuid_factory.generate(), request_timestamp=self.clock.utc_now(), ) # Act self.data_engine.send(request) # Assert self.assertEqual(1, self.data_engine.request_count) def test_send_data_request_with_duplicate_ids_logs_and_does_not_handle_second( self): # Arrange self.data_engine.register_client(self.binance_client) handler = [] uuid = self.uuid_factory.generate() # We'll use this as a duplicate request1 = DataRequest( venue=BINANCE, data_type=QuoteTick, # str data type is invalid metadata={ "Symbol": Symbol("SOMETHING", Venue("RANDOM")), "FromDateTime": None, "ToDateTime": None, "Limit": 1000, }, callback=handler.append, request_id=uuid, # Duplicate request_timestamp=self.clock.utc_now(), ) request2 = DataRequest( venue=BINANCE, data_type=QuoteTick, # str data type is invalid metadata={ "Symbol": Symbol("SOMETHING", Venue("RANDOM")), "FromDateTime": None, "ToDateTime": None, "Limit": 1000, }, callback=handler.append, request_id=uuid, # Duplicate request_timestamp=self.clock.utc_now(), ) # Act self.data_engine.send(request1) self.data_engine.send(request2) # Assert self.assertEqual(2, self.data_engine.request_count) def test_execute_subscribe_when_data_type_unrecognized_logs_and_does_nothing( self): # Arrange self.data_engine.register_client(self.binance_client) subscribe = Subscribe( venue=BINANCE, data_type=str, # str data type is invalid metadata={}, # Invalid anyway handler=[].append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual(1, self.data_engine.command_count) def test_execute_subscribe_when_already_subscribed_does_not_add_and_logs( self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() subscribe = Subscribe( venue=BINANCE, data_type=QuoteTick, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=[].append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(subscribe) self.data_engine.execute(subscribe) # Assert self.assertEqual(2, self.data_engine.command_count) def test_execute_unsubscribe_when_data_type_unrecognized_logs_and_does_nothing( self): # Arrange self.data_engine.register_client(self.binance_client) handler = [] unsubscribe = Unsubscribe( venue=BINANCE, data_type=str, # str data type is invalid metadata={}, # Invalid anyway handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual(1, self.data_engine.command_count) def test_execute_unsubscribe_when_not_subscribed_logs_and_does_nothing( self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] unsubscribe = Unsubscribe( venue=BINANCE, data_type=QuoteTick, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual(1, self.data_engine.command_count) def test_receive_response_when_no_data_clients_registered_does_nothing( self): # Arrange response = DataResponse( venue=BINANCE, data_type=QuoteTick, metadata={}, data=[], correlation_id=self.uuid_factory.generate(), response_id=self.uuid_factory.generate(), response_timestamp=self.clock.utc_now(), ) # Act self.data_engine.receive(response) # Assert self.assertEqual(1, self.data_engine.response_count) def test_process_unrecognized_data_type_logs_and_does_nothing(self): # Arrange # Act self.data_engine.process("DATA!") # Invalid # Assert self.assertEqual(1, self.data_engine.data_count) def test_process_data_places_data_on_queue(self): # Arrange tick = TestStubs.trade_tick_5decimal() # Act self.data_engine.process(tick) # Assert self.assertEqual(1, self.data_engine.data_count) def test_execute_subscribe_instrument_then_adds_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() subscribe = Subscribe( venue=BINANCE, data_type=Instrument, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=[].append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual([ETHUSDT_BINANCE.symbol], self.data_engine.subscribed_instruments) def test_execute_unsubscribe_instrument_then_removes_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( venue=BINANCE, data_type=Instrument, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( venue=BINANCE, data_type=Instrument, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual([], self.data_engine.subscribed_instruments) def test_process_instrument_when_subscriber_then_sends_to_registered_handler( self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( venue=BINANCE, data_type=Instrument, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) # Act self.data_engine.process(ETHUSDT_BINANCE) # Assert self.assertEqual([ETHUSDT_BINANCE], handler) def test_process_instrument_when_subscribers_then_sends_to_registered_handlers( self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler1 = [] subscribe1 = Subscribe( venue=BINANCE, data_type=Instrument, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=handler1.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) handler2 = [] subscribe2 = Subscribe( venue=BINANCE, data_type=Instrument, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=handler2.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe1) self.data_engine.execute(subscribe2) # Act self.data_engine.process(ETHUSDT_BINANCE) # Assert self.assertEqual([ETHUSDT_BINANCE.symbol], self.data_engine.subscribed_instruments) self.assertEqual([ETHUSDT_BINANCE], handler1) self.assertEqual([ETHUSDT_BINANCE], handler2) def test_execute_subscribe_order_book_stream_then_adds_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() subscribe = Subscribe( venue=BINANCE, data_type=OrderBook, metadata={ "Symbol": ETHUSDT_BINANCE.symbol, "Level": 2, "Depth": 10, "Interval": 0, }, handler=[].append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual([ETHUSDT_BINANCE.symbol], self.data_engine.subscribed_order_books) def test_execute_subscribe_order_book_intervals_then_adds_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() subscribe = Subscribe( venue=BINANCE, data_type=OrderBook, metadata={ "Symbol": ETHUSDT_BINANCE.symbol, "Level": 2, "Depth": 25, "Interval": 10, }, handler=[].append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual([ETHUSDT_BINANCE.symbol], self.data_engine.subscribed_order_books) def test_execute_unsubscribe_order_book_stream_then_removes_handler(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( venue=BINANCE, data_type=OrderBook, metadata={ "Symbol": ETHUSDT_BINANCE.symbol, "Level": 2, "Depth": 25, "Interval": 0, }, handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( venue=BINANCE, data_type=OrderBook, metadata={ "Symbol": ETHUSDT_BINANCE.symbol, "Interval": 0, }, handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual([], self.data_engine.subscribed_order_books) def test_execute_unsubscribe_order_book_interval_then_removes_handler( self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( venue=BINANCE, data_type=OrderBook, metadata={ "Symbol": ETHUSDT_BINANCE.symbol, "Level": 2, "Depth": 25, "Interval": 10, }, handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( venue=BINANCE, data_type=OrderBook, metadata={ "Symbol": ETHUSDT_BINANCE.symbol, "Interval": 10, }, handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual([], self.data_engine.subscribed_order_books) def test_process_order_book_when_subscriber_then_sends_to_registered_handler( self): pass # # Arrange # self.data_engine.register_client(self.binance_client) # self.binance_client.connect() # # handler = [] # subscribe = Subscribe( # venue=BINANCE, # data_type=Instrument, # metadata={"Symbol": ETHUSDT_BINANCE.symbol}, # handler=handler.append, # command_id=self.uuid_factory.generate(), # command_timestamp=self.clock.utc_now(), # ) # # self.data_engine.execute(subscribe) # # # Act # self.data_engine.process(ETHUSDT_BINANCE) # # # Assert # self.assertEqual([ETHUSDT_BINANCE], handler) def test_process_order_book_when_subscribers_then_sends_to_registered_handlers( self): pass # # Arrange # self.data_engine.register_client(self.binance_client) # self.binance_client.connect() # # handler1 = [] # subscribe1 = Subscribe( # venue=BINANCE, # data_type=Instrument, # metadata={"Symbol": ETHUSDT_BINANCE.symbol}, # handler=handler1.append, # command_id=self.uuid_factory.generate(), # command_timestamp=self.clock.utc_now(), # ) # # handler2 = [] # subscribe2 = Subscribe( # venue=BINANCE, # data_type=Instrument, # metadata={"Symbol": ETHUSDT_BINANCE.symbol}, # handler=handler2.append, # command_id=self.uuid_factory.generate(), # command_timestamp=self.clock.utc_now(), # ) # # self.data_engine.execute(subscribe1) # self.data_engine.execute(subscribe2) # # # Act # self.data_engine.process(ETHUSDT_BINANCE) # # # Assert # self.assertEqual([ETHUSDT_BINANCE.symbol], self.data_engine.subscribed_instruments) # self.assertEqual([ETHUSDT_BINANCE], handler1) # self.assertEqual([ETHUSDT_BINANCE], handler2) def test_execute_subscribe_for_quote_ticks(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( venue=BINANCE, data_type=QuoteTick, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) # Assert self.assertEqual([ETHUSDT_BINANCE.symbol], self.data_engine.subscribed_quote_ticks) def test_execute_unsubscribe_for_quote_ticks(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( venue=BINANCE, data_type=QuoteTick, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( venue=BINANCE, data_type=QuoteTick, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual([], self.data_engine.subscribed_quote_ticks) def test_process_quote_tick_when_subscriber_then_sends_to_registered_handler( self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( venue=BINANCE, data_type=QuoteTick, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) tick = QuoteTick( ETHUSDT_BINANCE.symbol, Price("100.003"), Price("100.003"), Quantity(1), Quantity(1), UNIX_EPOCH, ) # Act self.data_engine.process(tick) # Assert self.assertEqual([ETHUSDT_BINANCE.symbol], self.data_engine.subscribed_quote_ticks) self.assertEqual([tick], handler) def test_process_quote_tick_when_subscribers_then_sends_to_registered_handlers( self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler1 = [] subscribe1 = Subscribe( venue=BINANCE, data_type=QuoteTick, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=handler1.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) handler2 = [] subscribe2 = Subscribe( venue=BINANCE, data_type=QuoteTick, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=handler2.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe1) self.data_engine.execute(subscribe2) tick = QuoteTick( ETHUSDT_BINANCE.symbol, Price("100.003"), Price("100.003"), Quantity(1), Quantity(1), UNIX_EPOCH, ) # Act self.data_engine.process(tick) # Assert self.assertEqual([ETHUSDT_BINANCE.symbol], self.data_engine.subscribed_quote_ticks) self.assertEqual([tick], handler1) self.assertEqual([tick], handler2) def test_subscribe_trade_tick_then_subscribes(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( venue=BINANCE, data_type=TradeTick, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual([ETHUSDT_BINANCE.symbol], self.data_engine.subscribed_trade_ticks) def test_unsubscribe_trade_tick_then_unsubscribes(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( venue=BINANCE, data_type=TradeTick, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( venue=BINANCE, data_type=TradeTick, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual([], self.data_engine.subscribed_trade_ticks) def test_process_trade_tick_when_subscriber_then_sends_to_registered_handler( self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler = [] subscribe = Subscribe( venue=BINANCE, data_type=TradeTick, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=handler.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) tick = TradeTick( ETHUSDT_BINANCE.symbol, Price("1050.00000"), Quantity(100), OrderSide.BUY, TradeMatchId("123456789"), UNIX_EPOCH, ) # Act self.data_engine.process(tick) # Assert self.assertEqual([tick], handler) def test_process_trade_tick_when_subscribers_then_sends_to_registered_handlers( self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() handler1 = [] subscribe1 = Subscribe( venue=BINANCE, data_type=TradeTick, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=handler1.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) handler2 = [] subscribe2 = Subscribe( venue=BINANCE, data_type=TradeTick, metadata={"Symbol": ETHUSDT_BINANCE.symbol}, handler=handler2.append, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe1) self.data_engine.execute(subscribe2) tick = TradeTick( ETHUSDT_BINANCE.symbol, Price("1050.00000"), Quantity(100), OrderSide.BUY, TradeMatchId("123456789"), UNIX_EPOCH, ) # Act self.data_engine.process(tick) # Assert self.assertEqual([tick], handler1) self.assertEqual([tick], handler2) def test_subscribe_bar_type_then_subscribes(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID) bar_type = BarType(ETHUSDT_BINANCE.symbol, bar_spec, internal_aggregation=True) handler = ObjectStorer() subscribe = Subscribe( venue=BINANCE, data_type=Bar, metadata={"BarType": bar_type}, handler=handler.store_2, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(subscribe) # Assert self.assertEqual([bar_type], self.data_engine.subscribed_bars) def test_unsubscribe_bar_type_then_unsubscribes(self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID) bar_type = BarType(ETHUSDT_BINANCE.symbol, bar_spec, internal_aggregation=True) handler = ObjectStorer() subscribe = Subscribe( venue=BINANCE, data_type=Bar, metadata={"BarType": bar_type}, handler=handler.store_2, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) unsubscribe = Unsubscribe( venue=BINANCE, data_type=Bar, metadata={"BarType": bar_type}, handler=handler.store_2, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) # Act self.data_engine.execute(unsubscribe) # Assert self.assertEqual([], self.data_engine.subscribed_bars) def test_process_bar_when_subscriber_then_sends_to_registered_handler( self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID) bar_type = BarType(ETHUSDT_BINANCE.symbol, bar_spec, internal_aggregation=True) handler = ObjectStorer() subscribe = Subscribe( venue=BINANCE, data_type=Bar, metadata={"BarType": bar_type}, handler=handler.store_2, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe) bar = Bar( Price("1051.00000"), Price("1055.00000"), Price("1050.00000"), Price("1052.00000"), Quantity(100), UNIX_EPOCH, ) data = BarData(bar_type, bar) # Act self.data_engine.process(data) # Assert self.assertEqual([(bar_type, bar)], handler.get_store()) def test_process_bar_when_subscribers_then_sends_to_registered_handlers( self): # Arrange self.data_engine.register_client(self.binance_client) self.binance_client.connect() bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID) bar_type = BarType(ETHUSDT_BINANCE.symbol, bar_spec, internal_aggregation=True) handler1 = ObjectStorer() subscribe1 = Subscribe( venue=BINANCE, data_type=Bar, metadata={"BarType": bar_type}, handler=handler1.store_2, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) handler2 = ObjectStorer() subscribe2 = Subscribe( venue=BINANCE, data_type=Bar, metadata={"BarType": bar_type}, handler=handler2.store_2, command_id=self.uuid_factory.generate(), command_timestamp=self.clock.utc_now(), ) self.data_engine.execute(subscribe1) self.data_engine.execute(subscribe2) bar = Bar( Price("1051.00000"), Price("1055.00000"), Price("1050.00000"), Price("1052.00000"), Quantity(100), UNIX_EPOCH, ) data = BarData(bar_type, bar) # Act self.data_engine.process(data) # Assert self.assertEqual([(bar_type, bar)], handler1.get_store()) self.assertEqual([(bar_type, bar)], handler2.get_store())
class LiveExecutionPerformanceTests(unittest.TestCase): def setUp(self): # Fixture Setup self.clock = LiveClock() self.uuid_factory = UUIDFactory() self.logger = TestLogger(self.clock, bypass_logging=True) self.trader_id = TraderId("TESTER", "000") self.account_id = AccountId("BINANCE", "001") self.portfolio = Portfolio( clock=self.clock, logger=self.logger, ) self.portfolio.register_cache(DataCache(self.logger)) self.analyzer = PerformanceAnalyzer() # Fresh isolated loop testing pattern self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) database = BypassExecutionDatabase(trader_id=self.trader_id, logger=self.logger) self.exec_engine = LiveExecutionEngine( loop=self.loop, database=database, portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) exec_client = MockExecutionClient( venue=Venue("BINANCE"), account_id=self.account_id, engine=self.exec_engine, clock=self.clock, logger=self.logger, ) self.exec_engine.register_client(exec_client) self.exec_engine.process(TestStubs.event_account_state(self.account_id)) self.strategy = TradingStrategy(order_id_tag="001") self.strategy.register_trader( TraderId("TESTER", "000"), self.clock, self.logger, ) self.exec_engine.register_strategy(self.strategy) def submit_order(self): order = self.strategy.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity("1.00000000"), ) self.strategy.submit_order(order) def test_execute_command(self): order = self.strategy.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity("1.00000000"), ) command = SubmitOrder( order.venue, self.trader_id, self.account_id, self.strategy.id, PositionId.null(), order, self.uuid_factory.generate(), self.clock.utc_now(), ) def execute_command(): self.exec_engine.execute(command) PerformanceHarness.profile_function(execute_command, 10000, 1) # ~0.0ms / ~0.3μs / 253ns minimum of 10,000 runs @ 1 iteration each run. def test_submit_order(self): self.exec_engine.start() time.sleep(0.1) async def run_test(): def submit_order(): order = self.strategy.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity("1.00000000"), ) self.strategy.submit_order(order) PerformanceHarness.profile_function(submit_order, 10000, 1) self.loop.run_until_complete(run_test()) # ~0.0ms / ~24.5μs / 24455ns minimum of 10,000 runs @ 1 iteration each run. def test_submit_order_end_to_end(self): self.exec_engine.start() time.sleep(0.1) async def run_test(): for _ in range(10000): order = self.strategy.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity("1.00000000"), ) self.strategy.submit_order(order) stats_file = "perf_live_execution.prof" cProfile.runctx("self.loop.run_until_complete(run_test())", globals(), locals(), stats_file) s = pstats.Stats(stats_file) s.strip_dirs().sort_stats("time").print_stats()
class AccountTests(unittest.TestCase): def setUp(self): # Fixture Setup self.clock = TestClock() logger = Logger(self.clock) self.order_factory = OrderFactory( trader_id=TraderId("TESTER", "000"), strategy_id=StrategyId("S", "001"), clock=TestClock(), ) self.portfolio = Portfolio(self.clock, logger) self.portfolio.register_cache(DataCache(logger)) def test_instantiated_accounts_basic_properties(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money(1_000_000, USD)], [Money(1_000_000, USD)], [Money(0, USD)], info={"default_currency": "USD"}, # Set the default currency event_id=uuid4(), timestamp_ns=0, ) # Act account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Assert self.assertEqual(AccountId("SIM", "001"), account.id) self.assertEqual("Account(id=SIM-001)", str(account)) self.assertEqual("Account(id=SIM-001)", repr(account)) self.assertEqual(int, type(hash(account))) self.assertTrue(account == account) self.assertFalse(account != account) def test_instantiate_single_asset_account(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money(1_000_000, USD)], [Money(1_000_000, USD)], [Money(0, USD)], info={"default_currency": "USD"}, # Set the default currency event_id=uuid4(), timestamp_ns=0, ) # Act account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Assert self.assertEqual(USD, account.default_currency) self.assertEqual(event, account.last_event) self.assertEqual([event], account.events) self.assertEqual(1, account.event_count) self.assertEqual(Money(1_000_000, USD), account.balance()) self.assertEqual(Money(1_000_000, USD), account.balance_free()) self.assertEqual(Money(0, USD), account.balance_locked()) self.assertEqual({USD: Money(1_000_000, USD)}, account.balances()) self.assertEqual({USD: Money(1_000_000, USD)}, account.balances_free()) self.assertEqual({USD: Money(0, USD)}, account.balances_locked()) self.assertEqual(Money(0, USD), account.unrealized_pnl()) self.assertEqual(Money(1_000_000, USD), account.equity()) self.assertEqual({}, account.initial_margins()) self.assertEqual({}, account.maint_margins()) self.assertEqual(None, account.initial_margin()) self.assertEqual(None, account.maint_margin()) def test_instantiate_multi_asset_account(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) # Act account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Assert self.assertEqual(AccountId("SIM", "001"), account.id) self.assertEqual(None, account.default_currency) self.assertEqual(event, account.last_event) self.assertEqual([event], account.events) self.assertEqual(1, account.event_count) self.assertEqual(Money("10.00000000", BTC), account.balance(BTC)) self.assertEqual(Money("20.00000000", ETH), account.balance(ETH)) self.assertEqual(Money("10.00000000", BTC), account.balance_free(BTC)) self.assertEqual(Money("20.00000000", ETH), account.balance_free(ETH)) self.assertEqual(Money("0.00000000", BTC), account.balance_locked(BTC)) self.assertEqual(Money("0.00000000", ETH), account.balance_locked(ETH)) self.assertEqual( { BTC: Money("10.00000000", BTC), ETH: Money("20.00000000", ETH) }, account.balances(), ) self.assertEqual( { BTC: Money("10.00000000", BTC), ETH: Money("20.00000000", ETH) }, account.balances_free(), ) self.assertEqual( { BTC: Money("0.00000000", BTC), ETH: Money("0.00000000", ETH) }, account.balances_locked(), ) self.assertEqual(Money("0.00000000", BTC), account.unrealized_pnl(BTC)) self.assertEqual(Money("0.00000000", ETH), account.unrealized_pnl(ETH)) self.assertEqual(Money("10.00000000", BTC), account.equity(BTC)) self.assertEqual(Money("20.00000000", ETH), account.equity(ETH)) self.assertEqual({}, account.initial_margins()) self.assertEqual({}, account.maint_margins()) self.assertEqual(None, account.initial_margin(BTC)) self.assertEqual(None, account.initial_margin(ETH)) self.assertEqual(None, account.maint_margin(BTC)) self.assertEqual(None, account.maint_margin(ETH)) def test_apply_given_new_state_event_updates_correctly(self): # Arrange event1 = AccountState( AccountId("SIM", "001"), [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) # Act account = Account(event1) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) event2 = AccountState( AccountId("SIM", "001"), [Money("9.00000000", BTC), Money("20.00000000", ETH)], [Money("8.50000000", BTC), Money("20.00000000", ETH)], [Money("0.50000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) # Act account.apply(event=event2) # Assert self.assertEqual(event2, account.last_event) self.assertEqual([event1, event2], account.events) self.assertEqual(2, account.event_count) self.assertEqual(Money("9.00000000", BTC), account.balance(BTC)) self.assertEqual(Money("8.50000000", BTC), account.balance_free(BTC)) self.assertEqual(Money("0.50000000", BTC), account.balance_locked(BTC)) self.assertEqual(Money("20.00000000", ETH), account.balance(ETH)) self.assertEqual(Money("20.00000000", ETH), account.balance_free(ETH)) self.assertEqual(Money("0.00000000", ETH), account.balance_locked(ETH)) def test_update_initial_margin(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) # Act account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) margin = Money("0.00100000", BTC) # Act account.update_initial_margin(margin) # Assert self.assertEqual(margin, account.initial_margin(BTC)) self.assertEqual({BTC: margin}, account.initial_margins()) def test_update_maint_margin(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) # Act account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) margin = Money("0.00050000", BTC) # Act account.update_maint_margin(margin) # Assert self.assertEqual(margin, account.maint_margin(BTC)) self.assertEqual({BTC: margin}, account.maint_margins()) def test_unrealized_pnl_with_single_asset_account_when_no_open_positions_returns_zero( self, ): # Arrange event = AccountState( AccountId("SIM", "001"), balances=[Money(1_000_000, USD)], balances_free=[Money(1_000_000, USD)], balances_locked=[Money(0, USD)], info={"default_currency": "USD"}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result = account.unrealized_pnl() # Assert self.assertEqual(Money(0, USD), result) def test_unrealized_pnl_with_multi_asset_account_when_no_open_positions_returns_zero( self, ): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result = account.unrealized_pnl(BTC) # Assert self.assertEqual(Money("0.00000000", BTC), result) def test_equity_with_single_asset_account_no_default_returns_none(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("100000.00", USD)], [Money("0.00", USD)], [Money("0.00", USD)], info={}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result = account.equity(BTC) # Assert self.assertIsNone(result) def test_equity_with_single_asset_account_returns_expected_money(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("100000.00", USD)], [Money("0.00", USD)], [Money("0.00", USD)], info={"default_currency": "USD"}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result = account.equity() # Assert self.assertEqual(Money("100000.00", USD), result) def test_equity_with_multi_asset_account_returns_expected_money(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result = account.equity(BTC) # Assert self.assertEqual(Money("10.00000000", BTC), result) def test_margin_available_for_single_asset_account(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("100000.00", USD)], [Money("0.00", USD)], [Money("0.00", USD)], info={"default_currency": "USD"}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result1 = account.margin_available() account.update_initial_margin(Money("500.00", USD)) result2 = account.margin_available() account.update_maint_margin(Money("1000.00", USD)) result3 = account.margin_available() # Assert self.assertEqual(Money("100000.00", USD), result1) self.assertEqual(Money("99500.00", USD), result2) self.assertEqual(Money("98500.00", USD), result3) def test_margin_available_for_multi_asset_account(self): # Arrange event = AccountState( AccountId("SIM", "001"), [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("10.00000000", BTC), Money("20.00000000", ETH)], [Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, # No default currency set event_id=uuid4(), timestamp_ns=0, ) account = Account(event) # Wire up account to portfolio account.register_portfolio(self.portfolio) self.portfolio.register_account(account) # Act result1 = account.margin_available(BTC) account.update_initial_margin(Money("0.00010000", BTC)) result2 = account.margin_available(BTC) account.update_maint_margin(Money("0.00020000", BTC)) result3 = account.margin_available(BTC) result4 = account.margin_available(ETH) # Assert self.assertEqual(Money("10.00000000", BTC), result1) self.assertEqual(Money("9.99990000", BTC), result2) self.assertEqual(Money("9.99970000", BTC), result3) self.assertEqual(Money("20.00000000", ETH), result4)
class PortfolioTests(unittest.TestCase): def setUp(self): # Fixture Setup clock = TestClock() logger = TestLogger(clock) self.order_factory = OrderFactory( trader_id=TraderId("TESTER", "000"), strategy_id=StrategyId("S", "001"), clock=TestClock(), ) state = AccountState( account_id=AccountId("BINANCE", "1513111"), balances=[Money("10.00000000", BTC)], balances_free=[Money("0.00000000", BTC)], balances_locked=[Money("0.00000000", BTC)], info={}, event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) self.data_cache = DataCache(logger) self.account = Account(state) self.portfolio = Portfolio(clock, logger) self.portfolio.register_account(self.account) self.portfolio.register_cache(self.data_cache) self.data_cache.add_instrument(AUDUSD_SIM) self.data_cache.add_instrument(GBPUSD_SIM) self.data_cache.add_instrument(BTCUSDT_BINANCE) self.data_cache.add_instrument(BTCUSD_BITMEX) self.data_cache.add_instrument(ETHUSD_BITMEX) def test_account_when_no_account_returns_none(self): # Arrange # Act # Assert self.assertIsNone(self.portfolio.account(SIM)) def test_account_when_account_returns_the_account_facade(self): # Arrange # Act result = self.portfolio.account(BINANCE) # Assert self.assertEqual(self.account, result) def test_net_position_when_no_positions_returns_zero(self): # Arrange # Act # Assert self.assertEqual(Decimal(0), self.portfolio.net_position(AUDUSD_SIM.symbol)) def test_is_net_long_when_no_positions_returns_false(self): # Arrange # Act # Assert self.assertEqual(False, self.portfolio.is_net_long(AUDUSD_SIM.symbol)) def test_is_net_short_when_no_positions_returns_false(self): # Arrange # Act # Assert self.assertEqual(False, self.portfolio.is_net_short(AUDUSD_SIM.symbol)) def test_is_flat_when_no_positions_returns_true(self): # Arrange # Act # Assert self.assertEqual(True, self.portfolio.is_flat(AUDUSD_SIM.symbol)) def test_is_completely_flat_when_no_positions_returns_true(self): # Arrange # Act # Assert self.assertEqual(True, self.portfolio.is_flat(AUDUSD_SIM.symbol)) def test_unrealized_pnl_for_symbol_when_no_instrument_returns_none(self): # Arrange # Act # Assert self.assertIsNone(self.portfolio.unrealized_pnl(USDJPY_SIM.symbol)) def test_unrealized_pnl_for_venue_when_no_account_returns_empty_dict(self): # Arrange # Act # Assert self.assertEqual({}, self.portfolio.unrealized_pnls(SIM)) def test_initial_margins_when_no_account_returns_none(self): # Arrange # Act # Assert self.assertEqual(None, self.portfolio.initial_margins(SIM)) def test_maint_margins_when_no_account_returns_none(self): # Arrange # Act # Assert self.assertEqual(None, self.portfolio.maint_margins(SIM)) def test_open_value_when_no_account_returns_none(self): # Arrange # Act # Assert self.assertEqual(None, self.portfolio.market_values(SIM)) def test_update_tick(self): # Arrange tick = TestStubs.quote_tick_5decimal(GBPUSD_SIM.symbol) # Act self.portfolio.update_tick(tick) # Assert self.assertIsNone(self.portfolio.unrealized_pnl(GBPUSD_SIM.symbol)) def test_update_orders_working(self): # Arrange self.portfolio.register_account(self.account) # Create two working orders order1 = self.order_factory.stop_market( BTCUSDT_BINANCE.symbol, OrderSide.BUY, Quantity("10.5"), Price("25000.00"), ) order2 = self.order_factory.stop_market( BTCUSDT_BINANCE.symbol, OrderSide.BUY, Quantity("10.5"), Price("25000.00"), ) # Push state to FILLED order1.apply(TestStubs.event_order_submitted(order1)) order1.apply(TestStubs.event_order_accepted(order1)) filled1 = TestStubs.event_order_filled( order1, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-1"), strategy_id=StrategyId("S", "1"), fill_price=Price("25000.00"), ) order1.apply(filled1) # Push state to ACCEPTED order2.apply(TestStubs.event_order_submitted(order2)) order2.apply(TestStubs.event_order_accepted(order2)) # Update the last quote last = QuoteTick( BTCUSDT_BINANCE.symbol, Price("25001.00"), Price("25002.00"), Quantity(1), Quantity(1), UNIX_EPOCH, ) # Act self.portfolio.update_tick(last) self.portfolio.initialize_orders({order1, order2}) # Assert self.assertEqual({}, self.portfolio.initial_margins(BINANCE)) def test_update_positions(self): # Arrange self.portfolio.register_account(self.account) # Create a closed position order1 = self.order_factory.market( BTCUSDT_BINANCE.symbol, OrderSide.BUY, Quantity("10.50000000"), ) order2 = self.order_factory.market( BTCUSDT_BINANCE.symbol, OrderSide.SELL, Quantity("10.50000000"), ) filled1 = TestStubs.event_order_filled( order1, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-1"), strategy_id=StrategyId("S", "1"), fill_price=Price("25000.00"), ) filled2 = TestStubs.event_order_filled( order2, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-1"), strategy_id=StrategyId("S", "1"), fill_price=Price("25000.00"), ) position1 = Position(filled1) position1.apply(filled2) order3 = self.order_factory.market( BTCUSDT_BINANCE.symbol, OrderSide.BUY, Quantity("10.00000000"), ) filled3 = TestStubs.event_order_filled( order3, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-2"), strategy_id=StrategyId("S", "1"), fill_price=Price("25000.00"), ) position2 = Position(filled3) # Update the last quote last = QuoteTick( BTCUSDT_BINANCE.symbol, Price("25001.00"), Price("25002.00"), Quantity(1), Quantity(1), UNIX_EPOCH, ) # Act self.portfolio.initialize_positions({position1, position2}) self.portfolio.update_tick(last) # Assert self.assertTrue(self.portfolio.is_net_long(BTCUSDT_BINANCE.symbol)) def test_opening_one_long_position_updates_portfolio(self): # Arrange order = self.order_factory.market( BTCUSDT_BINANCE.symbol, OrderSide.BUY, Quantity("10.000000"), ) fill = TestStubs.event_order_filled( order=order, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-123456"), strategy_id=StrategyId("S", "001"), fill_price=Price("10500.00"), ) last = QuoteTick( BTCUSDT_BINANCE.symbol, Price("10510.00"), Price("10511.00"), Quantity("1.000000"), Quantity("1.000000"), UNIX_EPOCH, ) self.data_cache.add_quote_tick(last) self.portfolio.update_tick(last) position = Position(fill) # Act self.portfolio.update_position(TestStubs.event_position_opened(position)) # Assert self.assertEqual({USDT: Money("105100.00000000", USDT)}, self.portfolio.market_values(BINANCE)) self.assertEqual({USDT: Money("100.00000000", USDT)}, self.portfolio.unrealized_pnls(BINANCE)) self.assertEqual({}, self.portfolio.maint_margins(BINANCE)) self.assertEqual(Money("105100.00000000", USDT), self.portfolio.market_value(BTCUSDT_BINANCE.symbol)) self.assertEqual(Money("100.00000000", USDT), self.portfolio.unrealized_pnl(BTCUSDT_BINANCE.symbol)) self.assertEqual(Decimal("10.00000000"), self.portfolio.net_position(order.symbol)) self.assertTrue(self.portfolio.is_net_long(order.symbol)) self.assertFalse(self.portfolio.is_net_short(order.symbol)) self.assertFalse(self.portfolio.is_flat(order.symbol)) self.assertFalse(self.portfolio.is_completely_flat()) def test_opening_one_short_position_updates_portfolio(self): # Arrange order = self.order_factory.market( BTCUSDT_BINANCE.symbol, OrderSide.SELL, Quantity("0.515"), ) fill = TestStubs.event_order_filled( order=order, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-123456"), strategy_id=StrategyId("S", "001"), fill_price=Price("15000.00") ) last = QuoteTick( BTCUSDT_BINANCE.symbol, Price("15510.15"), Price("15510.25"), Quantity("12.62"), Quantity("3.1"), UNIX_EPOCH, ) self.data_cache.add_quote_tick(last) self.portfolio.update_tick(last) position = Position(fill) # Act self.portfolio.update_position(TestStubs.event_position_opened(position)) # Assert self.assertEqual({USDT: Money("7987.77875000", USDT)}, self.portfolio.market_values(BINANCE)) self.assertEqual({USDT: Money("-262.77875000", USDT)}, self.portfolio.unrealized_pnls(BINANCE)) self.assertEqual({}, self.portfolio.maint_margins(BINANCE)) self.assertEqual(Money("7987.77875000", USDT), self.portfolio.market_value(BTCUSDT_BINANCE.symbol)) self.assertEqual(Money("-262.77875000", USDT), self.portfolio.unrealized_pnl(BTCUSDT_BINANCE.symbol)) self.assertEqual(Decimal("-0.515"), self.portfolio.net_position(order.symbol)) self.assertFalse(self.portfolio.is_net_long(order.symbol)) self.assertTrue(self.portfolio.is_net_short(order.symbol)) self.assertFalse(self.portfolio.is_flat(order.symbol)) self.assertFalse(self.portfolio.is_completely_flat()) def test_opening_positions_with_multi_asset_account(self): # Arrange state = AccountState( account_id=AccountId("BITMEX", "01234"), balances=[Money("10.00000000", BTC), Money("10.00000000", ETH)], balances_free=[Money("0.00000000", BTC), Money("10.00000000", ETH)], balances_locked=[Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) account = Account(state) self.portfolio.register_account(account) last_ethusd = QuoteTick( ETHUSD_BITMEX.symbol, Price("376.05"), Price("377.10"), Quantity("16"), Quantity("25"), UNIX_EPOCH, ) last_btcusd = QuoteTick( BTCUSD_BITMEX.symbol, Price("10500.05"), Price("10501.51"), Quantity("2.54"), Quantity("0.91"), UNIX_EPOCH, ) self.data_cache.add_quote_tick(last_ethusd) self.data_cache.add_quote_tick(last_btcusd) self.portfolio.update_tick(last_ethusd) self.portfolio.update_tick(last_btcusd) order = self.order_factory.market( ETHUSD_BITMEX.symbol, OrderSide.BUY, Quantity(10000), ) fill = TestStubs.event_order_filled( order=order, instrument=ETHUSD_BITMEX, position_id=PositionId("P-123456"), strategy_id=StrategyId("S", "001"), fill_price=Price("376.05"), ) position = Position(fill) # Act self.portfolio.update_position(TestStubs.event_position_opened(position)) # Assert self.assertEqual({ETH: Money("2.65922085", ETH)}, self.portfolio.market_values(BITMEX)) self.assertEqual({ETH: Money("0.03855870", ETH)}, self.portfolio.maint_margins(BITMEX)) self.assertEqual(Money("2.65922085", ETH), self.portfolio.market_value(ETHUSD_BITMEX.symbol)) self.assertEqual(Money("0.00000000", ETH), self.portfolio.unrealized_pnl(ETHUSD_BITMEX.symbol)) def test_unrealized_pnl_when_insufficient_data_for_xrate_returns_none(self): # Arrange state = AccountState( account_id=AccountId("BITMEX", "01234"), balances=[Money("10.00000000", BTC), Money("10.00000000", ETH)], balances_free=[Money("10.00000000", BTC), Money("10.00000000", ETH)], balances_locked=[Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) account = Account(state) self.portfolio.register_account(account) order = self.order_factory.market( ETHUSD_BITMEX.symbol, OrderSide.BUY, Quantity(100), ) fill = TestStubs.event_order_filled( order=order, instrument=ETHUSD_BITMEX, position_id=PositionId("P-123456"), strategy_id=StrategyId("S", "001"), fill_price=Price("376.05"), ) position = Position(fill) self.portfolio.update_position(TestStubs.event_position_opened(position)) # Act result = self.portfolio.unrealized_pnls(BITMEX) # # Assert self.assertIsNone(result) def test_market_value_when_insufficient_data_for_xrate_returns_none(self): # Arrange state = AccountState( account_id=AccountId("BITMEX", "01234"), balances=[Money("10.00000000", BTC), Money("10.00000000", ETH)], balances_free=[Money("10.00000000", BTC), Money("10.00000000", ETH)], balances_locked=[Money("0.00000000", BTC), Money("0.00000000", ETH)], info={}, event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) account = Account(state) self.portfolio.register_account(account) order = self.order_factory.market( ETHUSD_BITMEX.symbol, OrderSide.BUY, Quantity(100), ) fill = TestStubs.event_order_filled( order=order, instrument=ETHUSD_BITMEX, position_id=PositionId("P-123456"), strategy_id=StrategyId("S", "001"), fill_price=Price("376.05"), ) last_ethusd = QuoteTick( ETHUSD_BITMEX.symbol, Price("376.05"), Price("377.10"), Quantity("16"), Quantity("25"), UNIX_EPOCH, ) position = Position(fill) self.portfolio.update_position(TestStubs.event_position_opened(position)) self.data_cache.add_quote_tick(last_ethusd) self.portfolio.update_tick(last_ethusd) # Act result = self.portfolio.market_values(BITMEX) # Assert # TODO: Currently no Quanto thus no xrate required self.assertEqual({ETH: Money('0.02659221', ETH)}, result) def test_opening_several_positions_updates_portfolio(self): # Arrange state = AccountState( AccountId("SIM", "01234"), balances=[Money(1_000_000.00, USD)], balances_free=[Money(1_000_000.00, USD)], balances_locked=[Money(0.00, USD)], info={"default_currency": "USD"}, event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) account = Account(state) self.portfolio.register_account(account) last_audusd = QuoteTick( AUDUSD_SIM.symbol, Price("0.80501"), Price("0.80505"), Quantity(1), Quantity(1), UNIX_EPOCH, ) last_gbpusd = QuoteTick( GBPUSD_SIM.symbol, Price("1.30315"), Price("1.30317"), Quantity(1), Quantity(1), UNIX_EPOCH, ) self.data_cache.add_quote_tick(last_audusd) self.data_cache.add_quote_tick(last_gbpusd) self.portfolio.update_tick(last_audusd) self.portfolio.update_tick(last_gbpusd) order1 = self.order_factory.market( AUDUSD_SIM.symbol, OrderSide.BUY, Quantity(100000), ) order2 = self.order_factory.market( GBPUSD_SIM.symbol, OrderSide.BUY, Quantity(100000), ) order1_filled = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=PositionId("P-1"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00000"), ) order2_filled = TestStubs.event_order_filled( order2, instrument=AUDUSD_SIM, position_id=PositionId("P-2"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00000"), ) position1 = Position(order1_filled) position2 = Position(order2_filled) position_opened1 = TestStubs.event_position_opened(position1) position_opened2 = TestStubs.event_position_opened(position2) # Act self.portfolio.update_position(position_opened1) self.portfolio.update_position(position_opened2) # Assert self.assertEqual({USD: Money("4216.32", USD)}, self.portfolio.market_values(SIM)) self.assertEqual({USD: Money("10816.00", USD)}, self.portfolio.unrealized_pnls(SIM)) self.assertEqual({USD: Money("130.71", USD)}, self.portfolio.maint_margins(SIM)) self.assertEqual(Money("1610.02", USD), self.portfolio.market_value(AUDUSD_SIM.symbol)) self.assertEqual(Money("2606.30", USD), self.portfolio.market_value(GBPUSD_SIM.symbol)) self.assertEqual(Money("-19499.00", USD), self.portfolio.unrealized_pnl(AUDUSD_SIM.symbol)) self.assertEqual(Money("30315.00", USD), self.portfolio.unrealized_pnl(GBPUSD_SIM.symbol)) self.assertEqual(Decimal(100000), self.portfolio.net_position(AUDUSD_SIM.symbol)) self.assertEqual(Decimal(100000), self.portfolio.net_position(GBPUSD_SIM.symbol)) self.assertTrue(self.portfolio.is_net_long(AUDUSD_SIM.symbol)) self.assertFalse(self.portfolio.is_net_short(AUDUSD_SIM.symbol)) self.assertFalse(self.portfolio.is_flat(AUDUSD_SIM.symbol)) self.assertFalse(self.portfolio.is_completely_flat()) def test_modifying_position_updates_portfolio(self): # Arrange state = AccountState( AccountId("SIM", "01234"), balances=[Money(1_000_000.00, USD)], balances_free=[Money(1_000_000.00, USD)], balances_locked=[Money(0.00, USD)], info={"default_currency": "USD"}, event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) account = Account(state) self.portfolio.register_account(account) last_audusd = QuoteTick( AUDUSD_SIM.symbol, Price("0.80501"), Price("0.80505"), Quantity(1), Quantity(1), UNIX_EPOCH, ) self.data_cache.add_quote_tick(last_audusd) self.portfolio.update_tick(last_audusd) order1 = self.order_factory.market( AUDUSD_SIM.symbol, OrderSide.BUY, Quantity(100000), ) order1_filled = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=PositionId("P-123456"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00000"), ) position = Position(order1_filled) self.portfolio.update_position(TestStubs.event_position_opened(position)) order2 = self.order_factory.market( AUDUSD_SIM.symbol, OrderSide.SELL, Quantity(50000), ) order2_filled = TestStubs.event_order_filled( order2, instrument=AUDUSD_SIM, position_id=PositionId("P-123456"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00000"), ) position.apply(order2_filled) # Act self.portfolio.update_position(TestStubs.event_position_changed(position)) # Assert self.assertEqual({USD: Money("805.01", USD)}, self.portfolio.market_values(SIM)) self.assertEqual({USD: Money("-9749.50", USD)}, self.portfolio.unrealized_pnls(SIM)) self.assertEqual({USD: Money("24.96", USD)}, self.portfolio.maint_margins(SIM)) self.assertEqual(Money("805.01", USD), self.portfolio.market_value(AUDUSD_SIM.symbol)) self.assertEqual(Money("-9749.50", USD), self.portfolio.unrealized_pnl(AUDUSD_SIM.symbol)) self.assertEqual(Decimal(50000), self.portfolio.net_position(AUDUSD_SIM.symbol)) self.assertTrue(self.portfolio.is_net_long(AUDUSD_SIM.symbol)) self.assertFalse(self.portfolio.is_net_short(AUDUSD_SIM.symbol)) self.assertFalse(self.portfolio.is_flat(AUDUSD_SIM.symbol)) self.assertFalse(self.portfolio.is_completely_flat()) self.assertEqual({}, self.portfolio.unrealized_pnls(BINANCE)) self.assertEqual({}, self.portfolio.market_values(BINANCE)) def test_closing_position_updates_portfolio(self): # Arrange state = AccountState( AccountId("SIM", "01234"), balances=[Money(1_000_000.00, USD)], balances_free=[Money(1_000_000.00, USD)], balances_locked=[Money(0.00, USD)], info={"default_currency": "USD"}, event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) account = Account(state) self.portfolio.register_account(account) order1 = self.order_factory.market( AUDUSD_SIM.symbol, OrderSide.BUY, Quantity(100000), ) order1_filled = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=PositionId("P-123456"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00000"), ) position = Position(order1_filled) self.portfolio.update_position(TestStubs.event_position_opened(position)) order2 = self.order_factory.market( AUDUSD_SIM.symbol, OrderSide.SELL, Quantity(100000), ) order2_filled = TestStubs.event_order_filled( order2, instrument=AUDUSD_SIM, position_id=PositionId("P-123456"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00010"), ) position.apply(order2_filled) # Act self.portfolio.update_position(TestStubs.event_position_closed(position)) # Assert self.assertEqual({}, self.portfolio.market_values(SIM)) self.assertEqual({}, self.portfolio.unrealized_pnls(SIM)) self.assertEqual({}, self.portfolio.maint_margins(SIM)) self.assertEqual(Money("0", USD), self.portfolio.market_value(AUDUSD_SIM.symbol)) self.assertEqual(Money("0", USD), self.portfolio.unrealized_pnl(AUDUSD_SIM.symbol)) self.assertEqual(Decimal(0), self.portfolio.net_position(AUDUSD_SIM.symbol)) self.assertFalse(self.portfolio.is_net_long(AUDUSD_SIM.symbol)) self.assertFalse(self.portfolio.is_net_short(AUDUSD_SIM.symbol)) self.assertTrue(self.portfolio.is_flat(AUDUSD_SIM.symbol)) self.assertTrue(self.portfolio.is_completely_flat()) def test_several_positions_with_different_symbols_updates_portfolio(self): # Arrange state = AccountState( AccountId("SIM", "01234"), balances=[Money(1_000_000.00, USD)], balances_free=[Money(1_000_000.00, USD)], balances_locked=[Money(0.00, USD)], info={"default_currency": "USD"}, event_id=uuid4(), event_timestamp=UNIX_EPOCH, ) account = Account(state) self.portfolio.register_account(account) order1 = self.order_factory.market( AUDUSD_SIM.symbol, OrderSide.BUY, Quantity(100000), ) order2 = self.order_factory.market( AUDUSD_SIM.symbol, OrderSide.BUY, Quantity(100000), ) order3 = self.order_factory.market( GBPUSD_SIM.symbol, OrderSide.BUY, Quantity(100000), ) order4 = self.order_factory.market( GBPUSD_SIM.symbol, OrderSide.SELL, Quantity(100000), ) order1_filled = TestStubs.event_order_filled( order1, instrument=GBPUSD_SIM, position_id=PositionId("P-1"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00000"), ) order2_filled = TestStubs.event_order_filled( order2, instrument=GBPUSD_SIM, position_id=PositionId("P-2"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00000"), ) order3_filled = TestStubs.event_order_filled( order3, instrument=GBPUSD_SIM, position_id=PositionId("P-3"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00000"), ) order4_filled = TestStubs.event_order_filled( order4, instrument=GBPUSD_SIM, position_id=PositionId("P-3"), strategy_id=StrategyId("S", "1"), fill_price=Price("1.00100"), ) position1 = Position(order1_filled) position2 = Position(order2_filled) position3 = Position(order3_filled) last_audusd = QuoteTick( AUDUSD_SIM.symbol, Price("0.80501"), Price("0.80505"), Quantity(1), Quantity(1), UNIX_EPOCH, ) last_gbpusd = QuoteTick( GBPUSD_SIM.symbol, Price("1.30315"), Price("1.30317"), Quantity(1), Quantity(1), UNIX_EPOCH, ) self.data_cache.add_quote_tick(last_audusd) self.data_cache.add_quote_tick(last_gbpusd) self.portfolio.update_tick(last_audusd) self.portfolio.update_tick(last_gbpusd) # Act self.portfolio.update_position(TestStubs.event_position_opened(position1)) self.portfolio.update_position(TestStubs.event_position_opened(position2)) self.portfolio.update_position(TestStubs.event_position_opened(position3)) position3.apply(order4_filled) self.portfolio.update_position(TestStubs.event_position_closed(position3)) # Assert self.assertEqual({USD: Money("-38998.00", USD)}, self.portfolio.unrealized_pnls(SIM)) self.assertEqual({USD: Money("3220.04", USD)}, self.portfolio.market_values(SIM)) self.assertEqual({USD: Money("99.82", USD)}, self.portfolio.maint_margins(SIM)) self.assertEqual(Money("3220.04", USD), self.portfolio.market_value(AUDUSD_SIM.symbol)) self.assertEqual(Money("-38998.00", USD), self.portfolio.unrealized_pnl(AUDUSD_SIM.symbol)) self.assertEqual(Money("0", USD), self.portfolio.unrealized_pnl(GBPUSD_SIM.symbol)) self.assertEqual(Decimal(200000), self.portfolio.net_position(AUDUSD_SIM.symbol)) self.assertEqual(Decimal(0), self.portfolio.net_position(GBPUSD_SIM.symbol)) self.assertTrue(self.portfolio.is_net_long(AUDUSD_SIM.symbol)) self.assertTrue(self.portfolio.is_flat(GBPUSD_SIM.symbol)) self.assertFalse(self.portfolio.is_completely_flat())