예제 #1
0
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())
예제 #2
0
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())
예제 #3
0
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())
예제 #4
0
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())