Repository: liampauling/flumine
Pull request #413 (2021-04-23 08:08:31)

minor refactor so all order types error on removed runner


Patch:
diff --git a/HISTORY.rst b/HISTORY.rst
index 417d10b..438c6e3 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -3,17 +3,6 @@
 Release History
 ---------------
 
-1.18.5 (2021-04-26)
-+++++++++++++++++++
-
-**Improvements**
-
-- #121 simulation improvements and further validations
-
-**Libraries**
-
-- black upgraded to 21.4b0
-
 1.18.4 (2021-04-20)
 +++++++++++++++++++
 
diff --git a/flumine/__version__.py b/flumine/__version__.py
index 9f750ab..8ff1b53 100644
--- a/flumine/__version__.py
+++ b/flumine/__version__.py
@@ -1,6 +1,6 @@
 __title__ = "flumine"
 __description__ = "Betfair trading framework"
 __url__ = "https://github.com/liampauling/flumine"
-__version__ = "1.18.5"
+__version__ = "1.18.4"
 __author__ = "Liam Pauling"
 __license__ = "MIT"
diff --git a/flumine/execution/simulatedexecution.py b/flumine/execution/simulatedexecution.py
index 4a72267..d02f835 100644
--- a/flumine/execution/simulatedexecution.py
+++ b/flumine/execution/simulatedexecution.py
@@ -12,7 +12,7 @@ class SimulatedExecution(BaseExecution):
     EXCHANGE = ExchangeType.SIMULATED
     PLACE_LATENCY = 0.120
     CANCEL_LATENCY = 0.170
-    UPDATE_LATENCY = 0.150  # todo confirm?
+    UPDATE_LATENCY = 0.150
     REPLACE_LATENCY = 0.280
 
     def handler(self, order_package: BaseOrderPackage) -> None:
@@ -43,7 +43,7 @@ class SimulatedExecution(BaseExecution):
             with order.trade:
                 self._bet_id += 1
                 simulated_response = order.simulated.place(
-                    order_package.client, market.market_book, instruction, self._bet_id
+                    order_package, market.market_book, instruction, self._bet_id
                 )
                 self._order_logger(
                     order, simulated_response, order_package.package_type
@@ -61,10 +61,11 @@ class SimulatedExecution(BaseExecution):
     ) -> None:
         if order_package.client.paper_trade:
             time.sleep(self.CANCEL_LATENCY)
+        market = self.flumine.markets.markets[order_package.market_id]
         failed_transaction_count = 0
         for order in order_package:
             with order.trade:
-                simulated_response = order.simulated.cancel()
+                simulated_response = order.simulated.cancel(market.market_book)
                 self._order_logger(
                     order, simulated_response, order_package.package_type
                 )
@@ -86,10 +87,13 @@ class SimulatedExecution(BaseExecution):
     ) -> None:
         if order_package.client.paper_trade:
             time.sleep(self.UPDATE_LATENCY)
+        market = self.flumine.markets.markets[order_package.market_id]
         failed_transaction_count = 0
         for order, instruction in zip(order_package, order_package.update_instructions):
             with order.trade:
-                simulated_response = order.simulated.update(instruction)
+                simulated_response = order.simulated.update(
+                    market.market_book, instruction
+                )
                 self._order_logger(
                     order, simulated_response, order_package.package_type
                 )
@@ -110,14 +114,14 @@ class SimulatedExecution(BaseExecution):
             order_package.client.paper_trade
         ):  # todo should the cancel happen without a delay?
             time.sleep(order_package.bet_delay + self.REPLACE_LATENCY)
-        failed_transaction_count = 0
         market = self.flumine.markets.markets[order_package.market_id]
+        failed_transaction_count = 0
         for order, instruction in zip(
             order_package, order_package.replace_instructions
         ):
             with order.trade:
                 # cancel current order
-                cancel_instruction_report = order.simulated.cancel()
+                cancel_instruction_report = order.simulated.cancel(market.market_book)
                 if cancel_instruction_report.status == "SUCCESS":
                     order.execution_complete()
                 elif cancel_instruction_report.status == "FAILURE":
@@ -139,7 +143,7 @@ class SimulatedExecution(BaseExecution):
                     cancel_instruction_report.size_cancelled,
                 )
                 place_instruction_report = replacement_order.simulated.place(
-                    order_package.client, market.market_book, instruction, self._bet_id
+                    order_package, market.market_book, instruction, self._bet_id
                 )
                 if place_instruction_report.status == "SUCCESS":
                     self._order_logger(


Test patch:
diff --git a/flumine/backtest/simulated.py b/flumine/backtest/simulated.py
index bfdb31d..b8a80bf 100644
--- a/flumine/backtest/simulated.py
+++ b/flumine/backtest/simulated.py
@@ -1,6 +1,6 @@
 import logging
 import datetime
-from typing import List
+from typing import List, Optional
 from betfairlightweight.resources.bettingresources import MarketBook, RunnerBook
 
 from .utils import (
@@ -59,31 +59,50 @@ class Simulated:
             )
 
     def place(
-        self, client, market_book: MarketBook, instruction: dict, bet_id: int
+        self, order_package, market_book: MarketBook, instruction: dict, bet_id: int
     ) -> SimulatedPlaceResponse:
         # simulates placeOrder request->matching->response
         # todo instruction/fillkill/timeInForce etc
-        # todo check marketVersion or reject entire package?
+        # validate market status
+        if market_book.status != "OPEN":
+            return self._create_place_response(
+                None,
+                status="FAILURE",
+                error_code="ERROR_IN_ORDER",
+            )
 
+        # validate market version
         self.market_version = market_book.version
-        if self.order.order_type.ORDER_TYPE == OrderTypes.LIMIT:
-            runner = self._get_runner(market_book)
-
-            if runner.status == "REMOVED":
-                return self._create_place_response(
-                    bet_id,
-                    status="FAILURE",
-                    error_code="RUNNER_REMOVED",
-                )
+        if (
+            order_package.market_version
+            and order_package.market_version["version"] != self.market_version
+        ):
+            return self._create_place_response(
+                None,
+                status="FAILURE",
+                error_code="ERROR_IN_ORDER",
+            )
 
+        runner = self._get_runner(market_book)
+        # validate runner status
+        if runner.status == "REMOVED":
+            return self._create_place_response(
+                None,
+                status="FAILURE",
+                error_code="RUNNER_REMOVED",
+            )
+        if self.order.order_type.ORDER_TYPE == OrderTypes.LIMIT:
             available_to_back = get_price(runner.ex.available_to_back, 0) or 1.01
             available_to_lay = get_price(runner.ex.available_to_lay, 0) or 1000
             price = self.order.order_type.price
             size = self.order.order_type.size
             if self.order.side == "BACK":
-                if not client.best_price_execution and available_to_back > price:
+                if (
+                    not order_package.client.best_price_execution
+                    and available_to_back > price
+                ):
                     return self._create_place_response(
-                        bet_id,
+                        None,
                         status="FAILURE",
                         error_code="BET_LAPSED_PRICE_IMPROVEMENT_TOO_LARGE",
                     )
@@ -97,9 +116,12 @@ class Simulated:
                     return self._create_place_response(bet_id)
                 available = runner.ex.available_to_lay
             else:
-                if not client.best_price_execution and available_to_lay < price:
+                if (
+                    not order_package.client.best_price_execution
+                    and available_to_lay < price
+                ):
                     return self._create_place_response(
-                        bet_id,
+                        None,
                         status="FAILURE",
                         error_code="BET_LAPSED_PRICE_IMPROVEMENT_TOO_LARGE",
                     )
@@ -124,11 +146,22 @@ class Simulated:
             )
             return self._create_place_response(bet_id)
         else:
+            # validate BSP available, not reconciled and market not inplay
+            if (
+                market_book.market_definition.bsp_market is False
+                or market_book.bsp_reconciled is True
+                or market_book.inplay is True
+            ):
+                return self._create_place_response(
+                    None,
+                    status="FAILURE",
+                    error_code="MARKET_NOT_OPEN_FOR_BSP_BETTING",
+                )
             return self._create_place_response(bet_id)
 
     def _create_place_response(
         self,
-        bet_id: int,
+        bet_id: Optional[int],
         status: str = "SUCCESS",
         order_status: str = None,
         error_code: str = None,
@@ -148,8 +181,13 @@ class Simulated:
             error_code=error_code,
         )
 
-    def cancel(self) -> SimulatedCancelResponse:
+    def cancel(self, market_book: MarketBook) -> SimulatedCancelResponse:
         # simulates cancelOrder request->cancel->response
+        if market_book.status != "OPEN":
+            return SimulatedCancelResponse(
+                status="FAILURE",
+                error_code="ERROR_IN_ORDER",
+            )
         if self.order.order_type.ORDER_TYPE == OrderTypes.LIMIT:
             _size_reduction = (
                 self.order.update_data.get("size_reduction") or self.size_remaining
@@ -166,11 +204,24 @@ class Simulated:
         else:
             return SimulatedCancelResponse(
                 status="FAILURE",
-                error_code="BET_ACTION_ERROR",  # todo ?
+                error_code="BET_ACTION_ERROR",
             )
 
-    def update(self, instruction: dict):
+    def update(
+        self, market_book: MarketBook, instruction: dict
+    ) -> SimulatedUpdateResponse:
         # simulates updateOrder request->update->response
+        if market_book.status != "OPEN":
+            return SimulatedUpdateResponse(
+                status="FAILURE",
+                error_code="ERROR_IN_ORDER",
+            )
+        # validate BSP available
+        if market_book.market_definition.persistence_enabled is False:
+            return SimulatedUpdateResponse(
+                status="FAILURE",
+                error_code="INVALID_PERSISTENCE_TYPE",
+            )
         if (
             self.order.order_type.ORDER_TYPE == OrderTypes.LIMIT
             and self.size_remaining > 0
@@ -180,7 +231,7 @@ class Simulated:
             )
             return SimulatedUpdateResponse(status="SUCCESS")
         else:
-            return SimulatedCancelResponse(
+            return SimulatedUpdateResponse(
                 status="FAILURE",
                 error_code="BET_ACTION_ERROR",
             )
diff --git a/requirements-test.txt b/requirements-test.txt
index c8f26a0..1264222 100644
--- a/requirements-test.txt
+++ b/requirements-test.txt
@@ -1,5 +1,5 @@
 # Tests & Linting
-black==21.4b0
+black==20.8b1
 coverage
 
 # Documentation
diff --git a/tests/test_execution.py b/tests/test_execution.py
index a1fd4f2..bf1484a 100644
--- a/tests/test_execution.py
+++ b/tests/test_execution.py
@@ -929,7 +929,7 @@ class SimulatedExecutionTest(unittest.TestCase):
         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.client,
+            mock_order_package,
             self.mock_market.market_book,
             1,
             self.execution._bet_id,
@@ -958,7 +958,7 @@ class SimulatedExecutionTest(unittest.TestCase):
         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.client,
+            mock_order_package,
             self.mock_market.market_book,
             1,
             self.execution._bet_id,
@@ -988,7 +988,7 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_order = mock.Mock(size_cancelled=2, size_remaining=0)
         mock_order.trade.__enter__ = mock.Mock()
         mock_order.trade.__exit__ = mock.Mock()
-        mock_order_package = mock.MagicMock()
+        mock_order_package = mock.MagicMock(market_id="1.23")
         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]))
@@ -996,7 +996,7 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_sim_resp.status = "SUCCESS"
         mock_order.simulated.cancel.return_value = mock_sim_resp
         self.execution.execute_cancel(mock_order_package, None)
-        mock_order.simulated.cancel.assert_called_with()
+        mock_order.simulated.cancel.assert_called_with(self.mock_market.market_book)
         mock__order_logger.assert_called_with(
             mock_order, mock_sim_resp, mock_order_package.package_type
         )
@@ -1017,7 +1017,7 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_sim_resp.status = "SUCCESS"
         mock_order.simulated.cancel.return_value = mock_sim_resp
         self.execution.execute_cancel(mock_order_package, None)
-        mock_order.simulated.cancel.assert_called_with()
+        mock_order.simulated.cancel.assert_called_with(self.mock_market.market_book)
         mock__order_logger.assert_called_with(
             mock_order, mock_sim_resp, mock_order_package.package_type
         )
@@ -1030,7 +1030,7 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_order = mock.Mock()
         mock_order.trade.__enter__ = mock.Mock()
         mock_order.trade.__exit__ = mock.Mock()
-        mock_order_package = mock.MagicMock()
+        mock_order_package = mock.MagicMock(market_id="1.23")
         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]))
@@ -1038,7 +1038,7 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_sim_resp.status = "FAILURE"
         mock_order.simulated.cancel.return_value = mock_sim_resp
         self.execution.execute_cancel(mock_order_package, None)
-        mock_order.simulated.cancel.assert_called_with()
+        mock_order.simulated.cancel.assert_called_with(self.mock_market.market_book)
         mock__order_logger.assert_called_with(
             mock_order, mock_sim_resp, mock_order_package.package_type
         )
@@ -1049,7 +1049,7 @@ class SimulatedExecutionTest(unittest.TestCase):
 
     @mock.patch("flumine.execution.simulatedexecution.time")
     def test_execute_cancel_paper_trade(self, mock_time):
-        mock_order_package = mock.Mock(bet_delay=1)
+        mock_order_package = mock.Mock(market_id="1.23", bet_delay=1)
         mock_order_package.__iter__ = mock.Mock(return_value=iter([]))
         mock_order_package.client.paper_trade = True
         self.execution.execute_cancel(mock_order_package, None)
@@ -1060,7 +1060,7 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_order = mock.Mock()
         mock_order.trade.__enter__ = mock.Mock()
         mock_order.trade.__exit__ = mock.Mock()
-        mock_order_package = mock.Mock()
+        mock_order_package = mock.Mock(market_id="1.23")
         mock_order_package.client.paper_trade = False
         mock_order_package.__iter__ = mock.Mock(return_value=iter([mock_order]))
         mock_order_package.update_instructions = ["PERSIST"]
@@ -1069,7 +1069,9 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_sim_resp.status = "SUCCESS"
         mock_order.simulated.update.return_value = mock_sim_resp
         self.execution.execute_update(mock_order_package, None)
-        mock_order.simulated.update.assert_called_with("PERSIST")
+        mock_order.simulated.update.assert_called_with(
+            self.mock_market.market_book, "PERSIST"
+        )
         mock__order_logger.assert_called_with(
             mock_order, mock_sim_resp, mock_order_package.package_type
         )
@@ -1082,7 +1084,7 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_order = mock.Mock()
         mock_order.trade.__enter__ = mock.Mock()
         mock_order.trade.__exit__ = mock.Mock()
-        mock_order_package = mock.Mock()
+        mock_order_package = mock.Mock(market_id="1.23")
         mock_order_package.client.paper_trade = False
         mock_order_package.__iter__ = mock.Mock(return_value=iter([mock_order]))
         mock_order_package.update_instructions = ["PERSIST"]
@@ -1091,7 +1093,9 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_sim_resp.status = "FAILURE"
         mock_order.simulated.update.return_value = mock_sim_resp
         self.execution.execute_update(mock_order_package, None)
-        mock_order.simulated.update.assert_called_with("PERSIST")
+        mock_order.simulated.update.assert_called_with(
+            self.mock_market.market_book, "PERSIST"
+        )
         mock__order_logger.assert_called_with(
             mock_order, mock_sim_resp, mock_order_package.package_type
         )
@@ -1102,7 +1106,9 @@ class SimulatedExecutionTest(unittest.TestCase):
 
     @mock.patch("flumine.execution.simulatedexecution.time")
     def test_execute_update_paper_trade(self, mock_time):
-        mock_order_package = mock.Mock(update_instructions=[], bet_delay=1)
+        mock_order_package = mock.Mock(
+            market_id="1.23", update_instructions=[], bet_delay=1
+        )
         mock_order_package.__iter__ = mock.Mock(return_value=iter([]))
         mock_order_package.client.paper_trade = True
         self.execution.execute_update(mock_order_package, None)
@@ -1130,9 +1136,9 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_replacement_order.simulated.place.return_value = mock_sim_resp
         mock_order.trade.create_order_replacement.return_value = mock_replacement_order
         self.execution.execute_replace(mock_order_package, None)
-        mock_order.simulated.cancel.assert_called_with()
+        mock_order.simulated.cancel.assert_called_with(self.mock_market.market_book)
         mock_replacement_order.simulated.place.assert_called_with(
-            mock_order_package.client,
+            mock_order_package,
             self.mock_market.market_book,
             {"newPrice": 2.03},
             self.execution._bet_id,
@@ -1174,9 +1180,9 @@ class SimulatedExecutionTest(unittest.TestCase):
         mock_replacement_order.simulated.place.return_value = mock_sim_resp
         mock_order.trade.create_order_replacement.return_value = mock_replacement_order
         self.execution.execute_replace(mock_order_package, None)
-        mock_order.simulated.cancel.assert_called_with()
+        mock_order.simulated.cancel.assert_called_with(self.mock_market.market_book)
         mock_replacement_order.simulated.place.assert_called_with(
-            mock_order_package.client,
+            mock_order_package,
             self.mock_market.market_book,
             {"newPrice": 2.54},
             self.execution._bet_id,
diff --git a/tests/test_integration.py b/tests/test_integration.py
index 3db382f..9157933 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -183,7 +183,7 @@ class IntegrationTest(unittest.TestCase):
             self.assertEqual(len(limit_replace_orders), 28)
             limit_inplay_orders = market.blotter.strategy_orders(limit_inplay_strategy)
             self.assertEqual(
-                round(sum([o.simulated.profit for o in limit_inplay_orders]), 2), 18.96
+                round(sum([o.simulated.profit for o in limit_inplay_orders]), 2), 19.88
             )
             self.assertEqual(len(limit_inplay_orders), 15)
             market_orders = market.blotter.strategy_orders(market_strategy)
@@ -192,7 +192,7 @@ class IntegrationTest(unittest.TestCase):
             )
             self.assertEqual(len(market_orders), 14)
             # check transaction count
-            self.assertEqual(market._transaction_id, 25428)
+            self.assertEqual(market._transaction_id, 25427)
 
     def test_event_processing(self):
         client = clients.BacktestClient()
@@ -246,16 +246,16 @@ class IntegrationTest(unittest.TestCase):
         win_market = framework.markets.markets["1.170258213"]
         limit_inplay_orders = win_market.blotter.strategy_orders(limit_inplay_strategy)
         self.assertEqual(
-            round(sum([o.simulated.profit for o in limit_inplay_orders]), 2), 18.96
+            round(sum([o.simulated.profit for o in limit_inplay_orders]), 2), 19.88
         )
         self.assertEqual(len(limit_inplay_orders), 15)
-        self.assertEqual(win_market._transaction_id, 166)
+        self.assertEqual(win_market._transaction_id, 165)
 
         # Same event
         win_market = framework.markets.markets["1.181223994"]
         limit_inplay_orders = win_market.blotter.strategy_orders(limit_inplay_strategy)
         self.assertEqual(
-            round(sum([o.simulated.profit for o in limit_inplay_orders]), 2), 99.58
+            round(sum([o.simulated.profit for o in limit_inplay_orders]), 2), 101.44
         )
         self.assertEqual(len(limit_inplay_orders), 88)
         self.assertEqual(win_market._transaction_id, 1329)
@@ -265,7 +265,7 @@ class IntegrationTest(unittest.TestCase):
             limit_inplay_strategy
         )
         self.assertEqual(
-            round(sum([o.simulated.profit for o in limit_inplay_orders]), 2), -96.66
+            round(sum([o.simulated.profit for o in limit_inplay_orders]), 2), -95.02
         )
         self.assertEqual(len(limit_inplay_orders), 200)
         self.assertEqual(place_market._transaction_id, 2436)
diff --git a/tests/test_simulated.py b/tests/test_simulated.py
index 8e457b6..2ac9b5d 100644
--- a/tests/test_simulated.py
+++ b/tests/test_simulated.py
@@ -121,12 +121,13 @@ class SimulatedTest(unittest.TestCase):
     @mock.patch("flumine.backtest.simulated.Simulated._get_runner")
     def test_place_limit_back(self, mock__get_runner):
         mock_client = mock.Mock(best_price_execution=True)
-        mock_market_book = mock.Mock()
+        mock_order_package = mock.Mock(client=mock_client, market_version=None)
+        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_client, mock_market_book, {}, 1)
+        resp = self.simulated.place(mock_order_package, mock_market_book, {}, 1)
         self.assertEqual(self.simulated.market_version, mock_market_book.version)
         self.assertEqual(resp.average_price_matched, 12)
         self.assertEqual(resp.size_matched, 2)
@@ -134,10 +135,33 @@ class SimulatedTest(unittest.TestCase):
             self.simulated.matched, [[mock_market_book.publish_time_epoch, 12, 2]]
         )
 
+    @mock.patch("flumine.backtest.simulated.Simulated._get_runner")
+    def test_place_market_status(self, mock__get_runner):
+        mock_client = mock.Mock(best_price_execution=False)
+        mock_order_package = mock.Mock(client=mock_client, market_version=None)
+        mock_market_book = mock.Mock(status="SUSPENDED")
+        resp = self.simulated.place(mock_order_package, mock_market_book, {}, 1)
+        self.assertEqual(resp.status, "FAILURE")
+        self.assertEqual(resp.error_code, "ERROR_IN_ORDER")
+        self.assertEqual(self.simulated.matched, [])
+
+    @mock.patch("flumine.backtest.simulated.Simulated._get_runner")
+    def test_place_market_version(self, mock__get_runner):
+        mock_client = mock.Mock(best_price_execution=False)
+        mock_order_package = mock.Mock(
+            client=mock_client, market_version={"version": 123}
+        )
+        mock_market_book = mock.Mock(status="OPEN", version=124)
+        resp = self.simulated.place(mock_order_package, mock_market_book, {}, 1)
+        self.assertEqual(resp.status, "FAILURE")
+        self.assertEqual(resp.error_code, "ERROR_IN_ORDER")
+        self.assertEqual(self.simulated.matched, [])
+
     @mock.patch("flumine.backtest.simulated.Simulated._get_runner")
     def test_place_limit_back_unmatched(self, mock__get_runner):
         mock_client = mock.Mock(best_price_execution=True)
-        mock_market_book = mock.Mock()
+        mock_order_package = mock.Mock(client=mock_client, market_version=None)
+        mock_market_book = mock.Mock(status="OPEN")
         mock_runner = mock.Mock()
         mock_runner.ex.available_to_back = [{"price": 10, "size": 120}]
         mock_runner.ex.available_to_lay = [
@@ -147,7 +171,7 @@ class SimulatedTest(unittest.TestCase):
             {"price": 15, "size": 32},
         ]
         mock__get_runner.return_value = mock_runner
-        resp = self.simulated.place(mock_client, mock_market_book, {}, 1)
+        resp = self.simulated.place(mock_order_package, mock_market_book, {}, 1)
         self.assertEqual(resp.average_price_matched, 0)
         self.assertEqual(resp.size_matched, 0)
         self.assertEqual(self.simulated.matched, [])
@@ -156,12 +180,13 @@ class SimulatedTest(unittest.TestCase):
     @mock.patch("flumine.backtest.simulated.Simulated._get_runner")
     def test_place_limit_back_bpe(self, mock__get_runner):
         mock_client = mock.Mock(best_price_execution=False)
-        mock_market_book = mock.Mock()
+        mock_order_package = mock.Mock(client=mock_client, market_version=None)
+        mock_market_book = mock.Mock(status="OPEN")
         mock_runner = mock.Mock()
         mock_runner.ex.available_to_back = [{"price": 15, "size": 120}]
         mock_runner.ex.available_to_lay = [{"price": 16, "size": 120}]
         mock__get_runner.return_value = mock_runner
-        resp = self.simulated.place(mock_client, mock_market_book, {}, 1)
+        resp = self.simulated.place(mock_order_package, mock_market_book, {}, 1)
         self.assertEqual(resp.status, "FAILURE")
         self.assertEqual(resp.error_code, "BET_LAPSED_PRICE_IMPROVEMENT_TOO_LARGE")
         self.assertEqual(self.simulated.matched, [])
@@ -169,13 +194,14 @@ class SimulatedTest(unittest.TestCase):
     @mock.patch("flumine.backtest.simulated.Simulated._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)
         self.simulated.order.side = "LAY"
-        mock_market_book = mock.Mock()
+        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_client, mock_market_book, {}, 1)
+        resp = self.simulated.place(mock_order_package, mock_market_book, {}, 1)
         self.assertEqual(resp.average_price_matched, 12)
         self.assertEqual(resp.size_matched, 2)
         self.assertEqual(
@@ -185,8 +211,9 @@ class SimulatedTest(unittest.TestCase):
     @mock.patch("flumine.backtest.simulated.Simulated._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)
         self.simulated.order.side = "LAY"
-        mock_market_book = mock.Mock()
+        mock_market_book = mock.Mock(status="OPEN")
         mock_runner = mock.Mock()
         mock_runner.ex.available_to_back = [
             {"price": 10.5, "size": 120},
@@ -196,7 +223,7 @@ class SimulatedTest(unittest.TestCase):
         ]
         mock_runner.ex.available_to_lay = [{"price": 15, "size": 32}]
         mock__get_runner.return_value = mock_runner
-        resp = self.simulated.place(mock_client, mock_market_book, {}, 1)
+        resp = self.simulated.place(mock_order_package, mock_market_book, {}, 1)
         self.assertEqual(resp.average_price_matched, 0)
         self.assertEqual(resp.size_matched, 0)
         self.assertEqual(self.simulated.matched, [])
@@ -205,13 +232,14 @@ class SimulatedTest(unittest.TestCase):
     @mock.patch("flumine.backtest.simulated.Simulated._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)
         self.simulated.order.side = "LAY"
-        mock_market_book = mock.Mock()
+        mock_market_book = mock.Mock(status="OPEN")
         mock_runner = mock.Mock()
         mock_runner.ex.available_to_back = [{"price": 10, "size": 120}]
         mock_runner.ex.available_to_lay = [{"price": 10.5, "size": 120}]
         mock__get_runner.return_value = mock_runner
-        resp = self.simulated.place(mock_client, mock_market_book, {}, 1)
+        resp = self.simulated.place(mock_order_package, mock_market_book, {}, 1)
         self.assertEqual(resp.status, "FAILURE")
         self.assertEqual(resp.error_code, "BET_LAPSED_PRICE_IMPROVEMENT_TOO_LARGE")
         self.assertEqual(self.simulated.matched, [])
@@ -219,27 +247,43 @@ class SimulatedTest(unittest.TestCase):
     @mock.patch("flumine.backtest.simulated.Simulated._get_runner")
     def test_place_limit_removed_runner(self, mock__get_runner):
         mock_client = mock.Mock(best_price_execution=False)
+        mock_order_package = mock.Mock(client=mock_client, market_version=None)
         self.simulated.order.side = "BACK"
-        mock_market_book = mock.Mock()
+        mock_market_book = mock.Mock(status="OPEN")
         mock_runner = mock.Mock()
         mock_runner.ex.available_to_back = [{"price": 10, "size": 120}]
         mock_runner.ex.available_to_lay = [{"price": 10.5, "size": 120}]
         mock_runner.status = "REMOVED"
         mock__get_runner.return_value = mock_runner
-        resp = self.simulated.place(mock_client, mock_market_book, {}, 1)
+        resp = self.simulated.place(mock_order_package, mock_market_book, {}, 1)
         self.assertEqual(resp.status, "FAILURE")
         self.assertEqual(resp.error_code, "RUNNER_REMOVED")
         self.assertEqual(self.simulated.matched, [])
 
     @mock.patch("flumine.backtest.simulated.Simulated._create_place_response")
-    def test_place_else(self, mock__create_place_response):
+    @mock.patch("flumine.backtest.simulated.Simulated._get_runner")
+    def test_place_else(self, mock__get_runner, mock__create_place_response):
+        mock__get_runner.status = "ACTIVE"
         mock_client = mock.Mock(best_price_execution=True)
+        mock_order_package = mock.Mock(client=mock_client, market_version=None)
         self.simulated.order.order_type.ORDER_TYPE = OrderTypes.MARKET_ON_CLOSE
-        mock_market_book = mock.Mock()
-        self.simulated.place(mock_client, mock_market_book, {}, 1)
+        mock_market_book = mock.Mock(status="OPEN")
+        self.simulated.place(mock_order_package, mock_market_book, {}, 1)
         self.assertEqual(self.simulated.matched, [])
         mock__create_place_response.assert_called_with(1)
 
+    @mock.patch("flumine.backtest.simulated.Simulated._get_runner")
+    def test_place_else_bsp_recon(self, mock__get_runner):
+        mock__get_runner.status = "ACTIVE"
+        mock_client = mock.Mock(best_price_execution=True)
+        mock_order_package = mock.Mock(client=mock_client, market_version=None)
+        self.simulated.order.order_type.ORDER_TYPE = OrderTypes.MARKET_ON_CLOSE
+        mock_market_book = mock.Mock(status="OPEN", bsp_reconciled=True, inplay=False)
+        resp = self.simulated.place(mock_order_package, mock_market_book, {}, 1)
+        self.assertEqual(resp.status, "FAILURE")
+        self.assertEqual(resp.error_code, "MARKET_NOT_OPEN_FOR_BSP_BETTING")
+        self.assertEqual(self.simulated.matched, [])
+
     def test__create_place_response(self):
         resp = self.simulated._create_place_response(
             1234, "FAILURE", error_code="dubs of the mad skint and british"
@@ -262,14 +306,23 @@ class SimulatedTest(unittest.TestCase):
 
     def test_cancel(self):
         self.simulated.order.update_data = {}
-        resp = self.simulated.cancel()
+        mock_market_book = mock.Mock(status="OPEN")
+        resp = self.simulated.cancel(mock_market_book)
         self.assertEqual(self.simulated.size_cancelled, 2.0)
         self.assertEqual(resp.status, "SUCCESS")
         self.assertEqual(resp.size_cancelled, 2.0)
 
+    def test_cancel_market_status(self):
+        mock_market_book = mock.Mock(status="SUSPENDED")
+        resp = self.simulated.cancel(mock_market_book)
+        self.assertEqual(resp.status, "FAILURE")
+        self.assertEqual(resp.error_code, "ERROR_IN_ORDER")
+        self.assertEqual(self.simulated.size_cancelled, 0)
+
     def test_cancel_reduction(self):
         self.simulated.order.update_data = {"size_reduction": 0.50}
-        resp = self.simulated.cancel()
+        mock_market_book = mock.Mock(status="OPEN")
+        resp = self.simulated.cancel(mock_market_book)
         self.assertEqual(self.simulated.size_cancelled, 0.50)
         self.assertEqual(self.simulated.size_remaining, 1.50)
         self.assertEqual(resp.status, "SUCCESS")
@@ -278,7 +331,8 @@ class SimulatedTest(unittest.TestCase):
     def test_cancel_reduction_multi(self):
         self.simulated.size_cancelled = 0.10
         self.simulated.order.update_data = {"size_reduction": 0.50}
-        resp = self.simulated.cancel()
+        mock_market_book = mock.Mock(status="OPEN")
+        resp = self.simulated.cancel(mock_market_book)
         self.assertEqual(self.simulated.size_cancelled, 0.60)
         self.assertEqual(self.simulated.size_remaining, 1.40)
         self.assertEqual(resp.status, "SUCCESS")
@@ -286,7 +340,8 @@ class SimulatedTest(unittest.TestCase):
 
     def test_cancel_reduction_greater_than(self):
         self.simulated.order.update_data = {"size_reduction": 64.0}
-        resp = self.simulated.cancel()
+        mock_market_book = mock.Mock(status="OPEN")
+        resp = self.simulated.cancel(mock_market_book)
         self.assertEqual(self.simulated.size_cancelled, 2.00)
         self.assertEqual(self.simulated.size_remaining, 0.00)
         self.assertEqual(resp.status, "SUCCESS")
@@ -294,17 +349,42 @@ class SimulatedTest(unittest.TestCase):
 
     def test_cancel_else(self):
         self.simulated.order.order_type.ORDER_TYPE = OrderTypes.MARKET_ON_CLOSE
-        resp = self.simulated.cancel()
+        mock_market_book = mock.Mock(status="OPEN")
+        resp = self.simulated.cancel(mock_market_book)
         self.assertEqual(resp.status, "FAILURE")
         self.assertEqual(resp.error_code, "BET_ACTION_ERROR")
 
     def test_update(self):
-        resp = self.simulated.update({"newPersistenceType": "PERSIST"})
+        mock_market_book = mock.Mock(status="OPEN")
+        resp = self.simulated.update(
+            mock_market_book, {"newPersistenceType": "PERSIST"}
+        )
         self.assertEqual(resp.status, "SUCCESS")
+        self.assertEqual(self.simulated.order.order_type.persistence_type, "PERSIST")
+
+    def test_update_market_status(self):
+        mock_market_book = mock.Mock(status="SUSPENDED")
+        resp = self.simulated.update(
+            mock_market_book, {"newPersistenceType": "PERSIST"}
+        )
+        self.assertEqual(resp.status, "FAILURE")
+        self.assertEqual(resp.error_code, "ERROR_IN_ORDER")
+
+    def test_update_market_p_enabled(self):
+        mock_market_book = mock.Mock(status="OPEN")
+        mock_market_book.market_definition.persistence_enabled = False
+        resp = self.simulated.update(
+            mock_market_book, {"newPersistenceType": "PERSIST"}
+        )
+        self.assertEqual(resp.status, "FAILURE")
+        self.assertEqual(resp.error_code, "INVALID_PERSISTENCE_TYPE")
 
     def test_update_else(self):
+        mock_market_book = mock.Mock(status="OPEN")
         self.simulated.order.order_type.ORDER_TYPE = OrderTypes.MARKET_ON_CLOSE
-        resp = self.simulated.update({"newPersistenceType": "PERSIST"})
+        resp = self.simulated.update(
+            mock_market_book, {"newPersistenceType": "PERSIST"}
+        )
         self.assertEqual(resp.status, "FAILURE")
         self.assertEqual(resp.error_code, "BET_ACTION_ERROR")