diff --git a/flumine/execution/simulatedexecution.py b/flumine/execution/simulatedexecution.py index ff830356..7d57871f 100644 --- a/flumine/execution/simulatedexecution.py +++ b/flumine/execution/simulatedexecution.py @@ -2,9 +2,10 @@ import requests from typing import Optional -from .baseexecution import BaseExecution from .. import config +from .baseexecution import BaseExecution from ..clients.clients import VenueType +from ..order.order import OrderStatus from ..order.orderpackage import BaseOrderPackage, OrderPackageType @@ -33,14 +34,32 @@ def execute_place( self, order_package, http_session: Optional[requests.Session] ) -> None: if order_package.client.paper_trade: - time.sleep(order_package.bet_delay + config.place_latency) + # todo PASSIVE matching + time.sleep(order_package.simulated_latency_plus_delay) market = self.flumine.markets.markets[order_package.market_id] - for order, instruction in zip(order_package, order_package.place_instructions): + market_book = market.market_book + + # calc current elapsed seconds + elapsed_seconds = order_package.elapsed_seconds + if elapsed_seconds < order_package.simulated_latency_plus_delay: + # check if PASSIVE execution available + if ( + market_book.market_definition.bet_delay_models is None + or "PASSIVE" not in market_book.market_definition.bet_delay_models + ): + return + + for order, instruction in zip( + order_package.orders_pending, order_package.place_instructions + ): with order.trade: self._bet_id += 1 simulated_response = order.simulated.place( - order_package, market.market_book, instruction, self._bet_id + order_package, market_book, instruction, self._bet_id ) + if simulated_response.status == "DELAY": # PENDING delay + self._bet_id -= 1 + continue self._order_logger( order, simulated_response, order_package.package_type ) @@ -48,15 +67,14 @@ def execute_place( order.executable() elif simulated_response.status == "FAILURE": order.execution_complete() - - # update transaction counts - order_package.client.add_transaction(len(order_package)) + # update transaction counts + order_package.client.add_transaction(1) def execute_cancel( self, order_package, http_session: Optional[requests.Session] ) -> None: if order_package.client.paper_trade: - time.sleep(config.cancel_latency) + time.sleep(order_package.simulated_latency_plus_delay) market = self.flumine.markets.markets[order_package.market_id] failed_transaction_count = 0 for order in order_package: @@ -82,7 +100,7 @@ def execute_update( self, order_package, http_session: Optional[requests.Session] ) -> None: if order_package.client.paper_trade: - time.sleep(config.update_latency) + time.sleep(order_package.simulated_latency_plus_delay) market = self.flumine.markets.markets[order_package.market_id] failed_transaction_count = 0 for order, instruction in zip(order_package, order_package.update_instructions): @@ -106,59 +124,82 @@ def execute_update( def execute_replace( self, order_package, http_session: Optional[requests.Session] ) -> None: - if ( - order_package.client.paper_trade - ): # todo should the cancel happen without a delay? - time.sleep(order_package.bet_delay + config.replace_latency) + if order_package.client.paper_trade: + # todo the cancel happens without a bet delay! + # todo PASSIVE matching + time.sleep(order_package.simulated_latency_plus_delay) market = self.flumine.markets.markets[order_package.market_id] + market_book = market.market_book + + # calc current elapsed seconds + elapsed_seconds = order_package.elapsed_seconds + if elapsed_seconds < config.cancel_latency: + return + failed_transaction_count = 0 - for order, instruction in zip( - order_package, order_package.replace_instructions - ): + for order in order_package.orders_pending: with order.trade: - # cancel current order - err = False - cancel_instruction_report = order.simulated.cancel(market.market_book) - if cancel_instruction_report.status == "SUCCESS": - order.execution_complete() - elif cancel_instruction_report.status == "FAILURE": - order.executable() - failed_transaction_count += 1 - err = True - self._order_logger( - order, - cancel_instruction_report, - OrderPackageType.CANCEL, - ) - if err: - continue - - # place new order - self._bet_id += 1 - replacement_order = order.trade.create_order_replacement( - order, - instruction.get("newPrice"), - cancel_instruction_report.size_cancelled, - order_package.date_time_created, - ) - place_instruction_report = replacement_order.simulated.place( - order_package, market.market_book, instruction, self._bet_id - ) - if place_instruction_report.status == "SUCCESS": + if order.status == OrderStatus.REPLACING: + # we can cancel after the latency (no bet delay) + instruction = order.create_replace_instruction() + # cancel current order + err = False + cancel_instruction_report = order.simulated.cancel(market_book) + if cancel_instruction_report.status == "SUCCESS": + order.execution_complete() + elif cancel_instruction_report.status == "FAILURE": + order.executable() + failed_transaction_count += 1 + err = True self._order_logger( - replacement_order, - place_instruction_report, - order_package.package_type, + order, + cancel_instruction_report, + OrderPackageType.CANCEL, ) - # add to blotter - market.place_order( - replacement_order, execute=False, client=order.client + if err: + continue + + # create the replacement order + replacement_order = order.trade.create_order_replacement( + order, + instruction.get("newPrice"), + cancel_instruction_report.size_cancelled, + order_package.date_time_created, ) - replacement_order.executable() - elif place_instruction_report.status == "FAILURE": - order.executable() + replacement_order.placing() + order_package._orders.append(replacement_order) + + elif order.status == OrderStatus.PENDING: + # we can re'place' after the bet delay latency or PASSIVE + if elapsed_seconds < config.place_latency + order_package.bet_delay: + # check if PASSIVE execution available + if ( + market_book.market_definition.bet_delay_models is None + or "PASSIVE" + not in market_book.market_definition.bet_delay_models + ): + return + instruction = order.create_place_instruction() + self._bet_id += 1 + place_instruction_report = order.simulated.place( + order_package, market_book, instruction, self._bet_id + ) + if place_instruction_report.status == "DELAY": # PENDING delay + self._bet_id -= 1 + continue + if place_instruction_report.status == "SUCCESS": + self._order_logger( + order, + place_instruction_report, + order_package.package_type, + ) + # add to blotter + market.place_order(order, execute=False, client=order.client) + order.executable() + elif place_instruction_report.status == "FAILURE": + order.execution_complete() + # update transaction counts + order_package.client.add_transaction(1) - # update transaction counts - order_package.client.add_transaction(len(order_package)) if failed_transaction_count: order_package.client.add_transaction(failed_transaction_count, failed=True) diff --git a/flumine/markets/middleware.py b/flumine/markets/middleware.py index 01c7b882..f5b01c42 100644 --- a/flumine/markets/middleware.py +++ b/flumine/markets/middleware.py @@ -74,7 +74,7 @@ def __call__(self, market) -> None: market.context["simulated"] = market_analytics # process simulated orders - if market.blotter.active: + if market.blotter.active and market.market_book.status == "OPEN": self._process_simulated_orders(market, market_analytics) def remove_market(self, market) -> None: diff --git a/flumine/order/orderpackage.py b/flumine/order/orderpackage.py index 535ec690..1cbf428d 100644 --- a/flumine/order/orderpackage.py +++ b/flumine/order/orderpackage.py @@ -54,7 +54,8 @@ def __init__( # following used for simulated execution self.processed = False self.bet_delay = bet_delay - self.simulated_delay = self.calc_simulated_delay() + self.simulated_latency = self.calc_simulated_latency() + self.simulated_latency_plus_delay = self.calc_simulated_latency_delay() def retry(self): if self._retry and self._retry_count < self._max_retries: @@ -71,7 +72,19 @@ def reset_orders(self, complete: bool = False) -> None: else: order.executable() - def calc_simulated_delay(self) -> float: + def calc_simulated_latency(self) -> float: + if self.client.execution.VENUE == VenueType.SIMULATED: + if self.package_type == OrderPackageType.PLACE: + return config.place_latency + elif self.package_type == OrderPackageType.CANCEL: + return config.cancel_latency + elif self.package_type == OrderPackageType.UPDATE: + return config.update_latency + elif self.package_type == OrderPackageType.REPLACE: + return config.replace_latency + return -1 + + def calc_simulated_latency_delay(self) -> float: if self.client.execution.VENUE == VenueType.SIMULATED: if self.package_type == OrderPackageType.PLACE: return config.place_latency + self.bet_delay @@ -81,6 +94,7 @@ def calc_simulated_delay(self) -> float: return config.update_latency elif self.package_type == OrderPackageType.REPLACE: return config.replace_latency + self.bet_delay + return -1 @property def place_instructions(self) -> list: @@ -106,6 +120,14 @@ def order_limit(cls, package_type: OrderPackageType) -> int: def orders(self) -> list: return [o for o in self._orders if o.status != OrderStatus.VIOLATION] + @property + def orders_pending(self) -> list: + return [ + o + for o in self._orders + if o.status in [OrderStatus.PENDING, OrderStatus.REPLACING] + ] + @property def retry_count(self) -> int: return self._retry_count @@ -149,7 +171,7 @@ class BetfairOrderPackage(BaseOrderPackage): @property def place_instructions(self) -> list: - return [order.create_place_instruction() for order in self] + return [order.create_place_instruction() for order in self.orders_pending] @property def cancel_instructions(self) -> list: diff --git a/flumine/order/trade.py b/flumine/order/trade.py index 1b876e54..7c80a5cb 100644 --- a/flumine/order/trade.py +++ b/flumine/order/trade.py @@ -58,8 +58,8 @@ def __init__( def _update_status(self, status: TradeStatus) -> None: self.status_log.append(status) self.status = status - if logger.isEnabledFor(logging.INFO): - logger.info("Trade status update: %s" % self.status.value, extra=self.info) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("Trade status update: %s" % self.status.value, extra=self.info) if self.complete: self.complete_trade() @@ -71,6 +71,7 @@ def complete_trade(self) -> None: self.market_id, self.selection_id, self.handicap ) runner_context.reset(self.id) + logger.info("Trade completed: %s" % self.status.value, extra=self.info) @property def complete(self) -> bool: diff --git a/flumine/simulation/simulatedorder.py b/flumine/simulation/simulatedorder.py index 21a40842..992ad2a8 100644 --- a/flumine/simulation/simulatedorder.py +++ b/flumine/simulation/simulatedorder.py @@ -123,6 +123,12 @@ def place( raise NotImplementedError( "Simulated betTargetSize placement not implemented" ) + # calc current delay + elapsed_seconds = order_package.elapsed_seconds + bet_delay_complete = False + if elapsed_seconds > order_package.simulated_latency_plus_delay: + bet_delay_complete = True + # Validate that min_fill_size <= size for fill or kill orders is_fill_or_kill_order = time_in_force == "FILL_OR_KILL" if is_fill_or_kill_order and min_fill_size > size: @@ -138,6 +144,10 @@ def place( ) if self.order.side == "BACK": available_to_back = get_price(runner.ex.available_to_back, 0) or 1.01 + if bet_delay_complete is False and price <= available_to_back: + # order would be 'aggressive' + return SimulatedPlaceResponse(status="DELAY") + if ( not order_package.client.best_price_execution and available_to_back > price @@ -184,6 +194,10 @@ def place( available = runner.ex.available_to_lay else: available_to_lay = get_price(runner.ex.available_to_lay, 0) or 1000 + if bet_delay_complete is False and price >= available_to_lay: + # order would be 'aggressive' + return SimulatedPlaceResponse(status="DELAY") + if ( not order_package.client.best_price_execution and available_to_lay < price diff --git a/flumine/simulation/simulation.py b/flumine/simulation/simulation.py index b800f4de..ab941349 100644 --- a/flumine/simulation/simulation.py +++ b/flumine/simulation/simulation.py @@ -151,7 +151,7 @@ def _process_market_books(self, event: events.MarketBookEvent) -> None: ) def process_order_package(self, order_package) -> None: - # place in pending list (wait for latency+delay) + # place in pending list (wait for latency) self.handler_queue.append(order_package) def _process_simulated_orders(self, market) -> None: @@ -187,10 +187,11 @@ def _check_pending_packages(self, market_id: str) -> None: for order_package in self.handler_queue: if ( order_package.market_id == market_id - and order_package.elapsed_seconds > order_package.simulated_delay + and order_package.elapsed_seconds > order_package.simulated_latency ): order_package.client.execution.handler(order_package) - processed.append(order_package) + if len(order_package.orders_pending) == 0: + processed.append(order_package) for p in processed: self.handler_queue.remove(p) diff --git a/tests/test_execution.py b/tests/test_execution.py index fd6a1a88..20dc2d07 100644 --- a/tests/test_execution.py +++ b/tests/test_execution.py @@ -6,7 +6,6 @@ from betdaq import BetdaqError from betfairlightweight import BetfairError -from flumine import config from flumine.clients.clients import VenueType from flumine.exceptions import OrderExecutionError from flumine.execution.baseexecution import ( @@ -18,6 +17,7 @@ from flumine.execution.betdaqexecution import BetdaqExecution from flumine.execution.betfairexecution import BetfairExecution from flumine.execution.simulatedexecution import SimulatedExecution +from flumine.order.order import OrderStatus class BaseExecutionTest(unittest.TestCase): @@ -1075,12 +1075,15 @@ def test_execute_place_success(self, mock__order_logger): mock_order = mock.Mock() mock_order.trade.__enter__ = mock.Mock() mock_order.trade.__exit__ = mock.Mock() - mock_order_package = mock.MagicMock(market_id="1.23") - mock_order_package.__len__.return_value = 1 + mock_order_package = mock.MagicMock( + market_id="1.23", + orders_pending=[mock_order], + place_instructions=[1], + info={}, + elapsed_seconds=0.25, + simulated_latency_plus_delay=0.2, + ) mock_order_package.client.paper_trade = False - mock_order_package.__iter__ = mock.Mock(return_value=iter([mock_order])) - mock_order_package.place_instructions = [1] - mock_order_package.info = {} mock_sim_resp = mock.Mock() mock_sim_resp.status = "SUCCESS" mock_order.simulated.place.return_value = mock_sim_resp @@ -1104,12 +1107,15 @@ def test_execute_place_failure(self, mock__order_logger): mock_order = mock.Mock() mock_order.trade.__enter__ = mock.Mock() mock_order.trade.__exit__ = mock.Mock() - mock_order_package = mock.MagicMock(market_id="1.23") - mock_order_package.__len__.return_value = 1 + mock_order_package = mock.MagicMock( + market_id="1.23", + orders_pending=[mock_order], + place_instructions=[1], + info={}, + elapsed_seconds=0.25, + simulated_latency_plus_delay=0.2, + ) mock_order_package.client.paper_trade = False - mock_order_package.__iter__ = mock.Mock(return_value=iter([mock_order])) - mock_order_package.place_instructions = [1] - mock_order_package.info = {} mock_sim_resp = mock.Mock() mock_sim_resp.status = "FAILURE" mock_order.simulated.place.return_value = mock_sim_resp @@ -1128,17 +1134,51 @@ def test_execute_place_failure(self, mock__order_logger): mock_order.trade.__exit__.assert_called_with(None, None, None) mock_order_package.client.add_transaction.assert_called_with(1) + @mock.patch("flumine.execution.simulatedexecution.SimulatedExecution._order_logger") + def test_execute_place_delay(self, mock__order_logger): + mock_order = mock.Mock() + mock_order.trade.__enter__ = mock.Mock() + mock_order.trade.__exit__ = mock.Mock() + mock_order_package = mock.MagicMock( + market_id="1.23", + place_instructions=[1], + info={}, + orders_pending=[mock_order], + elapsed_seconds=0.25, + simulated_latency_plus_delay=0.2, + ) + mock_order_package.client.paper_trade = False + mock_sim_resp = mock.Mock() + mock_sim_resp.status = "DELAY" + mock_order.simulated.place.return_value = mock_sim_resp + self.execution.execute_place(mock_order_package, None) + mock_order.simulated.place.assert_called_with( + mock_order_package, + self.mock_market.market_book, + 1, + self.execution._bet_id + 1, + ) + mock__order_logger.assert_not_called() + mock_order.executable.assert_not_called() + mock_order.trade.__enter__.assert_called_with() + mock_order.trade.__exit__.assert_called_with(None, None, None) + mock_order_package.client.add_transaction.assert_not_called() + @mock.patch("flumine.execution.simulatedexecution.time") def test_execute_place_paper_trade(self, mock_time): mock_order_package = mock.MagicMock( - market_id="1.23", place_instructions=[], bet_delay=1 + market_id="1.23", + place_instructions=[], + bet_delay=1, + orders_pending=[], + elapsed_seconds=0.25, + simulated_latency_plus_delay=0.2, ) - mock_order_package.__len__.return_value = 1 - mock_order_package.__iter__ = mock.Mock(return_value=iter([])) mock_order_package.client.paper_trade = True self.execution.execute_place(mock_order_package, None) - mock_time.sleep.assert_called_with(config.place_latency + 1) - mock_order_package.client.add_transaction.assert_called_with(1) + mock_time.sleep.assert_called_with( + mock_order_package.simulated_latency_plus_delay + ) @mock.patch("flumine.execution.simulatedexecution.SimulatedExecution._order_logger") def test_execute_cancel(self, mock__order_logger): @@ -1213,7 +1253,9 @@ def test_execute_cancel_paper_trade(self, mock_time): mock_order_package.__iter__ = mock.Mock(return_value=iter([])) mock_order_package.client.paper_trade = True self.execution.execute_cancel(mock_order_package, None) - mock_time.sleep.assert_called_with(config.cancel_latency) + mock_time.sleep.assert_called_with( + mock_order_package.simulated_latency_plus_delay + ) @mock.patch("flumine.execution.simulatedexecution.SimulatedExecution._order_logger") def test_execute_update(self, mock__order_logger): @@ -1274,16 +1316,28 @@ def test_execute_update_paper_trade(self, mock_time): mock_order_package.__iter__ = mock.Mock(return_value=iter([])) mock_order_package.client.paper_trade = True self.execution.execute_update(mock_order_package, None) - mock_time.sleep.assert_called_with(config.update_latency) + mock_time.sleep.assert_called_with( + mock_order_package.simulated_latency_plus_delay + ) @mock.patch("flumine.execution.simulatedexecution.SimulatedExecution._order_logger") def test_execute_replace(self, mock__order_logger): mock_client = mock.Mock() - mock_order = mock.Mock(client=mock_client) + mock_order = mock.Mock( + market_id="1.234", client=mock_client, status=OrderStatus.REPLACING + ) mock_order.trade.__enter__ = mock.Mock() mock_order.trade.__exit__ = mock.Mock() - mock_order.market_id = "1.234" - mock_order_package = mock.MagicMock(market_id="1.23", client=mock_client) + mock_order_package = mock.MagicMock( + market_id="1.23", + client=mock_client, + bet_delay=0, + elapsed_seconds=0.25, + simulated_latency=0.2, + simulated_latency_plus_delay=0.2, + orders_pending=[mock_order], + _orders=[mock_order], + ) mock_order_package.__len__.return_value = 1 mock_order_package.client.paper_trade = False mock_order_package.__iter__ = mock.Mock(return_value=iter([mock_order])) @@ -1293,14 +1347,26 @@ def test_execute_replace(self, mock__order_logger): mock_replacement_order_package.__iter__ = mock.Mock( return_value=iter([mock_order]) ) - mock_sim_resp = mock.Mock() - mock_sim_resp.status = "SUCCESS" + mock_sim_resp = mock.Mock(status="SUCCESS") mock_order.simulated.cancel.return_value = mock_sim_resp mock_replacement_order.simulated.place.return_value = mock_sim_resp mock_order.trade.create_order_replacement.return_value = mock_replacement_order + + # test order is cancelled self.execution.execute_replace(mock_order_package, None) + mock_replacement_order.placing.assert_called() mock_order.simulated.cancel.assert_called_with(self.mock_market.market_book) - mock_replacement_order.simulated.place.assert_called_with( + self.assertEqual(len(mock_order_package._orders), 2) + self.assertEqual(len(mock_order_package.orders_pending), 1) + + # now test if it can be replaced + r_order = mock_order_package.orders_pending[0] + r_order.status = OrderStatus.PENDING + r_order.create_place_instruction.return_value = {"newPrice": 2.03} + r_order.simulated.place.return_value = mock_sim_resp + self.execution.execute_replace(mock_order_package, None) + + r_order.simulated.place.assert_called_with( mock_order_package, self.mock_market.market_book, {"newPrice": 2.03}, @@ -1310,14 +1376,14 @@ def test_execute_replace(self, mock__order_logger): [ call(mock_order, mock_sim_resp, OrderPackageType.CANCEL), call( - mock_replacement_order, + r_order, mock_sim_resp, mock_order_package.package_type, ), ] ) mock_order.execution_complete.assert_called_with() - # mock_replacement_order.executable.assert_called_with() + r_order.executable.assert_called_with() mock_order.trade.__enter__.assert_called_with() mock_order.trade.__exit__.assert_called_with(None, None, None) mock_order_package.client.add_transaction.assert_called_with(1) @@ -1325,10 +1391,19 @@ def test_execute_replace(self, mock__order_logger): @mock.patch("flumine.execution.simulatedexecution.SimulatedExecution._order_logger") def test_execute_replace_failure(self, mock__order_logger): mock_client = mock.Mock() - mock_order = mock.Mock(client=mock_client) + mock_order = mock.Mock(client=mock_client, status=OrderStatus.REPLACING) mock_order.trade.__enter__ = mock.Mock() mock_order.trade.__exit__ = mock.Mock() - mock_order_package = mock.MagicMock(market_id="1.23", client=mock_client) + mock_order_package = mock.MagicMock( + market_id="1.23", + client=mock_client, + bet_delay=0, + elapsed_seconds=0.25, + simulated_latency=0.2, + simulated_latency_plus_delay=0.2, + orders_pending=[mock_order], + _orders=[mock_order], + ) mock_order_package.__len__.return_value = 1 mock_order_package.client.paper_trade = False mock_order_package.__iter__ = mock.Mock(return_value=iter([mock_order])) @@ -1357,13 +1432,19 @@ def test_execute_replace_failure(self, mock__order_logger): @mock.patch("flumine.execution.simulatedexecution.time") def test_execute_replace_paper_trade(self, mock_time): mock_order_package = mock.MagicMock( - market_id="1.23", replace_instructions=[], bet_delay=1 + market_id="1.23", + replace_instructions=[], + bet_delay=1, + elapsed_seconds=0.25, + simulated_latency_plus_delay=0.2, ) mock_order_package.__len__.return_value = 1 mock_order_package.__iter__ = mock.Mock(return_value=iter([])) mock_order_package.client.paper_trade = True self.execution.execute_replace(mock_order_package, None) - mock_time.sleep.assert_called_with(config.replace_latency + 1) + mock_time.sleep.assert_called_with( + mock_order_package.simulated_latency_plus_delay + ) class BetdaqExecutionTest(unittest.TestCase): diff --git a/tests/test_fluminesimulation.py b/tests/test_fluminesimulation.py index 5cd92031..8fee244e 100644 --- a/tests/test_fluminesimulation.py +++ b/tests/test_fluminesimulation.py @@ -190,11 +190,13 @@ def test__check_pending_packages_place(self): elapsed_seconds=5, bet_delay=1, client=mock_client, - simulated_delay=1.2, + orders_pending=[], + simulated_latency=1.2, ) self.flumine.handler_queue = [mock_order_package] self.flumine._check_pending_packages("1.23") mock_client.execution.handler.assert_called_with(mock_order_package) + self.assertEqual(self.flumine.handler_queue, []) def test__check_pending_packages_place_pending(self): mock_client = mock.Mock() @@ -204,11 +206,12 @@ def test__check_pending_packages_place_pending(self): elapsed_seconds=0.2, bet_delay=1, client=mock_client, - simulated_delay=1.2, + simulated_latency=1.2, ) self.flumine.handler_queue = [mock_order_package] self.flumine._check_pending_packages("1.23") mock_client.execution.handler.assert_not_called() + self.assertEqual(self.flumine.handler_queue, [mock_order_package]) def test__check_pending_packages_place_diff_market_id(self): mock_client = mock.Mock() @@ -218,47 +221,68 @@ def test__check_pending_packages_place_diff_market_id(self): elapsed_seconds=2, bet_delay=1, client=mock_client, - simulated_delay=1.2, + simulated_latency=1.2, ) self.flumine.handler_queue = [mock_order_package] self.flumine._check_pending_packages("1.24") mock_client.execution.handler.assert_not_called() + self.assertEqual(self.flumine.handler_queue, [mock_order_package]) def test__check_pending_packages_cancel(self): mock_client = mock.Mock() mock_order_package = mock.Mock( - market_id="1.23", elapsed_seconds=3, client=mock_client, simulated_delay=0.2 + market_id="1.23", + elapsed_seconds=3, + client=mock_client, + simulated_latency=0.2, + orders_pending=[], ) self.flumine.handler_queue = [mock_order_package] self.flumine._check_pending_packages("1.23") mock_client.execution.handler.assert_called_with(mock_order_package) + self.assertEqual(self.flumine.handler_queue, []) def test__check_pending_packages_cancel_pending(self): mock_client = mock.Mock() mock_order_package = mock.Mock( - market_id="1.23", elapsed_seconds=2, client=mock_client, simulated_delay=0.2 + market_id="1.23", + elapsed_seconds=2, + client=mock_client, + simulated_latency=0.2, + orders_pending=[], ) self.flumine.handler_queue = [mock_order_package] self.flumine._check_pending_packages("1.23") mock_client.execution.handler.assert_called_with(mock_order_package) + self.assertEqual(self.flumine.handler_queue, []) def test__check_pending_packages_update(self): mock_client = mock.Mock() mock_order_package = mock.Mock( - market_id="1.23", elapsed_seconds=3, client=mock_client, simulated_delay=0.2 + market_id="1.23", + elapsed_seconds=3, + client=mock_client, + orders_pending=[], + simulated_latency=0.2, ) self.flumine.handler_queue = [mock_order_package] self.flumine._check_pending_packages("1.23") mock_client.execution.handler.assert_called_with(mock_order_package) + self.assertEqual(self.flumine.handler_queue, []) def test__check_pending_packages_update_pending(self): mock_client = mock.Mock() mock_order_package = mock.Mock( - market_id="1.23", elapsed_seconds=2, client=mock_client, simulated_delay=0.2 + market_id="1.23", + elapsed_seconds=2, + client=mock_client, + orders_pending=[], + simulated_latency=0.2, ) self.flumine.handler_queue = [mock_order_package] self.flumine._check_pending_packages("1.23") mock_client.execution.handler.assert_called_with(mock_order_package) + self.assertEqual(self.flumine.handler_queue, []) def test__check_pending_packages_replace(self): mock_client = mock.Mock() @@ -268,11 +292,13 @@ def test__check_pending_packages_replace(self): elapsed_seconds=5, bet_delay=1, client=mock_client, - simulated_delay=1.2, + orders_pending=[], + simulated_latency=1.2, ) self.flumine.handler_queue = [mock_order_package] self.flumine._check_pending_packages("1.23") mock_client.execution.handler.assert_called_with(mock_order_package) + self.assertEqual(self.flumine.handler_queue, []) def test__check_pending_packages_replace_pending(self): mock_client = mock.Mock() @@ -282,11 +308,13 @@ def test__check_pending_packages_replace_pending(self): elapsed_seconds=2, bet_delay=1, client=mock_client, - simulated_delay=1.2, + orders_pending=[], + simulated_latency=1.2, ) self.flumine.handler_queue.append(mock_order_package) self.flumine._check_pending_packages("1.23") mock_client.execution.handler.assert_called_with(mock_order_package) + self.assertEqual(self.flumine.handler_queue, []) @mock.patch("flumine.baseflumine.BaseFlumine.info") @mock.patch("flumine.baseflumine.BaseFlumine.log_control") diff --git a/tests/test_middleware.py b/tests/test_middleware.py index b7decb56..1579b1a0 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -57,7 +57,7 @@ def test_init(self): @mock.patch("flumine.markets.middleware.SimulatedMiddleware._process_runner") def test_call(self, mock__process_runner, mock__process_simulated_orders): mock_market = mock.Mock(context={}) - mock_market_book = mock.Mock() + mock_market_book = mock.Mock(status="OPEN") mock_runner = mock.Mock(status="ACTIVE") mock_market_book.runners = [mock_runner] mock_market.market_book = mock_market_book diff --git a/tests/test_orderpackage.py b/tests/test_orderpackage.py index 5de52d6e..59d0de90 100644 --- a/tests/test_orderpackage.py +++ b/tests/test_orderpackage.py @@ -45,7 +45,8 @@ def test_init(self): self.assertTrue(self.order_package._retry) self.assertEqual(self.order_package._max_retries, 3) self.assertEqual(self.order_package._retry_count, 0) - self.assertIsNone(self.order_package.simulated_delay) + self.assertEqual(self.order_package.simulated_latency, -1) + self.assertEqual(self.order_package.simulated_latency_plus_delay, -1) self.assertEqual(self.order_package.customer_strategy_ref, "test") def test_retry(self): @@ -73,22 +74,37 @@ def test_reset_orders(self): self.order_package.reset_orders(True) mock_order.execution_complete.assert_called() - def test_calc_simulated_delay(self): + def test_calc_simulated_latency(self): config.place_latency = 0.1 config.cancel_latency = 0.2 config.update_latency = 0.3 config.replace_latency = 0.4 + self.assertEqual(self.order_package.calc_simulated_latency(), -1) + self.order_package.client.execution.VENUE = VenueType.SIMULATED + self.order_package.package_type = OrderPackageType.PLACE + self.assertEqual(self.order_package.calc_simulated_latency(), 0.1) + self.order_package.package_type = OrderPackageType.CANCEL + self.assertEqual(self.order_package.calc_simulated_latency(), 0.2) + self.order_package.package_type = OrderPackageType.UPDATE + self.assertEqual(self.order_package.calc_simulated_latency(), 0.3) + self.order_package.package_type = OrderPackageType.REPLACE + self.assertEqual(self.order_package.calc_simulated_latency(), 0.4) - self.assertIsNone(self.order_package.calc_simulated_delay()) + def test_calc_simulated_latency_delay(self): + config.place_latency = 0.1 + config.cancel_latency = 0.2 + config.update_latency = 0.3 + config.replace_latency = 0.4 + self.assertEqual(self.order_package.calc_simulated_latency_delay(), -1) self.order_package.client.execution.VENUE = VenueType.SIMULATED self.order_package.package_type = OrderPackageType.PLACE - self.assertEqual(self.order_package.calc_simulated_delay(), 1.1) + self.assertEqual(self.order_package.calc_simulated_latency_delay(), 1.1) self.order_package.package_type = OrderPackageType.CANCEL - self.assertEqual(self.order_package.calc_simulated_delay(), 0.2) + self.assertEqual(self.order_package.calc_simulated_latency_delay(), 0.2) self.order_package.package_type = OrderPackageType.UPDATE - self.assertEqual(self.order_package.calc_simulated_delay(), 0.3) + self.assertEqual(self.order_package.calc_simulated_latency_delay(), 0.3) self.order_package.package_type = OrderPackageType.REPLACE - self.assertEqual(self.order_package.calc_simulated_delay(), 1.4) + self.assertEqual(self.order_package.calc_simulated_latency_delay(), 1.4) def test_place_instructions(self): with self.assertRaises(NotImplementedError): @@ -119,6 +135,16 @@ def test_orders(self): ] self.assertEqual(len(self.order_package.orders), 2) + def test_orders_pending(self): + self.assertEqual(self.order_package.orders, [self.mock_order]) + self.order_package._orders = [ + mock.Mock(status=OrderStatus.PENDING), + mock.Mock(status=OrderStatus.PENDING), + mock.Mock(status=OrderStatus.VIOLATION), + mock.Mock(status=OrderStatus.REPLACING), + ] + self.assertEqual(len(self.order_package.orders_pending), 3) + def test_retry_count(self): self.order_package._retry_count = 1 self.assertEqual(self.order_package.retry_count, 1) diff --git a/tests/test_simulatedorder.py b/tests/test_simulatedorder.py index 4e3704c1..e558fcf9 100644 --- a/tests/test_simulatedorder.py +++ b/tests/test_simulatedorder.py @@ -185,7 +185,12 @@ def test_call_bsp_reconciled( @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_back(self, mock__get_runner): mock_client = mock.Mock(best_price_execution=True) - mock_order_package = mock.Mock(client=mock_client, market_version=None) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) mock_market_book = mock.Mock(status="OPEN") mock_runner = mock.Mock() mock_runner.ex.available_to_back = [{"price": 12, "size": 120}] @@ -199,6 +204,25 @@ def test_place_limit_back(self, mock__get_runner): self.simulated.matched, [[mock_market_book.publish_time_epoch, 12, 2]] ) + @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") + def test_place_limit_back_delay_aggressive(self, mock__get_runner): + mock_client = mock.Mock(best_price_execution=True) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=1.1, + ) + mock_market_book = mock.Mock(status="OPEN") + mock_runner = mock.Mock() + mock_runner.ex.available_to_back = [{"price": 12, "size": 120}] + mock_runner.ex.available_to_lay = [{"price": 13, "size": 120}] + mock__get_runner.return_value = mock_runner + resp = self.simulated.place(mock_order_package, mock_market_book, {}, 1) + self.assertEqual(self.simulated.market_version, mock_market_book.version) + self.assertEqual(resp.status, "DELAY") + self.assertEqual(self.simulated.matched, []) + @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_persistence_validation(self, mock__get_runner): self.mock_order.order_type.persistence_type = "MARKET_ON_CLOSE" @@ -215,6 +239,27 @@ def test_place_limit_persistence_validation(self, mock__get_runner): self.assertEqual(resp.error_code, "BET_ACTION_ERROR") self.assertEqual(self.simulated.matched, []) + @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") + def test_place_limit_back_delay_passive(self, mock__get_runner): + mock_client = mock.Mock(best_price_execution=True) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) + mock_market_book = mock.Mock(status="OPEN") + mock_runner = mock.Mock() + mock_runner.ex.available_to_back = [{"price": 11, "size": 120}] + mock_runner.ex.available_to_lay = [{"price": 12, "size": 120}] + mock__get_runner.return_value = mock_runner + resp = self.simulated.place(mock_order_package, mock_market_book, {}, 1) + self.assertEqual(self.simulated.market_version, mock_market_book.version) + self.assertEqual(resp.status, "SUCCESS") + self.assertEqual(resp.average_price_matched, 0) + self.assertEqual(resp.size_matched, 0) + self.assertEqual(self.simulated.matched, []) + @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_back_target_size(self, mock__get_runner): self.mock_order.order_type.size = None @@ -231,7 +276,12 @@ def test_place_limit_back_target_size(self, mock__get_runner): @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_fill_or_kill_invalid_min_fill_size(self, mock__get_runner): mock_client = mock.Mock(best_price_execution=False) - mock_order_package = mock.Mock(client=mock_client, market_version=None) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.25, + simulated_latency_plus_delay=0.2, + ) mock_market_book = mock.Mock(status="OPEN") instruction = self.mock_betfair_place_instruction_fill_or_kill( size=1, min_fill_size=2 @@ -248,7 +298,12 @@ def test_place_limit_fill_or_kill_invalid_min_fill_size(self, mock__get_runner): @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_back_fill_or_kill_matched(self, mock__get_runner): mock_client = mock.Mock(best_price_execution=True) - mock_order_package = mock.Mock(client=mock_client, market_version=None) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) mock_market_book = mock.Mock(status="OPEN") mock_runner = mock.Mock() mock_runner.ex.available_to_back = [{"price": 12, "size": 2}] @@ -270,7 +325,12 @@ def test_place_limit_back_fill_or_kill_matched(self, mock__get_runner): @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_back_fill_or_kill_lapsed(self, mock__get_runner): mock_client = mock.Mock(best_price_execution=True) - mock_order_package = mock.Mock(client=mock_client, market_version=None) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) mock_market_book = mock.Mock(status="OPEN") mock_runner = mock.Mock() mock_runner.ex.available_to_back = [{"price": 12, "size": 1}] @@ -291,7 +351,12 @@ def test_place_limit_back_fill_or_kill_lapsed(self, mock__get_runner): @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_back_fill_or_kill_no_price(self, mock__get_runner): mock_client = mock.Mock(best_price_execution=True) - mock_order_package = mock.Mock(client=mock_client, market_version=None) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) mock_market_book = mock.Mock(status="OPEN") mock_runner = mock.Mock() mock_runner.ex.available_to_back = [] @@ -314,7 +379,12 @@ def test_place_limit_back_fill_or_kill_min_fill_size_matched( self, mock__get_runner ): mock_client = mock.Mock(best_price_execution=True) - mock_order_package = mock.Mock(client=mock_client, market_version=None) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) mock_market_book = mock.Mock(status="OPEN") mock_runner = mock.Mock() mock_runner.ex.available_to_back = [{"price": 12, "size": 1}] @@ -337,7 +407,12 @@ def test_place_limit_back_fill_or_kill_min_fill_size_matched( @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_back_fill_or_kill_min_fill_size_lapsed(self, mock__get_runner): mock_client = mock.Mock(best_price_execution=True) - mock_order_package = mock.Mock(client=mock_client, market_version=None) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) mock_market_book = mock.Mock(status="OPEN") mock_runner = mock.Mock() mock_runner.ex.available_to_back = [{"price": 12, "size": 1}] @@ -360,7 +435,12 @@ def test_place_limit_back_fill_or_kill_min_fill_size_lapsed(self, mock__get_runn @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_back_fill_or_kill_vwap(self, mock__get_runner): mock_client = mock.Mock(best_price_execution=True) - mock_order_package = mock.Mock(client=mock_client, market_version=None) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) mock_market_book = mock.Mock(status="OPEN") mock_runner = mock.Mock() mock_runner.ex.available_to_back = [ @@ -416,7 +496,12 @@ def test_place_market_version(self, mock__get_runner): @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_back_unmatched(self, mock__get_runner): mock_client = mock.Mock(best_price_execution=True) - mock_order_package = mock.Mock(client=mock_client, market_version=None) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) mock_market_book = mock.Mock(status="OPEN") mock_runner = mock.Mock() mock_runner.ex.available_to_back = [{"price": 10, "size": 120}] @@ -436,7 +521,12 @@ def test_place_limit_back_unmatched(self, mock__get_runner): @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_back_bpe(self, mock__get_runner): mock_client = mock.Mock(best_price_execution=False) - mock_order_package = mock.Mock(client=mock_client, market_version=None) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) mock_market_book = mock.Mock(status="OPEN") mock_runner = mock.Mock() mock_runner.ex.available_to_back = [{"price": 15, "size": 120}] @@ -451,7 +541,12 @@ def test_place_limit_back_bpe(self, mock__get_runner): @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_lay(self, mock__get_runner): mock_client = mock.Mock(best_price_execution=True) - mock_order_package = mock.Mock(client=mock_client, market_version=None) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) self.simulated.order.side = "LAY" mock_market_book = mock.Mock(status="OPEN") mock_runner = mock.Mock() @@ -465,10 +560,55 @@ def test_place_limit_lay(self, mock__get_runner): self.simulated.matched, [[mock_market_book.publish_time_epoch, 12, 2]] ) + @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") + def test_place_limit_lay_delay_aggressive(self, mock__get_runner): + mock_client = mock.Mock(best_price_execution=True) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=1.1, + ) + self.simulated.order.side = "LAY" + mock_market_book = mock.Mock(status="OPEN") + mock_runner = mock.Mock() + mock_runner.ex.available_to_back = [{"price": 11, "size": 120}] + mock_runner.ex.available_to_lay = [{"price": 12, "size": 120}] + mock__get_runner.return_value = mock_runner + resp = self.simulated.place(mock_order_package, mock_market_book, {}, 1) + self.assertEqual(resp.status, "DELAY") + self.assertEqual(self.simulated.matched, []) + + @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") + def test_place_limit_lay_delay_passive(self, mock__get_runner): + mock_client = mock.Mock(best_price_execution=True) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) + self.simulated.order.side = "LAY" + mock_market_book = mock.Mock(status="OPEN") + mock_runner = mock.Mock() + mock_runner.ex.available_to_back = [{"price": 12, "size": 120}] + mock_runner.ex.available_to_lay = [{"price": 13, "size": 120}] + mock__get_runner.return_value = mock_runner + resp = self.simulated.place(mock_order_package, mock_market_book, {}, 1) + self.assertEqual(resp.status, "SUCCESS") + self.assertEqual(resp.average_price_matched, 0) + self.assertEqual(resp.size_matched, 0) + self.assertEqual(self.simulated.matched, []) + @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_lay_fill_or_kill_matched(self, mock__get_runner): mock_client = mock.Mock(best_price_execution=True) - mock_order_package = mock.Mock(client=mock_client, market_version=None) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) mock_market_book = mock.Mock(status="OPEN") mock_runner = mock.Mock() mock_runner.ex.available_to_back = [{"price": 11, "size": 120}] @@ -490,7 +630,12 @@ def test_place_limit_lay_fill_or_kill_matched(self, mock__get_runner): @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_lay_fill_or_kill_lapsed(self, mock__get_runner): mock_client = mock.Mock(best_price_execution=True) - mock_order_package = mock.Mock(client=mock_client, market_version=None) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) mock_market_book = mock.Mock(status="OPEN") mock_runner = mock.Mock() mock_runner.ex.available_to_back = [{"price": 11, "size": 120}] @@ -510,7 +655,12 @@ def test_place_limit_lay_fill_or_kill_lapsed(self, mock__get_runner): @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_lay_fill_or_kill_no_price(self, mock__get_runner): mock_client = mock.Mock(best_price_execution=True) - mock_order_package = mock.Mock(client=mock_client, market_version=None) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) mock_market_book = mock.Mock(status="OPEN") mock_runner = mock.Mock() mock_runner.ex.available_to_back = [] @@ -532,7 +682,12 @@ def test_place_limit_lay_fill_or_kill_no_price(self, mock__get_runner): @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_lay_fill_or_kill_min_fill_size_matched(self, mock__get_runner): mock_client = mock.Mock(best_price_execution=True) - mock_order_package = mock.Mock(client=mock_client, market_version=None) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) mock_market_book = mock.Mock(status="OPEN") mock_runner = mock.Mock() mock_runner.ex.available_to_back = [{"price": 11, "size": 120}] @@ -556,7 +711,12 @@ def test_place_limit_lay_fill_or_kill_min_fill_size_matched(self, mock__get_runn @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_lay_fill_or_kill_min_fill_size_lapsed(self, mock__get_runner): mock_client = mock.Mock(best_price_execution=True) - mock_order_package = mock.Mock(client=mock_client, market_version=None) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) mock_market_book = mock.Mock(status="OPEN") mock_runner = mock.Mock() mock_runner.ex.available_to_back = [{"price": 11, "size": 120}] @@ -578,7 +738,12 @@ def test_place_limit_lay_fill_or_kill_min_fill_size_lapsed(self, mock__get_runne @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_lay_fill_or_kill_vwap(self, mock__get_runner): mock_client = mock.Mock(best_price_execution=True) - mock_order_package = mock.Mock(client=mock_client, market_version=None) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) mock_market_book = mock.Mock(status="OPEN") mock_runner = mock.Mock() mock_runner.ex.available_to_back = [{"price": 11, "size": 120}] @@ -609,7 +774,12 @@ def test_place_limit_lay_fill_or_kill_vwap(self, mock__get_runner): @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_lay_unmatched(self, mock__get_runner): mock_client = mock.Mock(best_price_execution=True) - mock_order_package = mock.Mock(client=mock_client, market_version=None) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) self.simulated.order.side = "LAY" mock_market_book = mock.Mock(status="OPEN") mock_runner = mock.Mock() @@ -630,7 +800,12 @@ def test_place_limit_lay_unmatched(self, mock__get_runner): @mock.patch("flumine.simulation.simulatedorder.SimulatedOrder._get_runner") def test_place_limit_lay_bpe(self, mock__get_runner): mock_client = mock.Mock(best_price_execution=False) - mock_order_package = mock.Mock(client=mock_client, market_version=None) + mock_order_package = mock.Mock( + client=mock_client, + market_version=None, + elapsed_seconds=0.15, + simulated_latency_plus_delay=0.1, + ) self.simulated.order.side = "LAY" mock_market_book = mock.Mock(status="OPEN") mock_runner = mock.Mock()