Repository: liampauling/flumine
Pull request #235 (2020-07-15 08:01:10)

bugfix/regression on missing open market when reopened


Patch:
diff --git a/HISTORY.rst b/HISTORY.rst
index d51a46f..450cdf4 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -3,6 +3,17 @@
 Release History
 ---------------
 
+1.9.2 (2020-07-16)
++++++++++++++++++++
+
+**Improvements**
+
+- update_market_notes refactor and move to utils to make patching easier
+
+**Bug Fixes**
+
+- Market.closed now updated when reopened + logging improvements
+
 1.9.1 (2020-07-15)
 +++++++++++++++++++
 
diff --git a/flumine/__version__.py b/flumine/__version__.py
index c8281ac..6698faa 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.9.1"
+__version__ = "1.9.2"
 __author__ = "Liam Pauling"
 __license__ = "MIT"
diff --git a/flumine/baseflumine.py b/flumine/baseflumine.py
index 143e872..9823cf9 100644
--- a/flumine/baseflumine.py
+++ b/flumine/baseflumine.py
@@ -115,6 +115,8 @@ class BaseFlumine:
             market = self.markets.markets.get(market_id)
             if market is None:
                 market = self._add_market(market_id, market_book)
+            elif market.closed:
+                self.markets.add_market(market_id, market)
 
             # process market
             market(market_book)
diff --git a/flumine/markets/market.py b/flumine/markets/market.py
index e72743b..bfb9f8e 100644
--- a/flumine/markets/market.py
+++ b/flumine/markets/market.py
@@ -32,10 +32,21 @@ class Market:
 
     def open_market(self) -> None:
         self.closed = False
+        logger.info(
+            "Market {0} opened".format(self.market_id),
+            extra={"market_id": self.market_id},
+        )
 
     def close_market(self) -> None:
         self.closed = True
         self.date_time_closed = datetime.datetime.utcnow()
+        logger.info(
+            "Market {0} closed".format(self.market_id),
+            extra={
+                "market_id": self.market_id,
+                "date_time_closed": self.date_time_closed,
+            },
+        )
 
     # order
     def place_order(self, order, execute: bool = True) -> None:
@@ -43,7 +54,7 @@ class Market:
         if order.id not in self.blotter:
             self.blotter[order.id] = order
             if order.trade.market_notes is None:
-                order.trade.update_market_notes(self.market_book)
+                order.trade.update_market_notes(self)
             self.flumine.log_control(events.TradeEvent(order.trade))  # todo dupes?
         else:
             return  # retry attempt so ignore?
diff --git a/flumine/order/trade.py b/flumine/order/trade.py
index ecc3a5f..4fcb151 100644
--- a/flumine/order/trade.py
+++ b/flumine/order/trade.py
@@ -4,13 +4,13 @@ import datetime
 import collections
 from enum import Enum
 from typing import Union, Type
-from betfairlightweight.resources.bettingresources import MarketBook, CurrentOrder
+from betfairlightweight.resources.bettingresources import CurrentOrder
 
 from ..strategy.strategy import BaseStrategy
 from .order import BetfairOrder
 from .ordertype import LimitOrder, LimitOnCloseOrder, MarketOnCloseOrder
 from ..exceptions import OrderError
-from ..utils import get_price, get_runner_book
+from ..utils import get_market_notes
 
 logger = logging.getLogger(__name__)
 
@@ -32,7 +32,6 @@ class Trade:
         fill_kill=None,
         offset=None,
         green=None,
-        stop=None,
     ):
         self.id = uuid.uuid1()
         self.market_id = market_id
@@ -42,11 +41,10 @@ class Trade:
         self.notes = (
             notes if notes else collections.OrderedDict()
         )  # trade notes (e.g. triggers/market state)
-        self.market_notes = None  # back,lay,lpt (initial order only)
-        self.fill_kill = fill_kill
-        self.offset = offset
-        self.green = green
-        self.stop = stop
+        self.market_notes = None  # back,lay,lpt
+        self.fill_kill = fill_kill  # todo
+        self.offset = offset  # todo
+        self.green = green  # todo
         self.orders = []  # all orders linked to trade
         self.offset_orders = []  # pending offset orders once initial order has matched
         self.status_log = []
@@ -54,14 +52,8 @@ class Trade:
         self.date_time_created = datetime.datetime.utcnow()
         self.date_time_complete = None
 
-    def update_market_notes(self, market_book: MarketBook) -> None:
-        runner = get_runner_book(market_book, self.selection_id)
-        if runner:
-            self.market_notes = "{0},{1},{2}".format(
-                get_price(runner.ex.available_to_back, 0),
-                get_price(runner.ex.available_to_lay, 0),
-                runner.last_price_traded,
-            )
+    def update_market_notes(self, market) -> None:
+        self.market_notes = get_market_notes(market, self.selection_id)
 
     # status
     def _update_status(self, status: TradeStatus) -> None:
diff --git a/flumine/utils.py b/flumine/utils.py
index 8430c0e..4567b29 100644
--- a/flumine/utils.py
+++ b/flumine/utils.py
@@ -203,7 +203,9 @@ def call_process_market_book(
             raise
 
 
-def get_runner_book(market_book: MarketBook, selection_id: int, handicap=0):
+def get_runner_book(
+    market_book: MarketBook, selection_id: int, handicap=0
+) -> Optional[RunnerBook]:
     """Returns runner book based on selection id.
     """
     for runner_book in market_book.runners:
@@ -212,3 +214,16 @@ def get_runner_book(market_book: MarketBook, selection_id: int, handicap=0):
             and runner_book.handicap == handicap
         ):
             return runner_book
+
+
+def get_market_notes(market, selection_id: int) -> Optional[str]:
+    """Returns a string of notes for a runner,
+    currently 'back,lay,last_price_traded'
+    """
+    runner = get_runner_book(market.market_book, selection_id)
+    if runner:
+        return "{0},{1},{2}".format(
+            get_price(runner.ex.available_to_back, 0),
+            get_price(runner.ex.available_to_lay, 0),
+            runner.last_price_traded,
+        )


Test patch:
diff --git a/flumine/backtest/backtest.py b/flumine/backtest/backtest.py
index 335ad31..5aa592f 100644
--- a/flumine/backtest/backtest.py
+++ b/flumine/backtest/backtest.py
@@ -77,6 +77,8 @@ class FlumineBacktest(BaseFlumine):
             if market is None:
                 market = self._add_market(market_id, market_book)
                 self.log_control(events.MarketEvent(market))
+            elif market.closed:
+                self.markets.add_market(market_id, market)
 
             # process market
             market(market_book)
diff --git a/tests/test_markets.py b/tests/test_markets.py
index a17022f..ec7464a 100644
--- a/tests/test_markets.py
+++ b/tests/test_markets.py
@@ -137,7 +137,7 @@ class MarketTest(unittest.TestCase):
         mock_order.place.assert_called_with(self.market.market_book.publish_time)
         self.assertEqual(self.market.blotter.pending_place, [mock_order])
         self.mock_flumine.log_control.assert_called_with(mock_events.TradeEvent())
-        mock_order.trade.update_market_notes.assert_called_with(self.market.market_book)
+        mock_order.trade.update_market_notes.assert_called_with(self.market)
 
     @mock.patch("flumine.markets.market.events")
     def test_place_order_not_executed(self, mock_events):
diff --git a/tests/test_trade.py b/tests/test_trade.py
index c1fff6e..284b881 100644
--- a/tests/test_trade.py
+++ b/tests/test_trade.py
@@ -3,7 +3,6 @@ import collections
 import unittest
 from unittest import mock
 
-from flumine.order.order import OrderStatus
 from flumine.order.trade import Trade, OrderError, TradeStatus
 
 
@@ -14,7 +13,6 @@ class TradeTest(unittest.TestCase):
         self.mock_fill_kill = mock.Mock()
         self.mock_offset = mock.Mock()
         self.mock_green = mock.Mock()
-        self.mock_stop = mock.Mock()
         self.notes = collections.OrderedDict({"trigger": 123})
         self.trade = Trade(
             "1.234",
@@ -25,7 +23,6 @@ class TradeTest(unittest.TestCase):
             self.mock_fill_kill,
             self.mock_offset,
             self.mock_green,
-            self.mock_stop,
         )
 
     def test_init(self):
@@ -37,7 +34,6 @@ class TradeTest(unittest.TestCase):
         self.assertEqual(self.trade.fill_kill, self.mock_fill_kill)
         self.assertEqual(self.trade.offset, self.mock_offset)
         self.assertEqual(self.trade.green, self.mock_green)
-        self.assertEqual(self.trade.stop, self.mock_stop)
         self.assertEqual(self.trade.status_log, [])
         self.assertEqual(self.trade.orders, [])
         self.assertEqual(self.trade.offset_orders, [])
@@ -45,15 +41,11 @@ class TradeTest(unittest.TestCase):
         self.assertIsNone(self.trade.date_time_complete)
         self.assertIsNone(self.trade.market_notes)
 
-    @mock.patch("flumine.order.trade.get_price", return_value=1.01)
-    def test_update_market_notes(self, mock_get_price):
-        mock_market_book = mock.Mock()
-        mock_runner = mock.Mock(
-            selection_id=self.trade.selection_id, handicap=0, last_price_traded=5
-        )
-        mock_market_book.runners = [mock_runner]
-        self.trade.update_market_notes(mock_market_book)
-        self.assertEqual(self.trade.market_notes, "1.01,1.01,5")
+    @mock.patch("flumine.order.trade.get_market_notes")
+    def test_update_market_notes(self, mock_get_market_notes):
+        mock_market = mock.Mock()
+        self.trade.update_market_notes(mock_market)
+        self.assertEqual(self.trade.market_notes, mock_get_market_notes())
 
     def test__update_status(self):
         self.trade._update_status(TradeStatus.COMPLETE)
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 265dc4b..d253618 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -191,3 +191,11 @@ class UtilsTest(unittest.TestCase):
         mock_runner = mock.Mock(selection_id=123, handicap=0)
         mock_market_book.runners = [mock_runner]
         self.assertEqual(utils.get_runner_book(mock_market_book, 123), mock_runner)
+
+    @mock.patch("flumine.utils.get_price", return_value=1.01)
+    def test_get_market_notes(self, mock_get_price):
+        mock_market_book = mock.Mock()
+        mock_runner = mock.Mock(selection_id=123, handicap=0, last_price_traded=5)
+        mock_market_book.runners = [mock_runner]
+        mock_market = mock.Mock(market_book=mock_market_book)
+        self.assertEqual(utils.get_market_notes(mock_market, 123), "1.01,1.01,5")