investing-algorithm-framework 6.9.1__py3-none-any.whl → 7.19.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of investing-algorithm-framework might be problematic. Click here for more details.
- investing_algorithm_framework/__init__.py +147 -44
- investing_algorithm_framework/app/__init__.py +23 -6
- investing_algorithm_framework/app/algorithm/algorithm.py +5 -41
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +17 -10
- investing_algorithm_framework/app/analysis/__init__.py +15 -0
- investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
- investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
- investing_algorithm_framework/app/analysis/permutation.py +116 -0
- investing_algorithm_framework/app/analysis/ranking.py +297 -0
- investing_algorithm_framework/app/app.py +1322 -707
- investing_algorithm_framework/app/context.py +196 -88
- investing_algorithm_framework/app/eventloop.py +590 -0
- investing_algorithm_framework/app/reporting/__init__.py +16 -5
- investing_algorithm_framework/app/reporting/ascii.py +57 -202
- investing_algorithm_framework/app/reporting/backtest_report.py +284 -170
- investing_algorithm_framework/app/reporting/charts/__init__.py +10 -2
- investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +11 -26
- investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
- investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
- investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +1 -1
- investing_algorithm_framework/app/reporting/generate.py +100 -114
- investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +40 -32
- investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +34 -27
- investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +23 -19
- investing_algorithm_framework/app/reporting/tables/trades_table.py +1 -1
- investing_algorithm_framework/app/reporting/tables/utils.py +1 -0
- investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +10 -16
- investing_algorithm_framework/app/strategy.py +315 -175
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/cli/cli.py +30 -12
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +131 -34
- investing_algorithm_framework/cli/initialize_app.py +20 -1
- investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +18 -6
- investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
- investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -2
- investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +1 -1
- investing_algorithm_framework/create_app.py +3 -5
- investing_algorithm_framework/dependency_container.py +25 -39
- investing_algorithm_framework/domain/__init__.py +45 -38
- investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
- investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
- investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
- investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
- investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
- investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
- investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
- investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
- investing_algorithm_framework/domain/config.py +27 -0
- investing_algorithm_framework/domain/constants.py +6 -34
- investing_algorithm_framework/domain/data_provider.py +200 -56
- investing_algorithm_framework/domain/exceptions.py +34 -1
- investing_algorithm_framework/domain/models/__init__.py +10 -19
- investing_algorithm_framework/domain/models/base_model.py +0 -6
- investing_algorithm_framework/domain/models/data/__init__.py +7 -0
- investing_algorithm_framework/domain/models/data/data_source.py +214 -0
- investing_algorithm_framework/domain/models/{market_data_type.py → data/data_type.py} +7 -7
- investing_algorithm_framework/domain/models/market/market_credential.py +6 -0
- investing_algorithm_framework/domain/models/order/order.py +34 -13
- investing_algorithm_framework/domain/models/order/order_status.py +1 -1
- investing_algorithm_framework/domain/models/order/order_type.py +1 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +14 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +5 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +51 -11
- investing_algorithm_framework/domain/models/position/__init__.py +2 -1
- investing_algorithm_framework/domain/models/position/position.py +9 -0
- investing_algorithm_framework/domain/models/position/position_size.py +41 -0
- investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
- investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
- investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
- investing_algorithm_framework/domain/models/snapshot_interval.py +0 -1
- investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
- investing_algorithm_framework/domain/models/time_frame.py +7 -0
- investing_algorithm_framework/domain/models/time_interval.py +33 -0
- investing_algorithm_framework/domain/models/time_unit.py +63 -1
- investing_algorithm_framework/domain/models/trade/__init__.py +0 -2
- investing_algorithm_framework/domain/models/trade/trade.py +56 -32
- investing_algorithm_framework/domain/models/trade/trade_status.py +8 -2
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +106 -41
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +161 -99
- investing_algorithm_framework/domain/order_executor.py +19 -0
- investing_algorithm_framework/domain/portfolio_provider.py +20 -1
- investing_algorithm_framework/domain/services/__init__.py +0 -13
- investing_algorithm_framework/domain/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +5 -1
- investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
- investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
- investing_algorithm_framework/domain/utils/polars.py +17 -14
- investing_algorithm_framework/download_data.py +40 -10
- investing_algorithm_framework/infrastructure/__init__.py +13 -25
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +7 -4
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +811 -546
- investing_algorithm_framework/infrastructure/data_providers/csv.py +433 -122
- investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
- investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
- investing_algorithm_framework/infrastructure/database/sql_alchemy.py +81 -0
- investing_algorithm_framework/infrastructure/models/__init__.py +0 -13
- investing_algorithm_framework/infrastructure/models/order/order.py +9 -3
- investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +27 -8
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +21 -7
- investing_algorithm_framework/infrastructure/order_executors/__init__.py +2 -0
- investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +16 -2
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +2 -2
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +6 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +6 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +0 -4
- investing_algorithm_framework/services/__init__.py +105 -8
- investing_algorithm_framework/services/backtesting/backtest_service.py +536 -476
- investing_algorithm_framework/services/configuration_service.py +14 -4
- investing_algorithm_framework/services/data_providers/__init__.py +5 -0
- investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/__init__.py +48 -17
- investing_algorithm_framework/{app/reporting → services}/metrics/drawdown.py +10 -10
- investing_algorithm_framework/{app/reporting → services}/metrics/equity_curve.py +2 -2
- investing_algorithm_framework/{app/reporting → services}/metrics/exposure.py +60 -2
- investing_algorithm_framework/services/metrics/generate.py +358 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/profit_factor.py +36 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/recovery.py +2 -2
- investing_algorithm_framework/{app/reporting → services}/metrics/returns.py +146 -147
- investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
- investing_algorithm_framework/{app/reporting/metrics/sharp_ratio.py → services/metrics/sharpe_ratio.py} +6 -10
- investing_algorithm_framework/{app/reporting → services}/metrics/sortino_ratio.py +3 -7
- investing_algorithm_framework/services/metrics/trades.py +500 -0
- investing_algorithm_framework/services/metrics/volatility.py +97 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/win_rate.py +70 -3
- investing_algorithm_framework/services/order_service/order_backtest_service.py +21 -31
- investing_algorithm_framework/services/order_service/order_service.py +9 -71
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +0 -2
- investing_algorithm_framework/services/portfolios/portfolio_service.py +3 -13
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +62 -96
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +0 -3
- investing_algorithm_framework/services/repository_service.py +5 -2
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
- investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
- investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
- investing_algorithm_framework/services/trade_service/__init__.py +7 -1
- investing_algorithm_framework/services/trade_service/trade_service.py +51 -29
- investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
- investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
- investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/RECORD +159 -148
- investing_algorithm_framework/app/reporting/evaluation.py +0 -243
- investing_algorithm_framework/app/reporting/metrics/risk_free_rate.py +0 -8
- investing_algorithm_framework/app/reporting/metrics/volatility.py +0 -69
- investing_algorithm_framework/cli/templates/requirements_azure_function.txt.template +0 -3
- investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -9
- investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -47
- investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
- investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -0
- investing_algorithm_framework/domain/models/backtesting/backtest_results.py +0 -440
- investing_algorithm_framework/domain/models/data_source.py +0 -21
- investing_algorithm_framework/domain/models/date_range.py +0 -64
- investing_algorithm_framework/domain/models/trade/trade_risk_type.py +0 -34
- investing_algorithm_framework/domain/models/trading_data_types.py +0 -48
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
- investing_algorithm_framework/domain/services/market_data_sources.py +0 -543
- investing_algorithm_framework/domain/services/market_service.py +0 -153
- investing_algorithm_framework/domain/services/observable.py +0 -51
- investing_algorithm_framework/domain/services/observer.py +0 -19
- investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -16
- investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -746
- investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -270
- investing_algorithm_framework/infrastructure/models/market_data_sources/pandas.py +0 -312
- investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
- investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -471
- investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
- investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
- investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -322
- investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -10
- investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -269
- investing_algorithm_framework/services/market_data_source_service/data_provider_service.py +0 -350
- investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -377
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -296
- investing_algorithm_framework-6.9.1.dist-info/METADATA +0 -440
- /investing_algorithm_framework/{app/reporting → services}/metrics/alpha.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/beta.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/cagr.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/calmar_ratio.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/mean_daily_return.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/price_efficiency.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/standard_deviation.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/treynor_ratio.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/ulcer.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/value_at_risk.py +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from typing import List, Dict
|
|
2
|
+
|
|
3
|
+
import polars as pl
|
|
4
|
+
|
|
5
|
+
from investing_algorithm_framework.domain import OrderSide, OrderStatus, \
|
|
6
|
+
Trade, Order
|
|
7
|
+
from .trade_order_evaluator import TradeOrderEvaluator
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BacktestTradeOrderEvaluator(TradeOrderEvaluator):
|
|
11
|
+
|
|
12
|
+
def evaluate(
|
|
13
|
+
self,
|
|
14
|
+
open_trades: List[Trade],
|
|
15
|
+
open_orders: List[Order],
|
|
16
|
+
ohlcv_data: Dict[str, pl.DataFrame]
|
|
17
|
+
):
|
|
18
|
+
"""
|
|
19
|
+
Evaluate trades and orders based on OHLCV data.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
open_orders (List[Order]): List of open Order objects.
|
|
23
|
+
open_trades (List[Trade]): List of open Trade objects.
|
|
24
|
+
ohlcv_data (dict[str, pl.DataFrame]): Mapping of
|
|
25
|
+
symbol -> OHLCV Polars DataFrame.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
List[dict]: Updated trades with latest prices and execution status.
|
|
29
|
+
"""
|
|
30
|
+
# First check pending orders
|
|
31
|
+
for open_order in open_orders:
|
|
32
|
+
data = ohlcv_data.get(open_order.symbol)
|
|
33
|
+
self._check_has_executed(open_order, data)
|
|
34
|
+
|
|
35
|
+
if len(open_trades) > 0:
|
|
36
|
+
for open_trade in open_trades:
|
|
37
|
+
data = ohlcv_data[open_trade.symbol]
|
|
38
|
+
|
|
39
|
+
if data is None or data.is_empty():
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
# Get last row of data
|
|
43
|
+
last_row = data.tail(1)
|
|
44
|
+
update_data = {
|
|
45
|
+
"last_reported_price": last_row["Close"][0],
|
|
46
|
+
"last_reported_price_datetime": last_row["Datetime"][0],
|
|
47
|
+
"updated_at": last_row["Datetime"][0]
|
|
48
|
+
}
|
|
49
|
+
open_trade.update(update_data)
|
|
50
|
+
|
|
51
|
+
self.trade_service.save_all(open_trades)
|
|
52
|
+
self._check_take_profits()
|
|
53
|
+
self._check_stop_losses()
|
|
54
|
+
|
|
55
|
+
def _check_has_executed(self, order, ohlcv_df):
|
|
56
|
+
"""
|
|
57
|
+
Check if the order has been executed based on OHLCV data.
|
|
58
|
+
|
|
59
|
+
BUY ORDER filled Rules:
|
|
60
|
+
- Only uses prices after the last update_at of the order.
|
|
61
|
+
- If the lowest low price of the series is below or equal
|
|
62
|
+
to the order price, e.g. if you buy asset at price 100
|
|
63
|
+
and the low price of the series is 99, then the order is filled.
|
|
64
|
+
|
|
65
|
+
SELL ORDER filled Rules:
|
|
66
|
+
- Only uses prices after the last update_at of the order.
|
|
67
|
+
- If the highest high price of the series is above or equal
|
|
68
|
+
to the order price, e.g. if you sell asset at price 100
|
|
69
|
+
and the high price of the series is 101, then the order is filled.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
order (Order): Order.
|
|
73
|
+
ohlcv_df (pl.DataFrame): OHLCV DataFrame for the symbol.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
None
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
if ohlcv_df.is_empty():
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
# Extract attributes from the order object
|
|
83
|
+
updated_at = order.updated_at
|
|
84
|
+
order_side = order.order_side
|
|
85
|
+
order_price = order.price
|
|
86
|
+
ohlcv_data_after_order = ohlcv_df.filter(
|
|
87
|
+
pl.col('Datetime') >= updated_at
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if ohlcv_data_after_order.is_empty():
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
if OrderSide.BUY.equals(order_side):
|
|
94
|
+
# Check if the low price drops below or equals the order price
|
|
95
|
+
if (ohlcv_data_after_order['Low'] <= order_price).any():
|
|
96
|
+
self.order_service.update(
|
|
97
|
+
order.id, {
|
|
98
|
+
'status': OrderStatus.CLOSED.value,
|
|
99
|
+
'remaining': 0,
|
|
100
|
+
'filled': order.amount
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
elif OrderSide.SELL.equals(order_side):
|
|
105
|
+
# Check if the high price goes above or equals the order price
|
|
106
|
+
if (ohlcv_data_after_order['High'] >= order_price).any():
|
|
107
|
+
self.order_service.update(
|
|
108
|
+
order.id, {
|
|
109
|
+
'status': OrderStatus.CLOSED.value,
|
|
110
|
+
'remaining': 0,
|
|
111
|
+
'filled': order.amount
|
|
112
|
+
}
|
|
113
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from typing import List, Dict
|
|
2
|
+
|
|
3
|
+
import polars as pl
|
|
4
|
+
|
|
5
|
+
from investing_algorithm_framework.domain import Trade, Order, INDEX_DATETIME
|
|
6
|
+
from .trade_order_evaluator import TradeOrderEvaluator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DefaultTradeOrderEvaluator(TradeOrderEvaluator):
|
|
10
|
+
|
|
11
|
+
def evaluate(
|
|
12
|
+
self,
|
|
13
|
+
open_trades: List[Trade],
|
|
14
|
+
open_orders: List[Order],
|
|
15
|
+
ohlcv_data: Dict[str, pl.DataFrame]
|
|
16
|
+
):
|
|
17
|
+
"""
|
|
18
|
+
Evaluate trades and orders based on OHLCV data.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
open_orders (List[Order]): List of open Order objects.
|
|
22
|
+
open_trades (List[Trade]): List of open Trade objects.
|
|
23
|
+
ohlcv_data (dict[str, pl.DataFrame]): Mapping of
|
|
24
|
+
symbol -> OHLCV Polars DataFrame.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
List[dict]: Updated trades with latest prices and execution status.
|
|
28
|
+
"""
|
|
29
|
+
self.order_service.check_pending_orders()
|
|
30
|
+
current_date = self.configuration_service.config[INDEX_DATETIME]
|
|
31
|
+
|
|
32
|
+
if len(open_trades) > 0:
|
|
33
|
+
for open_trade in open_trades:
|
|
34
|
+
data = ohlcv_data[open_trade.symbol]
|
|
35
|
+
|
|
36
|
+
if data is None or data.is_empty():
|
|
37
|
+
continue
|
|
38
|
+
|
|
39
|
+
# Get last row of data
|
|
40
|
+
last_row = data.tail(1)
|
|
41
|
+
last_row_date = last_row["Datetime"][0]
|
|
42
|
+
update_data = {
|
|
43
|
+
"last_reported_price": last_row["Close"][0],
|
|
44
|
+
"last_reported_price_datetime": last_row_date,
|
|
45
|
+
"updated_at": current_date
|
|
46
|
+
}
|
|
47
|
+
open_trade.update(update_data)
|
|
48
|
+
|
|
49
|
+
self.trade_service.save_all(open_trades)
|
|
50
|
+
self._check_take_profits()
|
|
51
|
+
self._check_stop_losses()
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import List, Dict
|
|
3
|
+
import polars as pl
|
|
4
|
+
from investing_algorithm_framework.domain import Trade, Order, INDEX_DATETIME
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TradeOrderEvaluator(ABC):
|
|
8
|
+
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
trade_service,
|
|
12
|
+
trade_stop_loss_service,
|
|
13
|
+
trade_take_profit_service,
|
|
14
|
+
order_service,
|
|
15
|
+
configuration_service=None
|
|
16
|
+
):
|
|
17
|
+
self.trade_service = trade_service
|
|
18
|
+
self.trade_stop_loss_service = trade_stop_loss_service
|
|
19
|
+
self.trade_take_profit_service = trade_take_profit_service
|
|
20
|
+
self.order_service = order_service
|
|
21
|
+
self.configuration_service = configuration_service
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def evaluate(
|
|
25
|
+
self,
|
|
26
|
+
open_trades: List[Trade],
|
|
27
|
+
open_orders: List[Order],
|
|
28
|
+
ohlcv_data: Dict[str, pl.DataFrame]
|
|
29
|
+
):
|
|
30
|
+
"""
|
|
31
|
+
Evaluate trades and orders based on OHLCV data. This
|
|
32
|
+
function is responsible for updating open orders and open trades.
|
|
33
|
+
The evaluation process includes checking if orders have been executed
|
|
34
|
+
and updating the trades with the latest prices and execution status.
|
|
35
|
+
Additionally, it may trigger stop-loss and take-profit orders
|
|
36
|
+
based on the current market conditions.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
open_trades (List[Trade]): List of open Trade objects.
|
|
40
|
+
open_orders (List[Order]): List of open Order objects.
|
|
41
|
+
ohlcv_data (dict[str, pl.DataFrame]): Mapping of
|
|
42
|
+
symbol -> OHLCV Polars DataFrame.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
List[dict]: Updated trades with latest prices and execution status.
|
|
46
|
+
"""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
def _check_take_profits(self):
|
|
50
|
+
current_date = self.configuration_service.config[INDEX_DATETIME]
|
|
51
|
+
take_profits_orders_data = self.trade_service \
|
|
52
|
+
.get_triggered_take_profit_orders()
|
|
53
|
+
|
|
54
|
+
for take_profit_order in take_profits_orders_data:
|
|
55
|
+
take_profits = take_profit_order["take_profits"]
|
|
56
|
+
self.order_service.create(take_profit_order)
|
|
57
|
+
self.trade_take_profit_service.mark_triggered(
|
|
58
|
+
[
|
|
59
|
+
take_profit.get("take_profit_id")
|
|
60
|
+
for take_profit in take_profits
|
|
61
|
+
],
|
|
62
|
+
trigger_date=current_date
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def _check_stop_losses(self):
|
|
66
|
+
current_date = self.configuration_service.config[INDEX_DATETIME]
|
|
67
|
+
stop_losses_orders_data = self.trade_service \
|
|
68
|
+
.get_triggered_stop_loss_orders()
|
|
69
|
+
|
|
70
|
+
for stop_loss_order in stop_losses_orders_data:
|
|
71
|
+
stop_losses = stop_loss_order["stop_losses"]
|
|
72
|
+
|
|
73
|
+
self.order_service.create(stop_loss_order)
|
|
74
|
+
self.trade_stop_loss_service.mark_triggered(
|
|
75
|
+
[
|
|
76
|
+
stop_loss.get("stop_loss_id") for stop_loss in
|
|
77
|
+
stop_losses
|
|
78
|
+
],
|
|
79
|
+
trigger_date=current_date
|
|
80
|
+
)
|
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
from .trade_service import TradeService
|
|
2
|
+
from .trade_stop_loss_service import TradeStopLossService
|
|
3
|
+
from .trade_take_profit_service import TradeTakeProfitService
|
|
2
4
|
|
|
3
|
-
__all__ = [
|
|
5
|
+
__all__ = [
|
|
6
|
+
"TradeService",
|
|
7
|
+
"TradeStopLossService",
|
|
8
|
+
"TradeTakeProfitService"
|
|
9
|
+
]
|
|
@@ -4,9 +4,9 @@ from queue import PriorityQueue
|
|
|
4
4
|
from typing import Union
|
|
5
5
|
|
|
6
6
|
from investing_algorithm_framework.domain import OrderStatus, TradeStatus, \
|
|
7
|
-
Trade, OperationalException,
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
Trade, OperationalException, OrderType, TradeTakeProfit, \
|
|
8
|
+
TradeStopLoss, OrderSide, Environment, ENVIRONMENT, PeekableQueue, \
|
|
9
|
+
DataType, INDEX_DATETIME, random_number, random_string
|
|
10
10
|
from investing_algorithm_framework.services.repository_service import \
|
|
11
11
|
RepositoryService
|
|
12
12
|
|
|
@@ -29,14 +29,12 @@ class TradeService(RepositoryService):
|
|
|
29
29
|
trade_take_profit_repository,
|
|
30
30
|
position_repository,
|
|
31
31
|
portfolio_repository,
|
|
32
|
-
market_data_source_service,
|
|
33
32
|
configuration_service,
|
|
34
33
|
order_metadata_repository
|
|
35
34
|
):
|
|
36
35
|
super(TradeService, self).__init__(trade_repository)
|
|
37
36
|
self.order_repository = order_repository
|
|
38
37
|
self.portfolio_repository = portfolio_repository
|
|
39
|
-
self.market_data_source_service = market_data_source_service
|
|
40
38
|
self.position_repository = position_repository
|
|
41
39
|
self.configuration_service = configuration_service
|
|
42
40
|
self.trade_stop_loss_repository = trade_stop_loss_repository
|
|
@@ -249,7 +247,7 @@ class TradeService(RepositoryService):
|
|
|
249
247
|
|
|
250
248
|
if Environment.BACKTEST.equals(environment):
|
|
251
249
|
last_reported_price_date = \
|
|
252
|
-
config[
|
|
250
|
+
config[INDEX_DATETIME]
|
|
253
251
|
else:
|
|
254
252
|
last_reported_price_date = \
|
|
255
253
|
datetime.now(tz=timezone.utc)
|
|
@@ -383,12 +381,21 @@ class TradeService(RepositoryService):
|
|
|
383
381
|
Args:
|
|
384
382
|
sell_order: Order object representing the sell order that has
|
|
385
383
|
been created
|
|
384
|
+
trades: List of Trade objects representing the trades that
|
|
385
|
+
are associated with the sell order. Default is None.
|
|
386
|
+
stop_losses: List of StopLoss objects representing the stop
|
|
387
|
+
losses that are associated with the sell order. Default
|
|
388
|
+
is None.
|
|
389
|
+
take_profits: List of TakeProfit objects representing the take
|
|
390
|
+
profits that are associated with the sell order. Default
|
|
391
|
+
is None.
|
|
386
392
|
|
|
387
393
|
Returns:
|
|
388
394
|
None
|
|
389
395
|
"""
|
|
390
396
|
sell_order_id = sell_order.id
|
|
391
397
|
sell_price = sell_order.price
|
|
398
|
+
sell_amount = sell_order.amount
|
|
392
399
|
|
|
393
400
|
if (trades is None or len(trades) == 0) \
|
|
394
401
|
and (stop_losses is None or len(stop_losses) == 0) \
|
|
@@ -433,10 +440,11 @@ class TradeService(RepositoryService):
|
|
|
433
440
|
position.cost -= cost
|
|
434
441
|
self.position_repository.save(position)
|
|
435
442
|
|
|
436
|
-
# Update the net gain
|
|
443
|
+
# Update the net gain, net size of the portfolio
|
|
437
444
|
portfolio = self.portfolio_repository.get(position.portfolio_id)
|
|
438
445
|
portfolio.total_net_gain += net_gain
|
|
439
446
|
portfolio.net_size += net_gain
|
|
447
|
+
portfolio.total_revenue += sell_price * sell_amount
|
|
440
448
|
self.portfolio_repository.save(portfolio)
|
|
441
449
|
|
|
442
450
|
def update_trade_with_removed_sell_order(
|
|
@@ -599,7 +607,14 @@ class TradeService(RepositoryService):
|
|
|
599
607
|
function will update all the metadata objects that where
|
|
600
608
|
created by the sell order.
|
|
601
609
|
|
|
610
|
+
Args:
|
|
611
|
+
filled_difference: float representing the difference between
|
|
612
|
+
the filled amount of the sell order and the filled amount
|
|
613
|
+
of the trade
|
|
614
|
+
sell_order: Order object representing the sell order
|
|
602
615
|
|
|
616
|
+
Returns:
|
|
617
|
+
Trade object
|
|
603
618
|
"""
|
|
604
619
|
# Update all metadata objects
|
|
605
620
|
metadata_objects = self.order_metadata_repository.get_all({
|
|
@@ -702,7 +717,7 @@ class TradeService(RepositoryService):
|
|
|
702
717
|
meta_data = market_data["metadata"]
|
|
703
718
|
|
|
704
719
|
for open_trade in open_trades:
|
|
705
|
-
ohlcv_meta_data = meta_data[
|
|
720
|
+
ohlcv_meta_data = meta_data[DataType.OHLCV]
|
|
706
721
|
|
|
707
722
|
if open_trade.symbol not in ohlcv_meta_data:
|
|
708
723
|
continue
|
|
@@ -728,9 +743,10 @@ class TradeService(RepositoryService):
|
|
|
728
743
|
self,
|
|
729
744
|
trade,
|
|
730
745
|
percentage: float,
|
|
731
|
-
|
|
746
|
+
trailing: bool = False,
|
|
732
747
|
sell_percentage: float = 100,
|
|
733
|
-
|
|
748
|
+
created_at: datetime = None
|
|
749
|
+
) -> TradeStopLoss:
|
|
734
750
|
"""
|
|
735
751
|
Function to add a stop loss to a trade.
|
|
736
752
|
|
|
@@ -751,10 +767,12 @@ class TradeService(RepositoryService):
|
|
|
751
767
|
trade: Trade object representing the trade
|
|
752
768
|
percentage: float representing the percentage of the open price
|
|
753
769
|
that the stop loss should be set at
|
|
754
|
-
|
|
755
|
-
or
|
|
770
|
+
trailing (bool): representing whether the stop loss is a
|
|
771
|
+
trailing stop loss or not. Default is False.
|
|
756
772
|
sell_percentage: float representing the percentage of the trade
|
|
757
|
-
that should be sold if the stop loss is triggered
|
|
773
|
+
that should be sold if the stop loss is triggered.
|
|
774
|
+
created_at: datetime representing the creation date of the
|
|
775
|
+
stop loss. If None, the current datetime will be used.
|
|
758
776
|
|
|
759
777
|
Returns:
|
|
760
778
|
None
|
|
@@ -775,12 +793,14 @@ class TradeService(RepositoryService):
|
|
|
775
793
|
|
|
776
794
|
creation_data = {
|
|
777
795
|
"trade_id": trade.id,
|
|
778
|
-
"
|
|
796
|
+
"trailing": trailing,
|
|
779
797
|
"percentage": percentage,
|
|
780
798
|
"open_price": trade.open_price,
|
|
781
799
|
"total_amount_trade": trade.amount,
|
|
782
800
|
"sell_percentage": sell_percentage,
|
|
783
|
-
"active": True
|
|
801
|
+
"active": True,
|
|
802
|
+
"created_at": created_at if created_at is not None
|
|
803
|
+
else datetime.now(tz=timezone.utc)
|
|
784
804
|
}
|
|
785
805
|
return self.trade_stop_loss_repository.create(creation_data)
|
|
786
806
|
|
|
@@ -788,9 +808,10 @@ class TradeService(RepositoryService):
|
|
|
788
808
|
self,
|
|
789
809
|
trade,
|
|
790
810
|
percentage: float,
|
|
791
|
-
|
|
811
|
+
trailing: bool = False,
|
|
792
812
|
sell_percentage: float = 100,
|
|
793
|
-
|
|
813
|
+
created_at: datetime = None
|
|
814
|
+
) -> TradeTakeProfit:
|
|
794
815
|
"""
|
|
795
816
|
Function to add a take profit to a trade. This function will add a
|
|
796
817
|
take profit to the specified trade. If the take profit is triggered,
|
|
@@ -811,12 +832,16 @@ class TradeService(RepositoryService):
|
|
|
811
832
|
|
|
812
833
|
Args:
|
|
813
834
|
trade: Trade object representing the trade
|
|
814
|
-
percentage
|
|
815
|
-
that the stop loss should be set at
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
835
|
+
percentage (float): representing the percentage of the open price
|
|
836
|
+
that the stop loss should be set at. This must be a positive
|
|
837
|
+
number, e.g. 5 for 5%, or 10 for 10%.
|
|
838
|
+
trailing (bool): representing whether the take profit is a
|
|
839
|
+
trailing take profit or not. Default is False.
|
|
840
|
+
sell_percentage (float): representing the percentage of the trade
|
|
819
841
|
that should be sold if the stop loss is triggered
|
|
842
|
+
created_at (datetime): datetime representing the creation
|
|
843
|
+
date of the take profit. If None, the current datetime
|
|
844
|
+
will be used.
|
|
820
845
|
|
|
821
846
|
Returns:
|
|
822
847
|
None
|
|
@@ -834,15 +859,16 @@ class TradeService(RepositoryService):
|
|
|
834
859
|
"Combined sell percentages of stop losses belonging "
|
|
835
860
|
"to trade exceeds 100."
|
|
836
861
|
)
|
|
837
|
-
|
|
838
862
|
creation_data = {
|
|
839
863
|
"trade_id": trade.id,
|
|
840
|
-
"
|
|
864
|
+
"trailing": trailing,
|
|
841
865
|
"percentage": percentage,
|
|
842
866
|
"open_price": trade.open_price,
|
|
843
867
|
"total_amount_trade": trade.amount,
|
|
844
868
|
"sell_percentage": sell_percentage,
|
|
845
|
-
"active": True
|
|
869
|
+
"active": True,
|
|
870
|
+
"created_at": created_at if created_at is not None
|
|
871
|
+
else datetime.now(tz=timezone.utc)
|
|
846
872
|
}
|
|
847
873
|
return self.trade_take_profit_repository.create(creation_data)
|
|
848
874
|
|
|
@@ -854,7 +880,6 @@ class TradeService(RepositoryService):
|
|
|
854
880
|
Returns:
|
|
855
881
|
List of trade ids
|
|
856
882
|
"""
|
|
857
|
-
triggered_stop_losses = {}
|
|
858
883
|
sell_orders_data = []
|
|
859
884
|
query = {"status": TradeStatus.OPEN.value}
|
|
860
885
|
open_trades = self.get_all(query)
|
|
@@ -954,9 +979,7 @@ class TradeService(RepositoryService):
|
|
|
954
979
|
|
|
955
980
|
Returns:
|
|
956
981
|
List of trade objects. A trade object is a dictionary
|
|
957
|
-
|
|
958
982
|
"""
|
|
959
|
-
triggered_take_profits = {}
|
|
960
983
|
sell_orders_data = []
|
|
961
984
|
query = {"status": TradeStatus.OPEN.value}
|
|
962
985
|
open_trades = self.get_all(query)
|
|
@@ -974,7 +997,6 @@ class TradeService(RepositoryService):
|
|
|
974
997
|
continue
|
|
975
998
|
|
|
976
999
|
for take_profit in open_trade.take_profits:
|
|
977
|
-
|
|
978
1000
|
if (
|
|
979
1001
|
take_profit.active and
|
|
980
1002
|
take_profit.has_triggered(open_trade.last_reported_price)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
from investing_algorithm_framework.services.repository_service import \
|
|
5
|
+
RepositoryService
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TradeStopLossService(RepositoryService):
|
|
11
|
+
|
|
12
|
+
def mark_triggered(
|
|
13
|
+
self,
|
|
14
|
+
stop_loss_ids,
|
|
15
|
+
trigger_date: datetime
|
|
16
|
+
) -> None:
|
|
17
|
+
"""
|
|
18
|
+
Mark stop losses as triggered.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
stop_loss_ids (List[str]): List of stop loss IDs to
|
|
22
|
+
mark as triggered.
|
|
23
|
+
trigger_date (datetime): The date when the stop loss
|
|
24
|
+
was triggered.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
None
|
|
28
|
+
"""
|
|
29
|
+
update_data = {
|
|
30
|
+
"triggered": True,
|
|
31
|
+
"triggered_at": trigger_date,
|
|
32
|
+
"updated_at": trigger_date
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for id in stop_loss_ids:
|
|
36
|
+
try:
|
|
37
|
+
self.update(id, update_data)
|
|
38
|
+
except Exception as e:
|
|
39
|
+
logger.error(f"Error marking stop loss {id} as triggered: {e}")
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
from investing_algorithm_framework.services.repository_service import \
|
|
5
|
+
RepositoryService
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TradeTakeProfitService(RepositoryService):
|
|
11
|
+
|
|
12
|
+
def mark_triggered(
|
|
13
|
+
self,
|
|
14
|
+
take_profit_ids,
|
|
15
|
+
trigger_date: datetime
|
|
16
|
+
) -> None:
|
|
17
|
+
"""
|
|
18
|
+
Mark take profits as triggered.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
take_profit_ids (List[str]): List of take profit IDs to
|
|
22
|
+
mark as triggered.
|
|
23
|
+
trigger_date (datetime): The date and time when the
|
|
24
|
+
take profits were triggered.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
None
|
|
28
|
+
"""
|
|
29
|
+
update_data = {
|
|
30
|
+
"triggered": True,
|
|
31
|
+
"triggered_at": trigger_date,
|
|
32
|
+
"updated_at": trigger_date
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for id in take_profit_ids:
|
|
36
|
+
try:
|
|
37
|
+
self.update(id, update_data)
|
|
38
|
+
except Exception as e:
|
|
39
|
+
logger.error(
|
|
40
|
+
f"Error marking take profit {id} as triggered: {e}"
|
|
41
|
+
)
|