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
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
from typing import List, Dict
|
|
2
|
-
|
|
3
|
-
from investing_algorithm_framework.domain.exceptions import \
|
|
4
|
-
OperationalException
|
|
5
|
-
from investing_algorithm_framework.domain.models.backtesting\
|
|
6
|
-
.backtest_date_range import BacktestDateRange
|
|
7
|
-
from .backtest_report import BacktestReport
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class BacktestReportsEvaluation:
|
|
11
|
-
"""
|
|
12
|
-
A class to evaluate backtest reports.
|
|
13
|
-
|
|
14
|
-
This class is used to evaluate backtest reports and order them based on
|
|
15
|
-
different metrics. It also groups the reports by backtest date range.
|
|
16
|
-
|
|
17
|
-
The class has methods to get the best algorithm based on profit and growth.
|
|
18
|
-
"""
|
|
19
|
-
report_groupings: Dict[BacktestDateRange, List[BacktestReport]]
|
|
20
|
-
|
|
21
|
-
def __init__(self, backtest_reports: List[BacktestReport]):
|
|
22
|
-
|
|
23
|
-
if backtest_reports is None:
|
|
24
|
-
raise OperationalException("No backtest reports to evaluate")
|
|
25
|
-
|
|
26
|
-
self.backtest_reports = backtest_reports
|
|
27
|
-
self.group_reports(self.backtest_reports)
|
|
28
|
-
self.profit_order = {}
|
|
29
|
-
self.profit_order_grouped_by_date = {}
|
|
30
|
-
self.growth_order = {}
|
|
31
|
-
self.growth_order_grouped_by_date = {}
|
|
32
|
-
self.percentage_positive_trades_order = {}
|
|
33
|
-
self.percentage_positive_trades_order_grouped_by_date = {}
|
|
34
|
-
|
|
35
|
-
# Calculate all metrics and group reports
|
|
36
|
-
for key in self.report_groupings:
|
|
37
|
-
self.profit_order_grouped_by_date[key] = self.order_on_profit(
|
|
38
|
-
self.report_groupings[key].copy()
|
|
39
|
-
)
|
|
40
|
-
self.growth_order_grouped_by_date[key] = self.order_on_growth(
|
|
41
|
-
self.report_groupings[key].copy()
|
|
42
|
-
)
|
|
43
|
-
self.percentage_positive_trades_order_grouped_by_date[key] = \
|
|
44
|
-
self.order_on_positive_trades(
|
|
45
|
-
self.report_groupings[key].copy()
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
# Overall ordered reports
|
|
49
|
-
self.percentage_positive_trades_order = \
|
|
50
|
-
self.order_on_positive_trades(self.backtest_reports)
|
|
51
|
-
self.growth_order = self.order_on_growth(self.backtest_reports)
|
|
52
|
-
self.profit_order = self.order_on_profit(self.backtest_reports)
|
|
53
|
-
|
|
54
|
-
def order_on_profit(self, reports: List[BacktestReport]):
|
|
55
|
-
return sorted(reports, key=lambda x: x.total_net_gain, reverse=True)
|
|
56
|
-
|
|
57
|
-
def order_on_growth(self, reports: List[BacktestReport]):
|
|
58
|
-
return sorted(reports, key=lambda x: x.growth_rate, reverse=True)
|
|
59
|
-
|
|
60
|
-
def order_on_positive_trades(self, reports: List[BacktestReport]):
|
|
61
|
-
return sorted(
|
|
62
|
-
reports, key=lambda x: x.percentage_positive_trades, reverse=True
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
def group_reports(self, reports: List[BacktestReport]):
|
|
66
|
-
"""
|
|
67
|
-
Group reports by backtest start and end date.
|
|
68
|
-
"""
|
|
69
|
-
self.report_groupings = {}
|
|
70
|
-
|
|
71
|
-
for report in reports:
|
|
72
|
-
key = report.backtest_date_range
|
|
73
|
-
if key not in self.report_groupings:
|
|
74
|
-
self.report_groupings[key] = []
|
|
75
|
-
self.report_groupings[key].append(report)
|
|
76
|
-
|
|
77
|
-
def get_date_ranges(self):
|
|
78
|
-
"""
|
|
79
|
-
Get the date ranges of the backtest reports.
|
|
80
|
-
"""
|
|
81
|
-
return list(self.report_groupings.keys())
|
|
82
|
-
|
|
83
|
-
def get_profit_order(
|
|
84
|
-
self, backtest_date_range: BacktestDateRange = None
|
|
85
|
-
) -> List[BacktestReport]:
|
|
86
|
-
"""
|
|
87
|
-
Function to get profit order of all the backtest reports
|
|
88
|
-
in an ordered list.
|
|
89
|
-
|
|
90
|
-
:param backtest_date_range: Tuple with two datetime objects
|
|
91
|
-
:return: List of backtest reports
|
|
92
|
-
"""
|
|
93
|
-
|
|
94
|
-
if backtest_date_range is None:
|
|
95
|
-
return self.profit_order
|
|
96
|
-
else:
|
|
97
|
-
|
|
98
|
-
if backtest_date_range in self.profit_order_grouped_by_date:
|
|
99
|
-
return self.profit_order_grouped_by_date[backtest_date_range]
|
|
100
|
-
|
|
101
|
-
raise OperationalException("No matches for given date range")
|
|
102
|
-
|
|
103
|
-
def get_growth_order(
|
|
104
|
-
self, backtest_date_range: BacktestDateRange = None
|
|
105
|
-
) -> List[BacktestReport]:
|
|
106
|
-
"""
|
|
107
|
-
Function to get growth order of all the backtest reports
|
|
108
|
-
in an ordered list.
|
|
109
|
-
|
|
110
|
-
:param backtest_date_range: Tuple with two datetime objects
|
|
111
|
-
:return: List of backtest reports
|
|
112
|
-
"""
|
|
113
|
-
|
|
114
|
-
if backtest_date_range is None:
|
|
115
|
-
return self.growth_order
|
|
116
|
-
else:
|
|
117
|
-
|
|
118
|
-
if backtest_date_range in self.growth_order_grouped_by_date:
|
|
119
|
-
return self.growth_order_grouped_by_date[backtest_date_range]
|
|
120
|
-
|
|
121
|
-
raise OperationalException("No matches for given date range")
|
|
122
|
-
|
|
123
|
-
def get_percentage_positive_trades_order(
|
|
124
|
-
self, backtest_date_range: BacktestDateRange = None
|
|
125
|
-
) -> List[BacktestReport]:
|
|
126
|
-
"""
|
|
127
|
-
Function to get growth order of all the backtest reports
|
|
128
|
-
in an ordered list.
|
|
129
|
-
|
|
130
|
-
:param backtest_date_range: Tuple with two datetime objects
|
|
131
|
-
:return: List of backtest reports
|
|
132
|
-
"""
|
|
133
|
-
|
|
134
|
-
if backtest_date_range is None:
|
|
135
|
-
return self.percentage_positive_trades_order
|
|
136
|
-
else:
|
|
137
|
-
|
|
138
|
-
if backtest_date_range in \
|
|
139
|
-
self.percentage_positive_trades_order_grouped_by_date:
|
|
140
|
-
return self.percentage_positive_trades_order_grouped_by_date[
|
|
141
|
-
backtest_date_range
|
|
142
|
-
]
|
|
143
|
-
|
|
144
|
-
raise OperationalException("No matches for given date range")
|
|
145
|
-
|
|
146
|
-
def rank(
|
|
147
|
-
self,
|
|
148
|
-
weight_profit=0.7,
|
|
149
|
-
weight_growth=0.3,
|
|
150
|
-
backtest_date_range: BacktestDateRange = None
|
|
151
|
-
) -> str:
|
|
152
|
-
"""
|
|
153
|
-
Function to get the best overall algorithm based on the
|
|
154
|
-
weighted sum of profit and growth.
|
|
155
|
-
|
|
156
|
-
:param weight_profit: Weight for profit
|
|
157
|
-
:param weight_growth: Weight for growth
|
|
158
|
-
:return: Name of the best algorithm
|
|
159
|
-
"""
|
|
160
|
-
|
|
161
|
-
# Create a dictionary with the algorithm name as key and group
|
|
162
|
-
# the reports by name
|
|
163
|
-
ordered_reports = {}
|
|
164
|
-
|
|
165
|
-
if backtest_date_range is not None:
|
|
166
|
-
reports = self.report_groupings[backtest_date_range]
|
|
167
|
-
else:
|
|
168
|
-
reports = self.backtest_reports
|
|
169
|
-
|
|
170
|
-
for report in reports:
|
|
171
|
-
if report.name not in ordered_reports:
|
|
172
|
-
ordered_reports[report.name] = []
|
|
173
|
-
ordered_reports[report.name].append(report)
|
|
174
|
-
|
|
175
|
-
best_algorithm = None
|
|
176
|
-
best_score = 0
|
|
177
|
-
profit_score = 0
|
|
178
|
-
growth_score = 0
|
|
179
|
-
|
|
180
|
-
for algorithm in ordered_reports:
|
|
181
|
-
profit_score += sum(
|
|
182
|
-
[report.total_net_gain for
|
|
183
|
-
report in ordered_reports[algorithm]]
|
|
184
|
-
)
|
|
185
|
-
growth_score += sum(
|
|
186
|
-
[report.growth for report in
|
|
187
|
-
ordered_reports[algorithm]]
|
|
188
|
-
)
|
|
189
|
-
score = weight_profit * profit_score + weight_growth * growth_score
|
|
190
|
-
|
|
191
|
-
if score > best_score:
|
|
192
|
-
best_score = score
|
|
193
|
-
best_algorithm = algorithm
|
|
194
|
-
|
|
195
|
-
profit_score = 0
|
|
196
|
-
growth_score = 0
|
|
197
|
-
|
|
198
|
-
return best_algorithm
|
|
199
|
-
|
|
200
|
-
def get_reports(
|
|
201
|
-
self, name: str = None, backtest_date_range: BacktestDateRange = None
|
|
202
|
-
) -> List[BacktestReport]:
|
|
203
|
-
"""
|
|
204
|
-
Function to get all the reports for a given algorithm name.
|
|
205
|
-
|
|
206
|
-
:param name: Name of the algorithm
|
|
207
|
-
:param backtest_date_range: Tuple with two datetime objects
|
|
208
|
-
:return: List of backtest reports
|
|
209
|
-
"""
|
|
210
|
-
|
|
211
|
-
if name is not None and backtest_date_range is not None:
|
|
212
|
-
return [
|
|
213
|
-
report for report in self.report_groupings[backtest_date_range]
|
|
214
|
-
if report.name == name
|
|
215
|
-
]
|
|
216
|
-
|
|
217
|
-
if name is not None:
|
|
218
|
-
return [
|
|
219
|
-
report for report in self.backtest_reports
|
|
220
|
-
if report.name == name
|
|
221
|
-
]
|
|
222
|
-
|
|
223
|
-
if backtest_date_range is not None:
|
|
224
|
-
return self.report_groupings[backtest_date_range]
|
|
225
|
-
|
|
226
|
-
def get_report(
|
|
227
|
-
self, name: str, backtest_date_range: BacktestDateRange = None
|
|
228
|
-
):
|
|
229
|
-
"""
|
|
230
|
-
Function to get the report for a given algorithm name and date range.
|
|
231
|
-
|
|
232
|
-
:param name: Name of the algorithm
|
|
233
|
-
:param backtest_date_range: Tuple with two datetime objects
|
|
234
|
-
:return: Backtest report
|
|
235
|
-
"""
|
|
236
|
-
reports = self.get_reports(name, backtest_date_range)
|
|
237
|
-
|
|
238
|
-
if len(reports) == 0:
|
|
239
|
-
raise OperationalException(
|
|
240
|
-
"No matches for given name and date range"
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
return reports[0]
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Volatility is a statistical measure of the dispersion of returns for a
|
|
3
|
-
given portfolio. In finance, it is commonly used as a proxy for risk.
|
|
4
|
-
This function calculates the standard deviation of daily log returns and
|
|
5
|
-
annualizes it, giving an estimate of how much the portfolio's value
|
|
6
|
-
fluctuates on a yearly basis.
|
|
7
|
-
|
|
8
|
-
| **Annual Volatility** | **Risk Level** | **Typical for...** | **Comments** |
|
|
9
|
-
| --------------------- | -------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
10
|
-
| **< 5%** | Very Low Risk | Cash equivalents, short-term bonds | Low return expectations; often used for capital preservation |
|
|
11
|
-
| **5% – 10%** | Low Risk | Diversified bond portfolios, conservative allocation strategies | Suitable for risk-averse investors |
|
|
12
|
-
| **10% – 15%** | Moderate Risk | Balanced portfolios, large-cap equity indexes (e.g., S\&P 500 ≈ \~15%) | Standard for traditional diversified portfolios |
|
|
13
|
-
| **15% – 25%** | High Risk | Growth stocks, hedge funds, active equity strategies | Higher return potential, but more drawdowns |
|
|
14
|
-
| **> 25%** | Very High Risk | Crypto, leveraged ETFs, speculative strategies | High potential returns, but prone to large losses; often not suitable long-term |
|
|
15
|
-
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
from typing import List
|
|
19
|
-
|
|
20
|
-
import pandas as pd
|
|
21
|
-
import numpy as np
|
|
22
|
-
|
|
23
|
-
from investing_algorithm_framework.domain import PortfolioSnapshot
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def get_annual_volatility(snapshots: List[PortfolioSnapshot]) -> float:
|
|
27
|
-
"""
|
|
28
|
-
Calculate the annualized volatility of portfolio net values.
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
snapshots (List[PortfolioSnapshot]): List of portfolio snapshots
|
|
32
|
-
from the backtest report.
|
|
33
|
-
|
|
34
|
-
Returns:
|
|
35
|
-
Float: Annualized volatility as a float
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
if len(snapshots) < 2:
|
|
39
|
-
return 0.0 # Not enough data to calculate volatility
|
|
40
|
-
|
|
41
|
-
# Build DataFrame from snapshots
|
|
42
|
-
records = [
|
|
43
|
-
(snapshot.total_value, snapshot.created_at) for snapshot in snapshots
|
|
44
|
-
]
|
|
45
|
-
df = pd.DataFrame(records, columns=['total_value', 'created_at'])
|
|
46
|
-
df['created_at'] = pd.to_datetime(df['created_at'])
|
|
47
|
-
df = df.sort_values('created_at').drop_duplicates('created_at').copy()
|
|
48
|
-
|
|
49
|
-
# Calculate log returns
|
|
50
|
-
df['log_return'] = np.log(df['total_value'] / df['total_value'].shift(1))
|
|
51
|
-
df = df.dropna()
|
|
52
|
-
|
|
53
|
-
if df.empty:
|
|
54
|
-
return 0.0
|
|
55
|
-
|
|
56
|
-
daily_volatility = df['log_return'].std()
|
|
57
|
-
|
|
58
|
-
start_date = snapshots[0].created_at
|
|
59
|
-
end_date = snapshots[-1].created_at
|
|
60
|
-
# Estimate trading days per year based on snapshot frequency
|
|
61
|
-
total_days = (end_date - start_date).days
|
|
62
|
-
num_observations = len(df)
|
|
63
|
-
|
|
64
|
-
if total_days > 0:
|
|
65
|
-
trading_days_per_year = (num_observations / total_days) * 365
|
|
66
|
-
else:
|
|
67
|
-
trading_days_per_year = 365 # Default fallback
|
|
68
|
-
|
|
69
|
-
return daily_volatility * np.sqrt(trading_days_per_year)
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
|
-
from dateutil.parser import parse
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class BacktestDateRange:
|
|
6
|
-
"""
|
|
7
|
-
Represents a date range for a backtest
|
|
8
|
-
"""
|
|
9
|
-
def __init__(self, start_date, end_date=None, name=None):
|
|
10
|
-
|
|
11
|
-
if isinstance(start_date, str):
|
|
12
|
-
start_date = parse(start_date)
|
|
13
|
-
|
|
14
|
-
if end_date is not None and isinstance(end_date, str):
|
|
15
|
-
end_date = parse(end_date)
|
|
16
|
-
|
|
17
|
-
self._start_date = start_date
|
|
18
|
-
self._end_date = end_date
|
|
19
|
-
self._name = name
|
|
20
|
-
|
|
21
|
-
if end_date is None:
|
|
22
|
-
self._end_date = datetime.now()
|
|
23
|
-
|
|
24
|
-
if end_date < start_date:
|
|
25
|
-
raise ValueError(
|
|
26
|
-
"End date cannot be before start date for a backtest "
|
|
27
|
-
"date range. " +
|
|
28
|
-
f"(start_date: {start_date}, end_date: {end_date})"
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
@property
|
|
32
|
-
def start_date(self):
|
|
33
|
-
return self._start_date
|
|
34
|
-
|
|
35
|
-
@property
|
|
36
|
-
def end_date(self):
|
|
37
|
-
return self._end_date
|
|
38
|
-
|
|
39
|
-
@property
|
|
40
|
-
def name(self):
|
|
41
|
-
return self._name
|
|
42
|
-
|
|
43
|
-
def __repr__(self):
|
|
44
|
-
return f"{self.name}: {self._start_date} - {self._end_date}"
|
|
45
|
-
|
|
46
|
-
def __str__(self):
|
|
47
|
-
return self.__repr__()
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
from investing_algorithm_framework.domain.models.base_model import BaseModel
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class BacktestPosition(BaseModel):
|
|
5
|
-
|
|
6
|
-
def __init__(
|
|
7
|
-
self,
|
|
8
|
-
position,
|
|
9
|
-
trading_symbol=False,
|
|
10
|
-
amount_pending_buy=0.0,
|
|
11
|
-
amount_pending_sell=0.0,
|
|
12
|
-
total_value_portfolio=0.0
|
|
13
|
-
):
|
|
14
|
-
self._symbol = position.symbol
|
|
15
|
-
self._amount = position.amount + amount_pending_sell
|
|
16
|
-
self._cost = position.cost
|
|
17
|
-
self._price = 0.0
|
|
18
|
-
self._trading_symbol = trading_symbol
|
|
19
|
-
self._amount_pending_buy = amount_pending_buy
|
|
20
|
-
self._amount_pending_sell = amount_pending_sell
|
|
21
|
-
self._total_value_portfolio = total_value_portfolio
|
|
22
|
-
|
|
23
|
-
@property
|
|
24
|
-
def price(self):
|
|
25
|
-
return self._price
|
|
26
|
-
|
|
27
|
-
@price.setter
|
|
28
|
-
def price(self, value):
|
|
29
|
-
self._price = value
|
|
30
|
-
|
|
31
|
-
@property
|
|
32
|
-
def cost(self):
|
|
33
|
-
|
|
34
|
-
if self._trading_symbol:
|
|
35
|
-
return self.amount
|
|
36
|
-
|
|
37
|
-
return self._cost
|
|
38
|
-
|
|
39
|
-
@property
|
|
40
|
-
def value(self):
|
|
41
|
-
|
|
42
|
-
if self._trading_symbol:
|
|
43
|
-
return self.amount
|
|
44
|
-
|
|
45
|
-
return self._price * self.amount
|
|
46
|
-
|
|
47
|
-
@property
|
|
48
|
-
def growth(self):
|
|
49
|
-
|
|
50
|
-
if self._amount == 0:
|
|
51
|
-
return 0.0
|
|
52
|
-
|
|
53
|
-
if self._trading_symbol:
|
|
54
|
-
return 0.0
|
|
55
|
-
|
|
56
|
-
return self.value - self.cost
|
|
57
|
-
|
|
58
|
-
@property
|
|
59
|
-
def growth_rate(self):
|
|
60
|
-
|
|
61
|
-
if self._trading_symbol:
|
|
62
|
-
return 0.0
|
|
63
|
-
|
|
64
|
-
if self.cost == 0:
|
|
65
|
-
return 0.0
|
|
66
|
-
|
|
67
|
-
return self.growth / self.cost * 100
|
|
68
|
-
|
|
69
|
-
@property
|
|
70
|
-
def symbol(self):
|
|
71
|
-
return self._symbol
|
|
72
|
-
|
|
73
|
-
@property
|
|
74
|
-
def amount(self):
|
|
75
|
-
return self._amount
|
|
76
|
-
|
|
77
|
-
@property
|
|
78
|
-
def amount_pending_sell(self):
|
|
79
|
-
return self._amount_pending_sell
|
|
80
|
-
|
|
81
|
-
@amount_pending_sell.setter
|
|
82
|
-
def amount_pending_sell(self, value):
|
|
83
|
-
self._amount_pending_sell = value
|
|
84
|
-
|
|
85
|
-
@property
|
|
86
|
-
def amount_pending_buy(self):
|
|
87
|
-
return self._amount_pending_buy
|
|
88
|
-
|
|
89
|
-
@amount_pending_buy.setter
|
|
90
|
-
def amount_pending_buy(self, value):
|
|
91
|
-
self._amount_pending_buy = value
|
|
92
|
-
|
|
93
|
-
@property
|
|
94
|
-
def percentage_of_portfolio(self):
|
|
95
|
-
|
|
96
|
-
if self._total_value_portfolio == 0:
|
|
97
|
-
return 0.0
|
|
98
|
-
|
|
99
|
-
return (self.value / self._total_value_portfolio) * 100
|
|
100
|
-
|
|
101
|
-
def get_percentage_of_portfolio(self):
|
|
102
|
-
|
|
103
|
-
if self._total_value_portfolio == 0:
|
|
104
|
-
return 0.0
|
|
105
|
-
|
|
106
|
-
return self.value / self._total_value_portfolio * 100
|
|
107
|
-
|
|
108
|
-
def to_dict(self):
|
|
109
|
-
return {
|
|
110
|
-
"symbol": self.symbol,
|
|
111
|
-
"amount": self.amount,
|
|
112
|
-
"cost": self.cost,
|
|
113
|
-
"price": self.price,
|
|
114
|
-
"value": self.value,
|
|
115
|
-
"growth": self.growth,
|
|
116
|
-
"growth_rate": self.growth_rate,
|
|
117
|
-
"amount_pending_buy": self.amount_pending_buy,
|
|
118
|
-
"amount_pending_sell": self.amount_pending_sell,
|
|
119
|
-
"percentage_of_portfolio": self.percentage_of_portfolio
|
|
120
|
-
}
|
|
File without changes
|