Repository: liampauling/flumine
Pull request #550 (2022-01-10 08:40:42)

Process end of flumine on exit
flumine added as var to `strategy.finish` (*breaking change)
exit reorder


Patch:
diff --git a/HISTORY.rst b/HISTORY.rst
index 98afe61..9f75c3b 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -3,6 +3,19 @@
 Release History
 ---------------
 
+1.21.1 (2022-01-10)
++++++++++++++++++++
+
+**Improvements**
+
+- Process end of flumine on exit
+- Flumine added as var to `strategy.finish` (*breaking change)
+
+**Bug Fixes**
+
+- #548 handle execution complete during placement delay
+- Correctly add order datetimes on restart
+
 1.21.0 (2022-01-06)
 +++++++++++++++++++
 
diff --git a/flumine/__version__.py b/flumine/__version__.py
index d642a99..125ad2c 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.0"
+__version__ = "1.21.1"
 __author__ = "Liam Pauling"
 __license__ = "MIT"
diff --git a/flumine/baseflumine.py b/flumine/baseflumine.py
index fc6d3aa..20be37d 100644
--- a/flumine/baseflumine.py
+++ b/flumine/baseflumine.py
@@ -351,8 +351,7 @@ class BaseFlumine:
         self.log_control(event)
 
     def _process_end_flumine(self) -> None:
-        for strategy in self.strategies:
-            strategy.finish()
+        self.strategies.finish(self)
 
     @property
     def info(self) -> dict:
@@ -396,14 +395,16 @@ class BaseFlumine:
         self._running = True
 
     def __exit__(self, *args):
+        # shutdown framework
+        self._process_end_flumine()
+        # shutdown workers
+        for w in self._workers:
+            w.shutdown()
         # shutdown streams
         self.streams.stop()
         # shutdown thread pools
         self.simulated_execution.shutdown()
         self.betfair_execution.shutdown()
-        # shutdown workers
-        for w in self._workers:
-            w.shutdown()
         # shutdown logging controls
         self.log_control(events.TerminationEvent(self))
         for c in self._logging_controls:
diff --git a/flumine/flumine.py b/flumine/flumine.py
index 1f75f5c..17aff7c 100644
--- a/flumine/flumine.py
+++ b/flumine/flumine.py
@@ -16,7 +16,6 @@ class Flumine(BaseFlumine):
             while True:
                 event = self.handler_queue.get()
                 if event.EVENT_TYPE == EventType.TERMINATOR:
-                    self._process_end_flumine()
                     break
 
                 elif event.EVENT_TYPE == EventType.MARKET_CATALOGUE:
diff --git a/flumine/order/orderpackage.py b/flumine/order/orderpackage.py
index a318125..b0693ab 100644
--- a/flumine/order/orderpackage.py
+++ b/flumine/order/orderpackage.py
@@ -157,7 +157,11 @@ class BetfairOrderPackage(BaseOrderPackage):
 
     @property
     def replace_instructions(self):
-        return [order.create_replace_instruction() for order in self]
+        return [
+            order.create_replace_instruction()
+            for order in self
+            if order.status != OrderStatus.EXECUTION_COMPLETE
+        ]
 
     @classmethod
     def order_limit(cls, package_type: OrderPackageType) -> int:
diff --git a/flumine/order/trade.py b/flumine/order/trade.py
index e81d346..b8d6ffa 100644
--- a/flumine/order/trade.py
+++ b/flumine/order/trade.py
@@ -150,6 +150,13 @@ class Trade:
         )
         order.bet_id = current_order.bet_id
         order.id = order_id
+        # update dates
+        order.date_time_created = current_order.placed_date
+        order.date_time_execution_complete = (
+            current_order.matched_date
+            or current_order.cancelled_date
+            or current_order.lapsed_date
+        )
         self.orders.append(order)
         return order
 
diff --git a/flumine/strategy/strategy.py b/flumine/strategy/strategy.py
index 3a88aa4..66d18ce 100644
--- a/flumine/strategy/strategy.py
+++ b/flumine/strategy/strategy.py
@@ -123,7 +123,7 @@ class BaseStrategy:
         # process marketBook after closure
         return
 
-    def finish(self) -> None:
+    def finish(self, flumine) -> None:
         # called before flumine ends
         return
 
@@ -235,6 +235,10 @@ class Strategies:
         for s in self:
             s.start()
 
+    def finish(self, flumine) -> None:
+        for s in self:
+            s.finish(flumine)
+
     @property
     def hashes(self) -> dict:
         return {strategy.name_hash: strategy for strategy in self}


Test patch:
diff --git a/tests/test_baseflumine.py b/tests/test_baseflumine.py
index 24aff21..0d3ea05 100644
--- a/tests/test_baseflumine.py
+++ b/tests/test_baseflumine.py
@@ -372,14 +372,18 @@ class BaseFlumineTest(unittest.TestCase):
         mock_log_control.assert_called_with(mock_event)
 
     def test__process_end_flumine(self):
+        mock_strategies = mock.Mock()
+        self.base_flumine.strategies = mock_strategies
         self.base_flumine._process_end_flumine()
+        mock_strategies.finish.assert_called_with(self.base_flumine)
 
     def test_info(self):
         self.assertTrue(self.base_flumine.info)
 
+    @mock.patch("flumine.baseflumine.BaseFlumine._process_end_flumine")
     @mock.patch("flumine.baseflumine.events")
     @mock.patch("flumine.baseflumine.BaseFlumine.log_control")
-    def test_enter_exit(self, mock_log_control, mock_events):
+    def test_enter_exit(self, mock_log_control, mock_events, mock__process_end_flumine):
         control = mock.Mock()
         self.base_flumine._logging_controls = [control]
         self.base_flumine.simulated_execution = mock.Mock()
@@ -390,6 +394,7 @@ class BaseFlumineTest(unittest.TestCase):
             mock_log_control.assert_called_with(mock_events.ConfigEvent(None))
 
         self.assertFalse(self.base_flumine._running)
+        mock__process_end_flumine.assert_called_with()
         self.mock_client.logout.assert_called_with()
         self.base_flumine.simulated_execution.shutdown.assert_called_with()
         self.base_flumine.betfair_execution.shutdown.assert_called_with()
diff --git a/tests/test_strategy.py b/tests/test_strategy.py
index e1e7e95..4c5b7eb 100644
--- a/tests/test_strategy.py
+++ b/tests/test_strategy.py
@@ -25,6 +25,13 @@ class StrategiesTest(unittest.TestCase):
         self.strategies.start()
         mock_strategy.start.assert_called_with()
 
+    def test_finish(self):
+        mock_flumine = mock.Mock()
+        mock_strategy = mock.Mock()
+        self.strategies._strategies.append(mock_strategy)
+        self.strategies.finish(mock_flumine)
+        mock_strategy.finish.assert_called_with(mock_flumine)
+
     def test_iter(self):
         for i in self.strategies:
             assert i
@@ -152,7 +159,7 @@ class BaseStrategyTest(unittest.TestCase):
         self.strategy.process_closed_market(None, None)
 
     def test_finish(self):
-        self.strategy.finish()
+        self.strategy.finish(mock.Mock())
 
     def test_remove_market(self):
         self.strategy._invested = {
diff --git a/tests/test_trade.py b/tests/test_trade.py
index 454f39b..f78e4ea 100644
--- a/tests/test_trade.py
+++ b/tests/test_trade.py
@@ -96,10 +96,8 @@ class TradeTest(unittest.TestCase):
         self.assertEqual(self.trade.orders, [mock_order()])
 
     def test_create_order_error(self):
-        mock_order_type = mock.Mock()
-        mock_order_type.EXCHANGE = "SYM"
-        mock_order = mock.Mock()
-        mock_order.EXCHANGE = "MYS"
+        mock_order_type = mock.Mock(EXCHANGE="SYM")
+        mock_order = mock.Mock(EXCHANGE="MYS")
         with self.assertRaises(OrderError):
             self.trade.create_order("BACK", mock_order_type, order=mock_order)
 
@@ -109,32 +107,32 @@ class TradeTest(unittest.TestCase):
         self.assertEqual(self.trade.orders, [replacement_order])
 
     def test_create_order_from_current_limit(self):
-        mock_current_order = mock.Mock()
-        mock_current_order.order_type = "LIMIT"
+        mock_current_order = mock.Mock(
+            order_type="LIMIT", placed_date=12, matched_date=None, cancelled_date=34
+        )
         order = self.trade.create_order_from_current(mock_current_order, "12345")
         self.assertEqual(order.bet_id, mock_current_order.bet_id)
         self.assertEqual(order.id, "12345")
         self.assertEqual(self.trade.orders, [order])
+        self.assertEqual(order.date_time_created, 12)
+        self.assertEqual(order.date_time_execution_complete, 34)
 
     def test_create_order_from_current_limit_on_close(self):
-        mock_current_order = mock.Mock()
-        mock_current_order.order_type = "LIMIT_ON_CLOSE"
+        mock_current_order = mock.Mock(order_type="LIMIT_ON_CLOSE")
         order = self.trade.create_order_from_current(mock_current_order, "12345")
         self.assertEqual(order.bet_id, mock_current_order.bet_id)
         self.assertEqual(order.id, "12345")
         self.assertEqual(self.trade.orders, [order])
 
     def test_create_order_from_current_market_on_close(self):
-        mock_current_order = mock.Mock()
-        mock_current_order.order_type = "MARKET_ON_CLOSE"
+        mock_current_order = mock.Mock(order_type="MARKET_ON_CLOSE")
         order = self.trade.create_order_from_current(mock_current_order, "12345")
         self.assertEqual(order.bet_id, mock_current_order.bet_id)
         self.assertEqual(order.id, "12345")
         self.assertEqual(self.trade.orders, [order])
 
     def test_create_order_from_current_unknown(self):
-        mock_current_order = mock.Mock()
-        mock_current_order.order_type = "BE"
+        mock_current_order = mock.Mock(order_type="BE")
         with self.assertRaises(NotImplementedError):
             self.trade.create_order_from_current(mock_current_order, "12345")