def setUp(self): # Fixture Setup self.clock = LiveClock() self.uuid_factory = UUIDFactory() self.logger = Logger(self.clock, level_stdout=LogLevel.DEBUG) self.cache = TestStubs.cache() self.portfolio = Portfolio( cache=self.cache, 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, cache=self.cache, clock=self.clock, logger=self.logger, )
def test_message_qsize_at_max_blocks_on_receive_response(self): # Arrange 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) # Add over max size # Assert self.assertEqual(1, self.data_engine.message_qsize()) self.assertEqual(0, self.data_engine.command_count)
def setup(self): # Fixture Setup self.loop = asyncio.get_event_loop() self.loop.set_debug(True) self.clock = LiveClock() self.uuid_factory = UUIDFactory() self.logger = Logger(self.clock) self.trader_id = TestStubs.trader_id() self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestStubs.cache() self.portfolio = Portfolio( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.engine = LiveDataEngine( loop=self.loop, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, )
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)
async def test_message_qsize_at_max_blocks_on_put_data_command(self): # Arrange self.msgbus.deregister(endpoint="DataEngine.execute", handler=self.engine.execute) self.msgbus.deregister(endpoint="DataEngine.process", handler=self.engine.process) self.msgbus.deregister(endpoint="DataEngine.request", handler=self.engine.request) self.msgbus.deregister(endpoint="DataEngine.response", handler=self.engine.response) self.engine = LiveDataEngine( loop=self.loop, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, config=LiveDataEngineConfig(qsize=1), ) subscribe = Subscribe( client_id=ClientId(BINANCE.value), data_type=DataType(QuoteTick), command_id=self.uuid_factory.generate(), ts_init=self.clock.timestamp_ns(), ) # Act self.engine.execute(subscribe) self.engine.execute(subscribe) await asyncio.sleep(0.1) # Assert assert self.engine.message_qsize() == 1 assert self.engine.command_count == 0
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)
async def test_data_qsize_at_max_blocks_on_put_data(self): # Arrange self.msgbus.deregister(endpoint="DataEngine.execute", handler=self.engine.execute) self.msgbus.deregister(endpoint="DataEngine.process", handler=self.engine.process) self.msgbus.deregister(endpoint="DataEngine.request", handler=self.engine.request) self.msgbus.deregister(endpoint="DataEngine.response", handler=self.engine.response) self.engine = LiveDataEngine( loop=self.loop, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, config=LiveDataEngineConfig(qsize=1), ) data = Data(1_000_000_000, 1_000_000_000) # Act self.engine.process(data) self.engine.process(data) # Add over max size await asyncio.sleep(0.1) # Assert assert self.engine.data_qsize() == 1 assert self.engine.data_count == 0
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_put_data_command(self): # Arrange 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 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 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)
async def test_message_qsize_at_max_blocks_on_send_request(self): # Arrange self.msgbus.deregister(endpoint="DataEngine.execute", handler=self.engine.execute) self.msgbus.deregister(endpoint="DataEngine.process", handler=self.engine.process) self.msgbus.deregister(endpoint="DataEngine.request", handler=self.engine.request) self.msgbus.deregister(endpoint="DataEngine.response", handler=self.engine.response) self.engine = LiveDataEngine( loop=self.loop, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, config=LiveDataEngineConfig(qsize=1), ) handler = [] request = DataRequest( client_id=ClientId("RANDOM"), venue=None, data_type=DataType( QuoteTick, metadata={ "instrument_id": InstrumentId(Symbol("SOMETHING"), Venue("RANDOM")), "from_datetime": None, "to_datetime": None, "limit": 1000, }, ), callback=handler.append, request_id=self.uuid_factory.generate(), ts_init=self.clock.timestamp_ns(), ) # Act self.engine.request(request) self.engine.request(request) await asyncio.sleep(0.1) # Assert assert self.engine.message_qsize() == 1 assert self.engine.command_count == 0
def data_engine(event_loop, msgbus, clock, live_logger): return LiveDataEngine( loop=event_loop, msgbus=msgbus, clock=clock, logger=live_logger, )
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, ) self.client = LiveDataClient( client_id=ClientId("BLOOMBERG"), engine=self.engine, clock=self.clock, logger=self.logger, )
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.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, ) database = BypassExecutionDatabase(trader_id=self.trader_id, logger=self.logger) self.exec_engine = LiveExecutionEngine( loop=self.loop, database=database, portfolio=self.portfolio, clock=self.clock, logger=self.logger, )
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.engine = LiveDataEngine( loop=self.loop, portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) self.client = LiveDataClient( venue=BINANCE, engine=self.engine, clock=self.clock, logger=self.logger, )
def data_engine(event_loop, clock, live_logger, portfolio): return LiveDataEngine( loop=event_loop, portfolio=portfolio, clock=clock, logger=live_logger, )
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_message_qsize_at_max_blocks_on_send_request(self): # Arrange self.engine = LiveDataEngine( loop=self.loop, portfolio=self.portfolio, cache=self.cache, clock=self.clock, logger=self.logger, config={"qsize": 1}, ) handler = [] request = DataRequest( client_id=ClientId("RANDOM"), data_type=DataType( QuoteTick, metadata={ "instrument_id": InstrumentId(Symbol("SOMETHING"), Venue("RANDOM")), "from_datetime": None, "to_datetime": 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)
async def test_message_qsize_at_max_blocks_on_receive_response(self): # Arrange self.msgbus.deregister(endpoint="DataEngine.execute", handler=self.engine.execute) self.msgbus.deregister(endpoint="DataEngine.process", handler=self.engine.process) self.msgbus.deregister(endpoint="DataEngine.request", handler=self.engine.request) self.msgbus.deregister(endpoint="DataEngine.response", handler=self.engine.response) self.engine = LiveDataEngine( loop=self.loop, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, config=LiveDataEngineConfig(qsize=1), ) response = DataResponse( client_id=ClientId("BINANCE"), venue=BINANCE, data_type=DataType(QuoteTick), data=[], correlation_id=self.uuid_factory.generate(), response_id=self.uuid_factory.generate(), ts_init=self.clock.timestamp_ns(), ) # Act self.engine.response(response) self.engine.response(response) # Add over max size await asyncio.sleep(0.1) # Assert assert self.engine.message_qsize() == 1 assert self.engine.command_count == 0
def setup(self): # Fixture Setup self.loop = asyncio.get_event_loop() self.loop.set_debug(True) self.clock = LiveClock() self.uuid_factory = UUIDFactory() self.logger = Logger(self.clock) self.trader_id = TestIdStubs.trader_id() self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestComponentStubs.cache() self.portfolio = Portfolio( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.engine = LiveDataEngine( loop=self.loop, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.client = LiveMarketDataClient( loop=self.loop, client_id=ClientId(BINANCE.value), venue=BINANCE, instrument_provider=InstrumentProvider( venue=Venue("SIM"), logger=self.logger, ), msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, )
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.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.exec_engine = LiveExecutionEngine( loop=self.loop, portfolio=self.portfolio, cache=self.cache, clock=self.clock, logger=self.logger, )
def setup(self): # Fixture Setup self.loop = asyncio.get_event_loop() self.loop.set_debug(True) self.clock = LiveClock() self.uuid_factory = UUIDFactory() self.logger = Logger(self.clock) self.trader_id = TestIdStubs.trader_id() self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestComponentStubs.cache() self.engine = LiveDataEngine( loop=self.loop, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.client = LiveDataClient( loop=self.loop, client_id=ClientId("BLOOMBERG"), venue=None, # Multi-venue msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, )
def __init__( self, strategies: List[TradingStrategy], config: Dict[str, object], ): """ Initialize a new instance of the TradingNode class. Parameters ---------- strategies : list[TradingStrategy] The list of strategies to run on the trading node. config : dict[str, object] The configuration for the trading node. Raises ------ ValueError If strategies is None or empty. ValueError If config is None or empty. """ PyCondition.not_none(strategies, "strategies") PyCondition.not_none(config, "config") PyCondition.not_empty(strategies, "strategies") PyCondition.not_empty(config, "config") # Extract configs config_trader = config.get("trader", {}) config_log = config.get("logging", {}) config_exec_db = config.get("exec_database", {}) config_strategy = config.get("strategy", {}) config_adapters = config.get("adapters", {}) self._uuid_factory = UUIDFactory() self._loop = asyncio.get_event_loop() self._executor = concurrent.futures.ThreadPoolExecutor() self._loop.set_default_executor(self._executor) self._clock = LiveClock(loop=self._loop) self.created_time = self._clock.utc_now() self._is_running = False # Uncomment for debugging # self._loop.set_debug(True) # Setup identifiers self.trader_id = TraderId( name=config_trader["name"], tag=config_trader["id_tag"], ) # Setup logging self._logger = LiveLogger( clock=self._clock, name=self.trader_id.value, level_console=LogLevelParser.from_str_py(config_log.get("log_level_console")), level_file=LogLevelParser.from_str_py(config_log.get("log_level_file")), level_store=LogLevelParser.from_str_py(config_log.get("log_level_store")), run_in_process=config_log.get("run_in_process", True), # Run logger in a separate process log_thread=config_log.get("log_thread_id", False), log_to_file=config_log.get("log_to_file", False), log_file_path=config_log.get("log_file_path", ""), ) self._log = LoggerAdapter(component_name=self.__class__.__name__, logger=self._logger) self._log_header() self._log.info("Building...") self._setup_loop() # Requires the logger to be initialized 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, config={"qsize": 10000}, ) self.portfolio.register_cache(self._data_engine.cache) self.analyzer = PerformanceAnalyzer() if config_exec_db["type"] == "redis": exec_db = RedisExecutionDatabase( trader_id=self.trader_id, logger=self._logger, command_serializer=MsgPackCommandSerializer(), event_serializer=MsgPackEventSerializer(), config={ "host": config_exec_db["host"], "port": config_exec_db["port"], } ) else: exec_db = BypassExecutionDatabase( trader_id=self.trader_id, logger=self._logger, ) self._exec_engine = LiveExecutionEngine( loop=self._loop, database=exec_db, portfolio=self.portfolio, clock=self._clock, logger=self._logger, config={"qsize": 10000}, ) self._exec_engine.load_cache() self._setup_adapters(config_adapters, self._logger) self.trader = Trader( trader_id=self.trader_id, strategies=strategies, portfolio=self.portfolio, data_engine=self._data_engine, exec_engine=self._exec_engine, clock=self._clock, logger=self._logger, ) self._check_residuals_delay = config_trader.get("check_residuals_delay", 5.0) self._load_strategy_state = config_strategy.get("load_state", True) self._save_strategy_state = config_strategy.get("save_state", True) if self._load_strategy_state: self.trader.load() self._log.info("state=INITIALIZED.") self.time_to_initialize = self._clock.delta(self.created_time) self._log.info(f"Initialized in {self.time_to_initialize.total_seconds():.3f}s.")
class TradingNode: """ Provides an asynchronous network node for live trading. """ def __init__( self, strategies: List[TradingStrategy], config: Dict[str, object], ): """ Initialize a new instance of the TradingNode class. Parameters ---------- strategies : list[TradingStrategy] The list of strategies to run on the trading node. config : dict[str, object] The configuration for the trading node. Raises ------ ValueError If strategies is None or empty. ValueError If config is None or empty. """ PyCondition.not_none(strategies, "strategies") PyCondition.not_none(config, "config") PyCondition.not_empty(strategies, "strategies") PyCondition.not_empty(config, "config") # Extract configs config_trader = config.get("trader", {}) config_log = config.get("logging", {}) config_exec_db = config.get("exec_database", {}) config_strategy = config.get("strategy", {}) config_adapters = config.get("adapters", {}) self._uuid_factory = UUIDFactory() self._loop = asyncio.get_event_loop() self._executor = concurrent.futures.ThreadPoolExecutor() self._loop.set_default_executor(self._executor) self._clock = LiveClock(loop=self._loop) self.created_time = self._clock.utc_now() self._is_running = False # Uncomment for debugging # self._loop.set_debug(True) # Setup identifiers self.trader_id = TraderId( name=config_trader["name"], tag=config_trader["id_tag"], ) # Setup logging self._logger = LiveLogger( clock=self._clock, name=self.trader_id.value, level_console=LogLevelParser.from_str_py(config_log.get("log_level_console")), level_file=LogLevelParser.from_str_py(config_log.get("log_level_file")), level_store=LogLevelParser.from_str_py(config_log.get("log_level_store")), run_in_process=config_log.get("run_in_process", True), # Run logger in a separate process log_thread=config_log.get("log_thread_id", False), log_to_file=config_log.get("log_to_file", False), log_file_path=config_log.get("log_file_path", ""), ) self._log = LoggerAdapter(component_name=self.__class__.__name__, logger=self._logger) self._log_header() self._log.info("Building...") self._setup_loop() # Requires the logger to be initialized 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, config={"qsize": 10000}, ) self.portfolio.register_cache(self._data_engine.cache) self.analyzer = PerformanceAnalyzer() if config_exec_db["type"] == "redis": exec_db = RedisExecutionDatabase( trader_id=self.trader_id, logger=self._logger, command_serializer=MsgPackCommandSerializer(), event_serializer=MsgPackEventSerializer(), config={ "host": config_exec_db["host"], "port": config_exec_db["port"], } ) else: exec_db = BypassExecutionDatabase( trader_id=self.trader_id, logger=self._logger, ) self._exec_engine = LiveExecutionEngine( loop=self._loop, database=exec_db, portfolio=self.portfolio, clock=self._clock, logger=self._logger, config={"qsize": 10000}, ) self._exec_engine.load_cache() self._setup_adapters(config_adapters, self._logger) self.trader = Trader( trader_id=self.trader_id, strategies=strategies, portfolio=self.portfolio, data_engine=self._data_engine, exec_engine=self._exec_engine, clock=self._clock, logger=self._logger, ) self._check_residuals_delay = config_trader.get("check_residuals_delay", 5.0) self._load_strategy_state = config_strategy.get("load_state", True) self._save_strategy_state = config_strategy.get("save_state", True) if self._load_strategy_state: self.trader.load() self._log.info("state=INITIALIZED.") self.time_to_initialize = self._clock.delta(self.created_time) self._log.info(f"Initialized in {self.time_to_initialize.total_seconds():.3f}s.") @property def is_running(self) -> bool: """ If the trading node is running. Returns ------- bool True if running, else False. """ return self._is_running def get_event_loop(self) -> asyncio.AbstractEventLoop: """ Return the event loop of the trading node. Returns ------- asyncio.AbstractEventLoop """ return self._loop def get_logger(self) -> LiveLogger: """ Return the logger for the trading node. Returns ------- LiveLogger """ return self._logger def start(self) -> None: """ Start the trading node. """ try: if self._loop.is_running(): self._loop.create_task(self._run()) else: self._loop.run_until_complete(self._run()) except RuntimeError as ex: self._log.exception(ex) def stop(self) -> None: """ Stop the trading node gracefully. After a specified delay the internal `Trader` residuals will be checked. If save strategy is specified then strategy states will then be saved. """ try: if self._loop.is_running(): self._loop.create_task(self._stop()) else: self._loop.run_until_complete(self._stop()) except RuntimeError as ex: self._log.exception(ex) def dispose(self) -> None: """ Dispose of the trading node. Gracefully shuts down the executor and event loop. """ try: timeout = self._clock.utc_now() + timedelta(seconds=5) while self._is_running: time.sleep(0.1) if self._clock.utc_now() >= timeout: self._log.warning("Timed out (5s) waiting for node to stop.") break self._log.info("state=DISPOSING...") self._log.debug(f"{self._data_engine.get_run_queue_task()}") self._log.debug(f"{self._exec_engine.get_run_queue_task()}") self.trader.dispose() self._data_engine.dispose() self._exec_engine.dispose() self._log.info("Shutting down executor...") if sys.version_info >= (3, 9): # cancel_futures added in Python 3.9 self._executor.shutdown(wait=True, cancel_futures=True) else: self._executor.shutdown(wait=True) self._log.info("Stopping event loop...") self._loop.stop() self._cancel_all_tasks() except RuntimeError as ex: self._log.error("Shutdown coro issues will be fixed soon...") # TODO: Remove when fixed self._log.exception(ex) finally: if self._loop.is_running(): self._log.warning("Cannot close a running event loop.") else: self._log.info("Closing event loop...") self._loop.close() # Check and log if event loop is running if self._loop.is_running(): self._log.warning(f"loop.is_running={self._loop.is_running()}") else: self._log.info(f"loop.is_running={self._loop.is_running()}") # Check and log if event loop is closed if not self._loop.is_closed(): self._log.warning(f"loop.is_closed={self._loop.is_closed()}") else: self._log.info(f"loop.is_closed={self._loop.is_closed()}") self._log.info("state=DISPOSED.") self._logger.stop() # Ensure process is stopped time.sleep(0.1) # Ensure final log messages def _log_header(self) -> None: nautilus_header(self._log) self._log.info(f"redis {redis.__version__}") self._log.info(f"msgpack {msgpack.version[0]}.{msgpack.version[1]}.{msgpack.version[2]}") if uvloop_version: self._log.info(f"uvloop {uvloop_version}") self._log.info("=================================================================") def _setup_loop(self) -> None: if self._loop.is_closed(): self._log.error("Cannot setup signal handling (event loop was closed).") return signal.signal(signal.SIGINT, signal.SIG_DFL) signals = (signal.SIGTERM, signal.SIGINT, signal.SIGHUP, signal.SIGABRT) for sig in signals: self._loop.add_signal_handler(sig, self._loop_sig_handler, sig) self._log.debug(f"Event loop {signals} handling setup.") def _loop_sig_handler(self, sig: signal.signal) -> None: self._loop.remove_signal_handler(signal.SIGTERM) self._loop.add_signal_handler(signal.SIGINT, lambda: None) self._log.warning(f"Received {sig!s}, shutting down...") self.stop() def _setup_adapters(self, config: Dict[str, object], logger: LiveLogger) -> None: # Setup each data client for name, config in config.items(): if name.startswith("ccxt-"): try: import ccxtpro # TODO: Find a better way of doing this except ImportError: raise ImportError("ccxtpro is not installed, " "installation instructions can be found at https://ccxt.pro") client_cls = getattr(ccxtpro, name.partition('-')[2].lower()) data_client, exec_client = CCXTClientsFactory.create( client_cls=client_cls, config=config, data_engine=self._data_engine, exec_engine=self._exec_engine, clock=self._clock, logger=logger, ) elif name == "oanda": data_client = OandaDataClientFactory.create( config=config, data_engine=self._data_engine, clock=self._clock, logger=logger, ) exec_client = None # TODO: Implement else: self._log.error(f"No adapter available for `{name}`.") continue if data_client is not None: self._data_engine.register_client(data_client) if exec_client is not None: self._exec_engine.register_client(exec_client) async def _run(self) -> None: try: self._log.info("state=STARTING...") self._is_running = True self._data_engine.start() self._exec_engine.start() result: bool = await self._await_engines_connected() if not result: return result: bool = await self._exec_engine.resolve_state() if not result: return self.trader.start() if self._loop.is_running(): self._log.info("state=RUNNING.") else: self._log.warning("Event loop is not running.") # Continue to run while engines are running... await self._data_engine.get_run_queue_task() await self._exec_engine.get_run_queue_task() except asyncio.CancelledError as ex: self._log.error(str(ex)) async def _await_engines_connected(self) -> bool: self._log.info("Waiting for engines to initialize...") # The data engine clients will be set as connected when all # instruments are received and updated with the data engine. # The execution engine clients will be set as connected when all # accounts are updated and the current order and position status is # confirmed. Thus any delay here will be due to blocking network IO. seconds = 5 # Hard coded for now timeout: timedelta = self._clock.utc_now() + timedelta(seconds=seconds) while True: await asyncio.sleep(0.1) if self._clock.utc_now() >= timeout: self._log.error(f"Timed out ({seconds}s) waiting for " f"engines to initialize.") return False if not self._data_engine.check_connected(): continue if not self._exec_engine.check_connected(): continue break return True # Engines initialized async def _stop(self) -> None: self._is_stopping = True self._log.info("state=STOPPING...") if self.trader.state == ComponentState.RUNNING: self.trader.stop() self._log.info(f"Awaiting residual state ({self._check_residuals_delay}s delay)...") await asyncio.sleep(self._check_residuals_delay) self.trader.check_residuals() if self._save_strategy_state: self.trader.save() if self._data_engine.state == ComponentState.RUNNING: self._data_engine.stop() if self._exec_engine.state == ComponentState.RUNNING: self._exec_engine.stop() await self._await_engines_disconnected() # Clean up remaining timers timer_names = self._clock.timer_names() self._clock.cancel_timers() for name in timer_names: self._log.info(f"Cancelled Timer(name={name}).") self._log.info("state=STOPPED.") self._is_running = False async def _await_engines_disconnected(self) -> None: self._log.info("Waiting for engines to disconnect...") seconds = 5 # Hard coded for now timeout: timedelta = self._clock.utc_now() + timedelta(seconds=seconds) while True: await asyncio.sleep(0.1) if self._clock.utc_now() >= timeout: self._log.warning(f"Timed out ({seconds}s) waiting for engines to disconnect.") break if not self._data_engine.check_disconnected(): continue if not self._exec_engine.check_disconnected(): continue break # Engines initialized def _cancel_all_tasks(self) -> None: to_cancel = asyncio.tasks.all_tasks(self._loop) if not to_cancel: self._log.info("All tasks finished.") return for task in to_cancel: self._log.warning(f"Cancelling pending task {task}") task.cancel() if self._loop.is_running(): self._log.warning("Event loop still running during `cancel_all_tasks`.") return finish_all_tasks: asyncio.Future = asyncio.tasks.gather( *to_cancel, loop=self._loop, return_exceptions=True, ) self._loop.run_until_complete(finish_all_tasks) self._log.debug(f"{finish_all_tasks}") for task in to_cancel: if task.cancelled(): continue if task.exception() is not None: self._loop.call_exception_handler({ 'message': 'unhandled exception during asyncio.run() shutdown', 'exception': task.exception(), 'task': task, })
def setup(self): # Fixture Setup self.loop = asyncio.get_event_loop() self.loop.set_debug(True) self.clock = LiveClock() self.uuid_factory = UUIDFactory() self.logger = Logger(self.clock) self.trader_id = TestStubs.trader_id() self.order_factory = OrderFactory( trader_id=self.trader_id, strategy_id=StrategyId("S-001"), clock=self.clock, ) self.random_order_factory = OrderFactory( trader_id=TraderId("RANDOM-042"), strategy_id=StrategyId("S-042"), clock=self.clock, ) self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestStubs.cache() self.portfolio = Portfolio( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine = LiveDataEngine( loop=self.loop, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exec_engine = LiveExecutionEngine( loop=self.loop, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.risk_engine = LiveRiskEngine( loop=self.loop, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.instrument_provider = InstrumentProvider() self.instrument_provider.add(AUDUSD_SIM) self.instrument_provider.add(GBPUSD_SIM) self.client = MockLiveExecutionClient( loop=self.loop, client_id=ClientId(SIM.value), venue_type=VenueType.ECN, account_id=TestStubs.account_id(), account_type=AccountType.CASH, base_currency=USD, instrument_provider=self.instrument_provider, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.portfolio.update_account(TestStubs.event_cash_account_state()) self.exec_engine.register_client(self.client) self.cache.add_instrument(AUDUSD_SIM)
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())
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)
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())
def setup(self): # Fixture Setup self.loop = asyncio.get_event_loop() self.loop.set_debug(True) self.clock = LiveClock() self.uuid_factory = UUIDFactory() self.logger = Logger(self.clock) self.trader_id = TestStubs.trader_id() self.account_id = TestStubs.account_id() self.order_factory = OrderFactory( trader_id=self.trader_id, strategy_id=StrategyId("S-001"), clock=self.clock, ) self.random_order_factory = OrderFactory( trader_id=TraderId("RANDOM-042"), strategy_id=StrategyId("S-042"), clock=self.clock, ) self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestStubs.cache() self.portfolio = Portfolio( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine = LiveDataEngine( loop=self.loop, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exec_engine = LiveExecutionEngine( loop=self.loop, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.risk_engine = LiveRiskEngine( loop=self.loop, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exec_client = MockExecutionClient( client_id=ClientId("SIM"), venue_type=VenueType.ECN, account_id=TestStubs.account_id(), account_type=AccountType.MARGIN, base_currency=USD, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Wire up components self.exec_engine.register_client(self.exec_client)