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
示例#2
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())