Repository: liampauling/flumine (python)

Issue:
Adjustment Factor Bug
If I was matched at decimal odds of 3.2, and the adjustment factor was 75.18, what should the new price be?
From reading this document, I also get 0.79. But how is it possible to get matched at 0.79? 1.01 is the lowest possible odds.
10:01

https://en-betfair.custhelp.com/app/answers/detail/a_id/408/~/exchange%3A-in-a-horse-race%2C-how-will-non-runners-be-treated%3F

The adjustment factor code is here:
https://github.com/liampauling/flumine/blob/4f08f389a3c5c2722b60252055b143cfdfefb0f7/flumine/markets/middleware.py#L104-L107

Pull request description:
closes #347 Adjustment Factor Bug
refactor to function and make minimum 1.01

Patch:
diff --git a/HISTORY.rst b/HISTORY.rst
index 746a8cb..332624e 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -3,17 +3,6 @@
 Release History
 ---------------
 
-1.15.0 (2020-12-07)
-+++++++++++++++++++
-
-**Improvements**
-
-- Updates for bflw 2.11.0
-
-**Libraries**
-
-- betfairlightweight upgraded to 2.11.0
-
 1.14.13 (2020-12-05)
 +++++++++++++++++++
 
diff --git a/flumine/__version__.py b/flumine/__version__.py
index c279353..4889a40 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.15.0"
+__version__ = "1.14.13"
 __author__ = "Liam Pauling"
 __license__ = "MIT"
diff --git a/flumine/markets/middleware.py b/flumine/markets/middleware.py
index 73c86ed..318fb73 100644
--- a/flumine/markets/middleware.py
+++ b/flumine/markets/middleware.py
@@ -102,8 +102,8 @@ class SimulatedMiddleware(Middleware):
                     ):
                         # todo place market
                         for match in order.simulated.matched:
-                            match[1] = round(
-                                match[1] * (1 - (removal_adjustment_factor / 100)), 2
+                            match[1] = self._calculate_reduction_factor(
+                                match[1], removal_adjustment_factor
                             )
                         _, order.simulated.average_price_matched = wap(
                             order.simulated.matched
@@ -115,6 +115,11 @@ class SimulatedMiddleware(Middleware):
                             extra=order.info,
                         )
 
+    @staticmethod
+    def _calculate_reduction_factor(price: float, adjustment_factor: float) -> float:
+        price_adjusted = round(price * (1 - (adjustment_factor / 100)), 2)
+        return max(price_adjusted, 1.01)  # min: 1.01
+
     @staticmethod
     def _process_simulated_orders(market, market_analytics: dict) -> None:
         for order in market.blotter.live_orders:
diff --git a/flumine/streams/historicalstream.py b/flumine/streams/historicalstream.py
index 290eaba..5817aed 100644
--- a/flumine/streams/historicalstream.py
+++ b/flumine/streams/historicalstream.py
@@ -1,8 +1,8 @@
 import logging
 import datetime
 from betfairlightweight.streaming import StreamListener, HistoricalGeneratorStream
-from betfairlightweight.streaming.stream import MarketStream, RaceStream
-from betfairlightweight.streaming.cache import MarketBookCache, RaceCache
+from betfairlightweight.streaming.stream import MarketStream
+from betfairlightweight.streaming.cache import MarketBookCache
 from betfairlightweight.resources.baseresource import BaseResource
 
 from .basestream import BaseStream
@@ -75,29 +75,6 @@ class FlumineMarketStream(MarketStream):
         return market_books
 
 
-class FlumineRaceStream(RaceStream):
-    """
-    `_process` updated to not call `on_process`
-    which reduces some function calls.
-    # todo snap optimisation?
-    """
-
-    def _process(self, race_updates: list, publish_time: int) -> bool:
-        for update in race_updates:
-            market_id = update["mid"]
-            race_cache = self._caches.get(market_id)
-            if race_cache is None:
-                race_cache = RaceCache(publish_time=publish_time, **update)
-                self._caches[market_id] = race_cache
-                logger.info(
-                    "[%s: %s]: %s added, %s markets in cache"
-                    % (self, self.unique_id, market_id, len(self._caches))
-                )
-            race_cache.update_cache(update, publish_time)
-            self._updates_processed += 1
-        return False
-
-
 class HistoricListener(StreamListener):
     """
     Custom listener to restrict processing by
@@ -112,8 +89,6 @@ class HistoricListener(StreamListener):
     def _add_stream(self, unique_id, stream_type):
         if stream_type == "marketSubscription":
             return FlumineMarketStream(self)
-        elif stream_type == "raceSubscription":
-            return FlumineRaceStream(self)
 
 
 class HistoricalStream(BaseStream):
diff --git a/requirements.txt b/requirements.txt
index fd7de7e..706d632 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-betfairlightweight==2.11.0
+betfairlightweight==2.10.2
 tenacity==5.0.3
 python-json-logger==2.0.1
 requests


Test patch:
diff --git a/tests/test_middleware.py b/tests/test_middleware.py
index bec8d60..4a57d5b 100644
--- a/tests/test_middleware.py
+++ b/tests/test_middleware.py
@@ -135,6 +135,14 @@ class SimulatedMiddlewareTest(unittest.TestCase):
         self.middleware._process_runner_removal(mock_market, 12345, 0, None)
         self.assertEqual(mock_order.simulated.matched, [[123, 8.6, 10]])
 
+    def test__calculate_reduction_factor(self):
+        self.assertEqual(self.middleware._calculate_reduction_factor(10, 10), 9)
+        self.assertEqual(self.middleware._calculate_reduction_factor(1000, 0), 1000)
+        self.assertEqual(self.middleware._calculate_reduction_factor(1000, 5), 950)
+        self.assertEqual(self.middleware._calculate_reduction_factor(3.2, 75.18), 1.01)
+        self.assertEqual(self.middleware._calculate_reduction_factor(10, 75.18), 2.48)
+        self.assertEqual(self.middleware._calculate_reduction_factor(1.01, 75.18), 1.01)
+
     def test__process_simulated_orders(self):
         mock_market_book = mock.Mock()
         mock_order = mock.Mock()
diff --git a/tests/test_streams.py b/tests/test_streams.py
index bebe9bb..f305541 100644
--- a/tests/test_streams.py
+++ b/tests/test_streams.py
@@ -533,27 +533,6 @@ class TestFlumineMarketStream(unittest.TestCase):
         self.assertEqual(len(self.stream.snap()), 0)
 
 
-class TestFlumineRaceStream(unittest.TestCase):
-    def setUp(self) -> None:
-        self.listener = mock.Mock()
-        self.stream = historicalstream.FlumineRaceStream(self.listener)
-
-    @mock.patch("flumine.streams.historicalstream.RaceCache")
-    def test__process(self, mock_cache):
-        self.assertFalse(
-            self.stream._process(
-                [{"mid": "1.23", "img": {1: 2}}],
-                12345,
-            )
-        )
-        self.assertEqual(len(self.stream._caches), 1)
-        self.assertEqual(self.stream._updates_processed, 1)
-        mock_cache.assert_called_with(publish_time=12345, mid="1.23", img={1: 2})
-        mock_cache().update_cache.assert_called_with(
-            {"mid": "1.23", "img": {1: 2}}, 12345
-        )
-
-
 class TestHistoricListener(unittest.TestCase):
     def setUp(self) -> None:
         self.mock_flumine = mock.Mock()
@@ -566,17 +545,11 @@ class TestHistoricListener(unittest.TestCase):
         self.assertEqual(self.listener.seconds_to_start, 123)
 
     @mock.patch("flumine.streams.historicalstream.FlumineMarketStream")
-    def test__add_stream_market(self, mock_stream):
+    def test__add_stream(self, mock_stream):
         self.assertEqual(
             self.listener._add_stream(123, "marketSubscription"), mock_stream()
         )
 
-    @mock.patch("flumine.streams.historicalstream.FlumineRaceStream")
-    def test__add_stream_race(self, mock_stream):
-        self.assertEqual(
-            self.listener._add_stream(123, "raceSubscription"), mock_stream()
-        )
-
 
 class TestOrderStream(unittest.TestCase):
     def setUp(self) -> None: