Skip to content

Commit 44fd90a

Browse files
committed
refactor: remove BacktestOrderExecutor, route SL/TP through blotter, fix cancel_order - Delete dead BacktestOrderExecutor (was never called) - Route TradeOrderEvaluator SL/TP orders through blotter via _create_order() - Pass blotter+context to TradeOrderEvaluator in App and BacktestService - Fix cancel_order tests to match delegate-to-order_service pattern - Fix OrderBacktestService.cancel_order pre-existing bug - Clean up commented BacktestOrderExecutor refs in test_eventloop - 1423 passed, 42 skipped
1 parent 6994680 commit 44fd90a

11 files changed

Lines changed: 60 additions & 91 deletions

File tree

investing_algorithm_framework/app/app.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
LAST_SNAPSHOT_DATETIME, BACKTESTING_FLAG, DATA_DIRECTORY
2525
from investing_algorithm_framework.infrastructure import setup_sqlalchemy, \
2626
create_all_tables, CCXTOrderExecutor, CCXTPortfolioProvider, \
27-
BacktestOrderExecutor, CCXTOHLCVDataProvider, clear_db, \
27+
CCXTOHLCVDataProvider, clear_db, \
2828
PandasOHLCVDataProvider
2929
from investing_algorithm_framework.services import OrderBacktestService, \
3030
BacktestPortfolioService, DefaultTradeOrderEvaluator
@@ -659,7 +659,9 @@ def run(self, number_of_iterations: int = None):
659659
.trade_stop_loss_service(),
660660
trade_take_profit_service=self.container
661661
.trade_take_profit_service(),
662-
configuration_service=self.container.configuration_service()
662+
configuration_service=self.container.configuration_service(),
663+
blotter=self._blotter,
664+
context=self.context
663665
)
664666
event_loop_service = EventLoopService(
665667
configuration_service=self.container.configuration_service(),
@@ -1507,6 +1509,7 @@ def run_backtests(
15071509
checkpoint_batch_size=checkpoint_batch_size,
15081510
fill_missing_data=fill_missing_data,
15091511
iterative_summary_update=iterative_summary_update,
1512+
blotter=self._blotter,
15101513
)
15111514

15121515
# Cleanup resources
@@ -1745,6 +1748,7 @@ def run_backtest(
17451748
trading_symbol=trading_symbol,
17461749
fill_missing_data=fill_missing_data,
17471750
skip_data_sources_initialization=True,
1751+
blotter=self._blotter,
17481752
)
17491753

17501754
# Store run history
@@ -2325,10 +2329,10 @@ def initialize_order_executors(self):
23252329
"""
23262330
Function to initialize the order executors. This function will
23272331
first check if the app is running in backtest mode or not. If it is
2328-
running in backtest mode, all order executors will be removed and
2329-
a single BacktestOrderExecutor will be added to the order executors.
2330-
It will also set the SimulationBlotter as the default blotter if
2331-
no custom blotter has been configured.
2332+
running in backtest mode, all order executors will be removed
2333+
(OrderBacktestService handles execution directly) and the
2334+
SimulationBlotter will be set as the default blotter if no custom
2335+
blotter has been configured.
23322336
23332337
If it is not running in backtest mode, it will add the default
23342338
CCXTOrderExecutor with a priority 3.
@@ -2341,13 +2345,9 @@ def initialize_order_executors(self):
23412345
environment = self.config[ENVIRONMENT]
23422346

23432347
if Environment.BACKTEST.equals(environment):
2344-
# If the app is running in backtest mode,
2345-
# remove all order executors
2346-
# and add a single BacktestOrderExecutor
2348+
# In backtest mode, OrderBacktestService handles execution
2349+
# directly — no order executor needed
23472350
order_executor_lookup.reset()
2348-
order_executor_lookup.add_order_executor(
2349-
BacktestOrderExecutor(priority=1)
2350-
)
23512351

23522352
# Auto-set SimulationBlotter for backtesting if no
23532353
# custom blotter has been configured
@@ -2549,9 +2549,7 @@ def initialize_portfolio_providers(self):
25492549
environment = self.config[ENVIRONMENT]
25502550

25512551
if Environment.BACKTEST.equals(environment):
2552-
# If the app is running in backtest mode,
2553-
# remove all order executors
2554-
# and add a single BacktestOrderExecutor
2552+
# In backtest mode, remove all portfolio providers
25552553
portfolio_provider_lookup.reset()
25562554
else:
25572555
portfolio_provider_lookup.add_portfolio_provider(

investing_algorithm_framework/app/context.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1143,7 +1143,7 @@ def close_position(
11431143
"status": OrderStatus.OPEN.value
11441144
}
11451145
):
1146-
self.order_service.cancel_order(order)
1146+
self._blotter.cancel_order(order.id, self)
11471147

11481148
target_symbol = position.get_symbol()
11491149
symbol = f"{target_symbol.upper()}/{portfolio.trading_symbol.upper()}"
@@ -1764,16 +1764,11 @@ def close_trade(self, trade, precision=None) -> None:
17641764
date=self.config[INDEX_DATETIME]
17651765
)
17661766
logger.info(f"Closing trade {trade.id} {trade.symbol}")
1767-
self.order_service.create(
1768-
{
1769-
"portfolio_id": portfolio.id,
1770-
"trading_symbol": trade.trading_symbol,
1771-
"target_symbol": trade.target_symbol,
1772-
"amount": amount,
1773-
"order_side": OrderSide.SELL.value,
1774-
"order_type": OrderType.LIMIT.value,
1775-
"price": ticker["bid"],
1776-
}
1767+
self.create_limit_order(
1768+
target_symbol=trade.target_symbol,
1769+
amount=amount,
1770+
order_side=OrderSide.SELL,
1771+
price=ticker["bid"],
17771772
)
17781773

17791774
def get_number_of_positions(self):

investing_algorithm_framework/domain/blotter.py

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,11 @@ def place_order(self, order_data, context):
386386

387387
def cancel_order(self, order_id, context):
388388
"""
389-
Cancel a specific order.
389+
Cancel a specific order by delegating to the OrderService.
390+
391+
In live trading, this communicates with the exchange to
392+
cancel the order. In backtesting, it updates the order
393+
status directly.
390394
391395
Args:
392396
order_id: The ID of the order to cancel.
@@ -395,23 +399,17 @@ def cancel_order(self, order_id, context):
395399
Returns:
396400
Order: The cancelled order.
397401
"""
398-
from investing_algorithm_framework.domain.models.order.order_status \
399-
import OrderStatus
402+
from investing_algorithm_framework.domain.exceptions \
403+
import OperationalException
400404

401405
order = context.order_service.get(order_id)
402406

403407
if order is None:
404-
from investing_algorithm_framework.domain.exceptions \
405-
import OperationalException
406408
raise OperationalException(
407409
f"Order with id {order_id} not found"
408410
)
409411

410-
context.order_service.update(
411-
order_id,
412-
{"status": OrderStatus.CANCELED.value}
413-
)
414-
412+
context.order_service.cancel_order(order)
415413
return context.order_service.get(order_id)
416414

417415

@@ -514,7 +512,10 @@ def place_order(self, order_data, context):
514512

515513
def cancel_order(self, order_id, context):
516514
"""
517-
Cancel a specific order.
515+
Cancel a specific order by delegating to the OrderService.
516+
517+
In backtesting, applies any configured models before
518+
cancellation.
518519
519520
Args:
520521
order_id: The ID of the order to cancel.
@@ -523,21 +524,15 @@ def cancel_order(self, order_id, context):
523524
Returns:
524525
Order: The cancelled order.
525526
"""
526-
from investing_algorithm_framework.domain.models.order.order_status \
527-
import OrderStatus
527+
from investing_algorithm_framework.domain.exceptions \
528+
import OperationalException
528529

529530
order = context.order_service.get(order_id)
530531

531532
if order is None:
532-
from investing_algorithm_framework.domain.exceptions \
533-
import OperationalException
534533
raise OperationalException(
535534
f"Order with id {order_id} not found"
536535
)
537536

538-
context.order_service.update(
539-
order_id,
540-
{"status": OrderStatus.CANCELED.value}
541-
)
542-
537+
context.order_service.cancel_order(order)
543538
return context.order_service.get(order_id)

investing_algorithm_framework/infrastructure/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
OHLCVDataProviderBase, \
1919
YahooOHLCVDataProvider, AlphaVantageOHLCVDataProvider, \
2020
PolygonOHLCVDataProvider
21-
from .order_executors import CCXTOrderExecutor, BacktestOrderExecutor
21+
from .order_executors import CCXTOrderExecutor
2222
from .portfolio_providers import CCXTPortfolioProvider
2323

2424
__all__ = [
@@ -53,7 +53,6 @@
5353
"AWSS3StorageStateHandler",
5454
"CCXTOHLCVDataProvider",
5555
"CCXTTickerDataProvider",
56-
"BacktestOrderExecutor",
5756
"PandasOHLCVDataProvider",
5857
"OHLCVDataProviderBase",
5958
"CSVURLDataProvider",

investing_algorithm_framework/infrastructure/order_executors/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from .ccxt_order_executor import CCXTOrderExecutor
2-
from .backtest_oder_executor import BacktestOrderExecutor
32

43

54
def get_default_order_executors():
@@ -16,6 +15,5 @@ def get_default_order_executors():
1615

1716
__all__ = [
1817
'CCXTOrderExecutor',
19-
'BacktestOrderExecutor',
2018
'get_default_order_executors',
2119
]

investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py

Lines changed: 0 additions & 28 deletions
This file was deleted.

investing_algorithm_framework/infrastructure/services/backtesting/backtest_service.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1971,6 +1971,7 @@ def run_backtests(
19711971
checkpoint_batch_size: int = 25,
19721972
fill_missing_data: bool = True,
19731973
iterative_summary_update: bool = False,
1974+
blotter=None,
19741975
) -> List[Backtest]:
19751976
"""
19761977
Run event-driven backtests for multiple algorithms with optional
@@ -2253,6 +2254,8 @@ def run_backtests(
22532254
),
22542255
trading_costs=all_trading_costs,
22552256
portfolio_configuration=pc,
2257+
blotter=blotter,
2258+
context=context,
22562259
)
22572260
)
22582261

@@ -2586,6 +2589,7 @@ def run_backtest(
25862589
market: str = None,
25872590
trading_symbol: str = None,
25882591
fill_missing_data: bool = True,
2592+
blotter=None,
25892593
) -> tuple:
25902594
"""
25912595
Run an event-driven backtest for a single algorithm.
@@ -2639,6 +2643,7 @@ def run_backtest(
26392643
backtest_storage_directory=backtest_storage_directory,
26402644
use_checkpoints=use_checkpoints,
26412645
fill_missing_data=fill_missing_data,
2646+
blotter=blotter,
26422647
)
26432648

26442649
# Extract the single backtest result

investing_algorithm_framework/services/order_service/order_backtest_service.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ def check_pending_orders(self, market_data):
106106
)
107107

108108
def cancel_order(self, order):
109-
self.check_pending_orders()
110109
order = self.order_repository.get(order.id)
111110

112111
if order is not None:

investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,17 @@ def __init__(
1212
trade_stop_loss_service,
1313
trade_take_profit_service,
1414
order_service,
15-
configuration_service=None
15+
configuration_service=None,
16+
blotter=None,
17+
context=None
1618
):
1719
self.trade_service = trade_service
1820
self.trade_stop_loss_service = trade_stop_loss_service
1921
self.trade_take_profit_service = trade_take_profit_service
2022
self.order_service = order_service
2123
self.configuration_service = configuration_service
24+
self._blotter = blotter
25+
self._context = context
2226

2327
@abstractmethod
2428
def evaluate(
@@ -46,14 +50,23 @@ def evaluate(
4650
"""
4751
pass
4852

53+
def _create_order(self, order_data):
54+
"""
55+
Create an order through the blotter if available,
56+
otherwise fall back to the order service directly.
57+
"""
58+
if self._blotter is not None and self._context is not None:
59+
return self._blotter.place_order(order_data, self._context)
60+
return self.order_service.create(order_data)
61+
4962
def _check_take_profits(self):
5063
current_date = self.configuration_service.config[INDEX_DATETIME]
5164
take_profits_orders_data = self.trade_service \
5265
.get_triggered_take_profit_orders()
5366

5467
for take_profit_order in take_profits_orders_data:
5568
take_profits = take_profit_order["take_profits"]
56-
self.order_service.create(take_profit_order)
69+
self._create_order(take_profit_order)
5770
self.trade_take_profit_service.mark_triggered(
5871
[
5972
take_profit.get("take_profit_id")
@@ -70,7 +83,7 @@ def _check_stop_losses(self):
7083
for stop_loss_order in stop_losses_orders_data:
7184
stop_losses = stop_loss_order["stop_losses"]
7285

73-
self.order_service.create(stop_loss_order)
86+
self._create_order(stop_loss_order)
7487
self.trade_stop_loss_service.mark_triggered(
7588
[
7689
stop_loss.get("stop_loss_id") for stop_loss in

tests/app/test_eventloop.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from investing_algorithm_framework.domain import ENVIRONMENT, Environment, \
1212
BACKTESTING_START_DATE, LAST_SNAPSHOT_DATETIME, \
1313
SNAPSHOT_INTERVAL, SnapshotInterval
14-
from investing_algorithm_framework.infrastructure import BacktestOrderExecutor
1514
from investing_algorithm_framework.services import DataProviderService, \
1615
BacktestTradeOrderEvaluator
1716
from tests.resources import TestBase, OrderExecutorTest
@@ -344,7 +343,6 @@ def tearDown(self) -> None:
344343
# configuration_service.add_value(BACKTESTING_START_DATE,
345344
# backtest_date_range.start_date)
346345
#
347-
# self.app.add_order_executor(BacktestOrderExecutor())
348346
# self.app.add_portfolio_configuration(
349347
# PortfolioConfiguration(
350348
# market="bitvavo",
@@ -462,7 +460,6 @@ def tearDown(self) -> None:
462460
# strategies=[strategy],
463461
# tasks=[]
464462
# )
465-
# self.app.add_order_executor(BacktestOrderExecutor())
466463
# self.app.add_portfolio_configuration(
467464
# PortfolioConfiguration(
468465
# market="bitvavo",
@@ -570,7 +567,6 @@ def tearDown(self) -> None:
570567
# strategies=[strategy],
571568
# tasks=[]
572569
# )
573-
# self.app.add_order_executor(BacktestOrderExecutor())
574570
# self.app.add_portfolio_configuration(
575571
# PortfolioConfiguration(
576572
# market="bitvavo",
@@ -680,7 +676,6 @@ def tearDown(self) -> None:
680676
# strategies=[strategy],
681677
# tasks=[]
682678
# )
683-
# self.app.add_order_executor(BacktestOrderExecutor())
684679
# self.app.add_portfolio_configuration(
685680
# PortfolioConfiguration(
686681
# market="bitvavo",

0 commit comments

Comments
 (0)