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
|
@@ -3,10 +3,9 @@ from .sortino_ratio import get_sortino_ratio
|
|
|
3
3
|
from .drawdown import get_drawdown_series, get_max_drawdown
|
|
4
4
|
from .equity_curve import get_equity_curve
|
|
5
5
|
from .price_efficiency import get_price_efficiency_ratio
|
|
6
|
-
from .sharp_ratio import get_sharpe_ratio
|
|
7
6
|
from .profit_factor import get_profit_factor, \
|
|
8
7
|
get_cumulative_profit_factor_series, get_rolling_profit_factor_series
|
|
9
|
-
from .
|
|
8
|
+
from .sharpe_ratio import get_sharpe_ratio, get_rolling_sharpe_ratio
|
|
10
9
|
from .price_efficiency import get_price_efficiency_ratio
|
|
11
10
|
from .equity_curve import get_equity_curve
|
|
12
11
|
from .drawdown import get_drawdown_series, get_max_drawdown, \
|
|
@@ -16,17 +15,29 @@ from .cagr import get_cagr
|
|
|
16
15
|
from .standard_deviation import get_standard_deviation_downside_returns, \
|
|
17
16
|
get_standard_deviation_returns
|
|
18
17
|
from .returns import get_yearly_returns, get_monthly_returns, \
|
|
19
|
-
get_best_year, get_best_month, get_worst_month,
|
|
20
|
-
|
|
21
|
-
get_average_gain, get_average_loss, get_average_monthly_return, \
|
|
18
|
+
get_best_year, get_best_month, get_worst_month, get_total_return, \
|
|
19
|
+
get_average_yearly_return, get_average_monthly_return, \
|
|
22
20
|
get_percentage_winning_months, get_average_monthly_return_losing_months, \
|
|
23
|
-
get_average_monthly_return_winning_months,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
from .exposure import
|
|
27
|
-
get_trade_frequency, get_trades_per_day, get_trades_per_year
|
|
28
|
-
|
|
21
|
+
get_average_monthly_return_winning_months, get_total_growth, \
|
|
22
|
+
get_percentage_winning_years, get_worst_year, get_cumulative_return, \
|
|
23
|
+
get_total_loss, get_cumulative_return_series
|
|
24
|
+
from .exposure import get_average_trade_duration, \
|
|
25
|
+
get_trade_frequency, get_trades_per_day, get_trades_per_year, \
|
|
26
|
+
get_cumulative_exposure, get_exposure_ratio
|
|
27
|
+
from .win_rate import get_win_rate, get_win_loss_ratio, get_current_win_rate, \
|
|
28
|
+
get_current_win_loss_ratio
|
|
29
29
|
from .calmar_ratio import get_calmar_ratio
|
|
30
|
+
from .generate import create_backtest_metrics, \
|
|
31
|
+
create_backtest_metrics_for_backtest
|
|
32
|
+
from .risk_free_rate import get_risk_free_rate_us
|
|
33
|
+
from .trades import get_negative_trades, get_positive_trades, \
|
|
34
|
+
get_number_of_trades, get_number_of_closed_trades, \
|
|
35
|
+
get_average_trade_size, get_average_trade_return, get_best_trade, \
|
|
36
|
+
get_worst_trade, get_average_trade_gain, get_median_trade_return, \
|
|
37
|
+
get_average_trade_loss, get_current_average_trade_loss, \
|
|
38
|
+
get_current_average_trade_duration, get_current_average_trade_gain, \
|
|
39
|
+
get_current_average_trade_return, get_number_of_open_trades, \
|
|
40
|
+
get_average_trade_duration
|
|
30
41
|
|
|
31
42
|
__all__ = [
|
|
32
43
|
"get_annual_volatility",
|
|
@@ -45,8 +56,10 @@ __all__ = [
|
|
|
45
56
|
"get_standard_deviation_downside_returns",
|
|
46
57
|
"get_max_drawdown_absolute",
|
|
47
58
|
"get_total_return",
|
|
48
|
-
"
|
|
49
|
-
"
|
|
59
|
+
"get_total_loss",
|
|
60
|
+
"get_total_growth",
|
|
61
|
+
"get_cumulative_exposure",
|
|
62
|
+
"get_exposure_ratio",
|
|
50
63
|
"get_win_rate",
|
|
51
64
|
"get_win_loss_ratio",
|
|
52
65
|
"get_calmar_ratio",
|
|
@@ -60,8 +73,6 @@ __all__ = [
|
|
|
60
73
|
"get_best_trade",
|
|
61
74
|
"get_worst_trade",
|
|
62
75
|
"get_average_yearly_return",
|
|
63
|
-
"get_average_gain",
|
|
64
|
-
"get_average_loss",
|
|
65
76
|
"get_average_monthly_return",
|
|
66
77
|
"get_percentage_winning_months",
|
|
67
78
|
"get_average_trade_duration",
|
|
@@ -76,8 +87,28 @@ __all__ = [
|
|
|
76
87
|
"get_trades_per_year",
|
|
77
88
|
"get_average_monthly_return_losing_months",
|
|
78
89
|
"get_average_monthly_return_winning_months",
|
|
79
|
-
"get_best_trade_date",
|
|
80
|
-
"get_worst_trade_date",
|
|
81
90
|
"get_percentage_winning_years",
|
|
82
91
|
"get_rolling_sharpe_ratio",
|
|
92
|
+
"create_backtest_metrics",
|
|
93
|
+
"get_risk_free_rate_us",
|
|
94
|
+
"get_median_trade_return",
|
|
95
|
+
"get_average_trade_gain",
|
|
96
|
+
"get_average_trade_loss",
|
|
97
|
+
"get_average_trade_size",
|
|
98
|
+
"get_average_trade_return",
|
|
99
|
+
"get_number_of_trades",
|
|
100
|
+
"get_number_of_closed_trades",
|
|
101
|
+
"get_negative_trades",
|
|
102
|
+
"get_positive_trades",
|
|
103
|
+
"get_cumulative_return",
|
|
104
|
+
"get_cumulative_return_series",
|
|
105
|
+
"get_current_win_rate",
|
|
106
|
+
"get_current_win_loss_ratio",
|
|
107
|
+
"get_current_average_trade_loss",
|
|
108
|
+
"get_current_average_trade_duration",
|
|
109
|
+
"get_current_average_trade_gain",
|
|
110
|
+
"get_current_average_trade_return",
|
|
111
|
+
"get_number_of_open_trades",
|
|
112
|
+
"get_average_trade_duration",
|
|
113
|
+
"create_backtest_metrics_for_backtest"
|
|
83
114
|
]
|
|
@@ -13,11 +13,11 @@ peak-to-trough decline of a portfolio:
|
|
|
13
13
|
from typing import List, Tuple
|
|
14
14
|
import pandas as pd
|
|
15
15
|
from datetime import datetime
|
|
16
|
-
from investing_algorithm_framework.domain import PortfolioSnapshot
|
|
16
|
+
from investing_algorithm_framework.domain import PortfolioSnapshot, Trade
|
|
17
17
|
from .equity_curve import get_equity_curve
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def get_drawdown_series(snapshots: List[PortfolioSnapshot]) -> List[Tuple[
|
|
20
|
+
def get_drawdown_series(snapshots: List[PortfolioSnapshot]) -> List[Tuple[float, datetime]]:
|
|
21
21
|
"""
|
|
22
22
|
Calculate the drawdown series of a backtest report.
|
|
23
23
|
|
|
@@ -39,12 +39,12 @@ def get_drawdown_series(snapshots: List[PortfolioSnapshot]) -> List[Tuple[dateti
|
|
|
39
39
|
drawdown_series = []
|
|
40
40
|
max_value = None
|
|
41
41
|
|
|
42
|
-
for
|
|
42
|
+
for value, timestamp in equity_curve:
|
|
43
43
|
if max_value is None:
|
|
44
44
|
max_value = value
|
|
45
45
|
max_value = max(max_value, value)
|
|
46
46
|
drawdown = (value - max_value) / max_value # This will be <= 0
|
|
47
|
-
drawdown_series.append((
|
|
47
|
+
drawdown_series.append((drawdown, timestamp))
|
|
48
48
|
|
|
49
49
|
return drawdown_series
|
|
50
50
|
|
|
@@ -69,10 +69,10 @@ def get_max_drawdown(snapshots: List[PortfolioSnapshot]) -> float:
|
|
|
69
69
|
if not equity_curve:
|
|
70
70
|
return 0.0
|
|
71
71
|
|
|
72
|
-
peak = equity_curve[0][
|
|
72
|
+
peak = equity_curve[0][0]
|
|
73
73
|
max_drawdown_pct = 0.0
|
|
74
74
|
|
|
75
|
-
for
|
|
75
|
+
for equity, _ in equity_curve:
|
|
76
76
|
if equity > peak:
|
|
77
77
|
peak = equity
|
|
78
78
|
|
|
@@ -134,11 +134,11 @@ def get_max_drawdown_duration(snapshots: List[PortfolioSnapshot]) -> int:
|
|
|
134
134
|
if not equity_curve:
|
|
135
135
|
return 0
|
|
136
136
|
|
|
137
|
-
peak = equity_curve[0][
|
|
137
|
+
peak = equity_curve[0][0]
|
|
138
138
|
max_duration = 0
|
|
139
139
|
current_duration = 0
|
|
140
140
|
|
|
141
|
-
for
|
|
141
|
+
for equity, _ in equity_curve:
|
|
142
142
|
if equity < peak:
|
|
143
143
|
current_duration += 1
|
|
144
144
|
else:
|
|
@@ -168,10 +168,10 @@ def get_max_drawdown_absolute(snapshots: List[PortfolioSnapshot]) -> float:
|
|
|
168
168
|
if not equity_curve:
|
|
169
169
|
return 0.0
|
|
170
170
|
|
|
171
|
-
peak = equity_curve[0][
|
|
171
|
+
peak = equity_curve[0][0]
|
|
172
172
|
max_drawdown = 0.0
|
|
173
173
|
|
|
174
|
-
for
|
|
174
|
+
for equity, _ in equity_curve:
|
|
175
175
|
if equity > peak:
|
|
176
176
|
peak = equity
|
|
177
177
|
|
|
@@ -5,7 +5,7 @@ from investing_algorithm_framework.domain import PortfolioSnapshot
|
|
|
5
5
|
|
|
6
6
|
def get_equity_curve(
|
|
7
7
|
snapshots: List[PortfolioSnapshot]
|
|
8
|
-
) -> list[tuple[
|
|
8
|
+
) -> list[tuple[float, datetime]]:
|
|
9
9
|
"""
|
|
10
10
|
Calculate the total size of the portfolio at each snapshot timestamp.
|
|
11
11
|
|
|
@@ -19,6 +19,6 @@ def get_equity_curve(
|
|
|
19
19
|
for snapshot in snapshots:
|
|
20
20
|
timestamp = snapshot.created_at
|
|
21
21
|
total_size = snapshot.total_value
|
|
22
|
-
series.append((
|
|
22
|
+
series.append((total_size, timestamp))
|
|
23
23
|
|
|
24
24
|
return series
|
|
@@ -6,12 +6,70 @@ Exposure around 1 means capital is nearly fully invested most of the time, but n
|
|
|
6
6
|
Low exposure (<1) means capital is mostly idle or only partially invested.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
from datetime import datetime, timedelta
|
|
9
10
|
from typing import List
|
|
10
|
-
|
|
11
|
+
|
|
11
12
|
from investing_algorithm_framework.domain import Trade
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
def
|
|
15
|
+
def get_exposure_ratio(
|
|
16
|
+
trades: List["Trade"], start_date: datetime, end_date: datetime
|
|
17
|
+
) -> float:
|
|
18
|
+
"""
|
|
19
|
+
Calculates the exposure ratio (time in market) as the fraction of the total
|
|
20
|
+
backtest duration where at least one position was open.
|
|
21
|
+
|
|
22
|
+
Unlike cumulative exposure, overlapping trades are not double-counted.
|
|
23
|
+
The result is always between 0 and 1.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
trades (List[Trade]): List of trades executed during the backtest.
|
|
27
|
+
start_date (datetime): The start date of the backtest.
|
|
28
|
+
end_date (datetime): The end date of the backtest.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
A float between 0 and 1 representing the exposure ratio.
|
|
32
|
+
"""
|
|
33
|
+
if not trades:
|
|
34
|
+
return 0.0
|
|
35
|
+
|
|
36
|
+
# Collect trade intervals
|
|
37
|
+
intervals = []
|
|
38
|
+
for trade in trades:
|
|
39
|
+
entry = max(trade.opened_at, start_date)
|
|
40
|
+
exit = min(trade.closed_at or end_date, end_date)
|
|
41
|
+
if exit > entry:
|
|
42
|
+
intervals.append((entry, exit))
|
|
43
|
+
|
|
44
|
+
if not intervals:
|
|
45
|
+
return 0.0
|
|
46
|
+
|
|
47
|
+
# Sort intervals by start time
|
|
48
|
+
intervals.sort(key=lambda x: x[0])
|
|
49
|
+
|
|
50
|
+
# Merge overlapping intervals
|
|
51
|
+
merged = []
|
|
52
|
+
current_start, current_end = intervals[0]
|
|
53
|
+
for start, end in intervals[1:]:
|
|
54
|
+
if start <= current_end: # overlap
|
|
55
|
+
current_end = max(current_end, end)
|
|
56
|
+
else:
|
|
57
|
+
merged.append((current_start, current_end))
|
|
58
|
+
current_start, current_end = start, end
|
|
59
|
+
merged.append((current_start, current_end))
|
|
60
|
+
|
|
61
|
+
# Total time with at least one open trade
|
|
62
|
+
total_exposed_time = sum((end - start for start, end in merged), timedelta(0))
|
|
63
|
+
|
|
64
|
+
backtest_duration = end_date - start_date
|
|
65
|
+
if backtest_duration.total_seconds() == 0:
|
|
66
|
+
return 0.0
|
|
67
|
+
|
|
68
|
+
return total_exposed_time.total_seconds() \
|
|
69
|
+
/ backtest_duration.total_seconds()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_cumulative_exposure(
|
|
15
73
|
trades: List[Trade], start_date: datetime, end_date: datetime
|
|
16
74
|
) -> float:
|
|
17
75
|
"""
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from logging import getLogger
|
|
3
|
+
|
|
4
|
+
from investing_algorithm_framework.domain import BacktestMetrics, \
|
|
5
|
+
BacktestRun, OperationalException, Backtest, BacktestDateRange
|
|
6
|
+
from .cagr import get_cagr
|
|
7
|
+
from .calmar_ratio import get_calmar_ratio
|
|
8
|
+
from .drawdown import get_drawdown_series, get_max_drawdown, \
|
|
9
|
+
get_max_daily_drawdown, get_max_drawdown_absolute, \
|
|
10
|
+
get_max_drawdown_duration
|
|
11
|
+
from .equity_curve import get_equity_curve
|
|
12
|
+
from .exposure import get_exposure_ratio, get_cumulative_exposure, \
|
|
13
|
+
get_trades_per_year, get_trades_per_day
|
|
14
|
+
from .profit_factor import get_profit_factor, get_gross_loss, get_gross_profit
|
|
15
|
+
from .returns import get_monthly_returns, get_yearly_returns, \
|
|
16
|
+
get_worst_year, get_best_year, get_best_month, get_worst_month, \
|
|
17
|
+
get_percentage_winning_months, get_percentage_winning_years, \
|
|
18
|
+
get_average_monthly_return, get_average_monthly_return_winning_months, \
|
|
19
|
+
get_average_monthly_return_losing_months, get_cumulative_return, \
|
|
20
|
+
get_cumulative_return_series
|
|
21
|
+
from .returns import get_total_return, get_final_value, get_total_loss, \
|
|
22
|
+
get_total_growth
|
|
23
|
+
from .sharpe_ratio import get_sharpe_ratio, get_rolling_sharpe_ratio
|
|
24
|
+
from .sortino_ratio import get_sortino_ratio
|
|
25
|
+
from .volatility import get_annual_volatility
|
|
26
|
+
from .win_rate import get_win_rate, get_win_loss_ratio, get_current_win_rate, \
|
|
27
|
+
get_current_win_loss_ratio
|
|
28
|
+
from .trades import get_average_trade_duration, get_average_trade_size, \
|
|
29
|
+
get_number_of_trades, get_positive_trades, get_number_of_closed_trades, \
|
|
30
|
+
get_negative_trades, get_average_trade_return, get_number_of_open_trades, \
|
|
31
|
+
get_worst_trade, get_best_trade, get_average_trade_gain, \
|
|
32
|
+
get_average_trade_loss, get_median_trade_return, \
|
|
33
|
+
get_current_average_trade_gain, get_current_average_trade_return, \
|
|
34
|
+
get_current_average_trade_duration, get_current_average_trade_loss
|
|
35
|
+
|
|
36
|
+
logger = getLogger("investing_algorithm_framework")
|
|
37
|
+
|
|
38
|
+
def create_backtest_metrics_for_backtest(
|
|
39
|
+
backtest: Backtest,
|
|
40
|
+
risk_free_rate: float, metrics: List[str] = None,
|
|
41
|
+
backtest_date_range: BacktestDateRange = None
|
|
42
|
+
) -> Backtest:
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
Create BacktestMetrics for a Backtest object.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
backtest (Backtest): The Backtest object containing
|
|
49
|
+
backtest runs.
|
|
50
|
+
risk_free_rate (float): The risk-free rate used in certain
|
|
51
|
+
metric calculations.
|
|
52
|
+
metrics (List[str], optional): List of metric names to compute.
|
|
53
|
+
If None, a default set of metrics will be computed.
|
|
54
|
+
backtest_date_range (BacktestDateRange, optional): The date range
|
|
55
|
+
for the backtest. If None, all backtest metrics will be computed
|
|
56
|
+
for each backtest run.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Backtest: The Backtest object with computed metrics for each run.
|
|
60
|
+
"""
|
|
61
|
+
if backtest_date_range is not None:
|
|
62
|
+
backtest_runs = [
|
|
63
|
+
backtest.get_backtest_run(backtest_date_range)
|
|
64
|
+
]
|
|
65
|
+
else:
|
|
66
|
+
backtest_runs = backtest.get_all_backtest_runs()
|
|
67
|
+
|
|
68
|
+
for backtest_run in backtest_runs:
|
|
69
|
+
# If a date range is provided, check if the backtest run falls
|
|
70
|
+
# within the range
|
|
71
|
+
backtest_metrics = create_backtest_metrics(
|
|
72
|
+
backtest_run, risk_free_rate, metrics
|
|
73
|
+
)
|
|
74
|
+
backtest_run.backtest_metrics = backtest_metrics
|
|
75
|
+
|
|
76
|
+
backtest.backtest_runs = backtest_runs
|
|
77
|
+
return backtest
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def create_backtest_metrics(
|
|
81
|
+
backtest_run: BacktestRun, risk_free_rate: float, metrics: List[str] = None
|
|
82
|
+
) -> BacktestMetrics:
|
|
83
|
+
"""
|
|
84
|
+
Create a BacktestMetrics instance and optionally save it to a file.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
backtest_run (BacktestRun): The BacktestRun object containing
|
|
88
|
+
portfolio snapshots and trades.
|
|
89
|
+
risk_free_rate (float): The risk-free rate used in certain
|
|
90
|
+
metric calculations.
|
|
91
|
+
metrics (List[str], optional): List of metric names to compute.
|
|
92
|
+
If None, a default set of metrics will be computed.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
BacktestMetrics: The computed backtest metrics.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
if metrics is None:
|
|
99
|
+
metrics = [
|
|
100
|
+
"backtest_start_date",
|
|
101
|
+
"backtest_end_date",
|
|
102
|
+
"equity_curve",
|
|
103
|
+
"final_value",
|
|
104
|
+
"total_growth",
|
|
105
|
+
"total_growth_percentage",
|
|
106
|
+
"total_net_gain",
|
|
107
|
+
"total_net_gain_percentage",
|
|
108
|
+
"total_loss",
|
|
109
|
+
"total_loss_percentage",
|
|
110
|
+
"cumulative_return",
|
|
111
|
+
"cumulative_return_series",
|
|
112
|
+
"cagr",
|
|
113
|
+
"sharpe_ratio",
|
|
114
|
+
"rolling_sharpe_ratio",
|
|
115
|
+
"sortino_ratio",
|
|
116
|
+
"calmar_ratio",
|
|
117
|
+
"profit_factor",
|
|
118
|
+
"annual_volatility",
|
|
119
|
+
"monthly_returns",
|
|
120
|
+
"yearly_returns",
|
|
121
|
+
"drawdown_series",
|
|
122
|
+
"max_drawdown",
|
|
123
|
+
"max_drawdown_absolute",
|
|
124
|
+
"max_daily_drawdown",
|
|
125
|
+
"max_drawdown_duration",
|
|
126
|
+
"trades_per_year",
|
|
127
|
+
"trade_per_day",
|
|
128
|
+
"exposure_ratio",
|
|
129
|
+
"cumulative_exposure",
|
|
130
|
+
"best_trade",
|
|
131
|
+
"worst_trade",
|
|
132
|
+
"number_of_positive_trades",
|
|
133
|
+
"percentage_positive_trades",
|
|
134
|
+
"number_of_negative_trades",
|
|
135
|
+
"percentage_negative_trades",
|
|
136
|
+
"average_trade_duration",
|
|
137
|
+
"average_trade_size",
|
|
138
|
+
"average_trade_loss",
|
|
139
|
+
"average_trade_loss_percentage",
|
|
140
|
+
"average_trade_gain",
|
|
141
|
+
"average_trade_gain_percentage",
|
|
142
|
+
"average_trade_return",
|
|
143
|
+
"average_trade_return_percentage",
|
|
144
|
+
"median_trade_return",
|
|
145
|
+
"number_of_trades",
|
|
146
|
+
"number_of_trades_closed",
|
|
147
|
+
"number_of_trades_opened",
|
|
148
|
+
"number_of_trades_open_at_end",
|
|
149
|
+
"win_rate",
|
|
150
|
+
"current_win_rate",
|
|
151
|
+
"win_loss_ratio",
|
|
152
|
+
"current_win_loss_ratio",
|
|
153
|
+
"percentage_winning_months",
|
|
154
|
+
"percentage_winning_years",
|
|
155
|
+
"average_monthly_return",
|
|
156
|
+
"average_monthly_return_losing_months",
|
|
157
|
+
"average_monthly_return_winning_months",
|
|
158
|
+
"best_month",
|
|
159
|
+
"best_year",
|
|
160
|
+
"worst_month",
|
|
161
|
+
"worst_year",
|
|
162
|
+
"total_number_of_days",
|
|
163
|
+
"current_average_trade_gain",
|
|
164
|
+
"current_average_trade_return",
|
|
165
|
+
"current_average_trade_duration",
|
|
166
|
+
"current_average_trade_loss",
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
backtest_metrics = BacktestMetrics(
|
|
170
|
+
backtest_start_date=backtest_run.backtest_start_date,
|
|
171
|
+
backtest_end_date=backtest_run.backtest_end_date,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def safe_set(metric_name, func, *args, index=None):
|
|
175
|
+
if metric_name in metrics:
|
|
176
|
+
try:
|
|
177
|
+
value = func(*args)
|
|
178
|
+
if index is not None and isinstance(value, (list, tuple)):
|
|
179
|
+
setattr(backtest_metrics, metric_name, value[index])
|
|
180
|
+
else:
|
|
181
|
+
setattr(backtest_metrics, metric_name, value)
|
|
182
|
+
except OperationalException as e:
|
|
183
|
+
logger.warning(f"{metric_name} failed: {e}")
|
|
184
|
+
|
|
185
|
+
# Grouped metrics needing special handling
|
|
186
|
+
if "total_net_gain" in metrics or "total_net_gain_percentage" in metrics:
|
|
187
|
+
try:
|
|
188
|
+
total_return = get_total_return(backtest_run.portfolio_snapshots)
|
|
189
|
+
if "total_net_gain" in metrics:
|
|
190
|
+
backtest_metrics.total_net_gain = total_return[0]
|
|
191
|
+
if "total_net_gain_percentage" in metrics:
|
|
192
|
+
backtest_metrics.total_net_gain_percentage = total_return[1]
|
|
193
|
+
except OperationalException as e:
|
|
194
|
+
logger.warning(f"total_return failed: {e}")
|
|
195
|
+
|
|
196
|
+
if "total_growth" in metrics or "total_growth_percentage" in metrics:
|
|
197
|
+
try:
|
|
198
|
+
total_growth = get_total_growth(backtest_run.portfolio_snapshots)
|
|
199
|
+
if "total_growth" in metrics:
|
|
200
|
+
backtest_metrics.total_growth = total_growth[0]
|
|
201
|
+
if "total_growth_percentage" in metrics:
|
|
202
|
+
backtest_metrics.total_growth_percentage = total_growth[1]
|
|
203
|
+
except OperationalException as e:
|
|
204
|
+
logger.warning(f"total_growth failed: {e}")
|
|
205
|
+
|
|
206
|
+
if "total_loss" in metrics or "total_loss_percentage" in metrics:
|
|
207
|
+
try:
|
|
208
|
+
total_loss = get_total_loss(backtest_run.portfolio_snapshots)
|
|
209
|
+
if "total_loss" in metrics:
|
|
210
|
+
backtest_metrics.total_loss = total_loss[0]
|
|
211
|
+
if "total_loss_percentage" in metrics:
|
|
212
|
+
backtest_metrics.total_loss_percentage = total_loss[1]
|
|
213
|
+
except OperationalException as e:
|
|
214
|
+
logger.warning(f"total_loss failed: {e}")
|
|
215
|
+
|
|
216
|
+
if ("average_trade_return" in metrics
|
|
217
|
+
or "average_trade_return_percentage" in metrics):
|
|
218
|
+
try:
|
|
219
|
+
avg_return = get_average_trade_return(backtest_run.trades)
|
|
220
|
+
if "average_trade_return" in metrics:
|
|
221
|
+
backtest_metrics.average_trade_return = avg_return[0]
|
|
222
|
+
if "average_trade_return_percentage" in metrics:
|
|
223
|
+
backtest_metrics.average_trade_return_percentage = \
|
|
224
|
+
avg_return[1]
|
|
225
|
+
except OperationalException as e:
|
|
226
|
+
logger.warning(f"average_trade_return failed: {e}")
|
|
227
|
+
|
|
228
|
+
if ("average_trade_gain" in metrics
|
|
229
|
+
or "average_trade_gain_percentage" in metrics):
|
|
230
|
+
try:
|
|
231
|
+
avg_gain = get_average_trade_gain(backtest_run.trades)
|
|
232
|
+
if "average_trade_gain" in metrics:
|
|
233
|
+
backtest_metrics.average_trade_gain = avg_gain[0]
|
|
234
|
+
if "average_trade_gain_percentage" in metrics:
|
|
235
|
+
backtest_metrics.average_trade_gain_percentage = avg_gain[1]
|
|
236
|
+
except OperationalException as e:
|
|
237
|
+
logger.warning(f"average_trade_gain failed: {e}")
|
|
238
|
+
|
|
239
|
+
if ("average_trade_loss" in metrics
|
|
240
|
+
or "average_trade_loss_percentage" in metrics):
|
|
241
|
+
try:
|
|
242
|
+
avg_loss = get_average_trade_loss(backtest_run.trades)
|
|
243
|
+
if "average_trade_loss" in metrics:
|
|
244
|
+
backtest_metrics.average_trade_loss = avg_loss[0]
|
|
245
|
+
if "average_trade_loss_percentage" in metrics:
|
|
246
|
+
backtest_metrics.average_trade_loss_percentage = avg_loss[1]
|
|
247
|
+
except OperationalException as e:
|
|
248
|
+
logger.warning(f"average_trade_loss failed: {e}")
|
|
249
|
+
|
|
250
|
+
if ("current_average_trade_gain" in metrics
|
|
251
|
+
or "get_current_average_trade_gain_percentage" in metrics):
|
|
252
|
+
try:
|
|
253
|
+
current_avg_gain = get_current_average_trade_gain(
|
|
254
|
+
backtest_run.trades
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
if "current_average_trade_gain" in metrics:
|
|
258
|
+
backtest_metrics.current_average_trade_gain = \
|
|
259
|
+
current_avg_gain[0]
|
|
260
|
+
|
|
261
|
+
if "current_average_trade_gain_percentage" in metrics:
|
|
262
|
+
backtest_metrics.current_average_trade_gain_percentage = \
|
|
263
|
+
current_avg_gain[1]
|
|
264
|
+
except OperationalException as e:
|
|
265
|
+
logger.warning(f"current_average_trade_gain failed: {e}")
|
|
266
|
+
|
|
267
|
+
if ("current_average_trade_return" in metrics
|
|
268
|
+
or "current_average_trade_return_percentage" in metrics):
|
|
269
|
+
try:
|
|
270
|
+
current_avg_return = get_current_average_trade_return(
|
|
271
|
+
backtest_run.trades
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
if "current_average_trade_return" in metrics:
|
|
275
|
+
backtest_metrics.current_average_trade_return = \
|
|
276
|
+
current_avg_return[0]
|
|
277
|
+
if "current_average_trade_return_percentage" in metrics:
|
|
278
|
+
backtest_metrics.current_average_trade_return_percentage =\
|
|
279
|
+
current_avg_return[1]
|
|
280
|
+
except OperationalException as e:
|
|
281
|
+
logger.warning(f"current_average_trade_return failed: {e}")
|
|
282
|
+
|
|
283
|
+
if "current_average_trade_duration" in metrics:
|
|
284
|
+
try:
|
|
285
|
+
current_avg_duration = get_current_average_trade_duration(
|
|
286
|
+
backtest_run.trades, backtest_run
|
|
287
|
+
)
|
|
288
|
+
backtest_metrics.current_average_trade_duration = \
|
|
289
|
+
current_avg_duration
|
|
290
|
+
except OperationalException as e:
|
|
291
|
+
logger.warning(f"current_average_trade_duration failed: {e}")
|
|
292
|
+
|
|
293
|
+
if ("current_average_trade_loss" in metrics
|
|
294
|
+
or "current_average_trade_loss_percentage" in metrics):
|
|
295
|
+
try:
|
|
296
|
+
current_avg_loss = get_current_average_trade_loss(
|
|
297
|
+
backtest_run.trades
|
|
298
|
+
)
|
|
299
|
+
if "current_average_trade_loss" in metrics:
|
|
300
|
+
backtest_metrics.current_average_trade_loss = \
|
|
301
|
+
current_avg_loss[0]
|
|
302
|
+
if "current_average_trade_loss_percentage" in metrics:
|
|
303
|
+
backtest_metrics.current_average_trade_loss_percentage = \
|
|
304
|
+
current_avg_loss[1]
|
|
305
|
+
except OperationalException as e:
|
|
306
|
+
logger.warning(f"current_average_trade_loss failed: {e}")
|
|
307
|
+
|
|
308
|
+
safe_set("number_of_positive_trades", get_positive_trades, backtest_run.trades)
|
|
309
|
+
safe_set("percentage_positive_trades", get_positive_trades, backtest_run.trades, index=1)
|
|
310
|
+
safe_set("number_of_negative_trades", get_negative_trades, backtest_run.trades)
|
|
311
|
+
safe_set("percentage_negative_trades", get_negative_trades, backtest_run.trades, index=1)
|
|
312
|
+
safe_set("median_trade_return", get_median_trade_return, backtest_run.trades, index=0)
|
|
313
|
+
safe_set("median_trade_return_percentage", get_median_trade_return, backtest_run.trades, index=1)
|
|
314
|
+
safe_set("number_of_trades", get_number_of_trades, backtest_run.trades)
|
|
315
|
+
safe_set("number_of_trades_closed", get_number_of_closed_trades, backtest_run.trades)
|
|
316
|
+
safe_set("number_of_trades_opened", get_number_of_open_trades, backtest_run.trades)
|
|
317
|
+
safe_set("average_trade_duration", get_average_trade_duration, backtest_run.trades)
|
|
318
|
+
safe_set("average_trade_size", get_average_trade_size, backtest_run.trades)
|
|
319
|
+
safe_set("equity_curve", get_equity_curve, backtest_run.portfolio_snapshots)
|
|
320
|
+
safe_set("final_value", get_final_value, backtest_run.portfolio_snapshots)
|
|
321
|
+
safe_set("cagr", get_cagr, backtest_run.portfolio_snapshots)
|
|
322
|
+
safe_set("sharpe_ratio", get_sharpe_ratio, backtest_run.portfolio_snapshots, risk_free_rate)
|
|
323
|
+
safe_set("rolling_sharpe_ratio", get_rolling_sharpe_ratio, backtest_run.portfolio_snapshots, risk_free_rate)
|
|
324
|
+
safe_set("sortino_ratio", get_sortino_ratio, backtest_run.portfolio_snapshots, risk_free_rate)
|
|
325
|
+
safe_set("profit_factor", get_profit_factor, backtest_run.trades)
|
|
326
|
+
safe_set("calmar_ratio", get_calmar_ratio, backtest_run.portfolio_snapshots)
|
|
327
|
+
safe_set("annual_volatility", get_annual_volatility, backtest_run.portfolio_snapshots)
|
|
328
|
+
safe_set("monthly_returns", get_monthly_returns, backtest_run.portfolio_snapshots)
|
|
329
|
+
safe_set("yearly_returns", get_yearly_returns, backtest_run.portfolio_snapshots)
|
|
330
|
+
safe_set("drawdown_series", get_drawdown_series, backtest_run.portfolio_snapshots)
|
|
331
|
+
safe_set("max_drawdown", get_max_drawdown, backtest_run.portfolio_snapshots)
|
|
332
|
+
safe_set("max_drawdown_absolute", get_max_drawdown_absolute, backtest_run.portfolio_snapshots)
|
|
333
|
+
safe_set("max_daily_drawdown", get_max_daily_drawdown, backtest_run.portfolio_snapshots)
|
|
334
|
+
safe_set("max_drawdown_duration", get_max_drawdown_duration, backtest_run.portfolio_snapshots)
|
|
335
|
+
safe_set("trades_per_year", get_trades_per_year, backtest_run.trades, backtest_run.backtest_start_date, backtest_run.backtest_end_date)
|
|
336
|
+
safe_set("trades_per_day", get_trades_per_day, backtest_run.trades, backtest_run.backtest_start_date, backtest_run.backtest_end_date)
|
|
337
|
+
safe_set("exposure_ratio", get_exposure_ratio, backtest_run.trades, backtest_run.backtest_start_date, backtest_run.backtest_end_date)
|
|
338
|
+
safe_set("cumulative_exposure", get_cumulative_exposure, backtest_run.trades, backtest_run.backtest_start_date, backtest_run.backtest_end_date)
|
|
339
|
+
safe_set("best_trade", get_best_trade, backtest_run.trades)
|
|
340
|
+
safe_set("worst_trade", get_worst_trade, backtest_run.trades)
|
|
341
|
+
safe_set("win_rate", get_win_rate, backtest_run.trades)
|
|
342
|
+
safe_set("current_win_rate", get_current_win_rate, backtest_run.trades)
|
|
343
|
+
safe_set("win_loss_ratio", get_win_loss_ratio, backtest_run.trades)
|
|
344
|
+
safe_set("current_win_loss_ratio", get_current_win_loss_ratio, backtest_run.trades)
|
|
345
|
+
safe_set("percentage_winning_months", get_percentage_winning_months, backtest_run.portfolio_snapshots)
|
|
346
|
+
safe_set("percentage_winning_years", get_percentage_winning_years, backtest_run.portfolio_snapshots)
|
|
347
|
+
safe_set("average_monthly_return", get_average_monthly_return, backtest_run.portfolio_snapshots)
|
|
348
|
+
safe_set("average_monthly_return_winning_months", get_average_monthly_return_winning_months, backtest_run.portfolio_snapshots)
|
|
349
|
+
safe_set("average_monthly_return_losing_months", get_average_monthly_return_losing_months, backtest_run.portfolio_snapshots)
|
|
350
|
+
safe_set("best_month", get_best_month, backtest_run.portfolio_snapshots)
|
|
351
|
+
safe_set("best_year", get_best_year, backtest_run.portfolio_snapshots)
|
|
352
|
+
safe_set("worst_month", get_worst_month, backtest_run.portfolio_snapshots)
|
|
353
|
+
safe_set("worst_year", get_worst_year, backtest_run.portfolio_snapshots)
|
|
354
|
+
safe_set("gross_loss", get_gross_loss, backtest_run.trades)
|
|
355
|
+
safe_set("gross_profit", get_gross_profit, backtest_run.trades)
|
|
356
|
+
safe_set("cumulative_return_series", get_cumulative_return_series, backtest_run.portfolio_snapshots)
|
|
357
|
+
safe_set("cumulative_return", get_cumulative_return, backtest_run.portfolio_snapshots)
|
|
358
|
+
return backtest_metrics
|
|
@@ -127,3 +127,39 @@ def get_profit_factor(trades: List[Trade]) -> float:
|
|
|
127
127
|
return float('inf') if gross_profit > 0 else 0.0
|
|
128
128
|
|
|
129
129
|
return gross_profit / gross_loss
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def get_gross_profit(trades: List[Trade]) -> float:
|
|
133
|
+
"""
|
|
134
|
+
Function to calculate the total gross profit from a list of trades.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
trades (List[Trade]): List of closed trades from the backtest report.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
float: The total gross profit from the trades.
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
gross_profit = 0.0
|
|
144
|
+
for trade in trades:
|
|
145
|
+
if trade.net_gain > 0:
|
|
146
|
+
gross_profit += trade.net_gain
|
|
147
|
+
return gross_profit
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def get_gross_loss(trades: List[Trade]) -> float:
|
|
151
|
+
"""
|
|
152
|
+
Function to calculate the total gross loss from a list of trades.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
trades (List[Trade]): List of closed trades from the backtest report.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
float: The total gross loss from the trades.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
gross_loss = 0.0
|
|
162
|
+
for trade in trades:
|
|
163
|
+
if trade.net_gain < 0:
|
|
164
|
+
gross_loss += abs(trade.net_gain)
|
|
165
|
+
return gross_loss
|
|
@@ -40,7 +40,7 @@ from typing import List
|
|
|
40
40
|
|
|
41
41
|
from investing_algorithm_framework.domain import PortfolioSnapshot
|
|
42
42
|
from .drawdown import get_max_drawdown_absolute
|
|
43
|
-
from .returns import
|
|
43
|
+
from .returns import get_total_return
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
def get_recovery_factor(snapshots: List[PortfolioSnapshot]) -> float:
|
|
@@ -67,7 +67,7 @@ def get_recovery_factor(snapshots: List[PortfolioSnapshot]) -> float:
|
|
|
67
67
|
return 0.0
|
|
68
68
|
|
|
69
69
|
max_drawdown_absolute = get_max_drawdown_absolute(snapshots)
|
|
70
|
-
net_profit =
|
|
70
|
+
net_profit, _ = get_total_return(snapshots)
|
|
71
71
|
|
|
72
72
|
if max_drawdown_absolute == 0:
|
|
73
73
|
return float('inf') if net_profit > 0 else 0.0
|