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
|
@@ -2,10 +2,8 @@ import logging
|
|
|
2
2
|
|
|
3
3
|
import polars as pl
|
|
4
4
|
|
|
5
|
-
from investing_algorithm_framework.domain import
|
|
6
|
-
OrderStatus, OrderSide, Order,
|
|
7
|
-
from investing_algorithm_framework.services.market_data_source_service \
|
|
8
|
-
import BacktestMarketDataSourceService
|
|
5
|
+
from investing_algorithm_framework.domain import INDEX_DATETIME, \
|
|
6
|
+
OrderStatus, OrderSide, Order, DataType
|
|
9
7
|
from .order_service import OrderService
|
|
10
8
|
|
|
11
9
|
logger = logging.getLogger("investing_algorithm_framework")
|
|
@@ -22,7 +20,6 @@ class OrderBacktestService(OrderService):
|
|
|
22
20
|
portfolio_configuration_service,
|
|
23
21
|
portfolio_snapshot_service,
|
|
24
22
|
configuration_service,
|
|
25
|
-
market_data_source_service: BacktestMarketDataSourceService,
|
|
26
23
|
):
|
|
27
24
|
super().__init__(
|
|
28
25
|
configuration_service=configuration_service,
|
|
@@ -34,7 +31,6 @@ class OrderBacktestService(OrderService):
|
|
|
34
31
|
trade_service=trade_service,
|
|
35
32
|
)
|
|
36
33
|
self.configuration_service = configuration_service
|
|
37
|
-
self.market_data_source_service = market_data_source_service
|
|
38
34
|
|
|
39
35
|
def create(self, data, execute=True, validate=True, sync=True) -> Order:
|
|
40
36
|
"""
|
|
@@ -51,10 +47,9 @@ class OrderBacktestService(OrderService):
|
|
|
51
47
|
Order: Created order object
|
|
52
48
|
"""
|
|
53
49
|
config = self.configuration_service.get_config()
|
|
54
|
-
|
|
55
50
|
# Make sure the created_at is set to the current backtest time
|
|
56
|
-
data["created_at"] = config[
|
|
57
|
-
data["updated_at"] = config[
|
|
51
|
+
data["created_at"] = config[INDEX_DATETIME]
|
|
52
|
+
data["updated_at"] = config[INDEX_DATETIME]
|
|
58
53
|
# Call super to have standard behavior
|
|
59
54
|
return super(OrderBacktestService, self)\
|
|
60
55
|
.create(data, execute, validate, sync)
|
|
@@ -64,7 +59,7 @@ class OrderBacktestService(OrderService):
|
|
|
64
59
|
order.remaining = order.get_amount()
|
|
65
60
|
order.filled = 0
|
|
66
61
|
order.updated_at = self.configuration_service.config[
|
|
67
|
-
|
|
62
|
+
INDEX_DATETIME
|
|
68
63
|
]
|
|
69
64
|
return order
|
|
70
65
|
|
|
@@ -85,7 +80,7 @@ class OrderBacktestService(OrderService):
|
|
|
85
80
|
meta_data = market_data["metadata"]
|
|
86
81
|
|
|
87
82
|
for order in pending_orders:
|
|
88
|
-
ohlcv_meta_data = meta_data[
|
|
83
|
+
ohlcv_meta_data = meta_data[DataType.OHLCV]
|
|
89
84
|
|
|
90
85
|
if order.get_symbol() not in ohlcv_meta_data:
|
|
91
86
|
continue
|
|
@@ -106,7 +101,7 @@ class OrderBacktestService(OrderService):
|
|
|
106
101
|
"filled": order.get_amount(),
|
|
107
102
|
"remaining": 0,
|
|
108
103
|
"updated_at": self.configuration_service
|
|
109
|
-
.config[
|
|
104
|
+
.config[INDEX_DATETIME]
|
|
110
105
|
}
|
|
111
106
|
)
|
|
112
107
|
|
|
@@ -123,11 +118,11 @@ class OrderBacktestService(OrderService):
|
|
|
123
118
|
"status": OrderStatus.CANCELED.value,
|
|
124
119
|
"remaining": 0,
|
|
125
120
|
"updated_at": self.configuration_service
|
|
126
|
-
.config[
|
|
121
|
+
.config[INDEX_DATETIME]
|
|
127
122
|
}
|
|
128
123
|
)
|
|
129
124
|
|
|
130
|
-
def has_executed(self, order, ohlcv_data_frame):
|
|
125
|
+
def has_executed(self, order, ohlcv_data_frame: pl.DataFrame):
|
|
131
126
|
"""
|
|
132
127
|
Check if the order has executed based on the OHLCV data.
|
|
133
128
|
|
|
@@ -135,7 +130,7 @@ class OrderBacktestService(OrderService):
|
|
|
135
130
|
order price. Example: If the order price is 1000 and the low price
|
|
136
131
|
drops below or equals 1000, the order is executed. This simulates the
|
|
137
132
|
situation where a buyer is willing to pay a higher price than the
|
|
138
|
-
|
|
133
|
+
lowest price in the ohlcv data.
|
|
139
134
|
|
|
140
135
|
A sell order is executed if the high price goes above or equals the
|
|
141
136
|
order price. Example: If the order price is 1000 and the high price
|
|
@@ -143,27 +138,22 @@ class OrderBacktestService(OrderService):
|
|
|
143
138
|
situation where a seller is willing to accept a higher price for its
|
|
144
139
|
sell order.
|
|
145
140
|
|
|
146
|
-
:
|
|
147
|
-
|
|
148
|
-
|
|
141
|
+
Args:
|
|
142
|
+
order: Order object
|
|
143
|
+
ohlcv_data_frame: OHLCV data frame
|
|
144
|
+
True if the order has executed, False otherwise
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
bool: True if the order has executed, False otherwise
|
|
149
148
|
"""
|
|
150
149
|
|
|
151
150
|
# Extract attributes from the order object
|
|
152
151
|
created_at = order.get_created_at()
|
|
153
152
|
order_side = order.get_order_side()
|
|
154
153
|
order_price = order.get_price()
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
ohlcv_data_after_order = ohlcv_data_frame.filter(
|
|
159
|
-
pl.col('Datetime') >= created_at
|
|
160
|
-
)
|
|
161
|
-
else:
|
|
162
|
-
ohlcv_data_after_order = ohlcv_data_frame.filter(
|
|
163
|
-
pl.col('Datetime') >= created_at.strftime(
|
|
164
|
-
self.configuration_service.config["DATETIME_FORMAT"]
|
|
165
|
-
)
|
|
166
|
-
)
|
|
154
|
+
ohlcv_data_after_order = ohlcv_data_frame.filter(
|
|
155
|
+
pl.col('Datetime') >= created_at
|
|
156
|
+
)
|
|
167
157
|
|
|
168
158
|
# Check if the order execution conditions are met
|
|
169
159
|
if OrderSide.BUY.equals(order_side):
|
|
@@ -182,7 +172,7 @@ class OrderBacktestService(OrderService):
|
|
|
182
172
|
|
|
183
173
|
if created_at is None:
|
|
184
174
|
created_at = self.configuration_service \
|
|
185
|
-
.config[
|
|
175
|
+
.config[INDEX_DATETIME]
|
|
186
176
|
|
|
187
177
|
super(OrderBacktestService, self)\
|
|
188
178
|
.create_snapshot(portfolio_id, created_at=created_at)
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from datetime import datetime
|
|
3
|
-
from typing import List
|
|
4
|
-
|
|
5
|
-
from dateutil.tz import tzutc
|
|
6
2
|
|
|
7
3
|
from investing_algorithm_framework.domain import OrderType, OrderSide, \
|
|
8
|
-
OperationalException, OrderStatus, Order,
|
|
9
|
-
Observable, Event
|
|
4
|
+
OperationalException, OrderStatus, Order, random_number, INDEX_DATETIME
|
|
10
5
|
from investing_algorithm_framework.services.repository_service \
|
|
11
6
|
import RepositoryService
|
|
12
7
|
|
|
13
8
|
logger = logging.getLogger("investing_algorithm_framework")
|
|
14
9
|
|
|
15
10
|
|
|
16
|
-
class OrderService(RepositoryService
|
|
11
|
+
class OrderService(RepositoryService):
|
|
17
12
|
"""
|
|
18
13
|
Service to manage orders. This service will use the provided
|
|
19
14
|
order executors to execute the orders. The order service is
|
|
@@ -52,8 +47,6 @@ class OrderService(RepositoryService, Observable):
|
|
|
52
47
|
market_credential_service=None
|
|
53
48
|
):
|
|
54
49
|
super(OrderService, self).__init__(order_repository)
|
|
55
|
-
# Call the observable constructor
|
|
56
|
-
Observable.__init__(self)
|
|
57
50
|
self.configuration_service = configuration_service
|
|
58
51
|
self.order_repository = order_repository
|
|
59
52
|
self.position_service = position_service
|
|
@@ -62,36 +55,9 @@ class OrderService(RepositoryService, Observable):
|
|
|
62
55
|
self.portfolio_snapshot_service = portfolio_snapshot_service
|
|
63
56
|
self.market_credential_service = market_credential_service
|
|
64
57
|
self.trade_service = trade_service
|
|
65
|
-
self._order_executors = None
|
|
66
58
|
self._order_executor_lookup = order_executor_lookup
|
|
67
59
|
self._portfolio_provider_lookup = portfolio_provider_lookup
|
|
68
60
|
|
|
69
|
-
@property
|
|
70
|
-
def order_executors(self) -> List[OrderExecutor]:
|
|
71
|
-
"""
|
|
72
|
-
Returns the order executors for the order service.
|
|
73
|
-
"""
|
|
74
|
-
return self._order_executors
|
|
75
|
-
|
|
76
|
-
@order_executors.setter
|
|
77
|
-
def order_executors(self, value) -> None:
|
|
78
|
-
"""
|
|
79
|
-
Sets the order executors for the order service.
|
|
80
|
-
"""
|
|
81
|
-
self._order_executors = value
|
|
82
|
-
|
|
83
|
-
def get_order_executor(self, market) -> OrderExecutor:
|
|
84
|
-
"""
|
|
85
|
-
Returns the order executor for the given market.
|
|
86
|
-
|
|
87
|
-
Args:
|
|
88
|
-
market (str): The market for which to get the order executor.
|
|
89
|
-
|
|
90
|
-
Returns:
|
|
91
|
-
OrderExecutor: The order executor for the given market.
|
|
92
|
-
"""
|
|
93
|
-
return self._order_executor_lookup.get_order_executor(market)
|
|
94
|
-
|
|
95
61
|
def create(self, data, execute=True, validate=True, sync=True) -> Order:
|
|
96
62
|
"""
|
|
97
63
|
Function to create an order. The function will create the order and
|
|
@@ -189,6 +155,7 @@ class OrderService(RepositoryService, Observable):
|
|
|
189
155
|
self.validate_order(data, portfolio)
|
|
190
156
|
|
|
191
157
|
del data["portfolio_id"]
|
|
158
|
+
data["target_symbol"] = data["target_symbol"].upper()
|
|
192
159
|
symbol = data["target_symbol"]
|
|
193
160
|
data["id"] = self._create_order_id()
|
|
194
161
|
|
|
@@ -225,11 +192,6 @@ class OrderService(RepositoryService, Observable):
|
|
|
225
192
|
else:
|
|
226
193
|
self._sync_portfolio_with_created_sell_order(order)
|
|
227
194
|
|
|
228
|
-
self.notify_observers(
|
|
229
|
-
Event.ORDER_CREATED,
|
|
230
|
-
{"portfolio_id": portfolio.id, "created_at": order.created_at}
|
|
231
|
-
)
|
|
232
|
-
# self.create_snapshot(portfolio.id, created_at=created_at)
|
|
233
195
|
order = self.get(order_id)
|
|
234
196
|
return order
|
|
235
197
|
|
|
@@ -310,7 +272,8 @@ class OrderService(RepositoryService, Observable):
|
|
|
310
272
|
f"and price {order.get_price()}"
|
|
311
273
|
)
|
|
312
274
|
|
|
313
|
-
order_executor = self.
|
|
275
|
+
order_executor = self._order_executor_lookup\
|
|
276
|
+
.get_order_executor(portfolio.market)
|
|
314
277
|
market_credential = self.market_credential_service.get(
|
|
315
278
|
portfolio.market
|
|
316
279
|
)
|
|
@@ -322,7 +285,8 @@ class OrderService(RepositoryService, Observable):
|
|
|
322
285
|
order.set_status(external_order.get_status())
|
|
323
286
|
order.set_filled(external_order.get_filled())
|
|
324
287
|
order.set_remaining(external_order.get_remaining())
|
|
325
|
-
|
|
288
|
+
config = self.configuration_service.config
|
|
289
|
+
order.updated_at = config[INDEX_DATETIME]
|
|
326
290
|
return order
|
|
327
291
|
|
|
328
292
|
def validate_order(self, order_data, portfolio):
|
|
@@ -544,7 +508,8 @@ class OrderService(RepositoryService, Observable):
|
|
|
544
508
|
market_credential = self.market_credential_service.get(
|
|
545
509
|
portfolio.market
|
|
546
510
|
)
|
|
547
|
-
order_executor = self.
|
|
511
|
+
order_executor = self._order_executor_lookup\
|
|
512
|
+
.get_order_executor(portfolio.market)
|
|
548
513
|
order = order_executor\
|
|
549
514
|
.cancel_order(portfolio, order, market_credential)
|
|
550
515
|
self.update(order.id, order.to_dict())
|
|
@@ -840,33 +805,6 @@ class OrderService(RepositoryService, Observable):
|
|
|
840
805
|
|
|
841
806
|
self.trade_service.update_trade_with_removed_sell_order(order)
|
|
842
807
|
|
|
843
|
-
# def create_snapshot(self, portfolio_id, created_at=None):
|
|
844
|
-
#
|
|
845
|
-
# if created_at is None:
|
|
846
|
-
# created_at = datetime.now(tz=tzutc())
|
|
847
|
-
#
|
|
848
|
-
# portfolio = self.portfolio_repository.get(portfolio_id)
|
|
849
|
-
# pending_orders = self.get_all(
|
|
850
|
-
# {
|
|
851
|
-
# "order_side": OrderSide.BUY.value,
|
|
852
|
-
# "status": OrderStatus.OPEN.value,
|
|
853
|
-
# "portfolio_id": portfolio.id
|
|
854
|
-
# }
|
|
855
|
-
# )
|
|
856
|
-
# created_orders = self.get_all(
|
|
857
|
-
# {
|
|
858
|
-
# "order_side": OrderSide.BUY.value,
|
|
859
|
-
# "status": OrderStatus.CREATED.value,
|
|
860
|
-
# "portfolio_id": portfolio.id
|
|
861
|
-
# }
|
|
862
|
-
# )
|
|
863
|
-
# return self.portfolio_snapshot_service.create_snapshot(
|
|
864
|
-
# portfolio,
|
|
865
|
-
# pending_orders=pending_orders,
|
|
866
|
-
# created_orders=created_orders,
|
|
867
|
-
# created_at=created_at
|
|
868
|
-
# )
|
|
869
|
-
|
|
870
808
|
def _create_order_id(self):
|
|
871
809
|
"""
|
|
872
810
|
Function to create a new order id. The function will
|
|
@@ -2,8 +2,7 @@ import logging
|
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
|
|
4
4
|
from investing_algorithm_framework.domain import OperationalException, \
|
|
5
|
-
MarketCredentialService, Portfolio,
|
|
6
|
-
Environment, ENVIRONMENT, Observable, Event
|
|
5
|
+
MarketCredentialService, Portfolio, Environment, ENVIRONMENT
|
|
7
6
|
from investing_algorithm_framework.services.configuration_service import \
|
|
8
7
|
ConfigurationService
|
|
9
8
|
from investing_algorithm_framework.services.repository_service \
|
|
@@ -12,7 +11,7 @@ from investing_algorithm_framework.services.repository_service \
|
|
|
12
11
|
logger = logging.getLogger("investing_algorithm_framework")
|
|
13
12
|
|
|
14
13
|
|
|
15
|
-
class PortfolioService(RepositoryService
|
|
14
|
+
class PortfolioService(RepositoryService):
|
|
16
15
|
"""
|
|
17
16
|
Service to manage portfolios. This service will sync the portfolios with
|
|
18
17
|
the exchange balances and orders. It will also create portfolios based on
|
|
@@ -31,8 +30,6 @@ class PortfolioService(RepositoryService, Observable):
|
|
|
31
30
|
portfolio_provider_lookup
|
|
32
31
|
):
|
|
33
32
|
super().__init__(repository=portfolio_repository)
|
|
34
|
-
# Call the observable constructor
|
|
35
|
-
Observable.__init__(self)
|
|
36
33
|
self.configuration_service = configuration_service
|
|
37
34
|
self.market_credential_service = market_credential_service
|
|
38
35
|
self.portfolio_configuration_service = portfolio_configuration_service
|
|
@@ -79,7 +76,7 @@ class PortfolioService(RepositoryService, Observable):
|
|
|
79
76
|
{"identifier": identifier}
|
|
80
77
|
)
|
|
81
78
|
return portfolio
|
|
82
|
-
|
|
79
|
+
data["initial_balance"] = unallocated
|
|
83
80
|
portfolio = super(PortfolioService, self).create(data)
|
|
84
81
|
self.position_service.create(
|
|
85
82
|
{
|
|
@@ -89,13 +86,6 @@ class PortfolioService(RepositoryService, Observable):
|
|
|
89
86
|
"cost": unallocated
|
|
90
87
|
}
|
|
91
88
|
)
|
|
92
|
-
self.notify_observers(
|
|
93
|
-
Event.PORTFOLIO_CREATED,
|
|
94
|
-
{
|
|
95
|
-
"portfolio_id": portfolio.id,
|
|
96
|
-
"created_at": portfolio.created_at
|
|
97
|
-
}
|
|
98
|
-
)
|
|
99
89
|
return portfolio
|
|
100
90
|
|
|
101
91
|
def create_portfolio_from_configuration(
|
|
@@ -1,22 +1,13 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from investing_algorithm_framework.domain import Observer, Event, \
|
|
4
|
-
SNAPSHOT_INTERVAL, SnapshotInterval, OrderStatus, \
|
|
5
|
-
PortfolioSnapshot, Environment, ENVIRONMENT, BACKTESTING_INDEX_DATETIME
|
|
1
|
+
from investing_algorithm_framework.domain import OrderStatus, \
|
|
2
|
+
PortfolioSnapshot
|
|
6
3
|
from investing_algorithm_framework.services.repository_service import \
|
|
7
4
|
RepositoryService
|
|
8
5
|
|
|
9
6
|
|
|
10
|
-
class PortfolioSnapshotService(RepositoryService
|
|
7
|
+
class PortfolioSnapshotService(RepositoryService):
|
|
11
8
|
"""
|
|
12
9
|
Service to manage portfolio snapshots. This service will create snapshots
|
|
13
10
|
of the portfolio at specific intervals or based on certain events.
|
|
14
|
-
|
|
15
|
-
This service implements the Observer interface to listen for events.
|
|
16
|
-
This can be used to register this service to an observable object
|
|
17
|
-
and let it create snapshots based on the events that occur in the system.
|
|
18
|
-
For example, it can create a snapshot of the portfolio
|
|
19
|
-
at the end of each strategy iteration or when a trade is closed.
|
|
20
11
|
"""
|
|
21
12
|
|
|
22
13
|
def __init__(
|
|
@@ -26,55 +17,24 @@ class PortfolioSnapshotService(RepositoryService, Observer):
|
|
|
26
17
|
order_repository,
|
|
27
18
|
position_repository,
|
|
28
19
|
position_snapshot_service,
|
|
29
|
-
|
|
30
|
-
configuration_service
|
|
20
|
+
data_provider_service,
|
|
31
21
|
):
|
|
32
22
|
self.order_repository = order_repository
|
|
33
23
|
self.position_snapshot_service = position_snapshot_service
|
|
34
24
|
self.portfolio_repository = portfolio_repository
|
|
35
25
|
self.position_repository = position_repository
|
|
36
|
-
self.
|
|
37
|
-
self.datasource_service = datasource_service
|
|
26
|
+
self.data_provider_service = data_provider_service
|
|
38
27
|
super(PortfolioSnapshotService, self).__init__(repository)
|
|
39
28
|
|
|
40
|
-
def notify(self, event_type: Event, payload):
|
|
41
|
-
"""
|
|
42
|
-
Update the portfolio snapshot based on the event type and payload.
|
|
43
|
-
|
|
44
|
-
Args:
|
|
45
|
-
event_type: The type of event that occurred.
|
|
46
|
-
payload: The data associated with the event.
|
|
47
|
-
"""
|
|
48
|
-
config = self.configuration_service.get_config()
|
|
49
|
-
snapshot_interval = config[SNAPSHOT_INTERVAL]
|
|
50
|
-
|
|
51
|
-
if Event.STRATEGY_RUN.equals(event_type) and \
|
|
52
|
-
SnapshotInterval.STRATEGY_ITERATION.equals(snapshot_interval):
|
|
53
|
-
created_at = payload.get("created_at")
|
|
54
|
-
for portfolio in self.portfolio_repository.get_all():
|
|
55
|
-
self.create_snapshot(portfolio, created_at=created_at)
|
|
56
|
-
|
|
57
|
-
elif Event.TRADE_CLOSED.equals(event_type) and \
|
|
58
|
-
SnapshotInterval.TRADE_CLOSE.equals(snapshot_interval):
|
|
59
|
-
portfolio_id = payload.get("portfolio_id")
|
|
60
|
-
created_at = payload.get("created_at")
|
|
61
|
-
portfolio = self.portfolio_repository.get(portfolio_id)
|
|
62
|
-
self.create_snapshot(portfolio=portfolio, created_at=created_at)
|
|
63
|
-
|
|
64
|
-
elif Event.PORTFOLIO_CREATED.equals(event_type):
|
|
65
|
-
portfolio_id = payload.get("portfolio_id")
|
|
66
|
-
created_at = payload.get("created_at")
|
|
67
|
-
portfolio = self.portfolio_repository.get(portfolio_id)
|
|
68
|
-
self.create_snapshot(portfolio=portfolio, created_at=created_at)
|
|
69
|
-
|
|
70
|
-
# elif Event.ORDER_CREATED.equals(event_type):
|
|
71
|
-
# portfolio_id = payload.get("portfolio_id")
|
|
72
|
-
# portfolio = self.portfolio_repository.get(portfolio_id)
|
|
73
|
-
# created_at = payload.get("created_at")
|
|
74
|
-
# self.create_snapshot(portfolio=portfolio, created_at=created_at)
|
|
75
|
-
|
|
76
29
|
def create_snapshot(
|
|
77
|
-
self,
|
|
30
|
+
self,
|
|
31
|
+
portfolio,
|
|
32
|
+
created_at,
|
|
33
|
+
cash_flow=0,
|
|
34
|
+
created_orders=None,
|
|
35
|
+
open_orders=None,
|
|
36
|
+
positions=None,
|
|
37
|
+
save=True
|
|
78
38
|
) -> PortfolioSnapshot:
|
|
79
39
|
"""
|
|
80
40
|
Function to create a snapshot of the portfolio. During
|
|
@@ -93,52 +53,67 @@ class PortfolioSnapshotService(RepositoryService, Observer):
|
|
|
93
53
|
and time will be used.
|
|
94
54
|
cash_flow (float, optional): The cash flow to include
|
|
95
55
|
in the snapshot.
|
|
56
|
+
created_orders (list, optional): A list of created orders
|
|
57
|
+
to consider when calculating the pending value.
|
|
58
|
+
open_orders (list, optional): A list of open orders
|
|
59
|
+
to consider when calculating the pending value.
|
|
60
|
+
positions (list, optional): A list of positions to consider
|
|
61
|
+
when calculating the total value of the portfolio.
|
|
62
|
+
save (bool, optional): Whether to save the snapshot to
|
|
63
|
+
the database.
|
|
96
64
|
|
|
97
65
|
Returns:
|
|
98
|
-
|
|
66
|
+
PortfolioSnapshot: The created snapshot of the portfolio.
|
|
99
67
|
"""
|
|
100
68
|
pending_value = 0
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
69
|
+
pending_symbols = set()
|
|
70
|
+
allocated = 0
|
|
71
|
+
|
|
72
|
+
if open_orders is None:
|
|
73
|
+
open_orders = self.order_repository.get_all(
|
|
74
|
+
{
|
|
75
|
+
"status": OrderStatus.OPEN.value,
|
|
76
|
+
"portfolio_id": portfolio.id
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if created_orders is None:
|
|
81
|
+
created_orders = self.order_repository.get_all(
|
|
82
|
+
{
|
|
83
|
+
"status": OrderStatus.CREATED.value,
|
|
84
|
+
"portfolio_id": portfolio.id
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if positions is None:
|
|
89
|
+
positions = self.position_repository.get_all(
|
|
90
|
+
{"portfolio": portfolio.id}
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
for order in created_orders:
|
|
94
|
+
pending_value += order.get_price() * order.get_amount()
|
|
95
|
+
pending_symbols.add(order.get_symbol())
|
|
96
|
+
|
|
97
|
+
for order in open_orders:
|
|
98
|
+
pending_value += order.get_price() * order.get_remaining()
|
|
99
|
+
pending_symbols.add(order.get_symbol())
|
|
129
100
|
|
|
130
101
|
total_value = portfolio.get_unallocated() + pending_value
|
|
131
102
|
|
|
132
103
|
for position in \
|
|
133
104
|
self.position_repository.get_all({"portfolio": portfolio.id}):
|
|
134
|
-
symbol = \
|
|
135
|
-
f"{position.get_symbol()}/{portfolio.get_trading_symbol()}"
|
|
136
105
|
|
|
137
106
|
if position.get_symbol() != portfolio.get_trading_symbol():
|
|
138
|
-
|
|
139
|
-
|
|
107
|
+
symbol_pair = f"{position.get_symbol()}/" \
|
|
108
|
+
f"{portfolio.get_trading_symbol()}"
|
|
109
|
+
ticker = self.data_provider_service.get_ticker_data(
|
|
110
|
+
symbol=symbol_pair,
|
|
111
|
+
market=portfolio.market,
|
|
112
|
+
date=created_at
|
|
113
|
+
)
|
|
140
114
|
# Calculate the position worth
|
|
141
115
|
position_worth = position.get_amount() * ticker["bid"]
|
|
116
|
+
allocated += position_worth
|
|
142
117
|
total_value += position_worth
|
|
143
118
|
|
|
144
119
|
data = {
|
|
@@ -154,16 +129,7 @@ class PortfolioSnapshotService(RepositoryService, Observer):
|
|
|
154
129
|
"created_at": created_at,
|
|
155
130
|
"total_value": total_value,
|
|
156
131
|
}
|
|
157
|
-
snapshot = self.create(data)
|
|
158
|
-
positions = self.position_repository.get_all(
|
|
159
|
-
{"portfolio": portfolio.id}
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
for position in positions:
|
|
163
|
-
self.position_snapshot_service.create_snapshot(
|
|
164
|
-
snapshot.id, position
|
|
165
|
-
)
|
|
166
|
-
|
|
132
|
+
snapshot = self.create(data, save=save)
|
|
167
133
|
return snapshot
|
|
168
134
|
|
|
169
135
|
def get_latest_snapshot(self, portfolio_id):
|
|
@@ -20,7 +20,6 @@ class PortfolioSyncService(AbstractPortfolioSyncService):
|
|
|
20
20
|
position_repository: PositionRepository object
|
|
21
21
|
portfolio_repository: PortfolioRepository object
|
|
22
22
|
market_credential_service: MarketCredentialService object
|
|
23
|
-
market_service: MarketService object
|
|
24
23
|
portfolio_configuration_service: PortfolioConfigurationService object
|
|
25
24
|
portfolio_provider_lookup: PortfolioProviderLookup object
|
|
26
25
|
"""
|
|
@@ -34,7 +33,6 @@ class PortfolioSyncService(AbstractPortfolioSyncService):
|
|
|
34
33
|
portfolio_repository,
|
|
35
34
|
portfolio_configuration_service,
|
|
36
35
|
market_credential_service,
|
|
37
|
-
market_service,
|
|
38
36
|
portfolio_provider_lookup
|
|
39
37
|
):
|
|
40
38
|
self.trade_service = trade_service
|
|
@@ -43,7 +41,6 @@ class PortfolioSyncService(AbstractPortfolioSyncService):
|
|
|
43
41
|
self.position_repository = position_repository
|
|
44
42
|
self.portfolio_repository = portfolio_repository
|
|
45
43
|
self.market_credential_service = market_credential_service
|
|
46
|
-
self.market_service = market_service
|
|
47
44
|
self.portfolio_configuration_service = portfolio_configuration_service
|
|
48
45
|
self.portfolio_provider_lookup = portfolio_provider_lookup
|
|
49
46
|
|
|
@@ -3,8 +3,8 @@ class RepositoryService:
|
|
|
3
3
|
def __init__(self, repository):
|
|
4
4
|
self.repository = repository
|
|
5
5
|
|
|
6
|
-
def create(self, data):
|
|
7
|
-
return self.repository.create(data)
|
|
6
|
+
def create(self, data, save=True):
|
|
7
|
+
return self.repository.create(data, save=save)
|
|
8
8
|
|
|
9
9
|
def get(self, object_id):
|
|
10
10
|
return self.repository.get(object_id)
|
|
@@ -35,3 +35,6 @@ class RepositoryService:
|
|
|
35
35
|
|
|
36
36
|
def save(self, object):
|
|
37
37
|
return self.repository.save(object)
|
|
38
|
+
|
|
39
|
+
def save_all(self, objects):
|
|
40
|
+
return self.repository.save_objects(objects)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from .trade_order_evaluator import TradeOrderEvaluator
|
|
2
|
+
from .backtest_trade_oder_evaluator import BacktestTradeOrderEvaluator
|
|
3
|
+
from .default_trade_order_evaluator import DefaultTradeOrderEvaluator
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"TradeOrderEvaluator",
|
|
7
|
+
"BacktestTradeOrderEvaluator",
|
|
8
|
+
"DefaultTradeOrderEvaluator"
|
|
9
|
+
]
|