Repository: liampauling/flumine
Pull request #466 (2021-07-12 07:58:26)

history/version


Patch:
diff --git a/HISTORY.rst b/HISTORY.rst
index 9188f6f..115deec 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -3,6 +3,18 @@
 Release History
 ---------------
 
+1.19.7 (2021-07-12)
++++++++++++++++++++
+
+**Improvements**
+
+- #464 get session handling refactor to take oldest session
+
+**Bug Fixes**
+
+- #454 SP nr size adjustment (@jsphon)
+- #464 wrong order state after multiple connection reset errors
+
 1.19.6 (2021-07-09)
 +++++++++++++++++++
 
diff --git a/flumine/__version__.py b/flumine/__version__.py
index b622618..fe9ae70 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.19.6"
+__version__ = "1.19.7"
 __author__ = "Liam Pauling"
 __license__ = "MIT"
diff --git a/flumine/execution/baseexecution.py b/flumine/execution/baseexecution.py
index 53ecf8b..c1391cb 100644
--- a/flumine/execution/baseexecution.py
+++ b/flumine/execution/baseexecution.py
@@ -77,7 +77,7 @@ class BaseExecution:
     def _get_http_session(self) -> requests.Session:
         while self._sessions:
             try:
-                _session = self._sessions.pop()
+                _session = self._sessions.pop(0)
                 if (time.time() - _session.time_returned) > MAX_SESSION_AGE:
                     self._return_http_session(_session, err=True)
                     continue
diff --git a/flumine/execution/betfairexecution.py b/flumine/execution/betfairexecution.py
index 2c9d609..ca99fed 100644
--- a/flumine/execution/betfairexecution.py
+++ b/flumine/execution/betfairexecution.py
@@ -253,6 +253,12 @@ class BetfairExecution(BaseExecution):
                 )
                 if order_package.retry():
                     self.handler(order_package)
+                else:
+                    # reset orders
+                    if order_package.package_type == OrderPackageType.PLACE:
+                        order_package.reset_orders(complete=True)
+                    else:
+                        order_package.reset_orders()
 
                 self._return_http_session(http_session, err=True)
                 return
diff --git a/flumine/markets/middleware.py b/flumine/markets/middleware.py
index bafe57c..38c5e85 100644
--- a/flumine/markets/middleware.py
+++ b/flumine/markets/middleware.py
@@ -117,6 +117,13 @@ class SimulatedMiddleware(Middleware):
                                 / (100 - runner_adjustment_factor)
                             )
                             order.order_type.liability *= multiplier
+                            if order.average_price_matched:
+                                # We will get here if the NR is declared inplay
+                                order.current_order.size_matched = round(
+                                    order.order_type.liability
+                                    / (order.average_price_matched - 1),
+                                    2,
+                                )
                             logger.warning(
                                 "WIN MARKET_ON_CLOSE Order adjusted due to non runner {0}".format(
                                     order.selection_id
@@ -126,6 +133,13 @@ class SimulatedMiddleware(Middleware):
                         elif market.market_type in {"PLACE", "OTHER_PLACE"}:
                             multiplier = (100 - removal_adjustment_factor) * 0.01
                             order.order_type.liability *= multiplier
+                            if order.average_price_matched:
+                                # We will get here if the NR is declared inplay
+                                order.current_order.size_matched = round(
+                                    order.order_type.liability
+                                    / (order.average_price_matched - 1),
+                                    2,
+                                )
                             logger.warning(
                                 "PLACE MARKET_ON_CLOSE Order adjusted due to non runner {0}".format(
                                     order.selection_id
diff --git a/flumine/order/orderpackage.py b/flumine/order/orderpackage.py
index 046cccc..ebf8cad 100644
--- a/flumine/order/orderpackage.py
+++ b/flumine/order/orderpackage.py
@@ -61,6 +61,14 @@ class BaseOrderPackage(BaseEvent):
             return True
         return False
 
+    def reset_orders(self, complete: bool = False) -> None:
+        for order in self:
+            with order.trade:
+                if complete:
+                    order.execution_complete()
+                else:
+                    order.executable()
+
     def calc_simulated_delay(self) -> float:
         if self.client.execution.EXCHANGE == ExchangeType.SIMULATED:
             if self.package_type == OrderPackageType.PLACE:


Test patch:
diff --git a/tests/test_execution.py b/tests/test_execution.py
index 814a12d..e8e08b6 100644
--- a/tests/test_execution.py
+++ b/tests/test_execution.py
@@ -117,8 +117,8 @@ class BaseExecutionTest(unittest.TestCase):
         mock_session_one = mock.Mock(time_returned=time.time())
         mock_session_two = mock.Mock(time_returned=time.time())
         self.execution._sessions = [mock_session_one, mock_session_two]
-        self.assertEqual(self.execution._get_http_session(), mock_session_two)
         self.assertEqual(self.execution._get_http_session(), mock_session_one)
+        self.assertEqual(self.execution._get_http_session(), mock_session_two)
         self.assertEqual(self.execution._get_http_session(), mock__create_new_session())
 
     @mock.patch("flumine.execution.baseexecution.BaseExecution._return_http_session")
@@ -833,6 +833,33 @@ class BetfairExecutionTest(unittest.TestCase):
         mock_trading_function.assert_called_with(mock_order_package, mock_session)
         mock__return_http_session.assert_called_with(mock_session, err=True)
         mock_handler.put.assert_not_called()
+        mock_order_package.reset_orders.assert_called_with()
+
+    @mock.patch("flumine.execution.betfairexecution.BetfairExecution.handler")
+    @mock.patch(
+        "flumine.execution.betfairexecution.BetfairExecution._return_http_session"
+    )
+    def test__execution_helper_error_no_retry_place(
+        self, mock__return_http_session, mock_handler
+    ):
+        mock_trading_function = mock.Mock()
+        mock_trading_function.__name__ = "test"
+        mock_trading_function.side_effect = BetfairError()
+        mock_session = mock.Mock()
+        mock_order_package = mock.Mock(
+            elapsed_seconds=0.001, package_type=OrderPackageType.PLACE
+        )
+        mock_order_package.info = {}
+        mock_order_package.retry.return_value = False
+        self.assertIsNone(
+            self.execution._execution_helper(
+                mock_trading_function, mock_order_package, mock_session
+            )
+        )
+        mock_trading_function.assert_called_with(mock_order_package, mock_session)
+        mock__return_http_session.assert_called_with(mock_session, err=True)
+        mock_handler.put.assert_not_called()
+        mock_order_package.reset_orders.assert_called_with(complete=True)
 
     @mock.patch(
         "flumine.execution.betfairexecution.BetfairExecution._return_http_session"
diff --git a/tests/test_middleware.py b/tests/test_middleware.py
index 4cc68bd..3fcc046 100644
--- a/tests/test_middleware.py
+++ b/tests/test_middleware.py
@@ -140,7 +140,13 @@ class SimulatedMiddlewareTest(unittest.TestCase):
     def test__process_runner_removal_sp_win(self):
         order_type = MarketOnCloseOrder(liability=200)
         mock_order = mock.Mock(
-            selection_id=1234, handicap=0, order_type=order_type, info={}, side="LAY"
+            selection_id=1234,
+            handicap=0,
+            order_type=order_type,
+            info={},
+            side="LAY",
+            current_order=mock.Mock(size_matched=0),
+            average_price_matched=None,
         )
 
         mock_market_book = mock.Mock()
@@ -156,10 +162,67 @@ class SimulatedMiddlewareTest(unittest.TestCase):
         # defined in the example here: https://github.com/liampauling/flumine/issues/454
         self.assertEqual(mock_order.order_type.liability, 75)
 
+    def test__process_runner_removal_sp_win_inplay(self):
+        order_type = MarketOnCloseOrder(liability=200)
+        mock_order = mock.Mock(
+            selection_id=1234,
+            handicap=0,
+            order_type=order_type,
+            info={},
+            side="LAY",
+            current_order=mock.Mock(size_matched=0),
+            average_price_matched=10,
+        )
+
+        mock_market_book = mock.Mock()
+        mock_market_book.runners = [
+            mock.Mock(selection_id=1234, handicap=0, adjustment_factor=20)
+        ]
+        mock_market = mock.Mock(
+            market_type="WIN", blotter=[mock_order], market_book=mock_market_book
+        )
+        self.middleware._process_runner_removal(mock_market, 12345, 0, 50)
+
+        # The liability of £200 is adjusted by the multiplier of 37.5%, which s
+        # defined in the example here: https://github.com/liampauling/flumine/issues/454
+        self.assertEqual(mock_order.order_type.liability, 75)
+        # Size matched should be 75 / (10.0-1.0) \approx 8.33
+        self.assertEqual(8.33, mock_order.current_order.size_matched)
+
     def test__process_runner_removal_sp_place(self):
         order_type = MarketOnCloseOrder(liability=200)
         mock_order = mock.Mock(
-            selection_id=1234, handicap=0, order_type=order_type, info={}, side="LAY"
+            selection_id=1234,
+            handicap=0,
+            order_type=order_type,
+            info={},
+            side="LAY",
+            current_order=mock.Mock(size_matched=0),
+            average_price_matched=None,
+        )
+
+        mock_market_book = mock.Mock()
+        mock_market_book.runners = [
+            mock.Mock(selection_id=1234, handicap=0, adjustment_factor=20)
+        ]
+        mock_market = mock.Mock(
+            market_type="PLACE", blotter=[mock_order], market_book=mock_market_book
+        )
+        self.middleware._process_runner_removal(mock_market, 12345, 0, 50)
+
+        # The liability of £200 is reduced by the non runner's adjustment factor of 50%
+        self.assertEqual(mock_order.order_type.liability, 100)
+
+    def test__process_runner_removal_sp_place_inplay(self):
+        order_type = MarketOnCloseOrder(liability=200)
+        mock_order = mock.Mock(
+            selection_id=1234,
+            handicap=0,
+            order_type=order_type,
+            info={},
+            side="LAY",
+            current_order=mock.Mock(size_matched=0),
+            average_price_matched=10.0,
         )
 
         mock_market_book = mock.Mock()
@@ -173,6 +236,8 @@ class SimulatedMiddlewareTest(unittest.TestCase):
 
         # The liability of £200 is reduced by the non runner's adjustment factor of 50%
         self.assertEqual(mock_order.order_type.liability, 100)
+        # Size matched should be 100 / (10.0-1.0) \approx 11.11
+        self.assertEqual(11.11, mock_order.current_order.size_matched)
 
     def test__process_streaming_update(self):
         mock_market_book = mock.Mock(
diff --git a/tests/test_orderpackage.py b/tests/test_orderpackage.py
index 339b3d6..730ca3d 100644
--- a/tests/test_orderpackage.py
+++ b/tests/test_orderpackage.py
@@ -61,6 +61,15 @@ class OrderPackageTest(unittest.TestCase):
         self.assertEqual(self.order_package._retry_count, 3)
         mock_time.sleep.assert_called()
 
+    def test_reset_orders(self):
+        mock_order = mock.MagicMock()
+        self.order_package._orders = [mock_order]
+        self.order_package.reset_orders()
+        mock_order.executable.assert_called()
+        mock_order.trade.__enter__.assert_called()
+        self.order_package.reset_orders(True)
+        mock_order.execution_complete.assert_called()
+
     def test_calc_simulated_delay(self):
         config.place_latency = 0.1
         config.cancel_latency = 0.2