From 658e40003a3280c2cbfdfa038e7815cec3690aaf Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 25 May 2022 21:16:48 +0100 Subject: [PATCH 1/5] all_or_nothing transactions --- flumine/execution/transaction.py | 47 ++++++++++++++++++++--- flumine/markets/market.py | 8 +++- tests/test_markets.py | 2 + tests/test_transaction.py | 66 ++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 6 deletions(-) diff --git a/flumine/execution/transaction.py b/flumine/execution/transaction.py index 59050357..2b5793c7 100644 --- a/flumine/execution/transaction.py +++ b/flumine/execution/transaction.py @@ -1,5 +1,6 @@ import logging from collections import defaultdict +from typing import Optional from ..order.orderpackage import OrderPackageType, BetfairOrderPackage from ..events import events @@ -28,9 +29,27 @@ class Transaction: .. t.cancel_order(order) t.place_order(order) # both executed on transaction __exit__ + + When all_or_nothing==True, orders within a transaction will + only be placed if the all pass validation. + For example, supposed we have: + + with market.transaction() as t: + t.place_order(order1) # validation passes + t.place_order(order2) # validation does not pass and raises a ControlError + + Neither order1 nor order2 will be executed, as order2 failed validation. + """ - def __init__(self, market, id_: int, async_place_orders: bool, client): + def __init__( + self, + market, + id_: int, + async_place_orders: bool, + client, + all_or_nothing: Optional[bool] = False, + ): self.market = market self._client = client self._id = id_ # unique per market only @@ -40,6 +59,7 @@ def __init__(self, market, id_: int, async_place_orders: bool, client): self._pending_cancel = [] # list of (, None) self._pending_update = [] # list of (, None) self._pending_replace = [] # list of (, market_version) + self.all_or_nothing = all_or_nothing def place_order( self, @@ -52,6 +72,7 @@ def place_order( if ( execute and not force + and not self.all_or_nothing and self._validate_controls(order, OrderPackageType.PLACE) is False ): return False @@ -94,6 +115,7 @@ def cancel_order( ) if ( not force + and not self.all_or_nothing and self._validate_controls(order, OrderPackageType.CANCEL) is False ): return False @@ -114,6 +136,7 @@ def update_order( ) if ( not force + and not self.all_or_nothing and self._validate_controls(order, OrderPackageType.UPDATE) is False ): return False @@ -134,6 +157,7 @@ def replace_order( ) if ( not force + and not self.all_or_nothing and self._validate_controls(order, OrderPackageType.REPLACE) is False ): return False @@ -185,15 +209,18 @@ def execute(self) -> int: def _validate_controls(self, order, package_type: OrderPackageType) -> bool: # return False on violation try: - for control in self.market.flumine.trading_controls: - control(order, package_type) - for control in self._client.trading_controls: - control(order, package_type) + self._do_validate_controls(order, package_type) except ControlError: return False else: return True + def _do_validate_controls(self, order, package_type): + for control in self.market.flumine.trading_controls: + control(order, package_type) + for control in self._client.trading_controls: + control(order, package_type) + def _create_order_package( self, orders: list, package_type: OrderPackageType, async_: bool = False ) -> list: @@ -224,5 +251,15 @@ def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): + if self.all_or_nothing: + for order in self._pending_place: + self._do_validate_controls(order, OrderPackageType.PLACE) + for order in self._pending_cancel: + self._do_validate_controls(order, OrderPackageType.CANCEL) + for order in self._pending_update: + self._do_validate_controls(order, OrderPackageType.UPDATE) + for order in self._pending_replace: + self._do_validate_controls(order, OrderPackageType.REPLACE) + if self._pending_orders: self.execute() diff --git a/flumine/markets/market.py b/flumine/markets/market.py index 6d5951a0..6d837ec7 100644 --- a/flumine/markets/market.py +++ b/flumine/markets/market.py @@ -62,7 +62,12 @@ def close_market(self) -> None: extra=self.info, ) - def transaction(self, async_place_orders: bool = None, client=None) -> Transaction: + def transaction( + self, + async_place_orders: bool = None, + client=None, + all_or_nothing: Optional[bool] = False, + ) -> Transaction: if async_place_orders is None: async_place_orders = config.async_place_orders if client is None: @@ -73,6 +78,7 @@ def transaction(self, async_place_orders: bool = None, client=None) -> Transacti id_=self._transaction_id, async_place_orders=async_place_orders, client=client, + all_or_nothing=all_or_nothing, ) # order diff --git a/tests/test_markets.py b/tests/test_markets.py index 3d2dc6ae..056a0c66 100644 --- a/tests/test_markets.py +++ b/tests/test_markets.py @@ -160,6 +160,7 @@ def test_transaction(self, mock_transaction): id_=self.market._transaction_id, async_place_orders=False, client=self.market.flumine.clients.get_default(), + all_or_nothing=False, ) self.assertEqual(transaction, mock_transaction()) @@ -172,6 +173,7 @@ def test_transaction_async(self, mock_transaction): id_=self.market._transaction_id, async_place_orders=True, client=self.market.flumine.clients.get_default(), + all_or_nothing=False, ) self.assertEqual(transaction, mock_transaction()) diff --git a/tests/test_transaction.py b/tests/test_transaction.py index 826a7c06..933d7b42 100644 --- a/tests/test_transaction.py +++ b/tests/test_transaction.py @@ -61,6 +61,72 @@ def test_place_order( ) mock_order.update_client.assert_called_with(self.transaction._client) + @mock.patch("flumine.execution.transaction.get_market_notes") + @mock.patch( + "flumine.execution.transaction.Transaction._do_validate_controls", + return_value=True, + ) + def test_place_order_with_all_or_nothing_success( + self, + mock__do_validate_controls, + mock_get_market_notes, + ): + self.transaction.all_or_nothing = True + self.transaction.market.blotter = mock.MagicMock() + self.transaction.market.blotter.has_trade.return_value = False + self.transaction.execute = mock.Mock() + + mock_order1 = mock.Mock(id="123", lookup=(1, 2, 3)) + mock_order1.trade.market_notes = None + + mock_order2 = mock.Mock(id="123", lookup=(1, 2, 3)) + mock_order2.trade.market_notes = None + + with self.transaction: + self.assertTrue(self.transaction.place_order(mock_order1)) + self.assertTrue(self.transaction.place_order(mock_order2)) + self.assertEqual(0, self.transaction.execute.call_count) + + self.assertEqual(1, self.transaction.execute.call_count) + + @mock.patch("flumine.execution.transaction.get_market_notes") + def test_place_order_with_all_or_nothing_fail( + self, + mock_get_market_notes, + ): + c = 0 + + def side_effect(*args, **kwargs): + nonlocal c + if c == 0: + c = c + 1 + return + + raise ControlError("Intentional") + + self.transaction._do_validate_controls = mock.Mock(side_effect=side_effect) + + self.transaction.all_or_nothing = True + self.transaction.market.blotter = mock.MagicMock() + self.transaction.market.blotter.has_trade.return_value = False + self.transaction.execute = mock.Mock() + + mock_order1 = mock.Mock(id="123", lookup=(1, 2, 3)) + mock_order1.trade.market_notes = None + + mock_order2 = mock.Mock(id="123", lookup=(1, 2, 3)) + mock_order2.trade.market_notes = None + + """ + The second mock order will raise a ControlError + """ + with self.assertRaises(ControlError): + with self.transaction: + self.assertTrue(self.transaction.place_order(mock_order1)) + self.assertTrue(self.transaction.place_order(mock_order2)) + + self.assertEqual(0, self.transaction.execute.call_count) + @mock.patch("flumine.execution.transaction.get_market_notes") @mock.patch( "flumine.execution.transaction.Transaction._validate_controls", From 4dbaf318a66135116e5260918b0f4452a6b6fdbb Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 6 Jun 2022 22:00:00 +0100 Subject: [PATCH 2/5] Correcting doc string renaming all_or_nothing to atomic clearing the transaction if there's a failure. --- flumine/execution/transaction.py | 65 +++++++++---- flumine/markets/market.py | 2 +- tests/test_markets.py | 4 +- tests/test_transaction.py | 157 ++++++++++++++++++++++++++++++- 4 files changed, 204 insertions(+), 24 deletions(-) diff --git a/flumine/execution/transaction.py b/flumine/execution/transaction.py index 2b5793c7..b9c07e0d 100644 --- a/flumine/execution/transaction.py +++ b/flumine/execution/transaction.py @@ -30,11 +30,11 @@ class Transaction: t.cancel_order(order) t.place_order(order) # both executed on transaction __exit__ - When all_or_nothing==True, orders within a transaction will + When atomic==True, orders within a transaction will only be placed if the all pass validation. For example, supposed we have: - with market.transaction() as t: + with market.transaction(atomic=True) as t: t.place_order(order1) # validation passes t.place_order(order2) # validation does not pass and raises a ControlError @@ -48,7 +48,7 @@ def __init__( id_: int, async_place_orders: bool, client, - all_or_nothing: Optional[bool] = False, + atomic: Optional[bool] = False, ): self.market = market self._client = client @@ -59,7 +59,7 @@ def __init__( self._pending_cancel = [] # list of (, None) self._pending_update = [] # list of (, None) self._pending_replace = [] # list of (, market_version) - self.all_or_nothing = all_or_nothing + self.atomic = atomic def place_order( self, @@ -72,7 +72,7 @@ def place_order( if ( execute and not force - and not self.all_or_nothing + and not self.atomic and self._validate_controls(order, OrderPackageType.PLACE) is False ): return False @@ -115,7 +115,7 @@ def cancel_order( ) if ( not force - and not self.all_or_nothing + and not self.atomic and self._validate_controls(order, OrderPackageType.CANCEL) is False ): return False @@ -136,7 +136,7 @@ def update_order( ) if ( not force - and not self.all_or_nothing + and not self.atomic and self._validate_controls(order, OrderPackageType.UPDATE) is False ): return False @@ -157,7 +157,7 @@ def replace_order( ) if ( not force - and not self.all_or_nothing + and not self.atomic and self._validate_controls(order, OrderPackageType.REPLACE) is False ): return False @@ -251,15 +251,46 @@ def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): - if self.all_or_nothing: - for order in self._pending_place: - self._do_validate_controls(order, OrderPackageType.PLACE) - for order in self._pending_cancel: - self._do_validate_controls(order, OrderPackageType.CANCEL) - for order in self._pending_update: - self._do_validate_controls(order, OrderPackageType.UPDATE) - for order in self._pending_replace: - self._do_validate_controls(order, OrderPackageType.REPLACE) if self._pending_orders: + + if self.atomic: + try: + for order in self._pending_place: + self._do_validate_controls(order, OrderPackageType.PLACE) + for order in self._pending_cancel: + self._do_validate_controls(order, OrderPackageType.CANCEL) + for order in self._pending_update: + self._do_validate_controls(order, OrderPackageType.UPDATE) + for order in self._pending_replace: + self._do_validate_controls(order, OrderPackageType.REPLACE) + except ControlError as e: + if logger.isEnabledFor(logging.INFO): + extra = { + "market_id": self.market.market_id, + "transaction_id": self._id, + "client_username": self._client.username, + } + if self._pending_place: + extra["pending_place"] = self._pending_place + if self._pending_update: + extra["pending_update"] = self._pending_update + if self._pending_cancel: + extra["pending_cancel"] = self._pending_cancel + if self._pending_replace: + extra["pending_replace"] = self._pending_replace + logger.info( + "Failed to execute transaction. Validation failed: %s" + % str(e), + extra=extra, + ) + self._clear() + raise self.execute() + + def _clear(self): + self._pending_place.clear() + self._pending_update.clear() + self._pending_replace.clear() + self._pending_cancel.clear() + self._pending_orders = False diff --git a/flumine/markets/market.py b/flumine/markets/market.py index 6d837ec7..8aea287a 100644 --- a/flumine/markets/market.py +++ b/flumine/markets/market.py @@ -78,7 +78,7 @@ def transaction( id_=self._transaction_id, async_place_orders=async_place_orders, client=client, - all_or_nothing=all_or_nothing, + atomic=all_or_nothing, ) # order diff --git a/tests/test_markets.py b/tests/test_markets.py index 056a0c66..8da0aa0b 100644 --- a/tests/test_markets.py +++ b/tests/test_markets.py @@ -160,7 +160,7 @@ def test_transaction(self, mock_transaction): id_=self.market._transaction_id, async_place_orders=False, client=self.market.flumine.clients.get_default(), - all_or_nothing=False, + atomic=False, ) self.assertEqual(transaction, mock_transaction()) @@ -173,7 +173,7 @@ def test_transaction_async(self, mock_transaction): id_=self.market._transaction_id, async_place_orders=True, client=self.market.flumine.clients.get_default(), - all_or_nothing=False, + atomic=False, ) self.assertEqual(transaction, mock_transaction()) diff --git a/tests/test_transaction.py b/tests/test_transaction.py index 933d7b42..c2ae07a8 100644 --- a/tests/test_transaction.py +++ b/tests/test_transaction.py @@ -66,12 +66,12 @@ def test_place_order( "flumine.execution.transaction.Transaction._do_validate_controls", return_value=True, ) - def test_place_order_with_all_or_nothing_success( + def test_place_order_with_atomic_success( self, mock__do_validate_controls, mock_get_market_notes, ): - self.transaction.all_or_nothing = True + self.transaction.atomic = True self.transaction.market.blotter = mock.MagicMock() self.transaction.market.blotter.has_trade.return_value = False self.transaction.execute = mock.Mock() @@ -90,7 +90,7 @@ def test_place_order_with_all_or_nothing_success( self.assertEqual(1, self.transaction.execute.call_count) @mock.patch("flumine.execution.transaction.get_market_notes") - def test_place_order_with_all_or_nothing_fail( + def test_place_order_with_atomic_fail( self, mock_get_market_notes, ): @@ -106,7 +106,7 @@ def side_effect(*args, **kwargs): self.transaction._do_validate_controls = mock.Mock(side_effect=side_effect) - self.transaction.all_or_nothing = True + self.transaction.atomic = True self.transaction.market.blotter = mock.MagicMock() self.transaction.market.blotter.has_trade.return_value = False self.transaction.execute = mock.Mock() @@ -126,6 +126,7 @@ def side_effect(*args, **kwargs): self.assertTrue(self.transaction.place_order(mock_order2)) self.assertEqual(0, self.transaction.execute.call_count) + self.assert_cleared() @mock.patch("flumine.execution.transaction.get_market_notes") @mock.patch( @@ -212,6 +213,49 @@ def test_cancel_order(self, mock__validate_controls): self.transaction._pending_cancel = [(mock_order, None)] self.assertTrue(self.transaction._pending_orders) + @mock.patch("flumine.execution.transaction.get_market_notes") + def test_cancel_order_with_atomic_fail( + self, + mock_get_market_notes, + ): + c = 0 + + def side_effect(*args, **kwargs): + nonlocal c + if c == 0: + c = c + 1 + return + + raise ControlError("Intentional") + + self.transaction._do_validate_controls = mock.Mock(side_effect=side_effect) + + self.transaction.atomic = True + self.transaction.market.blotter = mock.MagicMock() + self.transaction.market.blotter.has_trade.return_value = False + self.transaction.execute = mock.Mock() + + mock_order1 = mock.Mock( + id="123", lookup=(1, 2, 3), client=self.transaction._client + ) + mock_order1.trade.market_notes = None + + mock_order2 = mock.Mock( + id="123", lookup=(1, 2, 3), client=self.transaction._client + ) + mock_order2.trade.market_notes = None + + """ + The second mock order will raise a ControlError + """ + with self.assertRaises(ControlError): + with self.transaction: + self.assertTrue(self.transaction.cancel_order(mock_order1)) + self.assertTrue(self.transaction.cancel_order(mock_order2)) + + self.assertEqual(0, self.transaction.execute.call_count) + self.assert_cleared() + def test_cancel_order_incorrect_client(self): mock_order = mock.Mock(client=123) with self.assertRaises(OrderError): @@ -252,6 +296,57 @@ def test_update_order(self, mock__validate_controls): self.transaction._pending_update = [(mock_order, None)] self.assertTrue(self.transaction._pending_orders) + @mock.patch("flumine.execution.transaction.get_market_notes") + def test_update_order_with_atomic_fail( + self, + mock_get_market_notes, + ): + c = 0 + + def side_effect(*args, **kwargs): + nonlocal c + if c == 0: + c = c + 1 + return + + raise ControlError("Intentional") + + self.transaction._do_validate_controls = mock.Mock(side_effect=side_effect) + + self.transaction.atomic = True + self.transaction.market.blotter = mock.MagicMock() + self.transaction.market.blotter.has_trade.return_value = False + self.transaction.execute = mock.Mock() + + mock_order1 = mock.Mock( + id="123", lookup=(1, 2, 3), client=self.transaction._client + ) + mock_order1.trade.market_notes = None + + mock_order2 = mock.Mock( + id="123", lookup=(1, 2, 3), client=self.transaction._client + ) + mock_order2.trade.market_notes = None + + """ + The second mock order will raise a ControlError + """ + with self.assertRaises(ControlError): + with self.transaction: + self.assertTrue( + self.transaction.update_order( + mock_order1, new_persistence_type="KEEP" + ) + ) + self.assertTrue( + self.transaction.update_order( + mock_order2, new_persistence_type="KEEP" + ) + ) + + self.assertEqual(0, self.transaction.execute.call_count) + self.assert_cleared() + def test_update_order_incorrect_client(self): mock_order = mock.Mock(client=123) with self.assertRaises(OrderError): @@ -294,6 +389,53 @@ def test_replace_order(self, mock__validate_controls): self.transaction._pending_replace = [(mock_order, None)] self.assertTrue(self.transaction._pending_orders) + @mock.patch("flumine.execution.transaction.get_market_notes") + def test_replace_order_with_atomic_fail( + self, + mock_get_market_notes, + ): + c = 0 + + def side_effect(*args, **kwargs): + nonlocal c + if c == 0: + c = c + 1 + return + + raise ControlError("Intentional") + + self.transaction._do_validate_controls = mock.Mock(side_effect=side_effect) + + self.transaction.atomic = True + self.transaction.market.blotter = mock.MagicMock() + self.transaction.market.blotter.has_trade.return_value = False + self.transaction.execute = mock.Mock() + + mock_order1 = mock.Mock( + id="123", lookup=(1, 2, 3), client=self.transaction._client + ) + mock_order1.trade.market_notes = None + + mock_order2 = mock.Mock( + id="123", lookup=(1, 2, 3), client=self.transaction._client + ) + mock_order2.trade.market_notes = None + + """ + The second mock order will raise a ControlError + """ + with self.assertRaises(ControlError): + with self.transaction: + self.assertTrue( + self.transaction.replace_order(mock_order1, new_price=1.01) + ) + self.assertTrue( + self.transaction.replace_order(mock_order2, new_price=1.01) + ) + + self.assertEqual(0, self.transaction.execute.call_count) + self.assert_cleared() + def test_replace_order_incorrect_client(self): mock_order = mock.Mock(client=123) with self.assertRaises(OrderError): @@ -460,3 +602,10 @@ def test_enter_exit(self, mock_execute): self.assertEqual(self.transaction, t) t._pending_orders = True mock_execute.assert_called() + + def assert_cleared(self): + self.assertEqual(0, len(self.transaction._pending_place)) + self.assertEqual(0, len(self.transaction._pending_cancel)) + self.assertEqual(0, len(self.transaction._pending_update)) + self.assertEqual(0, len(self.transaction._pending_replace)) + self.assertFalse(self.transaction._pending_orders) From 4dd5ce60bdf7dcd12dde52c0c96d3e955dbc83b6 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 9 Jun 2022 20:48:59 +0100 Subject: [PATCH 3/5] Moving the runner context update from place_order(...) to execute(...). --- flumine/execution/transaction.py | 5 +++-- tests/test_transaction.py | 11 ++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/flumine/execution/transaction.py b/flumine/execution/transaction.py index b9c07e0d..48a32e4f 100644 --- a/flumine/execution/transaction.py +++ b/flumine/execution/transaction.py @@ -98,8 +98,6 @@ def place_order( if new_trade: self.market.flumine.log_control(events.TradeEvent(order.trade)) if execute: # handles replaceOrder - runner_context = order.trade.strategy.get_runner_context(*order.lookup) - runner_context.place(order.trade.id) self._pending_place.append((order, market_version)) self._pending_orders = True return True @@ -170,6 +168,9 @@ def replace_order( def execute(self) -> int: packages = [] if self._pending_place: + for order, market_version in self._pending_place: + runner_context = order.trade.strategy.get_runner_context(*order.lookup) + runner_context.place(order.trade.id) packages += self._create_order_package( self._pending_place, OrderPackageType.PLACE, diff --git a/tests/test_transaction.py b/tests/test_transaction.py index c2ae07a8..51c7dd97 100644 --- a/tests/test_transaction.py +++ b/tests/test_transaction.py @@ -53,9 +53,6 @@ def test_place_order( mock__validate_controls.assert_called_with(mock_order, OrderPackageType.PLACE) self.transaction._pending_place = [(mock_order, None)] self.assertTrue(self.transaction._pending_orders) - mock_order.trade.strategy.get_runner_context.assert_called_with( - *mock_order.lookup - ) self.transaction.market.blotter.has_trade.assert_called_with( mock_order.trade.id ) @@ -472,7 +469,7 @@ def test_execute(self, mock__create_order_package): mock_package = mock.Mock() mock__create_order_package.return_value = [mock_package] self.assertEqual(self.transaction.execute(), 0) - mock_order = mock.Mock() + mock_order = mock.Mock(lookup=(1, 2, 3)) self.transaction._pending_place = [(mock_order, 1234)] self.transaction._pending_cancel = [(mock_order, None)] self.transaction._pending_update = [(mock_order, None)] @@ -496,6 +493,10 @@ def test_execute(self, mock__create_order_package): ) self.assertFalse(self.transaction._pending_orders) + mock_order.trade.strategy.get_runner_context.assert_called_with( + *mock_order.lookup + ) + @mock.patch("flumine.execution.transaction.Transaction._create_order_package") def test_execute_async(self, mock__create_order_package): self.transaction._pending_orders = True @@ -503,7 +504,7 @@ def test_execute_async(self, mock__create_order_package): mock_package = mock.Mock() mock__create_order_package.return_value = [mock_package] self.assertEqual(self.transaction.execute(), 0) - mock_order = mock.Mock() + mock_order = mock.Mock(lookup=(1, 2, 3)) self.transaction._pending_place = [(mock_order, 1234)] self.transaction._pending_cancel = [(mock_order, None)] self.transaction._pending_update = [(mock_order, None)] From 014fa9056e779636bdef0cdab275061cc99ccc76 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 10 Jun 2022 20:30:53 +0100 Subject: [PATCH 4/5] Revert "Moving the runner context update from place_order(...) to execute(...)." This reverts commit 4dd5ce60bdf7dcd12dde52c0c96d3e955dbc83b6. --- flumine/execution/transaction.py | 5 ++--- tests/test_transaction.py | 11 +++++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/flumine/execution/transaction.py b/flumine/execution/transaction.py index 48a32e4f..b9c07e0d 100644 --- a/flumine/execution/transaction.py +++ b/flumine/execution/transaction.py @@ -98,6 +98,8 @@ def place_order( if new_trade: self.market.flumine.log_control(events.TradeEvent(order.trade)) if execute: # handles replaceOrder + runner_context = order.trade.strategy.get_runner_context(*order.lookup) + runner_context.place(order.trade.id) self._pending_place.append((order, market_version)) self._pending_orders = True return True @@ -168,9 +170,6 @@ def replace_order( def execute(self) -> int: packages = [] if self._pending_place: - for order, market_version in self._pending_place: - runner_context = order.trade.strategy.get_runner_context(*order.lookup) - runner_context.place(order.trade.id) packages += self._create_order_package( self._pending_place, OrderPackageType.PLACE, diff --git a/tests/test_transaction.py b/tests/test_transaction.py index 51c7dd97..c2ae07a8 100644 --- a/tests/test_transaction.py +++ b/tests/test_transaction.py @@ -53,6 +53,9 @@ def test_place_order( mock__validate_controls.assert_called_with(mock_order, OrderPackageType.PLACE) self.transaction._pending_place = [(mock_order, None)] self.assertTrue(self.transaction._pending_orders) + mock_order.trade.strategy.get_runner_context.assert_called_with( + *mock_order.lookup + ) self.transaction.market.blotter.has_trade.assert_called_with( mock_order.trade.id ) @@ -469,7 +472,7 @@ def test_execute(self, mock__create_order_package): mock_package = mock.Mock() mock__create_order_package.return_value = [mock_package] self.assertEqual(self.transaction.execute(), 0) - mock_order = mock.Mock(lookup=(1, 2, 3)) + mock_order = mock.Mock() self.transaction._pending_place = [(mock_order, 1234)] self.transaction._pending_cancel = [(mock_order, None)] self.transaction._pending_update = [(mock_order, None)] @@ -493,10 +496,6 @@ def test_execute(self, mock__create_order_package): ) self.assertFalse(self.transaction._pending_orders) - mock_order.trade.strategy.get_runner_context.assert_called_with( - *mock_order.lookup - ) - @mock.patch("flumine.execution.transaction.Transaction._create_order_package") def test_execute_async(self, mock__create_order_package): self.transaction._pending_orders = True @@ -504,7 +503,7 @@ def test_execute_async(self, mock__create_order_package): mock_package = mock.Mock() mock__create_order_package.return_value = [mock_package] self.assertEqual(self.transaction.execute(), 0) - mock_order = mock.Mock(lookup=(1, 2, 3)) + mock_order = mock.Mock() self.transaction._pending_place = [(mock_order, 1234)] self.transaction._pending_cancel = [(mock_order, None)] self.transaction._pending_update = [(mock_order, None)] From 1711f52c5518c1381246f61a7ce3b5171bb3f768 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 10 Jun 2022 20:39:57 +0100 Subject: [PATCH 5/5] Removing failed trades from RunnerContext.live_trades --- flumine/execution/transaction.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/flumine/execution/transaction.py b/flumine/execution/transaction.py index b9c07e0d..e41d02a3 100644 --- a/flumine/execution/transaction.py +++ b/flumine/execution/transaction.py @@ -289,8 +289,14 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.execute() def _clear(self): + for order, _ in self._pending_place: + self._unwind(order) self._pending_place.clear() self._pending_update.clear() self._pending_replace.clear() self._pending_cancel.clear() self._pending_orders = False + + def _unwind(self, order): + runner_context = order.trade.strategy.get_runner_context(*order.lookup) + runner_context.live_trades.remove(order.trade.id)