investing-algorithm-framework 7.19.14__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 +197 -0
- investing_algorithm_framework/app/__init__.py +47 -0
- 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 +2204 -0
- investing_algorithm_framework/app/app_hook.py +28 -0
- investing_algorithm_framework/app/context.py +1667 -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/__init__.py +35 -0
- investing_algorithm_framework/app/stateless/action_handlers/__init__.py +84 -0
- investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +8 -0
- investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +15 -0
- investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +40 -0
- investing_algorithm_framework/app/stateless/exception_handler.py +40 -0
- investing_algorithm_framework/app/strategy.py +675 -0
- investing_algorithm_framework/app/task.py +41 -0
- investing_algorithm_framework/app/web/__init__.py +5 -0
- investing_algorithm_framework/app/web/controllers/__init__.py +13 -0
- investing_algorithm_framework/app/web/controllers/orders.py +20 -0
- investing_algorithm_framework/app/web/controllers/portfolio.py +20 -0
- investing_algorithm_framework/app/web/controllers/positions.py +18 -0
- investing_algorithm_framework/app/web/create_app.py +20 -0
- investing_algorithm_framework/app/web/error_handler.py +59 -0
- investing_algorithm_framework/app/web/responses.py +20 -0
- investing_algorithm_framework/app/web/run_strategies.py +4 -0
- investing_algorithm_framework/app/web/schemas/__init__.py +12 -0
- investing_algorithm_framework/app/web/schemas/order.py +12 -0
- investing_algorithm_framework/app/web/schemas/portfolio.py +22 -0
- investing_algorithm_framework/app/web/schemas/position.py +15 -0
- investing_algorithm_framework/app/web/setup_cors.py +6 -0
- investing_algorithm_framework/cli/__init__.py +0 -0
- investing_algorithm_framework/cli/cli.py +207 -0
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +499 -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 +54 -0
- investing_algorithm_framework/dependency_container.py +155 -0
- investing_algorithm_framework/domain/__init__.py +148 -0
- 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 +435 -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 +111 -0
- investing_algorithm_framework/domain/constants.py +83 -0
- investing_algorithm_framework/domain/data_provider.py +334 -0
- investing_algorithm_framework/domain/data_structures.py +42 -0
- investing_algorithm_framework/domain/decimal_parsing.py +40 -0
- investing_algorithm_framework/domain/exceptions.py +112 -0
- investing_algorithm_framework/domain/models/__init__.py +43 -0
- investing_algorithm_framework/domain/models/app_mode.py +34 -0
- investing_algorithm_framework/domain/models/base_model.py +25 -0
- 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/__init__.py +5 -0
- investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
- investing_algorithm_framework/domain/models/order/__init__.py +6 -0
- investing_algorithm_framework/domain/models/order/order.py +384 -0
- investing_algorithm_framework/domain/models/order/order_side.py +36 -0
- investing_algorithm_framework/domain/models/order/order_status.py +37 -0
- investing_algorithm_framework/domain/models/order/order_type.py +30 -0
- investing_algorithm_framework/domain/models/portfolio/__init__.py +9 -0
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +169 -0
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +93 -0
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
- investing_algorithm_framework/domain/models/position/__init__.py +4 -0
- investing_algorithm_framework/domain/models/position/position.py +68 -0
- investing_algorithm_framework/domain/models/position/position_snapshot.py +47 -0
- investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
- investing_algorithm_framework/domain/models/strategy_profile.py +33 -0
- investing_algorithm_framework/domain/models/time_frame.py +153 -0
- investing_algorithm_framework/domain/models/time_interval.py +124 -0
- investing_algorithm_framework/domain/models/time_unit.py +149 -0
- investing_algorithm_framework/domain/models/tracing/__init__.py +0 -0
- investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
- investing_algorithm_framework/domain/models/trade/__init__.py +13 -0
- investing_algorithm_framework/domain/models/trade/trade.py +388 -0
- investing_algorithm_framework/domain/models/trade/trade_risk_type.py +34 -0
- investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +267 -0
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +303 -0
- investing_algorithm_framework/domain/order_executor.py +112 -0
- investing_algorithm_framework/domain/portfolio_provider.py +118 -0
- investing_algorithm_framework/domain/positions/__init__.py +4 -0
- investing_algorithm_framework/domain/positions/position_size.py +41 -0
- investing_algorithm_framework/domain/services/__init__.py +11 -0
- investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
- investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
- investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
- investing_algorithm_framework/domain/services/rounding_service.py +27 -0
- investing_algorithm_framework/domain/services/state_handler.py +38 -0
- investing_algorithm_framework/domain/stateless_actions.py +7 -0
- investing_algorithm_framework/domain/strategy.py +44 -0
- investing_algorithm_framework/domain/utils/__init__.py +27 -0
- investing_algorithm_framework/domain/utils/csv.py +104 -0
- 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 +41 -0
- investing_algorithm_framework/domain/utils/signatures.py +17 -0
- investing_algorithm_framework/domain/utils/stoppable_thread.py +26 -0
- investing_algorithm_framework/domain/utils/synchronized.py +12 -0
- investing_algorithm_framework/download_data.py +108 -0
- investing_algorithm_framework/infrastructure/__init__.py +50 -0
- 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 +10 -0
- investing_algorithm_framework/infrastructure/database/sql_alchemy.py +120 -0
- investing_algorithm_framework/infrastructure/models/__init__.py +16 -0
- investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
- investing_algorithm_framework/infrastructure/models/model_extension.py +6 -0
- investing_algorithm_framework/infrastructure/models/order/__init__.py +4 -0
- investing_algorithm_framework/infrastructure/models/order/order.py +124 -0
- 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 +4 -0
- investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
- investing_algorithm_framework/infrastructure/models/portfolio/sql_portfolio.py +114 -0
- investing_algorithm_framework/infrastructure/models/position/__init__.py +4 -0
- investing_algorithm_framework/infrastructure/models/position/position.py +63 -0
- investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +23 -0
- 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 +40 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +41 -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 +21 -0
- investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
- investing_algorithm_framework/infrastructure/repositories/order_repository.py +96 -0
- investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +30 -0
- investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
- investing_algorithm_framework/infrastructure/repositories/position_repository.py +66 -0
- investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +299 -0
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +23 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +23 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +7 -0
- 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 +132 -0
- investing_algorithm_framework/services/backtesting/__init__.py +5 -0
- investing_algorithm_framework/services/backtesting/backtest_service.py +651 -0
- investing_algorithm_framework/services/configuration_service.py +96 -0
- 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 +40 -0
- 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/price_efficiency.py +57 -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 +9 -0
- investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
- investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
- investing_algorithm_framework/services/order_service/order_service.py +826 -0
- investing_algorithm_framework/services/portfolios/__init__.py +16 -0
- investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
- investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +75 -0
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
- investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
- investing_algorithm_framework/services/positions/__init__.py +7 -0
- investing_algorithm_framework/services/positions/position_service.py +210 -0
- investing_algorithm_framework/services/positions/position_snapshot_service.py +18 -0
- investing_algorithm_framework/services/repository_service.py +40 -0
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +132 -0
- investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +66 -0
- investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +41 -0
- investing_algorithm_framework/services/trade_service/__init__.py +3 -0
- investing_algorithm_framework/services/trade_service/trade_service.py +1083 -0
- investing_algorithm_framework-7.19.14.dist-info/LICENSE +201 -0
- investing_algorithm_framework-7.19.14.dist-info/METADATA +459 -0
- investing_algorithm_framework-7.19.14.dist-info/RECORD +260 -0
- investing_algorithm_framework-7.19.14.dist-info/WHEEL +4 -0
- investing_algorithm_framework-7.19.14.dist-info/entry_points.txt +3 -0
|
@@ -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
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
from .cagr import get_cagr
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_mean_daily_return(snapshots):
|
|
8
|
+
"""
|
|
9
|
+
Calculate the mean daily return from the total value of the snapshots.
|
|
10
|
+
|
|
11
|
+
This function computes the mean daily return based on the list of
|
|
12
|
+
snapshots in the report. If the snapshots have a granularity of less
|
|
13
|
+
than a day, the function will resample to daily frequency and compute
|
|
14
|
+
average daily returns.
|
|
15
|
+
|
|
16
|
+
If there is less data then for a year, it will use cagr to
|
|
17
|
+
calculate the mean daily return.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
snapshots (List[PortfolioSnapshot]): List of portfolio snapshots
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
float: The mean daily return.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
if len(snapshots) < 2:
|
|
27
|
+
return 0.0 # Not enough data
|
|
28
|
+
|
|
29
|
+
# Create DataFrame from snapshots
|
|
30
|
+
data = [(s.created_at, s.total_value) for s in snapshots]
|
|
31
|
+
df = pd.DataFrame(data, columns=["created_at", "total_value"])
|
|
32
|
+
df['created_at'] = pd.to_datetime(df['created_at'])
|
|
33
|
+
df = df.sort_values('created_at').drop_duplicates('created_at')\
|
|
34
|
+
.set_index('created_at')
|
|
35
|
+
|
|
36
|
+
start_date = df.iloc[0].name
|
|
37
|
+
end_date = df.iloc[-1].name
|
|
38
|
+
|
|
39
|
+
# Check if the period is less than a year
|
|
40
|
+
if (end_date - start_date).days < 365:
|
|
41
|
+
# Use CAGR to calculate mean daily return
|
|
42
|
+
cagr = get_cagr(snapshots)
|
|
43
|
+
if cagr == 0.0:
|
|
44
|
+
return 0.0
|
|
45
|
+
return (1 + cagr) ** (1 / 365) - 1
|
|
46
|
+
|
|
47
|
+
# Resample to daily frequency using last value of the day
|
|
48
|
+
daily_df = df.resample('1D').last().dropna()
|
|
49
|
+
|
|
50
|
+
# Calculate daily returns
|
|
51
|
+
daily_df['return'] = daily_df['total_value'].pct_change()
|
|
52
|
+
daily_df = daily_df.dropna()
|
|
53
|
+
|
|
54
|
+
if daily_df.empty:
|
|
55
|
+
return 0.0
|
|
56
|
+
|
|
57
|
+
mean_return = daily_df['return'].mean()
|
|
58
|
+
|
|
59
|
+
if np.isnan(mean_return):
|
|
60
|
+
return 0.0
|
|
61
|
+
|
|
62
|
+
return mean_return
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_mean_yearly_return(report, periods_per_year=365):
|
|
66
|
+
"""
|
|
67
|
+
Calculate the mean yearly return from a backtest report by
|
|
68
|
+
annualizing the mean daily return.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
report (BacktestReport): The report containing the snapshots.
|
|
72
|
+
periods_per_year (int): Number of periods in a year (e.g., 365 for daily data).
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
float: The mean yearly return (annualized).
|
|
76
|
+
"""
|
|
77
|
+
mean_daily_return = get_mean_daily_return(report)
|
|
78
|
+
|
|
79
|
+
if mean_daily_return == 0.0:
|
|
80
|
+
return 0.0
|
|
81
|
+
|
|
82
|
+
return (1 + mean_daily_return) ** periods_per_year - 1
|
|
83
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pandas import DataFrame, DatetimeIndex
|
|
3
|
+
from investing_algorithm_framework.domain.exceptions import \
|
|
4
|
+
OperationalException
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_price_efficiency_ratio(data: DataFrame):
|
|
10
|
+
"""
|
|
11
|
+
Calculate the price efficiency ratio (noise) for each symbol.
|
|
12
|
+
|
|
13
|
+
The price efficiency ratio is calculated as follows:
|
|
14
|
+
|
|
15
|
+
1. Calculate the net price change over the period
|
|
16
|
+
2. Calculate the sum of absolute daily price changes
|
|
17
|
+
3. Calculate Efficiency Ratio = Net Price Change / Sum of Absolute
|
|
18
|
+
Daily Price Changes
|
|
19
|
+
|
|
20
|
+
The price efficiency ratio is a measure of the efficiency of the
|
|
21
|
+
price movement over the period. A higher efficiency ratio indicates
|
|
22
|
+
a more efficient price movement.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
data (dict): A pandas DataFrame containing a column with either a
|
|
26
|
+
'Close' or 'Price' label and a datetime index.
|
|
27
|
+
|
|
28
|
+
returns:
|
|
29
|
+
float: The price efficiency ratio
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
# Check if close value and index is a datetime object
|
|
33
|
+
if 'Close' not in data.columns:
|
|
34
|
+
raise OperationalException(
|
|
35
|
+
"Close column not found in data, "
|
|
36
|
+
"required for price efficiency ratio calculation"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
if not isinstance(data.index, DatetimeIndex):
|
|
40
|
+
raise OperationalException(
|
|
41
|
+
"Index is not a datetime object,"
|
|
42
|
+
"required for price efficiency ratio calculation"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Calculate daily price changes
|
|
46
|
+
data['Daily Change'] = data['Close'].diff()
|
|
47
|
+
|
|
48
|
+
# Calculate net price change over the period
|
|
49
|
+
net_price_change = abs(
|
|
50
|
+
data['Close'].iloc[-1] - data['Close'].iloc[0])
|
|
51
|
+
|
|
52
|
+
# Calculate the sum of absolute daily price changes
|
|
53
|
+
sum_absolute_changes = data['Daily Change'] \
|
|
54
|
+
.abs().sum()
|
|
55
|
+
|
|
56
|
+
# Calculate Efficiency Ratio
|
|
57
|
+
return net_price_change / sum_absolute_changes
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module for calculating profit factor metrics from backtest trades.
|
|
3
|
+
|
|
4
|
+
| **Profit Factor** | **Interpretation** |
|
|
5
|
+
| ----------------- | ----------------------------------------------------------------------------------------- |
|
|
6
|
+
| **< 1.0** | **Losing strategy** — losses outweigh profits |
|
|
7
|
+
| **1.0 – 1.3** | Weak or barely breakeven — needs improvement or may not be sustainable |
|
|
8
|
+
| **1.3 – 1.6** | Average — possibly profitable but sensitive to market regime changes |
|
|
9
|
+
| **1.6 – 2.0** | Good — generally indicates a solid, sustainable edge |
|
|
10
|
+
| **2.0 – 3.0** | Very good — strong edge with lower drawdown risk |
|
|
11
|
+
| **> 3.0** | Excellent — rare in real markets; often associated with low-frequency or niche strategies |
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from collections import deque
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from typing import List, Tuple
|
|
18
|
+
|
|
19
|
+
from investing_algorithm_framework.domain.models import Trade
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_cumulative_profit_factor_series(
|
|
23
|
+
trades: List[Trade]
|
|
24
|
+
) -> list[tuple[datetime, float]]:
|
|
25
|
+
"""
|
|
26
|
+
Calculates the cumulative profit factor over time from a backtest report.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
trades (List[Trade]): List of closed trades from the backtest report.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
List of (datetime, float) tuples: (timestamp, cumulative profit factor)
|
|
33
|
+
"""
|
|
34
|
+
results = []
|
|
35
|
+
gross_profit = 0.0
|
|
36
|
+
gross_loss = 0.0
|
|
37
|
+
|
|
38
|
+
for trade in trades:
|
|
39
|
+
close_time = trade.closed_at
|
|
40
|
+
profit = trade.net_gain
|
|
41
|
+
|
|
42
|
+
if profit >= 0:
|
|
43
|
+
gross_profit += profit
|
|
44
|
+
else:
|
|
45
|
+
gross_loss += abs(profit)
|
|
46
|
+
|
|
47
|
+
# Calculate profit factor with division-by-zero protection
|
|
48
|
+
if gross_loss > 0:
|
|
49
|
+
profit_factor = gross_profit / gross_loss
|
|
50
|
+
else:
|
|
51
|
+
profit_factor = float('inf') if gross_profit > 0 else 0.0
|
|
52
|
+
|
|
53
|
+
results.append((close_time, profit_factor))
|
|
54
|
+
|
|
55
|
+
return results
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_rolling_profit_factor_series(
|
|
59
|
+
trades: List[Trade], window_size: int = 20
|
|
60
|
+
) -> List[Tuple[datetime, float]]:
|
|
61
|
+
"""
|
|
62
|
+
Calculates the rolling profit factor over time from a backtest report.
|
|
63
|
+
|
|
64
|
+
The rolling profit factor is computed using the most recent
|
|
65
|
+
`window_size` trades and updated after each closed trade.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
backtest_report (BacktestReport): A instance of BacktestReport
|
|
69
|
+
containing closed trades.
|
|
70
|
+
window_size: The number of most recent trades to include in
|
|
71
|
+
each rolling calculation.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
A list of tuples, where each tuple contains:
|
|
75
|
+
- datetime: The close time of the trade (or aligned date).
|
|
76
|
+
- float: The rolling profit factor at that time.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
results = []
|
|
80
|
+
trade_window = deque(maxlen=window_size)
|
|
81
|
+
|
|
82
|
+
for trade in trades:
|
|
83
|
+
close_time = trade.closed_at
|
|
84
|
+
profit = trade.net_gain
|
|
85
|
+
|
|
86
|
+
trade_window.append(profit)
|
|
87
|
+
|
|
88
|
+
gross_profit = sum(p for p in trade_window if p >= 0)
|
|
89
|
+
gross_loss = sum(abs(p) for p in trade_window if p < 0)
|
|
90
|
+
|
|
91
|
+
if gross_loss > 0:
|
|
92
|
+
profit_factor = gross_profit / gross_loss
|
|
93
|
+
else:
|
|
94
|
+
profit_factor = float('inf') if gross_profit > 0 else 0.0
|
|
95
|
+
|
|
96
|
+
results.append((close_time, profit_factor))
|
|
97
|
+
|
|
98
|
+
return results
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def get_profit_factor(trades: List[Trade]) -> float:
|
|
102
|
+
"""
|
|
103
|
+
Calculates the total profit factor at the end of the backtest.
|
|
104
|
+
|
|
105
|
+
The profit factor is defined as:
|
|
106
|
+
Total Gross Profit / Total Gross Loss
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
trades (List[Trade]): List of closed trades from the backtest report.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
float: The profit factor at the end of the backtest.
|
|
113
|
+
Returns float('inf') if there are no losses,
|
|
114
|
+
and 0.0 if there are no profits and losses.
|
|
115
|
+
"""
|
|
116
|
+
gross_profit = 0.0
|
|
117
|
+
gross_loss = 0.0
|
|
118
|
+
|
|
119
|
+
for trade in trades:
|
|
120
|
+
profit = trade.net_gain
|
|
121
|
+
if profit > 0:
|
|
122
|
+
gross_profit += profit
|
|
123
|
+
elif profit < 0:
|
|
124
|
+
gross_loss += abs(profit)
|
|
125
|
+
|
|
126
|
+
if gross_loss == 0:
|
|
127
|
+
return float('inf') if gross_profit > 0 else 0.0
|
|
128
|
+
|
|
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
|