Repository: liampauling/flumine
Pull request #208 (2020-06-08 10:36:43)

#205
add order created/execution complete time


Patch:
diff --git a/flumine/order/order.py b/flumine/order/order.py
index 96668fe..62efe83 100644
--- a/flumine/order/order.py
+++ b/flumine/order/order.py
@@ -57,6 +57,9 @@ class BaseOrder:
         self.simulated = Simulated(self)  # used in simulated execution
         self.publish_time = None  # marketBook.publish_time
 
+        self.date_time_created = datetime.datetime.utcnow()
+        self.date_time_execution_complete = None
+
     # status
     def _update_status(self, status: OrderStatus) -> None:
         self.status_log.append(status)
@@ -72,6 +75,7 @@ class BaseOrder:
 
     def execution_complete(self) -> None:
         self._update_status(OrderStatus.EXECUTION_COMPLETE)
+        self.date_time_execution_complete = datetime.datetime.utcnow()
         self.update_data.clear()
 
     def cancelling(self) -> None:
diff --git a/flumine/utils.py b/flumine/utils.py
index e3f1324..754e036 100644
--- a/flumine/utils.py
+++ b/flumine/utils.py
@@ -137,8 +137,8 @@ def wap(matched: list) -> Tuple[float, float]:
         return 0, 0
     a, b = 0, 0
     for match in matched:
-        a += match[0] * match[1]
-        b += match[1]
+        a += match[1] * match[2]
+        b += match[2]
     if b == 0 or a == 0:
         return 0, 0
     else:


Test patch:
diff --git a/flumine/backtest/simulated.py b/flumine/backtest/simulated.py
index 05e296d..9915ed1 100644
--- a/flumine/backtest/simulated.py
+++ b/flumine/backtest/simulated.py
@@ -19,7 +19,7 @@ class Simulated:
 
     def __init__(self, order):
         self.order = order
-        self.matched = []
+        self.matched = []  # [(publishTime, price, size)..]
         self.size_cancelled = 0.0
         self.size_lapsed = 0.0
         self.size_voided = 0.0
@@ -35,11 +35,13 @@ class Simulated:
             and market_book.bsp_reconciled
             and self.take_sp
         ):
-            self._process_sp(runner)
+            self._process_sp(market_book.publish_time_epoch, runner)
 
         if self.order.order_type.ORDER_TYPE == OrderTypes.LIMIT and self.size_remaining:
             # todo piq cancellations
-            self._process_traded(runner_analytics.traded)
+            self._process_traded(
+                market_book.publish_time_epoch, runner_analytics.traded
+            )
 
     def place(
         self, market_book: MarketBook, instruction: dict, bet_id: int
@@ -55,13 +57,21 @@ class Simulated:
             if self.order.side == "BACK":
                 if available_to_back >= price:
                     self._process_price_matched(
-                        price, size, runner.ex.available_to_back
+                        market_book.publish_time_epoch,
+                        price,
+                        size,
+                        runner.ex.available_to_back,
                     )
                     return self._create_place_response(bet_id)
                 available = runner.ex.available_to_lay
             else:
                 if available_to_lay <= price:
-                    self._process_price_matched(price, size, runner.ex.available_to_lay)
+                    self._process_price_matched(
+                        market_book.publish_time_epoch,
+                        price,
+                        size,
+                        runner.ex.available_to_lay,
+                    )
                     return self._create_place_response(bet_id)
                 available = runner.ex.available_to_back
 
@@ -125,7 +135,7 @@ class Simulated:
         return runner_dict.get((self.order.selection_id, self.order.handicap))
 
     def _process_price_matched(
-        self, price: float, size: float, available: list
+        self, publish_time: int, price: float, size: float, available: list
     ) -> None:
         # calculate matched on execution
         size_remaining = size
@@ -141,12 +151,12 @@ class Simulated:
                     _size_matched = _size_remaining
                 else:
                     _size_matched = avail["size"]
-                _matched = (avail["price"], round(_size_matched, 2))
+                _matched = (publish_time, avail["price"], round(_size_matched, 2))
                 self.matched.append(_matched)
             else:
                 break
 
-    def _process_sp(self, runner: RunnerBook) -> None:
+    def _process_sp(self, publish_time: int, runner: RunnerBook) -> None:
         # calculate matched on BSP reconciliation
         actual_sp = runner.sp.actual_sp
         if actual_sp:
@@ -170,18 +180,18 @@ class Simulated:
                     size = round(_order_type.liability / (actual_sp - 1), 2)
             else:
                 raise NotImplementedError()
-            self.matched.append((actual_sp, size))
+            self.matched.append((publish_time, actual_sp, size))
 
-    def _process_traded(self, traded: dict) -> None:
+    def _process_traded(self, publish_time: int, traded: dict) -> None:
         # calculate matched on MarketBook update
         price = self.order.order_type.price
         for traded_price, traded_size in traded.items():
             if self.side == "BACK" and traded_price >= price:
-                self._calculate_process_traded(traded_size)
+                self._calculate_process_traded(publish_time, traded_size)
             elif self.side == "LAY" and traded_price <= price:
-                self._calculate_process_traded(traded_size)
+                self._calculate_process_traded(publish_time, traded_size)
 
-    def _calculate_process_traded(self, traded_size: float) -> None:
+    def _calculate_process_traded(self, publish_time: int, traded_size: float) -> None:
         traded_size = traded_size / 2
         if self._piq - traded_size < 0:
             size = traded_size - self._piq
@@ -189,6 +199,7 @@ class Simulated:
             if size:
                 self.matched.append(
                     (
+                        publish_time,
                         self.order.order_type.price,
                         size,
                     )  # todo takes the worst price, i.e what was asked
diff --git a/tests/test_order.py b/tests/test_order.py
index 5342166..d847806 100644
--- a/tests/test_order.py
+++ b/tests/test_order.py
@@ -31,6 +31,8 @@ class BaseOrderTest(unittest.TestCase):
         self.assertIsNone(self.order.EXCHANGE)
         self.assertEqual(self.order.update_data, {})
         self.assertIsNone(self.order.publish_time)
+        self.assertIsNotNone(self.order.date_time_created)
+        self.assertIsNone(self.order.date_time_execution_complete)
 
     @mock.patch("flumine.order.order.BaseOrder.info")
     def test__update_status(self, mock_info):
@@ -55,6 +57,7 @@ class BaseOrderTest(unittest.TestCase):
         self.order.update_data = {123: 456}
         self.order.execution_complete()
         mock__update_status.assert_called_with(OrderStatus.EXECUTION_COMPLETE)
+        self.assertIsNotNone(self.order.date_time_execution_complete)
         self.assertEqual(self.order.update_data, {})
 
     @mock.patch("flumine.order.order.BaseOrder._update_status")
diff --git a/tests/test_simulated.py b/tests/test_simulated.py
index c1a5019..964661a 100644
--- a/tests/test_simulated.py
+++ b/tests/test_simulated.py
@@ -33,8 +33,12 @@ class SimulatedTest(unittest.TestCase):
         mock_market_book.bsp_reconciled = True
         mock_runner_analytics = mock.Mock()
         self.simulated(mock_market_book, mock_runner_analytics)
-        mock__process_sp.assert_called_with(mock__get_runner())
-        mock__process_traded.assert_called_with(mock_runner_analytics.traded)
+        mock__process_sp.assert_called_with(
+            mock_market_book.publish_time_epoch, mock__get_runner()
+        )
+        mock__process_traded.assert_called_with(
+            mock_market_book.publish_time_epoch, mock_runner_analytics.traded
+        )
 
     @mock.patch("flumine.backtest.simulated.Simulated._get_runner")
     @mock.patch("flumine.backtest.simulated.Simulated.take_sp", return_value=True)
@@ -52,7 +56,9 @@ class SimulatedTest(unittest.TestCase):
 
         mock_market_book.bsp_reconciled = True
         self.simulated(mock_market_book, {})
-        mock__process_sp.assert_called_with(mock__get_runner())
+        mock__process_sp.assert_called_with(
+            mock_market_book.publish_time_epoch, mock__get_runner()
+        )
         mock__process_traded.assert_not_called()
 
     @mock.patch("flumine.backtest.simulated.Simulated._get_runner")
@@ -80,7 +86,9 @@ class SimulatedTest(unittest.TestCase):
         resp = self.simulated.place(mock_market_book, {}, 1)
         self.assertEqual(resp.average_price_matched, 12)
         self.assertEqual(resp.size_matched, 2)
-        self.assertEqual(self.simulated.matched, [(12, 2)])
+        self.assertEqual(
+            self.simulated.matched, [(mock_market_book.publish_time_epoch, 12, 2)]
+        )
 
     @mock.patch("flumine.backtest.simulated.Simulated._get_runner")
     def test_place_limit_back_unmatched(self, mock__get_runner):
@@ -111,7 +119,9 @@ class SimulatedTest(unittest.TestCase):
         resp = self.simulated.place(mock_market_book, {}, 1)
         self.assertEqual(resp.average_price_matched, 12)
         self.assertEqual(resp.size_matched, 2)
-        self.assertEqual(self.simulated.matched, [(12, 2)])
+        self.assertEqual(
+            self.simulated.matched, [(mock_market_book.publish_time_epoch, 12, 2)]
+        )
 
     @mock.patch("flumine.backtest.simulated.Simulated._get_runner")
     def test_place_limit_lay_unmatched(self, mock__get_runner):
@@ -185,23 +195,25 @@ class SimulatedTest(unittest.TestCase):
         return_value="BACK",
     )
     def test__process_price_matched_back(self, mock_side):
-        self.simulated._process_price_matched(12.0, 2.00, [{"price": 15, "size": 120}])
-        self.assertEqual(self.simulated.matched, [(15, 2)])
+        self.simulated._process_price_matched(
+            1234567, 12.0, 2.00, [{"price": 15, "size": 120}]
+        )
+        self.assertEqual(self.simulated.matched, [(1234567, 15, 2)])
         self.simulated.matched = []
         self.simulated._process_price_matched(
-            12.0, 2.00, [{"price": 15, "size": 1}, {"price": 12, "size": 1}]
+            1234568, 12.0, 2.00, [{"price": 15, "size": 1}, {"price": 12, "size": 1}]
         )
-        self.assertEqual(self.simulated.matched, [(15, 1), (12, 1)])
+        self.assertEqual(self.simulated.matched, [(1234568, 15, 1), (1234568, 12, 1)])
         self.simulated.matched = []
         self.simulated._process_price_matched(
-            12.0, 2.00, [{"price": 15, "size": 1}, {"price": 12, "size": 0.5}]
+            1234569, 12.0, 2.00, [{"price": 15, "size": 1}, {"price": 12, "size": 0.5}]
         )
-        self.assertEqual(self.simulated.matched, [(15, 1), (12, 0.5)])
+        self.assertEqual(self.simulated.matched, [(1234569, 15, 1), (1234569, 12, 0.5)])
         self.simulated.matched = []
         self.simulated._process_price_matched(
-            12.0, 2.00, [{"price": 15, "size": 1}, {"price": 11, "size": 0.5}]
+            1234570, 12.0, 2.00, [{"price": 15, "size": 1}, {"price": 11, "size": 0.5}]
         )
-        self.assertEqual(self.simulated.matched, [(15, 1)])
+        self.assertEqual(self.simulated.matched, [(1234570, 15, 1)])
 
     @mock.patch(
         "flumine.backtest.simulated.Simulated.side",
@@ -210,45 +222,52 @@ class SimulatedTest(unittest.TestCase):
     )
     def test__process_price_matched_lay(self, mock_side):
         self.simulated._process_price_matched(
-            3.0, 20.00, [{"price": 2.02, "size": 120}]
+            1234571, 3.0, 20.00, [{"price": 2.02, "size": 120}]
         )
-        self.assertEqual(self.simulated.matched, [(2.02, 20)])
+        self.assertEqual(self.simulated.matched, [(1234571, 2.02, 20)])
         self.simulated.matched = []
         self.simulated._process_price_matched(
-            3.0, 20.00, [{"price": 2.02, "size": 1}, {"price": 3, "size": 20}]
+            1234571, 3.0, 20.00, [{"price": 2.02, "size": 1}, {"price": 3, "size": 20}]
         )
-        self.assertEqual(self.simulated.matched, [(2.02, 1), (3, 19)])
+        self.assertEqual(self.simulated.matched, [(1234571, 2.02, 1), (1234571, 3, 19)])
         self.simulated.matched = []
         self.simulated._process_price_matched(
-            3.0, 20.00, [{"price": 2.02, "size": 1}, {"price": 2.9, "size": 0.5}]
+            1234571,
+            3.0,
+            20.00,
+            [{"price": 2.02, "size": 1}, {"price": 2.9, "size": 0.5}],
+        )
+        self.assertEqual(
+            self.simulated.matched, [(1234571, 2.02, 1), (1234571, 2.9, 0.5)]
         )
-        self.assertEqual(self.simulated.matched, [(2.02, 1), (2.9, 0.5)])
         self.simulated.matched = []
         self.simulated._process_price_matched(
-            3.0, 20.00, [{"price": 3, "size": 1}, {"price": 11, "size": 0.5}]
+            1234571, 3.0, 20.00, [{"price": 3, "size": 1}, {"price": 11, "size": 0.5}]
         )
-        self.assertEqual(self.simulated.matched, [(3, 1)])
+        self.assertEqual(self.simulated.matched, [(1234571, 3, 1)])
 
     def test__process_sp(self):
         mock_runner = mock.Mock()
         mock_runner.sp.actual_sp = 12.20
-        self.simulated._process_sp(mock_runner)
-        self.assertEqual(self.simulated.matched, [(12.2, 2.00)])
+        self.simulated._process_sp(1234571, mock_runner)
+        self.assertEqual(self.simulated.matched, [(1234571, 12.2, 2.00)])
         self.assertTrue(self.simulated._bsp_reconciled)
 
     def test__process_sp_none(self):
         mock_runner = mock.Mock()
         mock_runner.sp.actual_sp = None
-        self.simulated._process_sp(mock_runner)
+        self.simulated._process_sp(1234571, mock_runner)
         self.assertEqual(self.simulated.matched, [])
         self.assertFalse(self.simulated._bsp_reconciled)
 
     def test__process_sp_processed_semi(self):
         mock_runner = mock.Mock()
         mock_runner.sp.actual_sp = 12.20
-        self.simulated.matched = [(10.0, 1)]
-        self.simulated._process_sp(mock_runner)
-        self.assertEqual(self.simulated.matched, [(10.0, 1), (12.2, 1)])
+        self.simulated.matched = [(1234571, 10.0, 1)]
+        self.simulated._process_sp(1234572, mock_runner)
+        self.assertEqual(
+            self.simulated.matched, [(1234571, 10.0, 1), (1234572, 12.2, 1)]
+        )
         self.assertTrue(self.simulated._bsp_reconciled)
 
     def test__process_sp_limit_on_close_back(self):
@@ -257,8 +276,8 @@ class SimulatedTest(unittest.TestCase):
         self.simulated.order.order_type = mock_limit_on_close_order
         mock_runner_book = mock.Mock()
         mock_runner_book.sp.actual_sp = 69
-        self.simulated._process_sp(mock_runner_book)
-        self.assertEqual(self.simulated.matched, [(69, 2.00)])
+        self.simulated._process_sp(1234573, mock_runner_book)
+        self.assertEqual(self.simulated.matched, [(1234573, 69, 2.00)])
         self.assertTrue(self.simulated._bsp_reconciled)
 
     def test__process_sp_limit_on_close_back_no_match(self):
@@ -267,7 +286,7 @@ class SimulatedTest(unittest.TestCase):
         self.simulated.order.order_type = mock_limit_on_close_order
         mock_runner_book = mock.Mock()
         mock_runner_book.sp.actual_sp = 8
-        self.simulated._process_sp(mock_runner_book)
+        self.simulated._process_sp(1234574, mock_runner_book)
         self.assertEqual(self.simulated.matched, [])
         self.assertTrue(self.simulated._bsp_reconciled)
 
@@ -278,8 +297,8 @@ class SimulatedTest(unittest.TestCase):
         self.simulated.order.side = "LAY"
         mock_runner_book = mock.Mock()
         mock_runner_book.sp.actual_sp = 69
-        self.simulated._process_sp(mock_runner_book)
-        self.assertEqual(self.simulated.matched, [(69, 0.03)])
+        self.simulated._process_sp(1234575, mock_runner_book)
+        self.assertEqual(self.simulated.matched, [(1234575, 69, 0.03)])
         self.assertTrue(self.simulated._bsp_reconciled)
 
     def test__process_sp_limit_on_close_lay_no_match(self):
@@ -289,7 +308,7 @@ class SimulatedTest(unittest.TestCase):
         self.simulated.order.side = "LAY"
         mock_runner_book = mock.Mock()
         mock_runner_book.sp.actual_sp = 69
-        self.simulated._process_sp(mock_runner_book)
+        self.simulated._process_sp(1234576, mock_runner_book)
         self.assertEqual(self.simulated.matched, [])
         self.assertTrue(self.simulated._bsp_reconciled)
 
@@ -299,8 +318,8 @@ class SimulatedTest(unittest.TestCase):
         self.simulated.order.order_type = mock_limit_on_close_order
         mock_runner_book = mock.Mock()
         mock_runner_book.sp.actual_sp = 69
-        self.simulated._process_sp(mock_runner_book)
-        self.assertEqual(self.simulated.matched, [(69, 2.00)])
+        self.simulated._process_sp(1234577, mock_runner_book)
+        self.assertEqual(self.simulated.matched, [(1234577, 69, 2.00)])
         self.assertTrue(self.simulated._bsp_reconciled)
 
     def test__process_sp_market_on_close_lay(self):
@@ -310,46 +329,48 @@ class SimulatedTest(unittest.TestCase):
         self.simulated.order.side = "LAY"
         mock_runner_book = mock.Mock()
         mock_runner_book.sp.actual_sp = 69
-        self.simulated._process_sp(mock_runner_book)
-        self.assertEqual(self.simulated.matched, [(69, 0.03)])
+        self.simulated._process_sp(1234578, mock_runner_book)
+        self.assertEqual(self.simulated.matched, [(1234578, 69, 0.03)])
         self.assertTrue(self.simulated._bsp_reconciled)
 
     @mock.patch("flumine.backtest.simulated.Simulated._calculate_process_traded")
     def test__process_traded_back(self, mock__calculate_process_traded):
-        self.simulated._process_traded({12: 120})
-        mock__calculate_process_traded.assert_called_with(120)
+        self.simulated._process_traded(1234579, {12: 120})
+        mock__calculate_process_traded.assert_called_with(1234579, 120)
 
     @mock.patch("flumine.backtest.simulated.Simulated._calculate_process_traded")
     def test__process_traded_back_no(self, mock__calculate_process_traded):
-        self.simulated._process_traded({11: 120})
+        self.simulated._process_traded(1234580, {11: 120})
         mock__calculate_process_traded.assert_not_called()
 
     @mock.patch("flumine.backtest.simulated.Simulated._calculate_process_traded")
     def test__process_traded_lay(self, mock__calculate_process_traded):
         self.simulated.order.side = "LAY"
-        self.simulated._process_traded({12: 120})
-        mock__calculate_process_traded.assert_called_with(120)
+        self.simulated._process_traded(1234581, {12: 120})
+        mock__calculate_process_traded.assert_called_with(1234581, 120)
 
     @mock.patch("flumine.backtest.simulated.Simulated._calculate_process_traded")
     def test__process_traded_lay_no(self, mock__calculate_process_traded):
         self.simulated.order.side = "LAY"
-        self.simulated._process_traded({13: 120})
+        self.simulated._process_traded(1234582, {13: 120})
         mock__calculate_process_traded.assert_not_called()
 
     def test__calculate_process_traded(self):
-        self.simulated._calculate_process_traded(2.00)
-        self.simulated._calculate_process_traded(2.00)
-        self.simulated._calculate_process_traded(2.00)
-        self.assertEqual(self.simulated.matched, [(12, 1.00), (12, 1.00)])
+        self.simulated._calculate_process_traded(1234582, 2.00)
+        self.simulated._calculate_process_traded(1234583, 2.00)
+        self.simulated._calculate_process_traded(1234584, 2.00)
+        self.assertEqual(
+            self.simulated.matched, [(1234582, 12, 1.00), (1234583, 12, 1.00)]
+        )
         self.assertEqual(self.simulated._piq, 0)
 
     def test__calculate_process_traded_piq(self):
         self.simulated._piq = 2.00
-        self.simulated._calculate_process_traded(4.00)
+        self.simulated._calculate_process_traded(1234585, 4.00)
         self.assertEqual(self.simulated.matched, [])
         self.assertEqual(self.simulated._piq, 0)
-        self.simulated._calculate_process_traded(4.00)
-        self.assertEqual(self.simulated.matched, [(12, 2.00)])
+        self.simulated._calculate_process_traded(1234586, 4.00)
+        self.assertEqual(self.simulated.matched, [(1234586, 12, 2.00)])
         self.assertEqual(self.simulated._piq, 0)
 
     def test_take_sp(self):
@@ -367,12 +388,12 @@ class SimulatedTest(unittest.TestCase):
 
     def test_average_price_matched(self):
         self.assertEqual(self.simulated.average_price_matched, 0)
-        self.simulated.matched = [(1, 2)]
+        self.simulated.matched = [(1234, 1, 2)]
         self.assertEqual(self.simulated.average_price_matched, 1)
 
     def test_size_matched(self):
         self.assertEqual(self.simulated.size_matched, 0)
-        self.simulated.matched = [(1, 2)]
+        self.simulated.matched = [(4321, 1, 2)]
         self.assertEqual(self.simulated.size_matched, 2)
 
     @mock.patch(
diff --git a/tests/test_utils.py b/tests/test_utils.py
index f9d4fab..85892bb 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -108,9 +108,11 @@ class UtilsTest(unittest.TestCase):
         self.assertEqual(utils.calculate_exposure([(5.6, 200)], [(5.6, 100)]), -100)
 
     def test_wap(self):
-        self.assertEqual(utils.wap([(1.5, 100), (1.6, 100)]), (200, 1.55))
+        self.assertEqual(
+            utils.wap([(123456789, 1.5, 100), (123456789, 1.6, 100)]), (200, 1.55)
+        )
         self.assertEqual(utils.wap([]), (0, 0))
-        self.assertEqual(utils.wap([(1.5, 0)]), (0, 0))
+        self.assertEqual(utils.wap([(123456789, 1.5, 0)]), (0, 0))
 
     def test_call_check_market(self):
         mock_strategy_check = mock.Mock()