Repository: liampauling/flumine
Pull request #239 (2020-07-20 10:04:26)

Task/192 simulation imp


Patch:
diff --git a/HISTORY.rst b/HISTORY.rst
index 6edb55b..8e0a93d 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -10,6 +10,7 @@ Release History
 
 - #180 client paper trade now implemented
 - #193 initial work on multi client implementation
+- #192 simulation improvements with handling on runner removal
 
 **Bug Fixes**
 
diff --git a/flumine/baseflumine.py b/flumine/baseflumine.py
index 22f8f1d..06a5582 100644
--- a/flumine/baseflumine.py
+++ b/flumine/baseflumine.py
@@ -11,7 +11,7 @@ from .worker import BackgroundWorker
 from .clients.baseclient import BaseClient
 from .markets.markets import Markets
 from .markets.market import Market
-from .markets.middleware import Middleware
+from .markets.middleware import Middleware, SimulatedMiddleware
 from .execution.betfairexecution import BetfairExecution
 from .execution.simulatedexecution import SimulatedExecution
 from .order.process import process_current_orders
@@ -296,6 +296,9 @@ class BaseFlumine:
             config.simulated = True
         else:
             config.simulated = False
+        # middleware
+        if self.BACKTEST or self.client.paper_trade:
+            self.add_market_middleware(SimulatedMiddleware())
         # login
         self.client.login()
         self.client.update_account_details()
diff --git a/flumine/markets/middleware.py b/flumine/markets/middleware.py
index 5e27c41..f1ffe9f 100644
--- a/flumine/markets/middleware.py
+++ b/flumine/markets/middleware.py
@@ -3,10 +3,13 @@ from collections import defaultdict
 from betfairlightweight.resources.bettingresources import RunnerBook
 
 from ..order.order import OrderStatus
-from ..utils import get_price
+from ..utils import get_price, wap
 
 logger = logging.getLogger(__name__)
 
+WIN_MINIMUM_ADJUSTMENT_FACTOR = 2.5
+PLACE_MINIMUM_ADJUSTMENT_FACTOR = 0  # todo implement correctly (https://en-betfair.custhelp.com/app/answers/detail/a_id/406)
+
 
 class Middleware:
     def __call__(self, market) -> None:
@@ -23,19 +26,38 @@ class SimulatedMiddleware(Middleware):
     """
     Calculates matched amounts per runner
     to be used in simulated matching.
-    # todo runner removal fucks everything
     # todo currency fluctuations fucks everything
     """
 
     def __init__(self):
         # {marketId: {(selectionId, handicap): RunnerAnalytics}}
         self.markets = defaultdict(dict)
+        self._runner_removals = []
 
     def __call__(self, market) -> None:
         market_analytics = self.markets[market.market_id]
+        runner_removals = []  # [(selectionId, handicap, adjustmentFactor)..]
         for runner in market.market_book.runners:
             if runner.status == "ACTIVE":
                 self._process_runner(market_analytics, runner)
+            elif runner.status == "REMOVED":
+                _removal = (
+                    runner.selection_id,
+                    runner.handicap,
+                    runner.adjustment_factor,
+                )
+                if _removal not in self._runner_removals:
+                    logger.warning(
+                        "Runner {0} ({2}) removed from market {3}".format(
+                            *_removal, market.market_id
+                        )
+                    )
+                    self._runner_removals.append(_removal)
+                    runner_removals.append(_removal)
+
+        for _removal in runner_removals:
+            self._process_runner_removal(market, *_removal)
+
         market.context["simulated"] = market_analytics
         # process simulated orders
         self._process_simulated_orders(market, market_analytics)
@@ -46,6 +68,50 @@ class SimulatedMiddleware(Middleware):
         except KeyError:
             pass
 
+    def _process_runner_removal(
+        self,
+        market,
+        removal_selection_id: int,
+        removal_handicap: int,
+        removal_adjustment_factor: float,
+    ) -> None:
+        for order in market.blotter:
+            if order.simulated:
+                if order.lookup == (
+                    market.market_id,
+                    removal_selection_id,
+                    removal_handicap,
+                ):
+                    # cancel and void order
+                    order.simulated.size_matched = 0
+                    order.simulated.average_price_matched = 0
+                    order.simulated.matched = []
+                    order.simulated.size_voided = order.order_type.size
+                    logger.warning(
+                        "Order voided on non runner {0}".format(order.selection_id),
+                        extra=order.info,
+                    )
+                else:
+                    if order.status == OrderStatus.EXECUTABLE:
+                        # todo cancel if not PERSIST
+                        # todo does a market version bump occur if withdrawal is below the limit?
+                        pass
+                    if removal_adjustment_factor >= WIN_MINIMUM_ADJUSTMENT_FACTOR:
+                        # todo place market
+                        for match in order.simulated.matched:
+                            match[1] = round(
+                                match[1] * (1 - (removal_adjustment_factor / 100)), 2
+                            )
+                        _, order.simulated.average_price_matched = wap(
+                            order.simulated.matched
+                        )
+                        logger.warning(
+                            "Order adjusted due to non runner {0}".format(
+                                order.selection_id
+                            ).format(order.selection_id),
+                            extra=order.info,
+                        )
+
     @staticmethod
     def _process_simulated_orders(market, market_analytics: dict) -> None:
         for order in market.blotter.live_orders:


Test patch:
diff --git a/flumine/backtest/backtest.py b/flumine/backtest/backtest.py
index 5aa592f..1a8e4f7 100644
--- a/flumine/backtest/backtest.py
+++ b/flumine/backtest/backtest.py
@@ -9,7 +9,6 @@ from ..exceptions import RunError
 from .utils import PendingPackages
 from ..order.orderpackage import OrderPackageType
 from ..order import process
-from ..markets.middleware import SimulatedMiddleware
 
 logger = logging.getLogger(__name__)
 
@@ -26,7 +25,6 @@ class FlumineBacktest(BaseFlumine):
     def __init__(self, client):
         super(FlumineBacktest, self).__init__(client)
         self._pending_packages = PendingPackages()
-        self._market_middleware.append(SimulatedMiddleware())
 
     def run(self) -> None:
         if self.client.EXCHANGE != ExchangeType.SIMULATED:
diff --git a/flumine/backtest/simulated.py b/flumine/backtest/simulated.py
index 668e4dd..3d66803 100644
--- a/flumine/backtest/simulated.py
+++ b/flumine/backtest/simulated.py
@@ -1,5 +1,6 @@
+import logging
 import datetime
-from typing import Tuple
+from typing import List
 from betfairlightweight.resources.bettingresources import MarketBook, RunnerBook
 
 from .utils import (
@@ -11,6 +12,8 @@ from ..utils import get_price, wap
 from ..order.ordertype import OrderTypes
 from .. import config
 
+logger = logging.getLogger(__name__)
+
 
 class Simulated:
     """
@@ -22,7 +25,7 @@ class Simulated:
         self.order = order
         self.size_matched = 0
         self.average_price_matched = 0
-        self.matched = []  # [(publishTime, price, size)..]
+        self.matched = []  # [[publishTime, price, size]..]
         self.size_cancelled = 0.0
         self.size_lapsed = 0.0
         self.size_voided = 0.0
@@ -96,6 +99,9 @@ class Simulated:
                     self._piq = avail["size"]
                     break
 
+            logger.debug(
+                "Simulated order {0} PIQ: {1}".format(self.order.id, self._piq)
+            )
             return self._create_place_response(bet_id)
         else:
             return self._create_place_response(bet_id)
@@ -166,7 +172,7 @@ class Simulated:
                     _size_matched = _size_remaining
                 else:
                     _size_matched = avail["size"]
-                _matched = (publish_time, avail["price"], round(_size_matched, 2))
+                _matched = [publish_time, avail["price"], round(_size_matched, 2)]
                 self._update_matched(_matched)
             else:
                 break
@@ -195,12 +201,17 @@ class Simulated:
                     size = round(_order_type.liability / (actual_sp - 1), 2)
             else:
                 raise NotImplementedError()
-            self._update_matched((publish_time, actual_sp, size))
+            self._update_matched([publish_time, actual_sp, size])
 
     def _process_traded(self, publish_time: int, traded: dict) -> None:
         # calculate matched on MarketBook update
         price = self.order.order_type.price
         for traded_price, traded_size in traded.items():
+            logger.debug(
+                "Simulated order {0} traded: {1} - {2}".format(
+                    self.order.id, traded_price, traded_size
+                )
+            )
             if self.side == "BACK" and traded_price >= price:
                 self._calculate_process_traded(publish_time, traded_size)
             elif self.side == "LAY" and traded_price <= price:
@@ -213,15 +224,18 @@ class Simulated:
             size = round(min(self.size_remaining, size), 2)
             if size:
                 self._update_matched(
-                    (
+                    [
                         publish_time,
                         self.order.order_type.price,
                         size,
-                    )  # todo takes the worst price, i.e what was asked
+                    ]  # todo takes the worst price, i.e what was asked
                 )
             self._piq = 0
         else:
             self._piq -= traded_size
+            logger.debug(
+                "Simulated order {0} PIQ: {1}".format(self.order.id, self._piq)
+            )
 
     @property
     def take_sp(self) -> bool:
@@ -236,7 +250,8 @@ class Simulated:
     def side(self) -> str:
         return self.order.side
 
-    def _update_matched(self, data: Tuple[int, float, float]) -> None:
+    def _update_matched(self, data: List) -> None:
+        logger.debug("Simulated order {0} matched: {1}".format(self.order.id, data))
         self.matched.append(data)
         self.size_matched, self.average_price_matched = wap(self.matched)
 
diff --git a/tests/test_middleware.py b/tests/test_middleware.py
index 1efb229..4dece69 100644
--- a/tests/test_middleware.py
+++ b/tests/test_middleware.py
@@ -6,6 +6,8 @@ from flumine.markets.middleware import (
     SimulatedMiddleware,
     RunnerAnalytics,
     OrderStatus,
+    WIN_MINIMUM_ADJUSTMENT_FACTOR,
+    PLACE_MINIMUM_ADJUSTMENT_FACTOR,
 )
 
 
@@ -32,6 +34,9 @@ class SimulatedMiddlewareTest(unittest.TestCase):
 
     def test_init(self):
         self.assertEqual(self.middleware.markets, {})
+        self.assertEqual(self.middleware._runner_removals, [])
+        self.assertEqual(WIN_MINIMUM_ADJUSTMENT_FACTOR, 2.5)
+        self.assertEqual(PLACE_MINIMUM_ADJUSTMENT_FACTOR, 0)
 
     @mock.patch(
         "flumine.markets.middleware.SimulatedMiddleware._process_simulated_orders"
@@ -40,8 +45,7 @@ class SimulatedMiddlewareTest(unittest.TestCase):
     def test_call(self, mock__process_runner, mock__process_simulated_orders):
         mock_market = mock.Mock(context={})
         mock_market_book = mock.Mock()
-        mock_runner = mock.Mock()
-        mock_runner.status = "ACTIVE"
+        mock_runner = mock.Mock(status="ACTIVE")
         mock_market_book.runners = [mock_runner]
         mock_market.market_book = mock_market_book
         self.middleware(mock_market)
@@ -49,6 +53,38 @@ class SimulatedMiddlewareTest(unittest.TestCase):
         self.assertEqual(mock_market.context, {"simulated": {}})
         mock__process_simulated_orders.assert_called_with(mock_market, {})
 
+    @mock.patch(
+        "flumine.markets.middleware.SimulatedMiddleware._process_simulated_orders"
+    )
+    @mock.patch(
+        "flumine.markets.middleware.SimulatedMiddleware._process_runner_removal"
+    )
+    def test_call_non_runner(
+        self, mock__process_runner_removal, mock__process_simulated_orders
+    ):
+        mock_market = mock.Mock(context={})
+        mock_market_book = mock.Mock()
+        mock_runner = mock.Mock(status="REMOVED")
+        mock_market_book.runners = [mock_runner]
+        mock_market.market_book = mock_market_book
+        self.middleware(mock_market)
+        self.assertEqual(
+            self.middleware._runner_removals,
+            [
+                (
+                    mock_runner.selection_id,
+                    mock_runner.handicap,
+                    mock_runner.adjustment_factor,
+                )
+            ],
+        )
+        mock__process_runner_removal.assert_called_with(
+            mock_market,
+            mock_runner.selection_id,
+            mock_runner.handicap,
+            mock_runner.adjustment_factor,
+        )
+
     def test_remove_market(self):
         mock_market = mock.Mock(market_id="1.23")
         self.middleware.markets = {mock_market.market_id: []}
@@ -56,6 +92,41 @@ class SimulatedMiddlewareTest(unittest.TestCase):
         self.middleware.remove_market(mock_market)
         self.assertEqual(self.middleware.markets, {})
 
+    def test__process_runner_removal(self):
+        mock_simulated = mock.MagicMock(matched=[[123, 8.6, 10]])
+        mock_simulated.__bool__.return_value = True
+        mock_order = mock.Mock(simulated=mock_simulated, info={})
+        mock_simulated_two = mock.MagicMock(matched=[[123, 8.6, 10]])
+        mock_simulated_two.__bool__.return_value = False
+        mock_order_two = mock.Mock(simulated=mock_simulated_two, info={})
+        mock_market = mock.Mock(blotter=[mock_order, mock_order_two])
+        self.middleware._process_runner_removal(mock_market, 12345, 0, 16.2)
+        self.assertEqual(mock_order.simulated.matched, [[123, 7.21, 10]])
+        self.assertEqual(mock_order.simulated.average_price_matched, 7.21)
+        self.assertEqual(mock_order_two.simulated.matched, [[123, 8.6, 10]])
+
+    def test__process_runner_removal_under_limit(self):
+        mock_simulated = mock.MagicMock(matched=[[123, 8.6, 10]])
+        mock_simulated.__bool__.return_value = True
+        mock_order = mock.Mock(simulated=mock_simulated)
+        mock_market = mock.Mock(blotter=[mock_order])
+        self.middleware._process_runner_removal(mock_market, 12345, 0, 2.4)
+        self.assertEqual(mock_order.simulated.matched, [[123, 8.6, 10]])
+
+    def test__process_runner_removal_void(self):
+        mock_simulated = mock.MagicMock(matched=[[123, 8.6, 10]])
+        mock_simulated.__bool__.return_value = True
+        mock_order = mock.Mock(
+            lookup=("1.23", 12345, 0), simulated=mock_simulated, info={}
+        )
+        mock_order.order_type.size = 10
+        mock_market = mock.Mock(market_id="1.23", blotter=[mock_order])
+        self.middleware._process_runner_removal(mock_market, 12345, 0, 16.2)
+        self.assertEqual(mock_order.simulated.size_matched, 0)
+        self.assertEqual(mock_order.simulated.average_price_matched, 0)
+        self.assertEqual(mock_order.simulated.matched, [])
+        self.assertEqual(mock_order.simulated.size_voided, 10)
+
     def test__process_simulated_orders(self):
         mock_market_book = mock.Mock()
         mock_order = mock.Mock()
diff --git a/tests/test_simulated.py b/tests/test_simulated.py
index 40fabc7..0d6c7cb 100644
--- a/tests/test_simulated.py
+++ b/tests/test_simulated.py
@@ -94,7 +94,7 @@ class SimulatedTest(unittest.TestCase):
         self.assertEqual(resp.average_price_matched, 12)
         self.assertEqual(resp.size_matched, 2)
         self.assertEqual(
-            self.simulated.matched, [(mock_market_book.publish_time_epoch, 12, 2)]
+            self.simulated.matched, [[mock_market_book.publish_time_epoch, 12, 2]]
         )
 
     @mock.patch("flumine.backtest.simulated.Simulated._get_runner")
@@ -142,7 +142,7 @@ class SimulatedTest(unittest.TestCase):
         self.assertEqual(resp.average_price_matched, 12)
         self.assertEqual(resp.size_matched, 2)
         self.assertEqual(
-            self.simulated.matched, [(mock_market_book.publish_time_epoch, 12, 2)]
+            self.simulated.matched, [[mock_market_book.publish_time_epoch, 12, 2]]
         )
 
     @mock.patch("flumine.backtest.simulated.Simulated._get_runner")
@@ -236,22 +236,22 @@ class SimulatedTest(unittest.TestCase):
         self.simulated._process_price_matched(
             1234567, 12.0, 2.00, [{"price": 15, "size": 120}]
         )
-        self.assertEqual(self.simulated.matched, [(1234567, 15, 2)])
+        self.assertEqual(self.simulated.matched, [[1234567, 15, 2]])
         self.simulated.matched = []
         self.simulated._process_price_matched(
             1234568, 12.0, 2.00, [{"price": 15, "size": 1}, {"price": 12, "size": 1}]
         )
-        self.assertEqual(self.simulated.matched, [(1234568, 15, 1), (1234568, 12, 1)])
+        self.assertEqual(self.simulated.matched, [[1234568, 15, 1], [1234568, 12, 1]])
         self.simulated.matched = []
         self.simulated._process_price_matched(
             1234569, 12.0, 2.00, [{"price": 15, "size": 1}, {"price": 12, "size": 0.5}]
         )
-        self.assertEqual(self.simulated.matched, [(1234569, 15, 1), (1234569, 12, 0.5)])
+        self.assertEqual(self.simulated.matched, [[1234569, 15, 1], [1234569, 12, 0.5]])
         self.simulated.matched = []
         self.simulated._process_price_matched(
             1234570, 12.0, 2.00, [{"price": 15, "size": 1}, {"price": 11, "size": 0.5}]
         )
-        self.assertEqual(self.simulated.matched, [(1234570, 15, 1)])
+        self.assertEqual(self.simulated.matched, [[1234570, 15, 1]])
 
     @mock.patch(
         "flumine.backtest.simulated.Simulated.side",
@@ -262,12 +262,12 @@ class SimulatedTest(unittest.TestCase):
         self.simulated._process_price_matched(
             1234571, 3.0, 20.00, [{"price": 2.02, "size": 120}]
         )
-        self.assertEqual(self.simulated.matched, [(1234571, 2.02, 20)])
+        self.assertEqual(self.simulated.matched, [[1234571, 2.02, 20]])
         self.simulated.matched = []
         self.simulated._process_price_matched(
             1234571, 3.0, 20.00, [{"price": 2.02, "size": 1}, {"price": 3, "size": 20}]
         )
-        self.assertEqual(self.simulated.matched, [(1234571, 2.02, 1), (1234571, 3, 19)])
+        self.assertEqual(self.simulated.matched, [[1234571, 2.02, 1], [1234571, 3, 19]])
         self.simulated.matched = []
         self.simulated._process_price_matched(
             1234571,
@@ -276,19 +276,19 @@ class SimulatedTest(unittest.TestCase):
             [{"price": 2.02, "size": 1}, {"price": 2.9, "size": 0.5}],
         )
         self.assertEqual(
-            self.simulated.matched, [(1234571, 2.02, 1), (1234571, 2.9, 0.5)]
+            self.simulated.matched, [[1234571, 2.02, 1], [1234571, 2.9, 0.5]]
         )
         self.simulated.matched = []
         self.simulated._process_price_matched(
             1234571, 3.0, 20.00, [{"price": 3, "size": 1}, {"price": 11, "size": 0.5}]
         )
-        self.assertEqual(self.simulated.matched, [(1234571, 3, 1)])
+        self.assertEqual(self.simulated.matched, [[1234571, 3, 1]])
 
     def test__process_sp(self):
         mock_runner = mock.Mock()
         mock_runner.sp.actual_sp = 12.20
         self.simulated._process_sp(1234571, mock_runner)
-        self.assertEqual(self.simulated.matched, [(1234571, 12.2, 2.00)])
+        self.assertEqual(self.simulated.matched, [[1234571, 12.2, 2.00]])
         self.assertTrue(self.simulated._bsp_reconciled)
 
     def test__process_sp_none(self):
@@ -301,12 +301,12 @@ class SimulatedTest(unittest.TestCase):
     def test__process_sp_processed_semi(self):
         mock_runner = mock.Mock()
         mock_runner.sp.actual_sp = 12.20
-        self.simulated.matched = [(1234571, 10.0, 1)]
+        self.simulated.matched = [[1234571, 10.0, 1]]
         self.simulated.size_matched = 1
         self.simulated.average_price_matched = 10.0
         self.simulated._process_sp(1234572, mock_runner)
         self.assertEqual(
-            self.simulated.matched, [(1234571, 10.0, 1), (1234572, 12.2, 1)]
+            self.simulated.matched, [[1234571, 10.0, 1], [1234572, 12.2, 1]]
         )
         self.assertTrue(self.simulated._bsp_reconciled)
 
@@ -317,7 +317,7 @@ class SimulatedTest(unittest.TestCase):
         mock_runner_book = mock.Mock()
         mock_runner_book.sp.actual_sp = 69
         self.simulated._process_sp(1234573, mock_runner_book)
-        self.assertEqual(self.simulated.matched, [(1234573, 69, 2.00)])
+        self.assertEqual(self.simulated.matched, [[1234573, 69, 2.00]])
         self.assertTrue(self.simulated._bsp_reconciled)
 
     def test__process_sp_limit_on_close_back_no_match(self):
@@ -338,7 +338,7 @@ class SimulatedTest(unittest.TestCase):
         mock_runner_book = mock.Mock()
         mock_runner_book.sp.actual_sp = 69
         self.simulated._process_sp(1234575, mock_runner_book)
-        self.assertEqual(self.simulated.matched, [(1234575, 69, 0.03)])
+        self.assertEqual(self.simulated.matched, [[1234575, 69, 0.03]])
         self.assertTrue(self.simulated._bsp_reconciled)
 
     def test__process_sp_limit_on_close_lay_no_match(self):
@@ -359,7 +359,7 @@ class SimulatedTest(unittest.TestCase):
         mock_runner_book = mock.Mock()
         mock_runner_book.sp.actual_sp = 69
         self.simulated._process_sp(1234577, mock_runner_book)
-        self.assertEqual(self.simulated.matched, [(1234577, 69, 2.00)])
+        self.assertEqual(self.simulated.matched, [[1234577, 69, 2.00]])
         self.assertTrue(self.simulated._bsp_reconciled)
 
     def test__process_sp_market_on_close_lay(self):
@@ -370,7 +370,7 @@ class SimulatedTest(unittest.TestCase):
         mock_runner_book = mock.Mock()
         mock_runner_book.sp.actual_sp = 69
         self.simulated._process_sp(1234578, mock_runner_book)
-        self.assertEqual(self.simulated.matched, [(1234578, 69, 0.03)])
+        self.assertEqual(self.simulated.matched, [[1234578, 69, 0.03]])
         self.assertTrue(self.simulated._bsp_reconciled)
 
     @mock.patch("flumine.backtest.simulated.Simulated._calculate_process_traded")
@@ -400,7 +400,7 @@ class SimulatedTest(unittest.TestCase):
         self.simulated._calculate_process_traded(1234583, 2.00)
         self.simulated._calculate_process_traded(1234584, 2.00)
         self.assertEqual(
-            self.simulated.matched, [(1234582, 12, 1.00), (1234583, 12, 1.00)]
+            self.simulated.matched, [[1234582, 12, 1.00], [1234583, 12, 1.00]]
         )
         self.assertEqual(self.simulated._piq, 0)
 
@@ -410,7 +410,7 @@ class SimulatedTest(unittest.TestCase):
         self.assertEqual(self.simulated.matched, [])
         self.assertEqual(self.simulated._piq, 0)
         self.simulated._calculate_process_traded(1234586, 4.00)
-        self.assertEqual(self.simulated.matched, [(1234586, 12, 2.00)])
+        self.assertEqual(self.simulated.matched, [[1234586, 12, 2.00]])
         self.assertEqual(self.simulated._piq, 0)
 
     def test_take_sp(self):
@@ -428,27 +428,27 @@ class SimulatedTest(unittest.TestCase):
 
     def test_average_price_matched(self):
         self.assertEqual(self.simulated.average_price_matched, 0)
-        self.simulated._update_matched((1234, 1, 2))
+        self.simulated._update_matched([1234, 1, 2])
         self.assertEqual(self.simulated.average_price_matched, 1)
 
     def test_size_matched(self):
         self.assertEqual(self.simulated.size_matched, 0)
-        self.simulated._update_matched((4321, 1, 2))
+        self.simulated._update_matched([4321, 1, 2])
         self.assertEqual(self.simulated.size_matched, 2)
 
     def test__update_matched(self):
-        self.simulated._update_matched((12345, 10.0, 2.64))
-        self.assertEqual(self.simulated.matched, [(12345, 10.0, 2.64)])
+        self.simulated._update_matched([12345, 10.0, 2.64])
+        self.assertEqual(self.simulated.matched, [[12345, 10.0, 2.64]])
         self.assertEqual(self.simulated.size_matched, 2.64)
         self.assertEqual(self.simulated.average_price_matched, 10.0)
 
     def test_size_remaining(self):
         self.assertEqual(self.simulated.size_remaining, 2)
-        self.simulated._update_matched((1234, 1, 1))
+        self.simulated._update_matched([1234, 1, 1])
         self.assertEqual(self.simulated.size_remaining, 1)
 
     def test_size_remaining_multi(self):
-        self.simulated._update_matched((1234, 1, 0.1))
+        self.simulated._update_matched([1234, 1, 0.1])
         self.simulated.size_cancelled = 0.2
         self.simulated.size_lapsed = 0.3
         self.simulated.size_voided = 0.4
@@ -462,7 +462,7 @@ class SimulatedTest(unittest.TestCase):
     def test_profit_back(self):
         self.assertEqual(self.simulated.profit, 0)
         self.simulated.order.runner_status = "WINNER"
-        self.simulated._update_matched((1234, 10.0, 2.0))
+        self.simulated._update_matched([1234, 10.0, 2.0])
         self.assertEqual(self.simulated.profit, 18.0)
         self.simulated.order.runner_status = "LOSER"
         self.assertEqual(self.simulated.profit, -2.0)
@@ -471,7 +471,7 @@ class SimulatedTest(unittest.TestCase):
         self.simulated.order.side = "LAY"
         self.assertEqual(self.simulated.profit, 0)
         self.simulated.order.runner_status = "WINNER"
-        self.simulated._update_matched((1234, 10.0, 2.0))
+        self.simulated._update_matched([1234, 10.0, 2.0])
         self.assertEqual(self.simulated.profit, -18.0)
         self.simulated.order.runner_status = "LOSER"
         self.assertEqual(self.simulated.profit, 2.0)