Repository: liampauling/flumine
Pull request #219 (2020-06-16 07:00:41)

Task/high order count opti


Patch:
diff --git a/flumine/markets/blotter.py b/flumine/markets/blotter.py
index 04f6814..9b77281 100644
--- a/flumine/markets/blotter.py
+++ b/flumine/markets/blotter.py
@@ -15,6 +15,7 @@ class Blotter:
     def __init__(self, market):
         self.market = market
         self._orders = {}  # {Order.id: Order}
+        self._live_orders = []  # cached list of live orders
         # pending orders
         self.pending_place = []
         self.pending_cancel = []
@@ -70,11 +71,12 @@ class Blotter:
         return packages
 
     @property
-    def live_orders(self) -> bool:
-        for order in self._orders.values():
-            if order.complete is False or order.trade.complete is False:
-                return True
-        return False
+    def live_orders(self):
+        return iter(self._live_orders)
+
+    @property
+    def has_live_orders(self):
+        return bool(self._live_orders)
 
     def process_closed_market(self, market_book):
         for order in self:
@@ -99,7 +101,11 @@ class Blotter:
         """
         mb, ml = [], []  # (price, size)
         for order in self:
-            if order.trade.strategy == strategy and order.lookup == lookup:
+            if (
+                order.trade.strategy == strategy
+                and order.size_matched
+                and order.lookup == lookup
+            ):
                 if order.side == "BACK":
                     mb.append((order.average_price_matched, order.size_matched))
                 else:
@@ -112,6 +118,9 @@ class Blotter:
 
     """ getters / setters """
 
+    def complete_order(self, order) -> None:
+        self._live_orders.remove(order)
+
     def has_order(self, customer_order_ref: str) -> bool:
         return customer_order_ref in self._orders
 
@@ -119,6 +128,7 @@ class Blotter:
 
     def __setitem__(self, customer_order_ref: str, order) -> None:
         self._orders[customer_order_ref] = order
+        self._live_orders.append(order)
 
     def __getitem__(self, customer_order_ref: str):
         return self._orders[customer_order_ref]
diff --git a/flumine/markets/markets.py b/flumine/markets/markets.py
index 714bcbc..4324f2d 100644
--- a/flumine/markets/markets.py
+++ b/flumine/markets/markets.py
@@ -49,7 +49,7 @@ class Markets:
     @property
     def live_orders(self) -> bool:
         for market in self:
-            if market.closed is False and market.blotter.live_orders is True:
+            if market.closed is False and market.blotter.has_live_orders:
                 return True
         return False
 
diff --git a/flumine/markets/middleware.py b/flumine/markets/middleware.py
index de95269..1330b1c 100644
--- a/flumine/markets/middleware.py
+++ b/flumine/markets/middleware.py
@@ -35,7 +35,7 @@ class SimulatedMiddleware(Middleware):
 
     @staticmethod
     def _process_simulated_orders(market, market_analytics: dict) -> None:
-        for order in market.blotter:
+        for order in market.blotter.live_orders:
             if order.simulated and order.status == OrderStatus.EXECUTABLE:
                 runner_analytics = market_analytics.get(
                     (order.selection_id, order.handicap)
diff --git a/flumine/order/order.py b/flumine/order/order.py
index 7b69613..9e4952d 100644
--- a/flumine/order/order.py
+++ b/flumine/order/order.py
@@ -55,6 +55,7 @@ class BaseOrder:
         self.update_data = {}  # stores cancel/update/replace data
         self.responses = Responses()  # raw api responses
         self.simulated = Simulated(self)  # used in simulated execution
+        self._simulated = bool(self.simulated)  # cache in current class (2x quicker)
         self.publish_time = None  # marketBook.publish_time
 
         self.date_time_created = datetime.datetime.utcnow()
@@ -131,7 +132,7 @@ class BaseOrder:
 
     @property
     def current_order(self) -> Union[CurrentOrder, Simulated]:
-        if self.simulated:
+        if self._simulated:
             return self.simulated
         elif self.responses.current_order:
             return self.responses.current_order
diff --git a/flumine/utils.py b/flumine/utils.py
index 99dbb33..a8b56c7 100644
--- a/flumine/utils.py
+++ b/flumine/utils.py
@@ -118,10 +118,12 @@ def price_ticks_away(price: float, n_ticks: int) -> float:
 
 
 # todo LRU cache?
-def calculate_exposure(mb: list, ml: list) -> int:
+def calculate_exposure(mb: list, ml: list) -> float:
     """Calculates exposure based on list
     of (price, size)
     """
+    if not mb and not ml:
+        return 0.0
     back_exp = sum(-i[1] for i in mb)
     back_profit = sum((i[0] - 1) * i[1] for i in mb)
     lay_exp = sum((i[0] - 1) * -i[1] for i in ml)


Test patch:
diff --git a/flumine/backtest/backtest.py b/flumine/backtest/backtest.py
index 2c7fdd3..afe25d9 100644
--- a/flumine/backtest/backtest.py
+++ b/flumine/backtest/backtest.py
@@ -94,8 +94,10 @@ class FlumineBacktest(BaseFlumine):
 
             # process current orders
             blotter = market.blotter
-            for order in blotter:
+            for order in blotter.live_orders:
                 process.process_current_order(order)
+                if order.trade.status.value == "Complete":
+                    blotter.complete_order(order)
             for strategy in self.strategies:
                 strategy_orders = blotter.strategy_orders(strategy)
                 strategy.process_orders(market, strategy_orders)
diff --git a/tests/test_blotter.py b/tests/test_blotter.py
index fed480f..11e64c2 100644
--- a/tests/test_blotter.py
+++ b/tests/test_blotter.py
@@ -16,6 +16,7 @@ class BlotterTest(unittest.TestCase):
         self.assertEqual(self.blotter.pending_cancel, [])
         self.assertEqual(self.blotter.pending_update, [])
         self.assertEqual(self.blotter.pending_replace, [])
+        self.assertEqual(self.blotter._live_orders, [])
 
     def test_strategy_orders(self):
         mock_order = mock.Mock()
@@ -69,17 +70,15 @@ class BlotterTest(unittest.TestCase):
         self.assertEqual(mock_orders, [])
 
     def test_live_orders(self):
-        self.assertFalse(self.blotter.live_orders)
+        self.assertEqual(list(self.blotter.live_orders), [])
         mock_order = mock.Mock(complete=False)
-        self.blotter._orders = {"12345": mock_order}
-        self.assertTrue(self.blotter.live_orders)
+        self.blotter._live_orders = [mock_order]
+        self.assertEqual(list(self.blotter.live_orders), [mock_order])
 
-    def test_live_orders_trade(self):
-        self.assertFalse(self.blotter.live_orders)
-        mock_order = mock.Mock()
-        mock_order.trade.complete = False
-        self.blotter._orders = {"12345": mock_order}
-        self.assertTrue(self.blotter.live_orders)
+    def test_has_live_orders(self):
+        self.assertFalse(self.blotter.has_live_orders)
+        self.blotter._live_orders = [mock.Mock()]
+        self.assertTrue(self.blotter.has_live_orders)
 
     def test_process_closed_market(self):
         mock_market_book = mock.Mock()
@@ -105,17 +104,32 @@ class BlotterTest(unittest.TestCase):
             size_matched=2.0,
         )
         self.blotter._orders = {"12345": mock_order}
+        self.assertEqual(
+            self.blotter.selection_exposure(mock_strategy, mock_order.lookup), -2,
+        )
 
+    def test_selection_exposure_no_match(self):
+        mock_strategy = mock.Mock()
+        mock_trade = mock.Mock(strategy=mock_strategy)
+        mock_order = mock.Mock(
+            trade=mock_trade,
+            lookup=(self.blotter.market_id, 123, 0),
+            side="BACK",
+            average_price_matched=5.6,
+            size_matched=0.0,
+        )
+        self.blotter._orders = {"12345": mock_order}
         self.assertEqual(
-            self.blotter.selection_exposure(
-                mock_strategy, (self.blotter.market_id, 123, 0)
-            ),
-            -2,
+            self.blotter.selection_exposure(mock_strategy, mock_order.lookup), 0,
         )
 
     def test_market_id(self):
         self.assertEqual(self.blotter.market_id, self.mock_market.market_id)
 
+    def test_complete_order(self):
+        self.blotter._live_orders = ["test"]
+        self.blotter.complete_order("test")
+
     def test__contains(self):
         self.blotter._orders = {"123": "test"}
         self.assertIn("123", self.blotter)
@@ -124,6 +138,7 @@ class BlotterTest(unittest.TestCase):
     def test__setitem(self):
         self.blotter["123"] = "test"
         self.assertEqual(self.blotter._orders, {"123": "test"})
+        self.assertEqual(self.blotter._live_orders, ["test"])
 
     def test__getitem(self):
         self.blotter._orders = {"12345": "test", "54321": "test2"}
diff --git a/tests/test_markets.py b/tests/test_markets.py
index c12d411..33ebc8d 100644
--- a/tests/test_markets.py
+++ b/tests/test_markets.py
@@ -80,9 +80,11 @@ class MarketsTest(unittest.TestCase):
         self.assertFalse(self.markets.live_orders)
         mock_market = mock.Mock()
         mock_market.closed = False
-        mock_market.blotter.live_orders = True
+        mock_market.blotter.has_live_orders = True
         self.markets._markets = {"1.234": mock_market}
         self.assertTrue(self.markets.live_orders)
+        mock_market.blotter.has_live_orders = False
+        self.assertFalse(self.markets.live_orders)
 
     def test_iter(self):
         self.assertEqual(len([i for i in self.markets]), 0)
diff --git a/tests/test_middleware.py b/tests/test_middleware.py
index fdd417a..5c7c52d 100644
--- a/tests/test_middleware.py
+++ b/tests/test_middleware.py
@@ -50,8 +50,12 @@ class SimulatedMiddlewareTest(unittest.TestCase):
         mock_order_two.status = OrderStatus.PENDING
         mock_order_three = mock.Mock()
         mock_order_three.status = OrderStatus.EXECUTABLE
-        mock_order_three.simulated = False
-        mock_market.blotter = [mock_order, mock_order_two, mock_order_three]
+        mock_order_three._simulated = False
+        mock_market.blotter.live_orders = [
+            mock_order,
+            mock_order_two,
+            mock_order_three,
+        ]
         mock_market_analytics = {(mock_order.selection_id, mock_order.handicap): "test"}
         mock_market.market_book = mock_market_book
         self.middleware._process_simulated_orders(mock_market, mock_market_analytics)
diff --git a/tests/test_order.py b/tests/test_order.py
index ac2edd7..0b3e181 100644
--- a/tests/test_order.py
+++ b/tests/test_order.py
@@ -33,6 +33,8 @@ class BaseOrderTest(unittest.TestCase):
         self.assertIsNone(self.order.publish_time)
         self.assertIsNotNone(self.order.date_time_created)
         self.assertIsNone(self.order.date_time_execution_complete)
+        self.assertFalse(self.order.simulated)
+        self.assertFalse(self.order._simulated)
 
     @mock.patch("flumine.order.order.BaseOrder.info")
     def test__update_status(self, mock_info):
@@ -142,9 +144,12 @@ class BaseOrderTest(unittest.TestCase):
         mock_responses.current_order = 1
         self.assertEqual(self.order.current_order, 1)
 
-    def test_current_order_simulated(self):
-        self.order.simulated = True
-        self.assertTrue(self.order.current_order)
+    @mock.patch("flumine.backtest.simulated.config")
+    def test_current_order_simulated(self, mock_config):
+        mock_config.simulated = True
+        order = BaseOrder(mock.Mock(), "", mock.Mock())
+        self.assertTrue(order.simulated)
+        self.assertTrue(order._simulated)
 
     def test_complete(self):
         self.order.status = None