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
investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py
DELETED
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
|
|
3
|
-
from investing_algorithm_framework.domain import OrderStatus, OrderSide, \
|
|
4
|
-
TradeStatus
|
|
5
|
-
|
|
6
|
-
logger = logging.getLogger(__name__)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class PerformanceService:
|
|
10
|
-
"""
|
|
11
|
-
Service to calculate the performance of a portfolio.
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
def __init__(
|
|
15
|
-
self,
|
|
16
|
-
order_repository,
|
|
17
|
-
position_repository,
|
|
18
|
-
portfolio_repository,
|
|
19
|
-
trade_repository,
|
|
20
|
-
):
|
|
21
|
-
self.order_repository = order_repository
|
|
22
|
-
self.position_repository = position_repository
|
|
23
|
-
self.portfolio_repository = portfolio_repository
|
|
24
|
-
self.trade_repository = trade_repository
|
|
25
|
-
|
|
26
|
-
def get_total_net_gain(self, portfolio_id):
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
|
-
def get_total_size(self, portfolio_id):
|
|
30
|
-
pass
|
|
31
|
-
|
|
32
|
-
def get_percentage_change(self, portfolio_id, time_frame):
|
|
33
|
-
pass
|
|
34
|
-
|
|
35
|
-
def get_total_cost(self, portfolio_id):
|
|
36
|
-
pass
|
|
37
|
-
|
|
38
|
-
def get_number_of_trades_closed(self, portfolio_id):
|
|
39
|
-
""""
|
|
40
|
-
Get the number of trades closed. This function will
|
|
41
|
-
return the number of trades that are already closed.
|
|
42
|
-
|
|
43
|
-
param portfolio_id: The id of the portfolio
|
|
44
|
-
type portfolio_id: str
|
|
45
|
-
|
|
46
|
-
return: The number of trades closed
|
|
47
|
-
"""
|
|
48
|
-
portfolio = self.portfolio_repository.find({"id": portfolio_id})
|
|
49
|
-
return self.trade_repository.count(
|
|
50
|
-
{"portfolio_id": portfolio.id, "status": TradeStatus.OPEN.value}
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
def get_number_of_trades_open(self, portfolio_id):
|
|
54
|
-
"""
|
|
55
|
-
Get the number of trades open. This function will
|
|
56
|
-
return the number of trades that are still open.
|
|
57
|
-
|
|
58
|
-
param portfolio_id: The id of the portfolio
|
|
59
|
-
type portfolio_id: str
|
|
60
|
-
|
|
61
|
-
return: The number of trades open
|
|
62
|
-
"""
|
|
63
|
-
portfolio = self.portfolio_repository.find({"id": portfolio_id})
|
|
64
|
-
return self.trade_repository.count(
|
|
65
|
-
{"portfolio_id": portfolio.id, "status": TradeStatus.OPEN.value}
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
def get_percentage_positive_trades(self, portfolio_id):
|
|
69
|
-
"""
|
|
70
|
-
Get the percentage of positive trades. This function will
|
|
71
|
-
calculate the percentage of positive trades by dividing the
|
|
72
|
-
total number of positive trades by the total number of trades
|
|
73
|
-
and then multiplying it by 100.
|
|
74
|
-
|
|
75
|
-
param portfolio_id: The id of the portfolio
|
|
76
|
-
type portfolio_id: str
|
|
77
|
-
|
|
78
|
-
return: The percentage of positive trades
|
|
79
|
-
"""
|
|
80
|
-
portfolio = self.portfolio_repository.find({"id": portfolio_id})
|
|
81
|
-
trades = self.trade_repository.get_all(
|
|
82
|
-
{"portfolio_id": portfolio.id, "status": OrderStatus.CLOSED.value}
|
|
83
|
-
)
|
|
84
|
-
total_number_of_trades = len(trades)
|
|
85
|
-
|
|
86
|
-
if total_number_of_trades == 0:
|
|
87
|
-
return 0.0
|
|
88
|
-
|
|
89
|
-
positive_trades = [
|
|
90
|
-
trade for trade in trades if trade.net_gain > 0
|
|
91
|
-
]
|
|
92
|
-
total_number_of_positive_trades = len(positive_trades)
|
|
93
|
-
return total_number_of_positive_trades / total_number_of_trades * 100
|
|
94
|
-
|
|
95
|
-
def get_percentage_negative_trades(self, portfolio_id):
|
|
96
|
-
"""
|
|
97
|
-
Get the percentage of negative trades. This function will
|
|
98
|
-
calculate the percentage of negative trades by dividing the
|
|
99
|
-
total number of negative trades by the total number of trades
|
|
100
|
-
and then multiplying it by 100.
|
|
101
|
-
|
|
102
|
-
param portfolio_id: The id of the portfolio
|
|
103
|
-
type portfolio_id: str
|
|
104
|
-
|
|
105
|
-
return: The percentage of negative trades
|
|
106
|
-
"""
|
|
107
|
-
|
|
108
|
-
portfolio = self.portfolio_repository.find({"id": portfolio_id})
|
|
109
|
-
trades = self.trade_repository.get_all(
|
|
110
|
-
{"portfolio_id": portfolio.id, "status": TradeStatus.CLOSED.value}
|
|
111
|
-
)
|
|
112
|
-
total_number_of_trades = len(trades)
|
|
113
|
-
|
|
114
|
-
if total_number_of_trades == 0:
|
|
115
|
-
return 0.0
|
|
116
|
-
|
|
117
|
-
negative_trades = [
|
|
118
|
-
trade for trade in trades if trade.net_gain < 0
|
|
119
|
-
]
|
|
120
|
-
total_number_of_negative_trades = len(negative_trades)
|
|
121
|
-
return total_number_of_negative_trades / total_number_of_trades * 100
|
|
122
|
-
|
|
123
|
-
def get_growth_rate_of_backtest(
|
|
124
|
-
self, portfolio_id, tickers, backtest_profile
|
|
125
|
-
):
|
|
126
|
-
"""
|
|
127
|
-
Get the growth rate of the backtest. This function will
|
|
128
|
-
calculate the total value of the portfolio and then
|
|
129
|
-
calculate the growth rate of the portfolio.
|
|
130
|
-
|
|
131
|
-
param portfolio_id: The id of the portfolio
|
|
132
|
-
type portfolio_id: str
|
|
133
|
-
param tickers: list of tickers of all the used symbols
|
|
134
|
-
type tickers: dict
|
|
135
|
-
|
|
136
|
-
return: The growth rate of the backtest
|
|
137
|
-
"""
|
|
138
|
-
total_value = self.get_total_value(
|
|
139
|
-
portfolio_id, tickers, backtest_profile
|
|
140
|
-
)
|
|
141
|
-
gain = total_value - backtest_profile.initial_unallocated
|
|
142
|
-
return gain / backtest_profile.initial_unallocated * 100
|
|
143
|
-
|
|
144
|
-
def get_growth_of_backtest(self, portfolio_id, tickers, backtest_profile):
|
|
145
|
-
"""
|
|
146
|
-
Get the growth of the backtest. This function will
|
|
147
|
-
calculate the total value of the portfolio and then
|
|
148
|
-
calculate the growth of the portfolio.
|
|
149
|
-
|
|
150
|
-
param portfolio_id: The id of the portfolio
|
|
151
|
-
type portfolio_id: str
|
|
152
|
-
param tickers: The tickers of the market
|
|
153
|
-
type tickers: dict
|
|
154
|
-
|
|
155
|
-
return: The growth of the backtest
|
|
156
|
-
"""
|
|
157
|
-
total_value = self.get_total_value(
|
|
158
|
-
portfolio_id, tickers, backtest_profile
|
|
159
|
-
)
|
|
160
|
-
return total_value - backtest_profile.initial_unallocated
|
|
161
|
-
|
|
162
|
-
def get_total_net_gain_percentage_of_backtest(
|
|
163
|
-
self, portfolio_id, backtest_profile
|
|
164
|
-
):
|
|
165
|
-
"""
|
|
166
|
-
Get the total net gain percentage of the backtest. This function
|
|
167
|
-
will calculate the total net gain percentage of the portfolio
|
|
168
|
-
by dividing the total net gain by the initial unallocated value
|
|
169
|
-
of the portfolio and then multiplying it by 100.
|
|
170
|
-
|
|
171
|
-
param portfolio_id: The id of the portfolio
|
|
172
|
-
type portfolio_id: str
|
|
173
|
-
param backtest_profile: The backtest profile
|
|
174
|
-
type backtest_profile: BacktestProfile
|
|
175
|
-
|
|
176
|
-
return: The total net gain percentage of the backtest
|
|
177
|
-
"""
|
|
178
|
-
portfolio = self.portfolio_repository.find({"id": portfolio_id})
|
|
179
|
-
|
|
180
|
-
if portfolio.total_net_gain == 0:
|
|
181
|
-
return 0
|
|
182
|
-
|
|
183
|
-
return portfolio.total_net_gain \
|
|
184
|
-
/ backtest_profile.initial_unallocated * 100
|
|
185
|
-
|
|
186
|
-
def get_total_value(self, portfolio_id, tickers, backtest_profile):
|
|
187
|
-
"""
|
|
188
|
-
Get the total value of the portfolio. This functions
|
|
189
|
-
will calculate the allocated value, pending buy value,
|
|
190
|
-
pending sell value and unallocated value.
|
|
191
|
-
|
|
192
|
-
At the end, it will sum all these values and return the
|
|
193
|
-
total value of the portfolio.
|
|
194
|
-
|
|
195
|
-
param portfolio_id: The id of the portfolio
|
|
196
|
-
type portfolio_id: str
|
|
197
|
-
param tickers: The tickers of the market
|
|
198
|
-
type tickers: dict
|
|
199
|
-
param backtest_profile: The backtest profile
|
|
200
|
-
type backtest_profile: BacktestProfile
|
|
201
|
-
|
|
202
|
-
return: The total value of the portfolio
|
|
203
|
-
rtype: float
|
|
204
|
-
"""
|
|
205
|
-
portfolio = self.portfolio_repository.find({"id": portfolio_id})
|
|
206
|
-
positions = self.position_repository.get_all(
|
|
207
|
-
{"portfolio_id": portfolio.id}
|
|
208
|
-
)
|
|
209
|
-
allocated = 0
|
|
210
|
-
|
|
211
|
-
for position in positions:
|
|
212
|
-
|
|
213
|
-
if position.symbol == portfolio.trading_symbol:
|
|
214
|
-
continue
|
|
215
|
-
|
|
216
|
-
ticker_symbol = f"{position.symbol.upper()}" \
|
|
217
|
-
f"/{portfolio.trading_symbol.upper()}"
|
|
218
|
-
if ticker_symbol not in tickers:
|
|
219
|
-
logger.warning(
|
|
220
|
-
f"Symbol {position.symbol} not found in tickers, "
|
|
221
|
-
f"cannot calculate the total value of the position"
|
|
222
|
-
)
|
|
223
|
-
continue
|
|
224
|
-
|
|
225
|
-
allocated += position.amount * tickers[ticker_symbol]["bid"]
|
|
226
|
-
|
|
227
|
-
# Calculate the pending sell value
|
|
228
|
-
pending_sell_orders = self.order_repository.get_all(
|
|
229
|
-
{
|
|
230
|
-
"portfolio_id": portfolio.id,
|
|
231
|
-
"status": OrderStatus.OPEN.value,
|
|
232
|
-
"order_side": OrderSide.SELL.value,
|
|
233
|
-
}
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
for order in pending_sell_orders:
|
|
237
|
-
|
|
238
|
-
if order.get_symbol() in tickers:
|
|
239
|
-
allocated += order.get_amount() \
|
|
240
|
-
* tickers[order.get_symbol()]["bid"]
|
|
241
|
-
else:
|
|
242
|
-
logger.warning(
|
|
243
|
-
f"Symbol {order.get_symbol()} not found in tickers, "
|
|
244
|
-
f"cannot calculate the total value of sell orders"
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
# Calculate the unallocated value by summing the unallocated and
|
|
248
|
-
# pending buy value
|
|
249
|
-
unallocated = portfolio.unallocated
|
|
250
|
-
pending_buy_orders = self.order_repository.get_all(
|
|
251
|
-
{
|
|
252
|
-
"portfolio_id": portfolio.id,
|
|
253
|
-
"status": OrderStatus.OPEN.value,
|
|
254
|
-
"order_side": OrderSide.BUY.value,
|
|
255
|
-
}
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
for order in pending_buy_orders:
|
|
259
|
-
if order.get_symbol() in tickers:
|
|
260
|
-
unallocated += order.get_amount() \
|
|
261
|
-
* tickers[order.get_symbol()]["ask"]
|
|
262
|
-
else:
|
|
263
|
-
logger.warning(
|
|
264
|
-
f"Symbol {order.get_symbol()} not found in tickers, "
|
|
265
|
-
f"cannot calculate the total value of buy orders"
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
# Add everything together
|
|
269
|
-
return allocated + unallocated
|
|
270
|
-
|
|
271
|
-
def get_average_trade_duration(self, portfolio_id):
|
|
272
|
-
"""
|
|
273
|
-
Get the average trade duration. This function will
|
|
274
|
-
calculate the average trade duration by summing the
|
|
275
|
-
duration of all the trades and then dividing it by the
|
|
276
|
-
total number of trades.
|
|
277
|
-
|
|
278
|
-
param portfolio_id: The id of the portfolio
|
|
279
|
-
type portfolio_id: str
|
|
280
|
-
|
|
281
|
-
return: The average trade duration
|
|
282
|
-
"""
|
|
283
|
-
portfolio = self.portfolio_repository.find({"id": portfolio_id})
|
|
284
|
-
trades = self.trade_repository.get_all(
|
|
285
|
-
{"portfolio_id": portfolio.id, "status": TradeStatus.CLOSED.value}
|
|
286
|
-
)
|
|
287
|
-
|
|
288
|
-
if len(trades) == 0:
|
|
289
|
-
return 0
|
|
290
|
-
|
|
291
|
-
total_duration = 0
|
|
292
|
-
|
|
293
|
-
for trade in trades:
|
|
294
|
-
duration = trade.closed_at - trade.opened_at
|
|
295
|
-
total_duration += duration.total_seconds() / 3600
|
|
296
|
-
|
|
297
|
-
return total_duration / len(trades)
|
|
298
|
-
|
|
299
|
-
def get_average_trade_size(self, portfolio_id):
|
|
300
|
-
"""
|
|
301
|
-
Get the average trade size. This function will calculate
|
|
302
|
-
the average trade size by summing the size of all the trades
|
|
303
|
-
and then dividing it by the total number of trades.
|
|
304
|
-
|
|
305
|
-
param portfolio_id: The id of the portfolio
|
|
306
|
-
type portfolio_id: str
|
|
307
|
-
|
|
308
|
-
return: The average trade size
|
|
309
|
-
"""
|
|
310
|
-
portfolio = self.portfolio_repository.find({"id": portfolio_id})
|
|
311
|
-
portfolio = self.portfolio_repository.find({"id": portfolio_id})
|
|
312
|
-
trades = self.trade_repository.get_all({"portfolio_id": portfolio.id})
|
|
313
|
-
|
|
314
|
-
if len(trades) == 0:
|
|
315
|
-
return 0
|
|
316
|
-
|
|
317
|
-
total_size = 0
|
|
318
|
-
|
|
319
|
-
for trade in trades:
|
|
320
|
-
total_size += trade.amount * trade.open_price
|
|
321
|
-
|
|
322
|
-
return total_size / len(trades)
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
from .backtest_market_data_source_service import \
|
|
2
|
-
BacktestMarketDataSourceService
|
|
3
|
-
from .market_data_source_service import MarketDataSourceService
|
|
4
|
-
from .data_provider_service import DataProviderService
|
|
5
|
-
|
|
6
|
-
__all__ = [
|
|
7
|
-
"MarketDataSourceService",
|
|
8
|
-
"BacktestMarketDataSourceService",
|
|
9
|
-
"DataProviderService"
|
|
10
|
-
]
|
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
from typing import List
|
|
2
|
-
|
|
3
|
-
from tqdm import tqdm
|
|
4
|
-
|
|
5
|
-
from investing_algorithm_framework.domain import MarketService, \
|
|
6
|
-
BacktestMarketDataSource, BACKTESTING_END_DATE, BACKTESTING_START_DATE, \
|
|
7
|
-
BACKTESTING_INDEX_DATETIME, OperationalException, OHLCVMarketDataSource, \
|
|
8
|
-
TickerMarketDataSource, OrderBookMarketDataSource, MarketDataType, \
|
|
9
|
-
TimeFrame
|
|
10
|
-
from investing_algorithm_framework.services.configuration_service import \
|
|
11
|
-
ConfigurationService
|
|
12
|
-
from investing_algorithm_framework.services.market_credential_service \
|
|
13
|
-
import MarketCredentialService
|
|
14
|
-
from .market_data_source_service import MarketDataSourceService
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class BacktestMarketDataSourceService(MarketDataSourceService):
|
|
18
|
-
"""
|
|
19
|
-
BacktestMarketDataSourceService is a subclass of MarketDataSourceService.
|
|
20
|
-
It is used to create market data sources for backtesting.
|
|
21
|
-
|
|
22
|
-
In the constructor, it takes a list of BacktestMarketDataSource objects.
|
|
23
|
-
These objects are used to prepare the data for backtesting.
|
|
24
|
-
|
|
25
|
-
The prepare_data method of BacktestMarketDataSource is called in the
|
|
26
|
-
constructor.
|
|
27
|
-
|
|
28
|
-
The main difference between MarketDataSourceService and
|
|
29
|
-
the BacktestMarketDataSourceService is that it will
|
|
30
|
-
prepare the data for backtesting in the constructor.
|
|
31
|
-
"""
|
|
32
|
-
def __init__(
|
|
33
|
-
self,
|
|
34
|
-
market_service: MarketService,
|
|
35
|
-
market_credential_service: MarketCredentialService,
|
|
36
|
-
configuration_service: ConfigurationService,
|
|
37
|
-
market_data_sources: List[BacktestMarketDataSource] = None
|
|
38
|
-
):
|
|
39
|
-
super().__init__(
|
|
40
|
-
market_service=market_service,
|
|
41
|
-
market_data_sources=None,
|
|
42
|
-
market_credential_service=market_credential_service,
|
|
43
|
-
configuration_service=configuration_service
|
|
44
|
-
)
|
|
45
|
-
self.market_data_sources = []
|
|
46
|
-
|
|
47
|
-
# Add all market data sources to the list
|
|
48
|
-
if market_data_sources is not None:
|
|
49
|
-
for market_data_source in market_data_sources:
|
|
50
|
-
self.add(market_data_source)
|
|
51
|
-
|
|
52
|
-
def initialize_market_data_sources(self):
|
|
53
|
-
config = self._configuration_service.get_config()
|
|
54
|
-
backtest_start_date = config[BACKTESTING_START_DATE]
|
|
55
|
-
backtest_end_date = config[BACKTESTING_END_DATE]
|
|
56
|
-
backtest_market_data_sources = [
|
|
57
|
-
market_data_source.to_backtest_market_data_source() for
|
|
58
|
-
market_data_source in self._market_data_sources
|
|
59
|
-
]
|
|
60
|
-
|
|
61
|
-
# Filter out the None values
|
|
62
|
-
backtest_market_data_sources = [
|
|
63
|
-
market_data_source for market_data_source in
|
|
64
|
-
backtest_market_data_sources if market_data_source is not None
|
|
65
|
-
]
|
|
66
|
-
|
|
67
|
-
for backtest_market_data_source in tqdm(
|
|
68
|
-
backtest_market_data_sources,
|
|
69
|
-
total=len(self._market_data_sources),
|
|
70
|
-
desc="Preparing backtest market data",
|
|
71
|
-
colour="GREEN"
|
|
72
|
-
):
|
|
73
|
-
backtest_market_data_source.market_credential_service = \
|
|
74
|
-
self._market_credential_service
|
|
75
|
-
backtest_market_data_source.prepare_data(
|
|
76
|
-
config=config,
|
|
77
|
-
backtest_start_date=backtest_start_date,
|
|
78
|
-
backtest_end_date=backtest_end_date
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
self.clear_market_data_sources()
|
|
82
|
-
self.market_data_sources = backtest_market_data_sources
|
|
83
|
-
|
|
84
|
-
def get_data(self, identifier):
|
|
85
|
-
"""
|
|
86
|
-
This method is used to get the data for backtesting. It loops
|
|
87
|
-
over all the backtest market data sources and returns the data
|
|
88
|
-
for the given identifier (If there is a match).
|
|
89
|
-
|
|
90
|
-
If there is no match, it raises an OperationalException.
|
|
91
|
-
|
|
92
|
-
Args:
|
|
93
|
-
identifier: The identifier of the market data source
|
|
94
|
-
|
|
95
|
-
Returns:
|
|
96
|
-
The data for the given identifier
|
|
97
|
-
"""
|
|
98
|
-
config = self._configuration_service.get_config()
|
|
99
|
-
backtest_index_date = config[BACKTESTING_INDEX_DATETIME]
|
|
100
|
-
|
|
101
|
-
for market_data_source in self._market_data_sources:
|
|
102
|
-
|
|
103
|
-
if market_data_source.get_identifier() == identifier:
|
|
104
|
-
config = self._configuration_service.get_config()
|
|
105
|
-
backtest_index_date = config[BACKTESTING_INDEX_DATETIME]
|
|
106
|
-
data = market_data_source.get_data(
|
|
107
|
-
date=backtest_index_date, config=config
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
result = {
|
|
111
|
-
"data": data,
|
|
112
|
-
"type": None,
|
|
113
|
-
"symbol": None,
|
|
114
|
-
"time_frame": None
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
# Add metadata to the data
|
|
118
|
-
if isinstance(market_data_source, OHLCVMarketDataSource):
|
|
119
|
-
result["type"] = MarketDataType.OHLCV
|
|
120
|
-
time_frame = TimeFrame.from_value(
|
|
121
|
-
market_data_source.time_frame
|
|
122
|
-
)
|
|
123
|
-
result["time_frame"] = time_frame.value
|
|
124
|
-
result["symbol"] = market_data_source.symbol
|
|
125
|
-
return result
|
|
126
|
-
|
|
127
|
-
if isinstance(market_data_source, TickerMarketDataSource):
|
|
128
|
-
result["type"] = MarketDataType.TICKER
|
|
129
|
-
result["time_frame"] = TimeFrame.CURRENT
|
|
130
|
-
result["symbol"] = market_data_source.symbol
|
|
131
|
-
return result
|
|
132
|
-
|
|
133
|
-
if isinstance(market_data_source, OrderBookMarketDataSource):
|
|
134
|
-
result["type"] = MarketDataType.ORDER_BOOK
|
|
135
|
-
result["time_frame"] = TimeFrame.CURRENT
|
|
136
|
-
result["symbol"] = market_data_source.symbol
|
|
137
|
-
return result
|
|
138
|
-
|
|
139
|
-
result["type"] = MarketDataType.CUSTOM
|
|
140
|
-
result["time_frame"] = TimeFrame.CURRENT
|
|
141
|
-
result["symbol"] = market_data_source.symbol
|
|
142
|
-
return result
|
|
143
|
-
|
|
144
|
-
raise OperationalException(
|
|
145
|
-
f"Backtest market data source not found for {identifier}"
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
def get_ticker(self, symbol, market=None):
|
|
149
|
-
ticker_market_data_source = self.get_ticker_market_data_source(
|
|
150
|
-
symbol=symbol, market=market
|
|
151
|
-
)
|
|
152
|
-
ohlcv_market_data_source = self.get_ohlcv_market_data_source(
|
|
153
|
-
symbol=symbol, market=market
|
|
154
|
-
)
|
|
155
|
-
if ohlcv_market_data_source is not None and \
|
|
156
|
-
ticker_market_data_source is None:
|
|
157
|
-
data = ohlcv_market_data_source.get_data(
|
|
158
|
-
date=self._configuration_service.get_config()[
|
|
159
|
-
BACKTESTING_INDEX_DATETIME
|
|
160
|
-
],
|
|
161
|
-
config=self._configuration_service.get_config()
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
# Get last row of the OHLCV data polars dataframe
|
|
165
|
-
close = data.tail(1)["Close"][0]
|
|
166
|
-
timestamp = data.tail(1)["Datetime"][0]
|
|
167
|
-
open = data.tail(1)["Open"][0]
|
|
168
|
-
high = data.tail(1)["High"][0]
|
|
169
|
-
low = data.tail(1)["Low"][0]
|
|
170
|
-
volume = data.tail(1)["Volume"][0]
|
|
171
|
-
|
|
172
|
-
spread = close * 0.001
|
|
173
|
-
bid = close - spread
|
|
174
|
-
ask = close + spread
|
|
175
|
-
|
|
176
|
-
ticker_data = {
|
|
177
|
-
"symbol": symbol,
|
|
178
|
-
"market": market,
|
|
179
|
-
"timestamp": timestamp,
|
|
180
|
-
# or the appropriate time column
|
|
181
|
-
"open": open,
|
|
182
|
-
"high": high,
|
|
183
|
-
"low": low,
|
|
184
|
-
"close": close,
|
|
185
|
-
"last": close,
|
|
186
|
-
"volume": volume,
|
|
187
|
-
"bid": bid,
|
|
188
|
-
"ask": ask,
|
|
189
|
-
}
|
|
190
|
-
return ticker_data
|
|
191
|
-
|
|
192
|
-
if ticker_market_data_source is None:
|
|
193
|
-
identifiers = self.get_market_data_source_identifiers()
|
|
194
|
-
raise OperationalException(
|
|
195
|
-
"Backtest ticker data source "
|
|
196
|
-
f"not found for {symbol} and market {market}, the available "
|
|
197
|
-
f"data sources are: {identifiers}"
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
config = self._configuration_service.get_config()
|
|
201
|
-
backtest_index_date = config[BACKTESTING_INDEX_DATETIME]
|
|
202
|
-
return ticker_market_data_source.get_data(
|
|
203
|
-
date=backtest_index_date, config=config
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
def get_order_book(self, symbol, market):
|
|
207
|
-
market_data_source = self.get_order_book_market_data_source(
|
|
208
|
-
symbol=symbol, market=market
|
|
209
|
-
)
|
|
210
|
-
market_data_source.market_credential_service = \
|
|
211
|
-
self._market_credential_service
|
|
212
|
-
config = self._configuration_service.get_config()
|
|
213
|
-
backtest_index_date = config[BACKTESTING_INDEX_DATETIME]
|
|
214
|
-
return market_data_source.get_data(
|
|
215
|
-
date=backtest_index_date, config=config
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
def get_ohlcv(
|
|
219
|
-
self,
|
|
220
|
-
symbol,
|
|
221
|
-
from_timestamp,
|
|
222
|
-
time_frame=None,
|
|
223
|
-
market=None,
|
|
224
|
-
to_timestamp=None
|
|
225
|
-
):
|
|
226
|
-
market_data_source = self.get_ohlcv_market_data_source(
|
|
227
|
-
symbol=symbol, market=market, time_frame=time_frame
|
|
228
|
-
)
|
|
229
|
-
market_data_source.market_credential_service = \
|
|
230
|
-
self._market_credential_service
|
|
231
|
-
config = self._configuration_service.get_config()
|
|
232
|
-
backtest_index_date = config[BACKTESTING_INDEX_DATETIME]
|
|
233
|
-
return market_data_source.get_data(
|
|
234
|
-
date=backtest_index_date, config=config
|
|
235
|
-
)
|
|
236
|
-
|
|
237
|
-
def is_ohlcv_data_source_present(self, symbol, time_frame, market):
|
|
238
|
-
market_data_source = self.get_ohlcv_market_data_source(
|
|
239
|
-
symbol=symbol, market=market, time_frame=time_frame
|
|
240
|
-
)
|
|
241
|
-
return market_data_source is not None
|
|
242
|
-
|
|
243
|
-
def add(self, market_data_source):
|
|
244
|
-
|
|
245
|
-
if market_data_source is None:
|
|
246
|
-
return
|
|
247
|
-
|
|
248
|
-
# Check if there is already a market data source with the same
|
|
249
|
-
# identifier
|
|
250
|
-
for existing_market_data_source in self._market_data_sources:
|
|
251
|
-
if existing_market_data_source.get_identifier() == \
|
|
252
|
-
market_data_source.get_identifier():
|
|
253
|
-
return
|
|
254
|
-
|
|
255
|
-
self._market_data_sources.append(market_data_source)
|
|
256
|
-
|
|
257
|
-
def has_ticker_market_data_source(self, symbol, market=None):
|
|
258
|
-
ticker_market_data_source = self.get_ticker_market_data_source(
|
|
259
|
-
symbol=symbol, market=market
|
|
260
|
-
)
|
|
261
|
-
ohlcv_market_data_source = self.get_ohlcv_market_data_source(
|
|
262
|
-
symbol=symbol, market=market
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
if ohlcv_market_data_source is not None or \
|
|
266
|
-
ticker_market_data_source is not None:
|
|
267
|
-
return True
|
|
268
|
-
|
|
269
|
-
return False
|