class LiveDataEngineTests(unittest.TestCase): def setUp(self): # Fixture Setup self.clock = LiveClock() self.uuid_factory = UUIDFactory() self.logger = TestLogger(self.clock, level_console=LogLevel.DEBUG) self.portfolio = Portfolio( clock=self.clock, logger=self.logger, ) # Fresh isolated loop testing pattern self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self.data_engine = LiveDataEngine( loop=self.loop, portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) def tearDown(self): self.data_engine.dispose() self.loop.stop() self.loop.close() def test_message_qsize_at_max_blocks_on_put_data_command(self): self.data_engine = LiveDataEngine(loop=self.loop, portfolio=self.portfolio, clock=self.clock, logger=self.logger, config={"qsize": 1}) subscribe = Subscribe( 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(subscribe) self.data_engine.execute(subscribe) # Assert self.assertEqual(1, self.data_engine.message_qsize()) self.assertEqual(0, self.data_engine.command_count) def test_message_qsize_at_max_blocks_on_send_request(self): self.data_engine = LiveDataEngine(loop=self.loop, portfolio=self.portfolio, clock=self.clock, logger=self.logger, config={"qsize": 1}) 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) self.data_engine.send(request) # Assert self.assertEqual(1, self.data_engine.message_qsize()) self.assertEqual(0, self.data_engine.command_count) def test_message_qsize_at_max_blocks_on_receive_response(self): self.data_engine = LiveDataEngine(loop=self.loop, portfolio=self.portfolio, clock=self.clock, logger=self.logger, config={"qsize": 1}) response = DataResponse( venue=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) self.data_engine.receive(response) # Assert self.assertEqual(1, self.data_engine.message_qsize()) self.assertEqual(0, self.data_engine.command_count) def test_data_qsize_at_max_blocks_on_put_data(self): self.data_engine = LiveDataEngine(loop=self.loop, portfolio=self.portfolio, clock=self.clock, logger=self.logger, config={"qsize": 1}) # Act self.data_engine.process("some_data") self.data_engine.process("some_data") # Assert self.assertEqual(1, self.data_engine.data_qsize()) self.assertEqual(0, self.data_engine.data_count) def test_get_event_loop_returns_expected_loop(self): # Arrange # Act loop = self.data_engine.get_event_loop() # Assert self.assertEqual(self.loop, loop) def test_start(self): async def run_test(): # Arrange # Act self.data_engine.start() await asyncio.sleep(0.1) # Assert self.assertEqual(ComponentState.RUNNING, self.data_engine.state) # Tear Down self.data_engine.stop() self.loop.run_until_complete(run_test()) def test_kill(self): async def run_test(): # Arrange # Act self.data_engine.start() await asyncio.sleep(0) self.data_engine.kill() # Assert self.assertEqual(ComponentState.STOPPED, self.data_engine.state) self.loop.run_until_complete(run_test()) def test_execute_command_processes_message(self): async def run_test(): # Arrange self.data_engine.start() subscribe = Subscribe( 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(subscribe) await asyncio.sleep(0.1) # Assert self.assertEqual(0, self.data_engine.message_qsize()) self.assertEqual(1, self.data_engine.command_count) # Tear Down self.data_engine.stop() self.loop.run_until_complete(run_test()) def test_send_request_processes_message(self): async def run_test(): # Arrange self.data_engine.start() 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) await asyncio.sleep(0.1) # Assert self.assertEqual(0, self.data_engine.message_qsize()) self.assertEqual(1, self.data_engine.request_count) # Tear Down self.data_engine.stop() self.loop.run_until_complete(run_test()) def test_receive_response_processes_message(self): async def run_test(): # Arrange self.data_engine.start() response = DataResponse( venue=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) await asyncio.sleep(0.1) # Assert self.assertEqual(0, self.data_engine.message_qsize()) self.assertEqual(1, self.data_engine.response_count) # Tear Down self.data_engine.stop() self.loop.run_until_complete(run_test()) def test_process_data_processes_data(self): async def run_test(): # Arrange self.data_engine.start() # Act tick = TestStubs.trade_tick_5decimal() # Act self.data_engine.process(tick) await asyncio.sleep(0.1) # Assert self.assertEqual(0, self.data_engine.data_qsize()) self.assertEqual(1, self.data_engine.data_count) # Tear Down self.data_engine.stop() self.loop.run_until_complete(run_test())
class CCXTDataClientTests(unittest.TestCase): def setUp(self): # Fixture Setup self.clock = LiveClock() self.uuid_factory = UUIDFactory() self.trader_id = TraderId("TESTER", "001") # Fresh isolated loop testing pattern self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) # Setup logging self.logger = LiveLogger( loop=self.loop, clock=self.clock, ) self.portfolio = Portfolio( clock=self.clock, logger=self.logger, ) self.data_engine = LiveDataEngine( loop=self.loop, portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) # Setup mock CCXT exchange with open(TEST_PATH + "markets.json") as response: markets = json.load(response) with open(TEST_PATH + "currencies.json") as response: currencies = json.load(response) with open(TEST_PATH + "watch_order_book.json") as response: order_book = json.load(response) with open(TEST_PATH + "fetch_trades.json") as response: fetch_trades = json.load(response) with open(TEST_PATH + "watch_trades.json") as response: watch_trades = json.load(response) self.mock_ccxt = MagicMock() self.mock_ccxt.name = "Binance" self.mock_ccxt.precisionMode = 2 self.mock_ccxt.markets = markets self.mock_ccxt.currencies = currencies self.mock_ccxt.watch_order_book = order_book self.mock_ccxt.watch_trades = watch_trades self.mock_ccxt.fetch_trades = fetch_trades self.client = CCXTDataClient( client=self.mock_ccxt, engine=self.data_engine, clock=self.clock, logger=self.logger, ) self.data_engine.register_client(self.client) def tearDown(self): self.loop.stop() self.loop.close() def test_connect(self): async def run_test(): # Arrange # Act self.data_engine.start() # Also starts client await asyncio.sleep(0.3) # Allow engine message queue to start # Assert self.assertTrue(self.client.is_connected) # Tear down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_disconnect(self): async def run_test(): # Arrange self.data_engine.start() # Also starts client await asyncio.sleep(0.3) # Allow engine message queue to start # Act self.client.disconnect() await asyncio.sleep(0.3) # Assert self.assertFalse(self.client.is_connected) # Tear down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_reset_when_not_connected_successfully_resets(self): async def run_test(): # Arrange self.data_engine.start() # Also starts client await asyncio.sleep(0.3) # Allow engine message queue to start self.data_engine.stop() await asyncio.sleep(0.3) # Allow engine message queue to stop # Act self.client.reset() # Assert self.assertFalse(self.client.is_connected) self.loop.run_until_complete(run_test()) def test_reset_when_connected_does_not_reset(self): async def run_test(): # Arrange self.data_engine.start() # Also starts client await asyncio.sleep(0.3) # Allow engine message queue to start # Act self.client.reset() # Assert self.assertTrue(self.client.is_connected) # Tear Down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_dispose_when_not_connected_does_not_dispose(self): async def run_test(): # Arrange self.data_engine.start() # Also starts client await asyncio.sleep(0.3) # Allow engine message queue to start # Act self.client.dispose() # Assert self.assertTrue(self.client.is_connected) # Tear Down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_subscribe_instrument(self): async def run_test(): # Arrange self.data_engine.start() # Also starts client await asyncio.sleep(0.3) # Allow engine message queue to start # Act self.client.subscribe_instrument(BTCUSDT) # Assert self.assertIn(BTCUSDT, self.client.subscribed_instruments) # Tear Down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_subscribe_quote_ticks(self): async def run_test(): # Arrange self.data_engine.start() # Also starts client await asyncio.sleep(0.3) # Allow engine message queue to start # Act self.client.subscribe_quote_ticks(ETHUSDT) await asyncio.sleep(0.3) # Assert self.assertIn(ETHUSDT, self.client.subscribed_quote_ticks) self.assertTrue(self.data_engine.cache.has_quote_ticks(ETHUSDT)) # Tear Down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_subscribe_trade_ticks(self): async def run_test(): # Arrange self.data_engine.start() # Also starts client await asyncio.sleep(0.3) # Allow engine message queue to start # Act self.client.subscribe_trade_ticks(ETHUSDT) await asyncio.sleep(0.3) # Assert self.assertIn(ETHUSDT, self.client.subscribed_trade_ticks) self.assertTrue(self.data_engine.cache.has_trade_ticks(ETHUSDT)) # Tear Down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_subscribe_bars(self): async def run_test(): # Arrange self.data_engine.start() # Also starts client await asyncio.sleep(0.5) # Allow engine message queue to start bar_type = TestStubs.bartype_btcusdt_binance_100tick_last() # Act self.client.subscribe_bars(bar_type) # Assert self.assertIn(bar_type, self.client.subscribed_bars) # Tear Down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_unsubscribe_instrument(self): async def run_test(): # Arrange self.data_engine.start() # Also starts client await asyncio.sleep(0.3) # Allow engine message queue to start self.client.subscribe_instrument(BTCUSDT) # Act self.client.unsubscribe_instrument(BTCUSDT) # Assert self.assertNotIn(BTCUSDT, self.client.subscribed_instruments) # Tear Down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_unsubscribe_quote_ticks(self): async def run_test(): # Arrange self.data_engine.start() # Also starts client await asyncio.sleep(0.3) # Allow engine message queue to start self.client.subscribe_quote_ticks(ETHUSDT) await asyncio.sleep(0.3) # Act self.client.unsubscribe_quote_ticks(ETHUSDT) # Assert self.assertNotIn(ETHUSDT, self.client.subscribed_quote_ticks) # Tear Down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_unsubscribe_trade_ticks(self): async def run_test(): # Arrange self.data_engine.start() # Also starts client await asyncio.sleep(0.3) # Allow engine message queue to start self.client.subscribe_trade_ticks(ETHUSDT) # Act self.client.unsubscribe_trade_ticks(ETHUSDT) # Assert self.assertNotIn(ETHUSDT, self.client.subscribed_trade_ticks) # Tear Down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_unsubscribe_bars(self): async def run_test(): # Arrange self.data_engine.start() # Also starts client await asyncio.sleep(0.3) # Allow engine message queue to start bar_type = TestStubs.bartype_btcusdt_binance_100tick_last() self.client.subscribe_bars(bar_type) # Act self.client.unsubscribe_bars(bar_type) # Assert self.assertNotIn(bar_type, self.client.subscribed_bars) # Tear Down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_request_instrument(self): async def run_test(): # Arrange self.data_engine.start() await asyncio.sleep(0.5) # Allow engine message queue to start # Act self.client.request_instrument(BTCUSDT, uuid4()) await asyncio.sleep(0.5) # Assert # Instruments additionally requested on start self.assertEqual(1, self.data_engine.response_count) # Tear Down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_request_instruments(self): async def run_test(): # Arrange self.data_engine.start() # Also starts client await asyncio.sleep(0.5) # Allow engine message queue to start # Act self.client.request_instruments(uuid4()) await asyncio.sleep(0.5) # Assert # Instruments additionally requested on start self.assertEqual(1, self.data_engine.response_count) # Tear Down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_request_quote_ticks(self): async def run_test(): # Arrange self.data_engine.start() # Also starts client await asyncio.sleep(0.3) # Allow engine message queue to start # Act self.client.request_quote_ticks(BTCUSDT, None, None, 0, uuid4()) # Assert self.assertTrue(True) # Logs warning # Tear Down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_request_trade_ticks(self): async def run_test(): # Arrange self.data_engine.start() # Also starts client await asyncio.sleep(0.3) # Allow engine message queue to start handler = ObjectStorer() request = DataRequest( client_name=BINANCE.value, data_type=DataType( TradeTick, metadata={ "InstrumentId": ETHUSDT, "FromDateTime": None, "ToDateTime": None, "Limit": 100, }, ), callback=handler.store, request_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.send(request) await asyncio.sleep(1) # Assert self.assertEqual(1, self.data_engine.response_count) self.assertEqual(1, handler.count) # Tear Down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_request_bars(self): async def run_test(): # Arrange with open(TEST_PATH + "fetch_ohlcv.json") as response: fetch_ohlcv = json.load(response) self.mock_ccxt.fetch_ohlcv = fetch_ohlcv self.data_engine.start() # Also starts client await asyncio.sleep(0.3) # Allow engine message queue to start handler = ObjectStorer() bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.LAST) bar_type = BarType(instrument_id=ETHUSDT, bar_spec=bar_spec) request = DataRequest( client_name=BINANCE.value, data_type=DataType( Bar, metadata={ "BarType": bar_type, "FromDateTime": None, "ToDateTime": None, "Limit": 100, }, ), callback=handler.store, request_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.send(request) await asyncio.sleep(0.3) # Assert self.assertEqual(1, self.data_engine.response_count) self.assertEqual(1, handler.count) self.assertEqual(100, len(handler.get_store()[0])) # Tear Down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test())
class LiveDataEngineTests(unittest.TestCase): def setUp(self): # Fixture Setup self.clock = LiveClock() self.uuid_factory = UUIDFactory() self.logger = Logger(self.clock, level_stdout=LogLevel.DEBUG) self.portfolio = Portfolio( clock=self.clock, logger=self.logger, ) # Fresh isolated loop testing pattern self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self.engine = LiveDataEngine( loop=self.loop, portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) def tearDown(self): self.engine.dispose() self.loop.stop() self.loop.close() def test_start_when_loop_not_running_logs(self): # Arrange # Act self.engine.start() # Assert self.assertTrue(True) # No exceptions raised self.engine.stop() def test_message_qsize_at_max_blocks_on_put_data_command(self): # Arrange self.engine = LiveDataEngine( loop=self.loop, portfolio=self.portfolio, clock=self.clock, logger=self.logger, config={"qsize": 1}, ) subscribe = Subscribe( client_id=ClientId(BINANCE.value), data_type=DataType(QuoteTick), handler=[].append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.engine.execute(subscribe) self.engine.execute(subscribe) # Assert self.assertEqual(1, self.engine.message_qsize()) self.assertEqual(0, self.engine.command_count) def test_message_qsize_at_max_blocks_on_send_request(self): # Arrange self.engine = LiveDataEngine( loop=self.loop, portfolio=self.portfolio, clock=self.clock, logger=self.logger, config={"qsize": 1}, ) handler = [] request = DataRequest( client_id=ClientId("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.engine.send(request) self.engine.send(request) # Assert self.assertEqual(1, self.engine.message_qsize()) self.assertEqual(0, self.engine.command_count) def test_message_qsize_at_max_blocks_on_receive_response(self): # Arrange self.engine = LiveDataEngine( loop=self.loop, portfolio=self.portfolio, clock=self.clock, logger=self.logger, config={"qsize": 1}, ) response = DataResponse( client_id=ClientId("BINANCE"), 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.engine.receive(response) self.engine.receive(response) # Add over max size # Assert self.assertEqual(1, self.engine.message_qsize()) self.assertEqual(0, self.engine.command_count) def test_data_qsize_at_max_blocks_on_put_data(self): # Arrange self.engine = LiveDataEngine( loop=self.loop, portfolio=self.portfolio, clock=self.clock, logger=self.logger, config={"qsize": 1}, ) data = Data(1_000_000_000) # Act self.engine.process(data) self.engine.process(data) # Add over max size # Assert self.assertEqual(1, self.engine.data_qsize()) self.assertEqual(0, self.engine.data_count) def test_get_event_loop_returns_expected_loop(self): # Arrange # Act loop = self.engine.get_event_loop() # Assert self.assertEqual(self.loop, loop) def test_start(self): async def run_test(): # Arrange # Act self.engine.start() await asyncio.sleep(0.1) # Assert self.assertEqual(ComponentState.RUNNING, self.engine.state) # Tear Down self.engine.stop() self.loop.run_until_complete(run_test()) def test_kill_when_running_and_no_messages_on_queues(self): async def run_test(): # Arrange # Act self.engine.start() await asyncio.sleep(0) self.engine.kill() # Assert self.assertEqual(ComponentState.STOPPED, self.engine.state) self.loop.run_until_complete(run_test()) def test_kill_when_not_running_with_messages_on_queue(self): async def run_test(): # Arrange # Act self.engine.kill() # Assert self.assertEqual(0, self.engine.data_qsize()) self.loop.run_until_complete(run_test()) def test_execute_command_processes_message(self): async def run_test(): # Arrange self.engine.start() subscribe = Subscribe( client_id=ClientId(BINANCE.value), data_type=DataType(QuoteTick), handler=[].append, command_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.engine.execute(subscribe) await asyncio.sleep(0.1) # Assert self.assertEqual(0, self.engine.message_qsize()) self.assertEqual(1, self.engine.command_count) # Tear Down self.engine.stop() self.loop.run_until_complete(run_test()) def test_send_request_processes_message(self): async def run_test(): # Arrange self.engine.start() handler = [] request = DataRequest( client_id=ClientId("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.engine.send(request) await asyncio.sleep(0.1) # Assert self.assertEqual(0, self.engine.message_qsize()) self.assertEqual(1, self.engine.request_count) # Tear Down self.engine.stop() self.loop.run_until_complete(run_test()) def test_receive_response_processes_message(self): async def run_test(): # Arrange self.engine.start() response = DataResponse( client_id=ClientId("BINANCE"), 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.engine.receive(response) await asyncio.sleep(0.1) # Assert self.assertEqual(0, self.engine.message_qsize()) self.assertEqual(1, self.engine.response_count) # Tear Down self.engine.stop() self.loop.run_until_complete(run_test()) def test_process_data_processes_data(self): async def run_test(): # Arrange self.engine.start() # Act tick = TestStubs.trade_tick_5decimal() # Act self.engine.process(tick) await asyncio.sleep(0.1) # Assert self.assertEqual(0, self.engine.data_qsize()) self.assertEqual(1, self.engine.data_count) # Tear Down self.engine.stop() self.loop.run_until_complete(run_test())
class OandaDataClientTests(unittest.TestCase): def setUp(self): # Fixture Setup self.clock = LiveClock() self.uuid_factory = UUIDFactory() self.trader_id = TraderId("TESTER", "001") # Fresh isolated loop testing pattern self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self.executor = concurrent.futures.ThreadPoolExecutor() self.loop.set_default_executor(self.executor) self.loop.set_debug(True) # TODO: Development # Setup logging logger = LiveLogger( clock=self.clock, name=self.trader_id.value, level_console=LogLevel.DEBUG, level_file=LogLevel.DEBUG, level_store=LogLevel.WARNING, ) self.logger = LiveLogger(self.clock) self.portfolio = Portfolio( clock=self.clock, logger=self.logger, ) self.data_engine = LiveDataEngine( loop=self.loop, portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) self.mock_oanda = MagicMock() self.client = OandaDataClient( client=self.mock_oanda, account_id="001", engine=self.data_engine, clock=self.clock, logger=logger, ) self.data_engine.register_client(self.client) with open(TEST_PATH + "instruments.json") as response: instruments = json.load(response) self.mock_oanda.request.return_value = instruments def tearDown(self): self.executor.shutdown(wait=True) self.loop.stop() self.loop.close() # TODO: WIP # def test_connect(self): # async def run_test(): # # Arrange # # Act # self.data_engine.start() # Also connects client # await asyncio.sleep(0.3) # # # Assert # self.assertTrue(self.client.is_connected) # # # Tear Down # self.data_engine.stop() # # self.loop.run_until_complete(run_test()) def test_disconnect(self): # Arrange self.client.connect() # Act self.client.disconnect() # Assert self.assertFalse(self.client.is_connected) def test_reset(self): # Arrange # Act self.client.reset() # Assert self.assertFalse(self.client.is_connected) def test_dispose(self): # Arrange # Act self.client.dispose() # Assert self.assertFalse(self.client.is_connected) def test_subscribe_instrument(self): # Arrange self.client.connect() # Act self.client.subscribe_instrument(AUDUSD) # Assert self.assertIn(AUDUSD, self.client.subscribed_instruments) def test_subscribe_quote_ticks(self): async def run_test(): # Arrange self.mock_oanda.request.return_value = {"type": {"HEARTBEAT": "0"}} self.data_engine.start() # Act self.client.subscribe_quote_ticks(AUDUSD) await asyncio.sleep(0.3) # Assert self.assertIn(AUDUSD, self.client.subscribed_quote_ticks) # Tear Down self.data_engine.stop() self.loop.run_until_complete(run_test()) def test_subscribe_bars(self): # Arrange bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.MID) bar_type = BarType(instrument_id=AUDUSD, bar_spec=bar_spec) # Act self.client.subscribe_bars(bar_type) # Assert self.assertTrue(True) def test_unsubscribe_instrument(self): # Arrange self.client.connect() # Act self.client.unsubscribe_instrument(AUDUSD) # Assert self.assertTrue(True) def test_unsubscribe_quote_ticks(self): async def run_test(): # Arrange self.mock_oanda.request.return_value = {"type": {"HEARTBEAT": "0"}} self.data_engine.start() self.client.subscribe_quote_ticks(AUDUSD) await asyncio.sleep(0.3) # # Act self.client.unsubscribe_quote_ticks(AUDUSD) await asyncio.sleep(0.3) # Assert self.assertNotIn(AUDUSD, self.client.subscribed_quote_ticks) # Tear Down self.data_engine.stop() self.loop.run_until_complete(run_test()) def test_unsubscribe_bars(self): # Arrange bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.MID) bar_type = BarType(instrument_id=AUDUSD, bar_spec=bar_spec) # Act self.client.unsubscribe_bars(bar_type) # Assert self.assertTrue(True) def test_request_instrument(self): async def run_test(): # Arrange self.data_engine.start() # Also starts client await asyncio.sleep(0.5) # Act self.client.request_instrument(AUDUSD, uuid4()) await asyncio.sleep(0.5) # Assert # Instruments additionally requested on start self.assertEqual(1, self.data_engine.response_count) # Tear Down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_request_instruments(self): async def run_test(): # Arrange self.data_engine.start() # Also starts client await asyncio.sleep(0.5) # Act self.client.request_instruments(uuid4()) await asyncio.sleep(0.5) # Assert # Instruments additionally requested on start self.assertEqual(1, self.data_engine.response_count) # Tear Down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_request_bars(self): async def run_test(): # Arrange with open(TEST_PATH + "instruments.json") as response: instruments = json.load(response) # Arrange with open(TEST_PATH + "bars.json") as response: bars = json.load(response) self.mock_oanda.request.side_effect = [instruments, bars] handler = ObjectStorer() self.data_engine.start() await asyncio.sleep(0.3) bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.MID) bar_type = BarType(instrument_id=AUDUSD, bar_spec=bar_spec) request = DataRequest( provider=OANDA.value, data_type=DataType(Bar, metadata={ "BarType": bar_type, "FromDateTime": None, "ToDateTime": None, "Limit": 1000, }), callback=handler.store_2, request_id=self.uuid_factory.generate(), request_timestamp=self.clock.utc_now(), ) # Act self.data_engine.send(request) # Allow time for request to be sent, processed and response returned await asyncio.sleep(0.3) # Assert self.assertEqual(1, self.data_engine.response_count) self.assertEqual(1, handler.count) # Final bar incomplete so becomes partial self.assertEqual(99, len(handler.get_store()[0][1])) # Tear Down self.data_engine.stop() self.data_engine.dispose() self.loop.run_until_complete(run_test())
class TestOandaDataClient: def setup(self): # Fixture Setup self.clock = LiveClock() self.uuid_factory = UUIDFactory() self.trader_id = TraderId("TESTER-001") # Fresh isolated loop testing pattern self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self.executor = concurrent.futures.ThreadPoolExecutor() self.loop.set_default_executor(self.executor) self.loop.set_debug(True) # Setup logging logger = LiveLogger( loop=self.loop, clock=self.clock, trader_id=self.trader_id, level_stdout=LogLevel.DEBUG, ) self.logger = LiveLogger( loop=self.loop, clock=self.clock, ) self.cache = TestStubs.cache() self.portfolio = Portfolio( cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine = LiveDataEngine( loop=self.loop, portfolio=self.portfolio, cache=self.cache, clock=self.clock, logger=self.logger, ) self.mock_oanda = MagicMock() self.client = OandaDataClient( client=self.mock_oanda, account_id="001", engine=self.data_engine, clock=self.clock, logger=logger, ) self.data_engine.register_client(self.client) with open(TEST_PATH + "instruments.json") as response: instruments = json.load(response) self.mock_oanda.request.return_value = instruments def teardown(self): self.executor.shutdown(wait=True) self.loop.stop() self.loop.close() # TODO: WIP - why is this failing?? # def test_connect(self): # async def run_test(): # # Arrange # # Act # self.data_engine.start() # Also connects client # self.client.connect() # await asyncio.sleep(1) # # # Assert # assert self.client.is_connected # # # Tear Down # self.data_engine.stop() # # self.loop.run_until_complete(run_test()) def test_disconnect(self): # Arrange self.client.connect() # Act self.client.disconnect() # Assert assert not self.client.is_connected def test_reset(self): # Arrange # Act self.client.reset() # Assert assert not self.client.is_connected def test_dispose(self): # Arrange # Act self.client.dispose() # Assert assert not self.client.is_connected def test_subscribe_instrument(self): # Arrange self.client.connect() # Act self.client.subscribe_instrument(AUDUSD) # Assert assert AUDUSD in self.client.subscribed_instruments def test_subscribe_quote_ticks(self): async def run_test(): # Arrange self.mock_oanda.request.return_value = {"type": {"HEARTBEAT": "0"}} self.data_engine.start() # Act self.client.subscribe_quote_ticks(AUDUSD) await asyncio.sleep(0.3) # Assert assert AUDUSD in self.client.subscribed_quote_ticks # Tear Down self.data_engine.stop() self.loop.run_until_complete(run_test()) def test_subscribe_bars(self): # Arrange bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.MID) bar_type = BarType(instrument_id=AUDUSD, bar_spec=bar_spec) # Act self.client.subscribe_bars(bar_type) # Assert assert True def test_unsubscribe_instrument(self): # Arrange self.client.connect() # Act self.client.unsubscribe_instrument(AUDUSD) # Assert assert True def test_unsubscribe_quote_ticks(self): async def run_test(): # Arrange self.mock_oanda.request.return_value = {"type": {"HEARTBEAT": "0"}} self.data_engine.start() self.client.subscribe_quote_ticks(AUDUSD) await asyncio.sleep(0.3) # # Act self.client.unsubscribe_quote_ticks(AUDUSD) await asyncio.sleep(0.3) # Assert assert AUDUSD not in self.client.subscribed_quote_ticks # Tear Down self.data_engine.stop() self.loop.run_until_complete(run_test()) def test_unsubscribe_bars(self): # Arrange bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.MID) bar_type = BarType(instrument_id=AUDUSD, bar_spec=bar_spec) # Act self.client.unsubscribe_bars(bar_type) # Assert assert True def test_request_instrument(self): async def run_test(): # Arrange self.data_engine.start() # Also starts client # Act self.client.request_instrument(AUDUSD, uuid4()) await asyncio.sleep(1) # Assert # Instruments additionally requested on start assert self.data_engine.response_count == 1 # Tear Down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_request_instruments(self): async def run_test(): # Arrange self.data_engine.start() # Also starts client await asyncio.sleep(0.5) # Act self.client.request_instruments(uuid4()) await asyncio.sleep(1) # Assert # Instruments additionally requested on start assert self.data_engine.response_count == 1 # Tear Down self.data_engine.stop() await self.data_engine.get_run_queue_task() self.loop.run_until_complete(run_test()) def test_request_bars(self): async def run_test(): # Arrange with open(TEST_PATH + "instruments.json") as response: instruments = json.load(response) # Arrange with open(TEST_PATH + "bars.json") as response: bars = json.load(response) self.mock_oanda.request.side_effect = [instruments, bars] handler = ObjectStorer() self.data_engine.start() await asyncio.sleep(0.3) bar_spec = BarSpecification(1, BarAggregation.MINUTE, PriceType.MID) bar_type = BarType(instrument_id=AUDUSD, bar_spec=bar_spec) request = DataRequest( client_id=ClientId(OANDA.value), data_type=DataType( Bar, metadata={ "bar_type": bar_type, "from_datetime": None, "to_datetime": None, "limit": 1000, }, ), callback=handler.store, request_id=self.uuid_factory.generate(), timestamp_ns=self.clock.timestamp_ns(), ) # Act self.data_engine.send(request) # Allow time for request to be sent, processed and response returned await asyncio.sleep(1) # Assert assert self.data_engine.response_count == 1 assert handler.count == 1 # Final bar incomplete so becomes partial assert len(handler.get_store()[0]) == 99 # Tear Down self.data_engine.stop() self.data_engine.dispose() self.loop.run_until_complete(run_test())