Repository: liampauling/flumine
Pull request #551 (2022-01-13 09:15:02)

Release/1.21.2


Patch:
diff --git a/HISTORY.rst b/HISTORY.rst
index 9f75c3b..e021263 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -3,6 +3,15 @@
 Release History
 ---------------
 
+1.21.2 (2022-01-13)
++++++++++++++++++++
+
+**Improvements**
+
+- license update
+- strategy warning on duplicate names
+- minor optimisations for simulation
+
 1.21.1 (2022-01-10)
 +++++++++++++++++++
 
diff --git a/LICENSE b/LICENSE
index f32fff9..776029e 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2021, Liam Pauling
+Copyright (c) 2022, Liam Pauling
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/flumine/__version__.py b/flumine/__version__.py
index 125ad2c..0d0c49e 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.21.1"
+__version__ = "1.21.2"
 __author__ = "Liam Pauling"
 __license__ = "MIT"
diff --git a/flumine/markets/blotter.py b/flumine/markets/blotter.py
index 00add47..e9405d4 100644
--- a/flumine/markets/blotter.py
+++ b/flumine/markets/blotter.py
@@ -14,6 +14,11 @@ logger = logging.getLogger(__name__)
 
 # https://www.betfair.com/aboutUs/Betfair.Charges/#charges6
 IMPLIED_COMMISSION_RATE = 0.03
+PENDING_STATUS = [
+    OrderStatus.PENDING,
+    OrderStatus.VIOLATION,
+    OrderStatus.EXPIRED,
+]
 
 
 class Blotter:
@@ -151,11 +156,7 @@ class Blotter:
         for order in self.strategy_selection_orders(strategy, *lookup[1:]):
             if order == exclusion:
                 continue
-            if order.status in [
-                OrderStatus.PENDING,
-                OrderStatus.VIOLATION,
-                OrderStatus.EXPIRED,
-            ]:
+            if order.status in PENDING_STATUS:
                 continue
             if order.order_type.ORDER_TYPE == OrderTypes.LIMIT:
                 _size_matched = order.size_matched  # cache
diff --git a/flumine/markets/middleware.py b/flumine/markets/middleware.py
index 85fee17..238de30 100644
--- a/flumine/markets/middleware.py
+++ b/flumine/markets/middleware.py
@@ -10,6 +10,12 @@ 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)
+LIVE_STATUS = [
+    OrderStatus.EXECUTABLE,
+    OrderStatus.CANCELLING,
+    OrderStatus.UPDATING,
+    OrderStatus.REPLACING,
+]
 
 
 class Middleware:
@@ -195,16 +201,7 @@ class SimulatedMiddleware(Middleware):
         if config.simulated_strategy_isolation:
             for strategy, orders in market.blotter._strategy_orders.items():
                 live_orders = [
-                    o
-                    for o in orders
-                    if o.status
-                    in [
-                        OrderStatus.EXECUTABLE,
-                        OrderStatus.CANCELLING,
-                        OrderStatus.UPDATING,
-                        OrderStatus.REPLACING,
-                    ]
-                    and o.simulated
+                    o for o in orders if o.status in LIVE_STATUS and o.simulated
                 ]
                 if live_orders:
                     _lookup = {k: v.traded.copy() for k, v in market_analytics.items()}
@@ -218,16 +215,7 @@ class SimulatedMiddleware(Middleware):
                 _lookup = {k: v.traded.copy() for k, v in market_analytics.items()}
                 live_orders_sorted = self._sort_orders(live_orders)
                 for order in live_orders_sorted:
-                    if (
-                        order.status
-                        in [
-                            OrderStatus.EXECUTABLE,
-                            OrderStatus.CANCELLING,
-                            OrderStatus.UPDATING,
-                            OrderStatus.REPLACING,
-                        ]
-                        and order.simulated
-                    ):
+                    if order.status in LIVE_STATUS and order.simulated:
                         runner_traded = _lookup[(order.selection_id, order.handicap)]
                         order.simulated(market.market_book, runner_traded)
 
diff --git a/flumine/order/order.py b/flumine/order/order.py
index 33ed927..f449abd 100644
--- a/flumine/order/order.py
+++ b/flumine/order/order.py
@@ -39,6 +39,20 @@ class OrderStatus(Enum):
     VIOLATION = "Violation"  # order never placed due to failing controls
 
 
+LIVE_STATUS = [
+    OrderStatus.PENDING,
+    OrderStatus.CANCELLING,
+    OrderStatus.UPDATING,
+    OrderStatus.REPLACING,
+    OrderStatus.EXECUTABLE,
+]
+COMPLETE_STATUS = [
+    OrderStatus.EXECUTION_COMPLETE,
+    OrderStatus.EXPIRED,
+    OrderStatus.VIOLATION,
+]
+
+
 class BaseOrder:
 
     EXCHANGE = None
@@ -157,19 +171,9 @@ class BaseOrder:
     def _is_complete(self) -> bool:
         """Returns False if order is
         live or pending in the market"""
-        if self.status in [
-            OrderStatus.PENDING,
-            OrderStatus.CANCELLING,
-            OrderStatus.UPDATING,
-            OrderStatus.REPLACING,
-            OrderStatus.EXECUTABLE,
-        ]:
+        if self.status in LIVE_STATUS:
             return False
-        elif self.status in [
-            OrderStatus.EXECUTION_COMPLETE,
-            OrderStatus.EXPIRED,
-            OrderStatus.VIOLATION,
-        ]:
+        elif self.status in COMPLETE_STATUS:
             return True
         else:
             return False  # default to False
diff --git a/flumine/strategy/strategy.py b/flumine/strategy/strategy.py
index 66d18ce..db3ca70 100644
--- a/flumine/strategy/strategy.py
+++ b/flumine/strategy/strategy.py
@@ -227,6 +227,8 @@ class Strategies:
         self._strategies = []
 
     def __call__(self, strategy: BaseStrategy, client: BaseClient) -> None:
+        if strategy.name in [s.name for s in self]:
+            logger.warning("Strategy of same name '{0}' already added".format(strategy))
         strategy.client = client
         self._strategies.append(strategy)
         strategy.add()
diff --git a/flumine/utils.py b/flumine/utils.py
index ce87b80..3a11ce3 100644
--- a/flumine/utils.py
+++ b/flumine/utils.py
@@ -175,16 +175,13 @@ def calculate_matched_exposure(mb: list, ml: list) -> Tuple:
     """
     if not mb and not ml:
         return 0.0, 0.0
-    if mb:
-        back_exp = sum(-i[1] for i in mb)
-        back_profit = sum((i[0] - 1) * i[1] for i in mb)
-    else:
-        back_exp, back_profit = 0, 0
-    if ml:
-        lay_exp = sum((i[0] - 1) * -i[1] for i in ml)
-        lay_profit = sum(i[1] for i in ml)
-    else:
-        lay_exp, lay_profit = 0, 0
+    back_exp, back_profit, lay_exp, lay_profit = 0, 0, 0, 0
+    for p, s in mb:
+        back_exp += -s
+        back_profit += (p - 1) * s
+    for p, s in ml:
+        lay_exp += (p - 1) * -s
+        lay_profit += s
     _win = back_profit + lay_exp
     _lose = lay_profit + back_exp
     return round(_win, 2), round(_lose, 2)
@@ -201,8 +198,11 @@ def calculate_unmatched_exposure(ub: list, ul: list) -> Tuple:
     """
     if not ub and not ul:
         return 0.0, 0.0
-    back_exp = sum(-i[1] for i in ub)
-    lay_exp = sum((i[0] - 1) * -i[1] for i in ul)
+    back_exp, lay_exp = 0, 0
+    for p, s in ub:
+        back_exp += -s
+    for p, s in ul:
+        lay_exp += (p - 1) * -s
     return round(lay_exp, 2), round(back_exp, 2)
 
 
@@ -210,9 +210,9 @@ def wap(matched: list) -> Tuple[float, float]:
     if not matched:
         return 0, 0
     a, b = 0, 0
-    for match in matched:
-        a += match[1] * match[2]
-        b += match[2]
+    for _, p, s in matched:
+        a += p * s
+        b += s
     if b == 0 or a == 0:
         return 0, 0
     else:


Test patch:
diff --git a/tests/test_blotter.py b/tests/test_blotter.py
index ba3553b..057e65f 100644
--- a/tests/test_blotter.py
+++ b/tests/test_blotter.py
@@ -1,7 +1,7 @@
 import unittest
 from unittest import mock
 
-from flumine.markets.blotter import Blotter
+from flumine.markets.blotter import Blotter, PENDING_STATUS
 from flumine.order.order import OrderStatus
 from flumine.order.ordertype import MarketOnCloseOrder, LimitOrder, LimitOnCloseOrder
 
@@ -19,6 +19,14 @@ class BlotterTest(unittest.TestCase):
         self.assertEqual(self.blotter._trades, {})
         self.assertEqual(self.blotter._strategy_orders, {})
         self.assertEqual(self.blotter._strategy_selection_orders, {})
+        self.assertEqual(
+            PENDING_STATUS,
+            [
+                OrderStatus.PENDING,
+                OrderStatus.VIOLATION,
+                OrderStatus.EXPIRED,
+            ],
+        )
 
     def test_get_order_bet_id(self):
         self.assertIsNone(self.blotter.get_order_bet_id("123"))
diff --git a/tests/test_middleware.py b/tests/test_middleware.py
index 21d64c0..d5c53aa 100644
--- a/tests/test_middleware.py
+++ b/tests/test_middleware.py
@@ -9,6 +9,7 @@ from flumine.markets.middleware import (
     OrderTypes,
     WIN_MINIMUM_ADJUSTMENT_FACTOR,
     PLACE_MINIMUM_ADJUSTMENT_FACTOR,
+    LIVE_STATUS,
 )
 from flumine.order.ordertype import MarketOnCloseOrder
 
@@ -38,6 +39,15 @@ class SimulatedMiddlewareTest(unittest.TestCase):
         self.assertEqual(self.middleware._runner_removals, [])
         self.assertEqual(WIN_MINIMUM_ADJUSTMENT_FACTOR, 2.5)
         self.assertEqual(PLACE_MINIMUM_ADJUSTMENT_FACTOR, 0)
+        self.assertEqual(
+            LIVE_STATUS,
+            [
+                OrderStatus.EXECUTABLE,
+                OrderStatus.CANCELLING,
+                OrderStatus.UPDATING,
+                OrderStatus.REPLACING,
+            ],
+        )
 
     @mock.patch(
         "flumine.markets.middleware.SimulatedMiddleware._process_simulated_orders"
diff --git a/tests/test_order.py b/tests/test_order.py
index 574e6c0..e134e2d 100644
--- a/tests/test_order.py
+++ b/tests/test_order.py
@@ -11,6 +11,8 @@ from flumine.order.order import (
     OrderTypes,
     OrderStatus,
     VALID_BETFAIR_CUSTOMER_ORDER_REF_CHARACTERS,
+    LIVE_STATUS,
+    COMPLETE_STATUS,
 )
 from flumine.exceptions import OrderUpdateError
 
@@ -55,6 +57,24 @@ class BaseOrderTest(unittest.TestCase):
         self.assertIsNone(self.order.date_time_execution_complete)
         self.assertFalse(self.order.simulated)
         self.assertFalse(self.order._simulated)
+        self.assertEqual(
+            LIVE_STATUS,
+            [
+                OrderStatus.PENDING,
+                OrderStatus.CANCELLING,
+                OrderStatus.UPDATING,
+                OrderStatus.REPLACING,
+                OrderStatus.EXECUTABLE,
+            ],
+        )
+        self.assertEqual(
+            COMPLETE_STATUS,
+            [
+                OrderStatus.EXECUTION_COMPLETE,
+                OrderStatus.EXPIRED,
+                OrderStatus.VIOLATION,
+            ],
+        )
 
     @mock.patch("flumine.order.order.BaseOrder._is_complete")
     @mock.patch("flumine.order.order.BaseOrder.info")