investing-algorithm-framework 3.7.0__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 +168 -45
- investing_algorithm_framework/app/__init__.py +32 -1
- investing_algorithm_framework/app/algorithm/__init__.py +7 -0
- investing_algorithm_framework/app/algorithm/algorithm.py +239 -0
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +114 -0
- 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 +1933 -589
- investing_algorithm_framework/app/app_hook.py +28 -0
- investing_algorithm_framework/app/context.py +1725 -0
- investing_algorithm_framework/app/eventloop.py +590 -0
- investing_algorithm_framework/app/reporting/__init__.py +27 -0
- investing_algorithm_framework/app/reporting/ascii.py +921 -0
- investing_algorithm_framework/app/reporting/backtest_report.py +349 -0
- investing_algorithm_framework/app/reporting/charts/__init__.py +19 -0
- 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 +74 -0
- investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
- investing_algorithm_framework/app/reporting/charts/monthly_returns_heatmap.py +70 -0
- investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
- investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +79 -0
- investing_algorithm_framework/app/reporting/charts/yearly_returns_barchart.py +55 -0
- investing_algorithm_framework/app/reporting/generate.py +185 -0
- investing_algorithm_framework/app/reporting/tables/__init__.py +11 -0
- investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +217 -0
- investing_algorithm_framework/app/reporting/tables/stop_loss_table.py +0 -0
- investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
- investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
- investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
- investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
- investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
- investing_algorithm_framework/app/stateless/action_handlers/__init__.py +4 -2
- investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
- investing_algorithm_framework/app/strategy.py +664 -84
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/app/web/__init__.py +2 -1
- investing_algorithm_framework/app/web/create_app.py +4 -2
- investing_algorithm_framework/cli/__init__.py +0 -0
- investing_algorithm_framework/cli/cli.py +226 -0
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
- investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
- investing_algorithm_framework/cli/initialize_app.py +603 -0
- investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
- investing_algorithm_framework/cli/templates/app.py.template +18 -0
- investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
- investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
- investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
- 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_readme.md.template +110 -0
- investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
- investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
- investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
- investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
- investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
- investing_algorithm_framework/cli/templates/env.example.template +2 -0
- investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
- investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
- investing_algorithm_framework/cli/templates/readme.md.template +135 -0
- investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
- investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
- investing_algorithm_framework/create_app.py +40 -6
- investing_algorithm_framework/dependency_container.py +72 -56
- investing_algorithm_framework/domain/__init__.py +71 -47
- 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 +59 -91
- investing_algorithm_framework/domain/constants.py +13 -38
- investing_algorithm_framework/domain/data_provider.py +334 -0
- investing_algorithm_framework/domain/data_structures.py +3 -2
- investing_algorithm_framework/domain/exceptions.py +51 -1
- investing_algorithm_framework/domain/models/__init__.py +17 -12
- 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/data/data_type.py +46 -0
- investing_algorithm_framework/domain/models/event.py +35 -0
- investing_algorithm_framework/domain/models/market/market_credential.py +55 -1
- investing_algorithm_framework/domain/models/order/order.py +77 -83
- investing_algorithm_framework/domain/models/order/order_status.py +2 -2
- investing_algorithm_framework/domain/models/order/order_type.py +1 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +81 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +26 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +108 -11
- investing_algorithm_framework/domain/models/position/__init__.py +2 -1
- investing_algorithm_framework/domain/models/position/position.py +12 -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 +45 -0
- investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
- investing_algorithm_framework/domain/models/time_frame.py +37 -0
- investing_algorithm_framework/domain/models/time_interval.py +33 -0
- investing_algorithm_framework/domain/models/time_unit.py +66 -2
- investing_algorithm_framework/domain/models/trade/__init__.py +8 -1
- investing_algorithm_framework/domain/models/trade/trade.py +295 -171
- investing_algorithm_framework/domain/models/trade/trade_status.py +9 -2
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
- investing_algorithm_framework/domain/order_executor.py +112 -0
- investing_algorithm_framework/domain/portfolio_provider.py +118 -0
- investing_algorithm_framework/domain/services/__init__.py +2 -9
- investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +0 -6
- investing_algorithm_framework/domain/services/state_handler.py +38 -0
- investing_algorithm_framework/domain/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +12 -7
- investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
- investing_algorithm_framework/domain/utils/dates.py +57 -0
- investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
- investing_algorithm_framework/domain/utils/polars.py +53 -0
- investing_algorithm_framework/domain/utils/random.py +29 -0
- investing_algorithm_framework/download_data.py +108 -0
- investing_algorithm_framework/infrastructure/__init__.py +31 -18
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
- investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
- 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 +86 -12
- investing_algorithm_framework/infrastructure/models/__init__.py +6 -11
- investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -1
- investing_algorithm_framework/infrastructure/models/order/order.py +35 -49
- investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
- investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
- investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +1 -1
- investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +8 -0
- investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +17 -5
- investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
- investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
- investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
- investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
- investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
- investing_algorithm_framework/infrastructure/repositories/__init__.py +8 -0
- investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
- investing_algorithm_framework/infrastructure/repositories/order_repository.py +5 -0
- investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +1 -1
- investing_algorithm_framework/infrastructure/repositories/position_repository.py +11 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +81 -27
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +4 -4
- investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
- investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
- investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
- investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
- investing_algorithm_framework/services/__init__.py +113 -16
- investing_algorithm_framework/services/backtesting/__init__.py +0 -7
- investing_algorithm_framework/services/backtesting/backtest_service.py +566 -359
- investing_algorithm_framework/services/configuration_service.py +77 -11
- 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/services/market_credential_service.py +16 -1
- investing_algorithm_framework/services/metrics/__init__.py +114 -0
- investing_algorithm_framework/services/metrics/alpha.py +0 -0
- investing_algorithm_framework/services/metrics/beta.py +0 -0
- investing_algorithm_framework/services/metrics/cagr.py +60 -0
- investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
- investing_algorithm_framework/services/metrics/drawdown.py +181 -0
- investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
- investing_algorithm_framework/services/metrics/exposure.py +210 -0
- investing_algorithm_framework/services/metrics/generate.py +358 -0
- investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
- investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
- investing_algorithm_framework/services/metrics/recovery.py +113 -0
- investing_algorithm_framework/services/metrics/returns.py +452 -0
- investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
- investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
- investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
- investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
- investing_algorithm_framework/services/metrics/trades.py +500 -0
- investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
- investing_algorithm_framework/services/metrics/ulcer.py +0 -0
- investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
- investing_algorithm_framework/services/metrics/volatility.py +97 -0
- investing_algorithm_framework/services/metrics/win_rate.py +177 -0
- investing_algorithm_framework/services/order_service/__init__.py +3 -1
- investing_algorithm_framework/services/order_service/order_backtest_service.py +76 -89
- investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
- investing_algorithm_framework/services/order_service/order_service.py +407 -326
- investing_algorithm_framework/services/portfolios/__init__.py +3 -1
- investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +37 -3
- investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +22 -8
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
- investing_algorithm_framework/services/portfolios/portfolio_service.py +96 -28
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +97 -28
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +116 -313
- investing_algorithm_framework/services/positions/__init__.py +7 -0
- investing_algorithm_framework/services/positions/position_service.py +210 -0
- investing_algorithm_framework/services/repository_service.py +8 -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 +1013 -315
- 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-7.19.15.dist-info/RECORD +263 -0
- investing_algorithm_framework-7.19.15.dist-info/entry_points.txt +3 -0
- investing_algorithm_framework/app/algorithm.py +0 -1105
- investing_algorithm_framework/domain/graphs.py +0 -382
- investing_algorithm_framework/domain/metrics/__init__.py +0 -6
- investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -11
- investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -43
- investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
- investing_algorithm_framework/domain/models/backtesting/backtest_report.py +0 -580
- investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -243
- investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
- investing_algorithm_framework/domain/services/market_data_sources.py +0 -344
- investing_algorithm_framework/domain/services/market_service.py +0 -153
- investing_algorithm_framework/domain/singleton.py +0 -9
- investing_algorithm_framework/domain/utils/backtesting.py +0 -472
- investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -12
- investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -559
- investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -254
- investing_algorithm_framework/infrastructure/models/market_data_sources/us_treasury_yield.py +0 -47
- investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
- investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -455
- 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 -350
- investing_algorithm_framework/services/backtesting/backtest_report_writer_service.py +0 -53
- investing_algorithm_framework/services/backtesting/graphs.py +0 -61
- investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -8
- investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -150
- investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -189
- investing_algorithm_framework/services/position_service.py +0 -31
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -264
- investing_algorithm_framework-3.7.0.dist-info/METADATA +0 -339
- investing_algorithm_framework-3.7.0.dist-info/RECORD +0 -147
- /investing_algorithm_framework/{domain → services}/metrics/price_efficiency.py +0 -0
- /investing_algorithm_framework/services/{position_snapshot_service.py → positions/position_snapshot_service.py} +0 -0
- {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
- {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
|
@@ -4,7 +4,14 @@ from investing_algorithm_framework.domain import MarketCredential
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class MarketCredentialService:
|
|
7
|
-
|
|
7
|
+
"""
|
|
8
|
+
Service to manage market credentials.
|
|
9
|
+
|
|
10
|
+
This service is responsible for adding, retrieving, and
|
|
11
|
+
initializing market credentials.
|
|
12
|
+
"""
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self._market_credentials = {}
|
|
8
15
|
|
|
9
16
|
def add(self, market_data_credential: MarketCredential):
|
|
10
17
|
self._market_credentials[market_data_credential.market.upper()] \
|
|
@@ -23,3 +30,11 @@ class MarketCredentialService:
|
|
|
23
30
|
|
|
24
31
|
def get_all(self) -> List[MarketCredential]:
|
|
25
32
|
return list(self._market_credentials.values())
|
|
33
|
+
|
|
34
|
+
def initialize(self):
|
|
35
|
+
"""
|
|
36
|
+
Initialize all market credentials.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
for market_credential in self.get_all():
|
|
40
|
+
market_credential.initialize()
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from .volatility import get_annual_volatility
|
|
2
|
+
from .sortino_ratio import get_sortino_ratio
|
|
3
|
+
from .drawdown import get_drawdown_series, get_max_drawdown
|
|
4
|
+
from .equity_curve import get_equity_curve
|
|
5
|
+
from .price_efficiency import get_price_efficiency_ratio
|
|
6
|
+
from .profit_factor import get_profit_factor, \
|
|
7
|
+
get_cumulative_profit_factor_series, get_rolling_profit_factor_series
|
|
8
|
+
from .sharpe_ratio import get_sharpe_ratio, get_rolling_sharpe_ratio
|
|
9
|
+
from .price_efficiency import get_price_efficiency_ratio
|
|
10
|
+
from .equity_curve import get_equity_curve
|
|
11
|
+
from .drawdown import get_drawdown_series, get_max_drawdown, \
|
|
12
|
+
get_max_drawdown_absolute, get_max_drawdown_duration, \
|
|
13
|
+
get_max_daily_drawdown
|
|
14
|
+
from .cagr import get_cagr
|
|
15
|
+
from .standard_deviation import get_standard_deviation_downside_returns, \
|
|
16
|
+
get_standard_deviation_returns
|
|
17
|
+
from .returns import get_yearly_returns, get_monthly_returns, \
|
|
18
|
+
get_best_year, get_best_month, get_worst_month, get_total_return, \
|
|
19
|
+
get_average_yearly_return, get_average_monthly_return, \
|
|
20
|
+
get_percentage_winning_months, get_average_monthly_return_losing_months, \
|
|
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
|
+
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
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"get_annual_volatility",
|
|
44
|
+
"get_sortino_ratio",
|
|
45
|
+
"get_drawdown_series",
|
|
46
|
+
"get_max_drawdown",
|
|
47
|
+
"get_equity_curve",
|
|
48
|
+
"get_price_efficiency_ratio",
|
|
49
|
+
"get_sharpe_ratio",
|
|
50
|
+
"get_profit_factor",
|
|
51
|
+
"get_cumulative_profit_factor_series",
|
|
52
|
+
"get_rolling_profit_factor_series",
|
|
53
|
+
"get_sharpe_ratio",
|
|
54
|
+
"get_cagr",
|
|
55
|
+
"get_standard_deviation_returns",
|
|
56
|
+
"get_standard_deviation_downside_returns",
|
|
57
|
+
"get_max_drawdown_absolute",
|
|
58
|
+
"get_total_return",
|
|
59
|
+
"get_total_loss",
|
|
60
|
+
"get_total_growth",
|
|
61
|
+
"get_cumulative_exposure",
|
|
62
|
+
"get_exposure_ratio",
|
|
63
|
+
"get_win_rate",
|
|
64
|
+
"get_win_loss_ratio",
|
|
65
|
+
"get_calmar_ratio",
|
|
66
|
+
"get_trade_frequency",
|
|
67
|
+
"get_yearly_returns",
|
|
68
|
+
"get_monthly_returns",
|
|
69
|
+
"get_best_year",
|
|
70
|
+
"get_best_month",
|
|
71
|
+
"get_worst_year",
|
|
72
|
+
"get_worst_month",
|
|
73
|
+
"get_best_trade",
|
|
74
|
+
"get_worst_trade",
|
|
75
|
+
"get_average_yearly_return",
|
|
76
|
+
"get_average_monthly_return",
|
|
77
|
+
"get_percentage_winning_months",
|
|
78
|
+
"get_average_trade_duration",
|
|
79
|
+
"get_trade_frequency",
|
|
80
|
+
"get_win_rate",
|
|
81
|
+
"get_win_loss_ratio",
|
|
82
|
+
"get_calmar_ratio",
|
|
83
|
+
"get_max_drawdown_absolute",
|
|
84
|
+
"get_max_drawdown_duration",
|
|
85
|
+
"get_max_daily_drawdown",
|
|
86
|
+
"get_trades_per_day",
|
|
87
|
+
"get_trades_per_year",
|
|
88
|
+
"get_average_monthly_return_losing_months",
|
|
89
|
+
"get_average_monthly_return_winning_months",
|
|
90
|
+
"get_percentage_winning_years",
|
|
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"
|
|
114
|
+
]
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The Compound Annual Growth Rate (CAGR) normalizes returns over a one-year
|
|
3
|
+
period, allowing you to compare performance across different timeframes.
|
|
4
|
+
It assumes the investment grows at a steady rate and compounds over time.
|
|
5
|
+
|
|
6
|
+
This formula is suitable whether your data spans:
|
|
7
|
+
|
|
8
|
+
* Less than a year (e.g. 30 days)
|
|
9
|
+
* Exactly a year (365 days)
|
|
10
|
+
* More than a year (e.g. 500 days)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import pandas as pd
|
|
14
|
+
from typing import List
|
|
15
|
+
from investing_algorithm_framework.domain import PortfolioSnapshot
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_cagr(snapshots: List[PortfolioSnapshot]) -> float:
|
|
19
|
+
"""
|
|
20
|
+
Calculate the Compound Annual Growth Rate (CAGR) of a backtest report.
|
|
21
|
+
CAGR is a useful metric to evaluate the performance of an investment
|
|
22
|
+
over a period of time, normalizing the return to a one-year basis.
|
|
23
|
+
|
|
24
|
+
The formula for CAGR is:
|
|
25
|
+
CAGR = (End Value / Start Value) ^ (1 / Number of Years) - 1
|
|
26
|
+
|
|
27
|
+
Where:
|
|
28
|
+
- End Value is the final value of the investment
|
|
29
|
+
- Start Value is the initial value of the investment
|
|
30
|
+
- Number of Years is the total time period in years
|
|
31
|
+
This function assumes that the snapshots in the report are ordered by
|
|
32
|
+
creation date and that the net size represents the value of the investment.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
snapshots (list[Snapshot]): A list of snapshots
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Float: The CAGR as a decimal. Returns 0.0 if not enough
|
|
39
|
+
data is available.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
if len(snapshots) < 2:
|
|
43
|
+
return 0.0 # Not enough data
|
|
44
|
+
|
|
45
|
+
# Convert snapshots to DataFrame
|
|
46
|
+
data = [(s.total_value, s.created_at) for s in snapshots]
|
|
47
|
+
df = pd.DataFrame(data, columns=["total_value", "created_at"])
|
|
48
|
+
df['created_at'] = pd.to_datetime(df['created_at'])
|
|
49
|
+
df = df.sort_values('created_at')
|
|
50
|
+
start_value = df.iloc[0]['total_value']
|
|
51
|
+
end_value = df.iloc[-1]['total_value']
|
|
52
|
+
start_date = df.iloc[0]['created_at']
|
|
53
|
+
end_date = df.iloc[-1]['created_at']
|
|
54
|
+
num_days = (end_date - start_date).days
|
|
55
|
+
|
|
56
|
+
if num_days == 0 or start_value == 0:
|
|
57
|
+
return 0.0
|
|
58
|
+
|
|
59
|
+
# Apply CAGR formula
|
|
60
|
+
return (end_value / start_value) ** (365 / num_days) - 1
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
| **Calmar Ratio** | **Interpretation** |
|
|
3
|
+
| ---------------- | ----------------------------------------------------------- |
|
|
4
|
+
| **> 3.0** | **Excellent** – strong return vs. drawdown |
|
|
5
|
+
| **2.0 – 3.0** | **Very Good** – solid risk-adjusted performance |
|
|
6
|
+
| **1.0 – 2.0** | **Acceptable** – decent, especially for volatile strategies |
|
|
7
|
+
| **< 1.0** | **Poor** – high drawdowns relative to return |
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import List
|
|
11
|
+
from investing_algorithm_framework.domain import PortfolioSnapshot
|
|
12
|
+
from .cagr import get_cagr
|
|
13
|
+
from .drawdown import get_max_drawdown
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_calmar_ratio(snapshots: List[PortfolioSnapshot]):
|
|
17
|
+
"""
|
|
18
|
+
Calculate the Calmar Ratio, which is the ratio of the annualized
|
|
19
|
+
return to the maximum drawdown.
|
|
20
|
+
|
|
21
|
+
Formula:
|
|
22
|
+
Calmar Ratio = CAGR / |Maximum Drawdown|
|
|
23
|
+
|
|
24
|
+
The Calmar Ratio is a measure of risk-adjusted return,
|
|
25
|
+
where a higher ratio indicates a more favorable risk-return profile.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
snapshots (List[PortfolioSnapshot]): List of portfolio snapshots
|
|
29
|
+
from the backtest report.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
float: The Calmar Ratio.
|
|
33
|
+
"""
|
|
34
|
+
cagr = get_cagr(snapshots)
|
|
35
|
+
max_drawdown = get_max_drawdown(snapshots)
|
|
36
|
+
|
|
37
|
+
if max_drawdown == 0 or max_drawdown is None:
|
|
38
|
+
return 0.0
|
|
39
|
+
|
|
40
|
+
return cagr / max_drawdown
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
""""
|
|
2
|
+
Max Drawdown (MDD) — a key risk metric that shows the worst
|
|
3
|
+
peak-to-trough decline of a portfolio:
|
|
4
|
+
|
|
5
|
+
| **Max Drawdown (%)** | **Interpretation** |
|
|
6
|
+
|-----------------------|----------------------------------------------------------------------|
|
|
7
|
+
| **0% to -5%** | 🟢 Excellent — Very low risk, typical for conservative strategies |
|
|
8
|
+
| **-5% to -10%** | ✅ Good — Moderate volatility, acceptable for balanced portfolios |
|
|
9
|
+
| **-10% to -20%** | ⚠️ Elevated Risk — Common in growth or actively managed strategies |
|
|
10
|
+
| **-20% to -40%** | 🔻 High Risk — Significant drawdown, typical of aggressive strategies |
|
|
11
|
+
| **> -40%** | 🚨 Very High Risk — Risk of capital loss or strategy failure |
|
|
12
|
+
"""
|
|
13
|
+
from typing import List, Tuple
|
|
14
|
+
import pandas as pd
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from investing_algorithm_framework.domain import PortfolioSnapshot, Trade
|
|
17
|
+
from .equity_curve import get_equity_curve
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_drawdown_series(snapshots: List[PortfolioSnapshot]) -> List[Tuple[float, datetime]]:
|
|
21
|
+
"""
|
|
22
|
+
Calculate the drawdown series of a backtest report.
|
|
23
|
+
|
|
24
|
+
The drawdown is calculated as the percentage difference
|
|
25
|
+
between the current equity value and the maximum equity value
|
|
26
|
+
observed up to that point in time.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
snapshots (List[PortfolioSnapshot]): List of portfolio snapshots
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
List[Tuple[datetime, float]]: A list of tuples with datetime
|
|
33
|
+
and drawdown percentage. The drawdown is expressed as a
|
|
34
|
+
negative percentage, where 0% means no drawdown and -100%
|
|
35
|
+
means the portfolio has lost all its value.
|
|
36
|
+
"""
|
|
37
|
+
equity_curve = get_equity_curve(snapshots)
|
|
38
|
+
|
|
39
|
+
drawdown_series = []
|
|
40
|
+
max_value = None
|
|
41
|
+
|
|
42
|
+
for value, timestamp in equity_curve:
|
|
43
|
+
if max_value is None:
|
|
44
|
+
max_value = value
|
|
45
|
+
max_value = max(max_value, value)
|
|
46
|
+
drawdown = (value - max_value) / max_value # This will be <= 0
|
|
47
|
+
drawdown_series.append((drawdown, timestamp))
|
|
48
|
+
|
|
49
|
+
return drawdown_series
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_max_drawdown(snapshots: List[PortfolioSnapshot]) -> float:
|
|
53
|
+
"""
|
|
54
|
+
Calculate the maximum drawdown of the portfolio as a percentage from the peak.
|
|
55
|
+
|
|
56
|
+
Max Drawdown is the maximum observed loss from a peak to a
|
|
57
|
+
trough before a new peak is achieved.
|
|
58
|
+
|
|
59
|
+
It is expressed here as a negative percentage.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
snapshots (List[PortfolioSnapshot]): List of portfolio snapshots
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
float: The maximum drawdown as a negative percentage (e.g., -12.5 for a 12.5% drawdown).
|
|
66
|
+
"""
|
|
67
|
+
equity_curve = get_equity_curve(snapshots)
|
|
68
|
+
|
|
69
|
+
if not equity_curve:
|
|
70
|
+
return 0.0
|
|
71
|
+
|
|
72
|
+
peak = equity_curve[0][0]
|
|
73
|
+
max_drawdown_pct = 0.0
|
|
74
|
+
|
|
75
|
+
for equity, _ in equity_curve:
|
|
76
|
+
if equity > peak:
|
|
77
|
+
peak = equity
|
|
78
|
+
|
|
79
|
+
drawdown_pct = (equity - peak) / peak # Will be 0 or negative
|
|
80
|
+
max_drawdown_pct = min(max_drawdown_pct, drawdown_pct)
|
|
81
|
+
|
|
82
|
+
return abs(max_drawdown_pct)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_max_daily_drawdown(snapshots: List[PortfolioSnapshot]) -> float:
|
|
86
|
+
"""
|
|
87
|
+
Calculate the maximum daily drawdown of the portfolio as a percentage from the peak.
|
|
88
|
+
|
|
89
|
+
This is the largest drop in equity (in percentage) from a peak to a trough
|
|
90
|
+
during the backtest period, calculated on a daily basis.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
snapshots (List[PortfolioSnapshot]): List of portfolio snapshots
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
float: The maximum daily drawdown as a negative percentage (e.g., -5.0 for a 5% drawdown).
|
|
97
|
+
"""
|
|
98
|
+
# Create DataFrame from snapshots
|
|
99
|
+
data = [(s.created_at, s.total_value) for s in snapshots]
|
|
100
|
+
df = pd.DataFrame(data, columns=["created_at", "total_value"])
|
|
101
|
+
df['created_at'] = pd.to_datetime(df['created_at'])
|
|
102
|
+
df = df.sort_values('created_at').drop_duplicates('created_at')\
|
|
103
|
+
.set_index('created_at')
|
|
104
|
+
|
|
105
|
+
# Resample to daily frequency using last value of the day
|
|
106
|
+
daily_df = df.resample('1D').last().dropna()
|
|
107
|
+
|
|
108
|
+
if daily_df.empty:
|
|
109
|
+
return 0.0
|
|
110
|
+
|
|
111
|
+
peak = daily_df['total_value'].iloc[0]
|
|
112
|
+
max_daily_drawdown_pct = 0.0
|
|
113
|
+
for equity in daily_df['total_value']:
|
|
114
|
+
if equity > peak:
|
|
115
|
+
peak = equity
|
|
116
|
+
|
|
117
|
+
drawdown_pct = (equity - peak) / peak
|
|
118
|
+
max_daily_drawdown_pct = min(max_daily_drawdown_pct, drawdown_pct)
|
|
119
|
+
return abs(max_daily_drawdown_pct) # Return as positive percentage (e.g., 5.0 for a 5% drawdown)
|
|
120
|
+
|
|
121
|
+
def get_max_drawdown_duration(snapshots: List[PortfolioSnapshot]) -> int:
|
|
122
|
+
"""
|
|
123
|
+
Calculate the maximum duration of drawdown in days.
|
|
124
|
+
|
|
125
|
+
This is the longest period where the portfolio equity was below its peak.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
snapshots (List[PortfolioSnapshot]): List of portfolio snapshots
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
int: The maximum drawdown duration in days.
|
|
132
|
+
"""
|
|
133
|
+
equity_curve = get_equity_curve(snapshots)
|
|
134
|
+
if not equity_curve:
|
|
135
|
+
return 0
|
|
136
|
+
|
|
137
|
+
peak = equity_curve[0][0]
|
|
138
|
+
max_duration = 0
|
|
139
|
+
current_duration = 0
|
|
140
|
+
|
|
141
|
+
for equity, _ in equity_curve:
|
|
142
|
+
if equity < peak:
|
|
143
|
+
current_duration += 1
|
|
144
|
+
else:
|
|
145
|
+
max_duration = max(max_duration, current_duration)
|
|
146
|
+
current_duration = 0
|
|
147
|
+
peak = equity # Reset peak to current equity
|
|
148
|
+
|
|
149
|
+
max_duration = max(max_duration, current_duration) # Final check
|
|
150
|
+
|
|
151
|
+
return max_duration
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def get_max_drawdown_absolute(snapshots: List[PortfolioSnapshot]) -> float:
|
|
155
|
+
"""
|
|
156
|
+
Calculate the maximum absolute drawdown of the portfolio.
|
|
157
|
+
|
|
158
|
+
This is the largest drop in equity (in currency units) from a peak to a trough
|
|
159
|
+
during the backtest period.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
snapshots (List[PortfolioSnapshot]): List of portfolio snapshots
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
float: The maximum absolute drawdown as a positive number (e.g., €10,000).
|
|
166
|
+
"""
|
|
167
|
+
equity_curve = get_equity_curve(snapshots)
|
|
168
|
+
if not equity_curve:
|
|
169
|
+
return 0.0
|
|
170
|
+
|
|
171
|
+
peak = equity_curve[0][0]
|
|
172
|
+
max_drawdown = 0.0
|
|
173
|
+
|
|
174
|
+
for equity, _ in equity_curve:
|
|
175
|
+
if equity > peak:
|
|
176
|
+
peak = equity
|
|
177
|
+
|
|
178
|
+
drawdown = peak - equity # Drop from peak
|
|
179
|
+
max_drawdown = max(max_drawdown, drawdown)
|
|
180
|
+
|
|
181
|
+
return abs(max_drawdown) # Return as positive number (e.g., €10,000)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import List
|
|
3
|
+
from investing_algorithm_framework.domain import PortfolioSnapshot
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_equity_curve(
|
|
7
|
+
snapshots: List[PortfolioSnapshot]
|
|
8
|
+
) -> list[tuple[float, datetime]]:
|
|
9
|
+
"""
|
|
10
|
+
Calculate the total size of the portfolio at each snapshot timestamp.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
snapshots (List[PortfolioSnapshot]): List of portfolio snapshots.
|
|
14
|
+
Returns:
|
|
15
|
+
list[tuple[datetime, float]]: A list of tuples with
|
|
16
|
+
timestamps and total sizes.
|
|
17
|
+
"""
|
|
18
|
+
series = []
|
|
19
|
+
for snapshot in snapshots:
|
|
20
|
+
timestamp = snapshot.created_at
|
|
21
|
+
total_size = snapshot.total_value
|
|
22
|
+
series.append((total_size, timestamp))
|
|
23
|
+
|
|
24
|
+
return series
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""
|
|
2
|
+
High exposure (>1) means you’re deploying capital aggressively, possibly with many simultaneous positions.
|
|
3
|
+
|
|
4
|
+
Exposure around 1 means capital is nearly fully invested most of the time, but not overlapping.
|
|
5
|
+
|
|
6
|
+
Low exposure (<1) means capital is mostly idle or only partially invested.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
from typing import List
|
|
11
|
+
|
|
12
|
+
from investing_algorithm_framework.domain import Trade
|
|
13
|
+
|
|
14
|
+
|
|
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(
|
|
73
|
+
trades: List[Trade], start_date: datetime, end_date: datetime
|
|
74
|
+
) -> float:
|
|
75
|
+
"""
|
|
76
|
+
Calculates the exposure time as a fraction of the total backtest duration
|
|
77
|
+
that the strategy had capital deployed (i.e., at least one open position).
|
|
78
|
+
|
|
79
|
+
This value can be greater than 1 if the strategy had overlapping trades.
|
|
80
|
+
For example, if the strategy had two trades open at the same time,
|
|
81
|
+
the exposure factor would be 2.0, indicating that capital was deployed
|
|
82
|
+
for twice the duration of the backtest period.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
trades (List[Trade]): List of trades executed during the backtest.
|
|
86
|
+
start_date (datetime): The start date.
|
|
87
|
+
end_date (datetime): The end date.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
A float representing the exposure factor, which is the fraction of time
|
|
91
|
+
the strategy had capital deployed during the backtest period.
|
|
92
|
+
|
|
93
|
+
"""
|
|
94
|
+
if not trades:
|
|
95
|
+
return 0.0
|
|
96
|
+
|
|
97
|
+
total_trade_duration = timedelta(0)
|
|
98
|
+
for trade in trades:
|
|
99
|
+
entry = trade.opened_at
|
|
100
|
+
exit = trade.closed_at or end_date # open trades counted up to end
|
|
101
|
+
|
|
102
|
+
if exit > entry:
|
|
103
|
+
total_trade_duration += exit - entry
|
|
104
|
+
|
|
105
|
+
backtest_duration = end_date - start_date
|
|
106
|
+
|
|
107
|
+
if backtest_duration.total_seconds() == 0:
|
|
108
|
+
return 0.0
|
|
109
|
+
|
|
110
|
+
return (total_trade_duration.total_seconds()
|
|
111
|
+
/ backtest_duration.total_seconds())
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_average_trade_duration(trades: List[Trade]):
|
|
115
|
+
"""
|
|
116
|
+
Calculates the average duration of trades in the backtest report.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
trades (List[Trade]): List of trades executed during the backtest.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
A float representing the average trade duration in hours.
|
|
123
|
+
"""
|
|
124
|
+
if not trades:
|
|
125
|
+
return 0.0
|
|
126
|
+
|
|
127
|
+
total_duration = 0
|
|
128
|
+
|
|
129
|
+
for trade in trades:
|
|
130
|
+
trade_duration = trade.duration
|
|
131
|
+
|
|
132
|
+
if trade_duration is not None:
|
|
133
|
+
total_duration += trade_duration
|
|
134
|
+
|
|
135
|
+
average_trade_duration = total_duration / len(trades)
|
|
136
|
+
return average_trade_duration
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_trade_frequency(
|
|
140
|
+
trades: List[Trade], start_date: datetime, end_date: datetime
|
|
141
|
+
) -> float:
|
|
142
|
+
"""
|
|
143
|
+
Calculates the trade frequency as the number of trades per day
|
|
144
|
+
during the backtest period.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
trades (List[Trade]): List of trades executed during the backtest.
|
|
148
|
+
start_date (datetime): The start date of the backtest.
|
|
149
|
+
end_date (datetime): The end date of the backtest.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
A float representing the average number of trades per day.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
if not trades:
|
|
156
|
+
return 0.0
|
|
157
|
+
|
|
158
|
+
total_days = (end_date - start_date).days + 1
|
|
159
|
+
if total_days <= 0:
|
|
160
|
+
return 0.0
|
|
161
|
+
|
|
162
|
+
return len(trades) / total_days
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def get_trades_per_day(
|
|
166
|
+
trades: List[Trade], start_date: datetime, end_date: datetime
|
|
167
|
+
) -> float:
|
|
168
|
+
"""
|
|
169
|
+
Calculates the average number of trades per day during the backtest period.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
trades (List[Trade]): List of trades executed during the backtest.
|
|
173
|
+
start_date (datetime): The start date of the backtest.
|
|
174
|
+
end_date (datetime): The end date of the backtest.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
A float representing the average number of trades per day.
|
|
178
|
+
"""
|
|
179
|
+
if not trades:
|
|
180
|
+
return 0.0
|
|
181
|
+
|
|
182
|
+
total_days = (end_date - start_date).days + 1
|
|
183
|
+
if total_days <= 0:
|
|
184
|
+
return 0.0
|
|
185
|
+
|
|
186
|
+
return len(trades) / total_days
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def get_trades_per_year(
|
|
190
|
+
trades: List[Trade], start_date: datetime, end_date: datetime
|
|
191
|
+
) -> float:
|
|
192
|
+
"""
|
|
193
|
+
Calculates the average number of trades per year during the backtest period.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
trades (List[Trade]): List of trades executed during the backtest.
|
|
197
|
+
start_date (datetime): The start date of the backtest.
|
|
198
|
+
end_date (datetime): The end date of the backtest.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
A float representing the average number of trades per year.
|
|
202
|
+
"""
|
|
203
|
+
if not trades:
|
|
204
|
+
return 0.0
|
|
205
|
+
|
|
206
|
+
total_years = (end_date - start_date).days / 365.25
|
|
207
|
+
if total_years <= 0:
|
|
208
|
+
return 0.0
|
|
209
|
+
|
|
210
|
+
return len(trades) / total_years
|