Repository: liampauling/flumine
Pull request #324 (2020-11-13 08:46:02)

improve _calculate_traded by caching previous dictionary creation
halves function execution time, roughly 15% improvement when backtesting


Patch:
diff --git a/HISTORY.rst b/HISTORY.rst
index 12c5b6a..7d0c305 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -3,6 +3,17 @@
 Release History
 ---------------
 
+1.14.6 (2020-11-13)
++++++++++++++++++++
+
+**Improvements**
+
+- Refactor on calculate_traded func (15% speed increase)
+
+**Bug Fixes**
+
+- Refactoring create_order_from_current, so that it is not dependent on the '-' separator (@jsphon)
+
 1.14.5 (2020-11-11)
 +++++++++++++++++++
 
diff --git a/flumine/__version__.py b/flumine/__version__.py
index 92b98ae..7a4da11 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.14.5"
+__version__ = "1.14.6"
 __author__ = "Liam Pauling"
 __license__ = "MIT"
diff --git a/flumine/markets/middleware.py b/flumine/markets/middleware.py
index 3e121ab..c385308 100644
--- a/flumine/markets/middleware.py
+++ b/flumine/markets/middleware.py
@@ -138,28 +138,26 @@ class SimulatedMiddleware(Middleware):
 class RunnerAnalytics:
     def __init__(self, runner: RunnerBook):
         self._runner = runner
-        self.traded = {}  # price: size traded since last event
-        self.middle = None  # middle of odds at last event
-        self.matched = 0  # amount matched since last event
+        self.traded = {}  # price: size traded since last update
+        self.middle = None  # middle of odds at last update
+        self.matched = 0  # amount matched since last update
         self._traded_volume = runner.ex.traded_volume
+        self._p_v = {}  # cached previous volume
 
     def __call__(self, runner: RunnerBook):
-        self.middle = self._calculate_middle(self._runner)  # use last event
+        self.middle = self._calculate_middle(self._runner)  # use last update
         self.matched = self._calculate_matched(runner)
         self.traded = self._calculate_traded(runner)
         self._traded_volume = runner.ex.traded_volume
         self._runner = runner
 
-    def _calculate_traded(self, runner: RunnerBook) -> dict:
+    def _calculate_traded(self, runner) -> dict:
         if self._traded_volume == runner.ex.traded_volume:
             return {}
         else:
-            c_v, p_v, traded = {}, {}, {}
-            # create dictionaries
-            for i in runner.ex.traded_volume:
-                c_v[i["price"]] = i["size"]
-            for i in self._traded_volume:
-                p_v[i["price"]] = i["size"]  # todo cache from previous run?
+            c_v, p_v, traded = {}, self._p_v, {}
+            # create dictionary
+            c_v = {i["price"]: i["size"] for i in runner.ex.traded_volume}
             # calculate difference
             for key in c_v.keys():
                 if key in p_v:
@@ -167,8 +165,9 @@ class RunnerAnalytics:
                 else:
                     new_value = float(c_v[key])
                 if new_value > 0:
-                    new_value = round(new_value, 2)
-                    traded[key] = new_value
+                    traded[key] = round(new_value, 2)
+            # cache for next update
+            self._p_v = c_v
             return traded
 
     @staticmethod
diff --git a/flumine/order/order.py b/flumine/order/order.py
index 9195cd2..27ca471 100644
--- a/flumine/order/order.py
+++ b/flumine/order/order.py
@@ -1,8 +1,8 @@
 import uuid
 import logging
 import datetime
-from enum import Enum
 import string
+from enum import Enum
 from typing import Union, Optional
 from betfairlightweight import filters
 from betfairlightweight.resources.bettingresources import CurrentOrder
@@ -233,7 +233,6 @@ class BaseOrder:
                 self.date_time_execution_complete - self.responses.date_time_placed
             ).total_seconds()
 
-    # todo cached properties?
     @property
     def market_id(self) -> str:
         return self.trade.market_id
@@ -242,7 +241,7 @@ class BaseOrder:
     def selection_id(self) -> int:
         return self.trade.selection_id
 
-    @property
+    @property  # todo cache (slow when lots of orders)
     def lookup(self) -> tuple:
         return self.market_id, self.selection_id, self.handicap
 
diff --git a/flumine/order/process.py b/flumine/order/process.py
index 37f7bc6..8467cb2 100644
--- a/flumine/order/process.py
+++ b/flumine/order/process.py
@@ -72,7 +72,8 @@ def process_current_order(order: BaseOrder):
 
 
 def create_order_from_current(markets: Markets, strategies: Strategies, current_order):
-    strategy_name_hash, order_id = current_order.customer_order_ref.split("-")
+    strategy_name_hash = current_order.customer_order_ref[:STRATEGY_NAME_HASH_LENGTH]
+    order_id = current_order.customer_order_ref[STRATEGY_NAME_HASH_LENGTH + 1 :]
     # get market
     market = markets.markets.get(current_order.market_id)
     if market is None:


Test patch:
diff --git a/tests/test_middleware.py b/tests/test_middleware.py
index b60b904..f319c7d 100644
--- a/tests/test_middleware.py
+++ b/tests/test_middleware.py
@@ -180,6 +180,7 @@ class RunnerAnalyticsTest(unittest.TestCase):
         self.assertEqual(self.runner_analytics.traded, {})
         self.assertEqual(self.runner_analytics.matched, 0)
         self.assertIsNone(self.runner_analytics.middle)
+        self.assertEqual(self.runner_analytics._p_v, {})
 
     @mock.patch("flumine.markets.middleware.RunnerAnalytics._calculate_matched")
     @mock.patch("flumine.markets.middleware.RunnerAnalytics._calculate_middle")
@@ -210,7 +211,9 @@ class RunnerAnalyticsTest(unittest.TestCase):
         mock_runner = mock.Mock()
         mock_runner.ex.traded_volume = [{"price": 1.01, "size": 69}]
         self.runner_analytics._traded_volume = [{"price": 1.01, "size": 69}]
+        self.runner_analytics._p_v = {1.01: 69}
         self.assertEqual(self.runner_analytics._calculate_traded(mock_runner), {})
+        self.assertEqual(self.runner_analytics._p_v, {1.01: 69})
 
     def test__calculate_traded_dict_new(self):
         mock_runner = mock.Mock()
@@ -219,6 +222,7 @@ class RunnerAnalyticsTest(unittest.TestCase):
         self.assertEqual(
             self.runner_analytics._calculate_traded(mock_runner), {1.01: 69.0}
         )
+        self.assertEqual(self.runner_analytics._p_v, {1.01: 69})
 
     def test__calculate_traded_dict_new_multi(self):
         mock_runner = mock.Mock()
@@ -227,10 +231,12 @@ class RunnerAnalyticsTest(unittest.TestCase):
             {"price": 10, "size": 32},
         ]
         self.runner_analytics._traded_volume = [{"price": 1.01, "size": 30}]
+        self.runner_analytics._p_v = {1.01: 30}
         self.assertEqual(
             self.runner_analytics._calculate_traded(mock_runner),
             {1.01: 39.0, 10: 32},
         )
+        self.assertEqual(self.runner_analytics._p_v, {1.01: 69, 10: 32})
 
     def test__calculate_middle(self):
         mock_runner = mock.Mock()
diff --git a/tests/test_process.py b/tests/test_process.py
index 508a07b..3c00fcd 100644
--- a/tests/test_process.py
+++ b/tests/test_process.py
@@ -1,6 +1,10 @@
 import unittest
 from unittest import mock
 
+from flumine.order.ordertype import OrderTypes
+
+from betfairlightweight.resources.bettingresources import PriceSize
+
 from flumine import config
 from flumine.markets.market import Market
 from flumine.markets.markets import Markets
@@ -8,7 +12,7 @@ from flumine.order.order import (
     BaseOrder,
     BetfairOrder,
 )
-from flumine.order.process import process_current_orders
+from flumine.order.process import process_current_orders, create_order_from_current
 from flumine.strategy.strategy import Strategies
 from flumine.utils import create_cheap_hash
 
@@ -53,3 +57,42 @@ class BaseOrderTest(unittest.TestCase):
         process_current_orders(markets=markets, strategies=strategies, event=event)
 
         self.assertEqual(current_order, betfair_order.responses.current_order)
+
+    def test_create_order_from_current(self):
+        market_book = mock.Mock()
+        markets = Markets()
+        market = Market(
+            flumine=mock.Mock(), market_id="market_id", market_book=market_book
+        )
+
+        markets.add_market("market_id", market)
+
+        cheap_hash = create_cheap_hash("strategy_name", 13)
+
+        strategy = mock.Mock(name_hash=cheap_hash)
+
+        strategies = Strategies()
+        strategies(strategy=strategy, client=mock.Mock())
+
+        current_order = mock.Mock(
+            customer_order_ref=f"{cheap_hash}I123",
+            market_id="market_id",
+            bet_id=None,
+            selection_id="selection_id",
+            handicap="handicap",
+            order_type="LIMIT",
+            price_size=PriceSize(price=10.0, size=2.0),
+            persistence_type="LAPSE",
+        )
+
+        new_order = create_order_from_current(
+            markets=markets, strategies=strategies, current_order=current_order
+        )
+
+        self.assertEqual(market.blotter["123"], new_order)
+        self.assertEqual(new_order.market_id, "market_id")
+        self.assertEqual(new_order.selection_id, "selection_id")
+        self.assertEqual(new_order.handicap, "handicap")
+        self.assertEqual(new_order.order_type.ORDER_TYPE, OrderTypes.LIMIT)
+        self.assertEqual(new_order.order_type.size, 2.0)
+        self.assertEqual(new_order.order_type.price, 10.0)