investing-algorithm-framework 1.5__py3-none-any.whl → 7.25.6__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.
- investing_algorithm_framework/__init__.py +192 -16
- investing_algorithm_framework/analysis/__init__.py +16 -0
- investing_algorithm_framework/analysis/backtest_data_ranges.py +202 -0
- investing_algorithm_framework/analysis/data.py +170 -0
- investing_algorithm_framework/analysis/markdown.py +91 -0
- investing_algorithm_framework/analysis/ranking.py +298 -0
- investing_algorithm_framework/app/__init__.py +29 -4
- investing_algorithm_framework/app/algorithm/__init__.py +7 -0
- investing_algorithm_framework/app/algorithm/algorithm.py +193 -0
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +118 -0
- investing_algorithm_framework/app/app.py +2220 -379
- investing_algorithm_framework/app/app_hook.py +28 -0
- investing_algorithm_framework/app/context.py +1724 -0
- investing_algorithm_framework/app/eventloop.py +620 -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/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 +6 -3
- investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +2 -1
- investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
- investing_algorithm_framework/app/strategy.py +867 -60
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/app/web/__init__.py +2 -1
- investing_algorithm_framework/app/web/controllers/__init__.py +2 -2
- investing_algorithm_framework/app/web/controllers/orders.py +3 -2
- investing_algorithm_framework/app/web/controllers/positions.py +2 -2
- investing_algorithm_framework/app/web/create_app.py +4 -2
- investing_algorithm_framework/app/web/schemas/position.py +1 -0
- investing_algorithm_framework/cli/__init__.py +0 -0
- investing_algorithm_framework/cli/cli.py +231 -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/cli/validate_backtest_checkpoints.py +197 -0
- investing_algorithm_framework/create_app.py +40 -7
- investing_algorithm_framework/dependency_container.py +100 -47
- investing_algorithm_framework/domain/__init__.py +97 -30
- investing_algorithm_framework/domain/algorithm_id.py +69 -0
- investing_algorithm_framework/domain/backtesting/__init__.py +25 -0
- investing_algorithm_framework/domain/backtesting/backtest.py +548 -0
- investing_algorithm_framework/domain/backtesting/backtest_date_range.py +113 -0
- investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +241 -0
- investing_algorithm_framework/domain/backtesting/backtest_metrics.py +470 -0
- investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
- investing_algorithm_framework/domain/backtesting/backtest_run.py +663 -0
- investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
- investing_algorithm_framework/domain/backtesting/backtest_utils.py +198 -0
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +392 -0
- investing_algorithm_framework/domain/config.py +59 -136
- investing_algorithm_framework/domain/constants.py +18 -37
- investing_algorithm_framework/domain/data_provider.py +334 -0
- investing_algorithm_framework/domain/data_structures.py +42 -0
- investing_algorithm_framework/domain/exceptions.py +51 -1
- investing_algorithm_framework/domain/models/__init__.py +26 -19
- investing_algorithm_framework/domain/models/app_mode.py +34 -0
- investing_algorithm_framework/domain/models/data/__init__.py +7 -0
- investing_algorithm_framework/domain/models/data/data_source.py +222 -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 +3 -4
- investing_algorithm_framework/domain/models/order/order.py +198 -65
- 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/__init__.py +6 -2
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +98 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +37 -43
- 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 +20 -0
- investing_algorithm_framework/domain/models/position/position_size.py +41 -0
- investing_algorithm_framework/domain/models/position/position_snapshot.py +0 -2
- 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 -141
- investing_algorithm_framework/domain/models/time_frame.py +94 -98
- 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/tracing/__init__.py +0 -0
- investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
- investing_algorithm_framework/domain/models/trade/__init__.py +11 -0
- investing_algorithm_framework/domain/models/trade/trade.py +389 -0
- investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
- 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 +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/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +15 -5
- investing_algorithm_framework/domain/utils/csv.py +22 -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 +29 -0
- investing_algorithm_framework/download_data.py +244 -0
- investing_algorithm_framework/infrastructure/__init__.py +37 -11
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1152 -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 +7 -3
- investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -2
- investing_algorithm_framework/infrastructure/models/order/order.py +53 -53
- 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 -2
- investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +17 -6
- investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +3 -1
- 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 +10 -4
- investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
- investing_algorithm_framework/infrastructure/repositories/order_repository.py +16 -5
- investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +2 -2
- investing_algorithm_framework/infrastructure/repositories/position_repository.py +11 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +84 -30
- 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 +9 -4
- investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
- investing_algorithm_framework/infrastructure/services/aws/state_handler.py +193 -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/infrastructure/services/backtesting/__init__.py +9 -0
- investing_algorithm_framework/infrastructure/services/backtesting/backtest_service.py +2596 -0
- investing_algorithm_framework/infrastructure/services/backtesting/event_backtest_service.py +285 -0
- investing_algorithm_framework/infrastructure/services/backtesting/vector_backtest_service.py +468 -0
- investing_algorithm_framework/services/__init__.py +123 -15
- 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 +1058 -0
- investing_algorithm_framework/services/market_credential_service.py +40 -0
- investing_algorithm_framework/services/metrics/__init__.py +119 -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 +218 -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 +84 -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 +156 -0
- investing_algorithm_framework/services/metrics/trades.py +473 -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 +118 -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/{portfolio_configuration_service.py → portfolios/portfolio_configuration_service.py} +27 -12
- 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/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 +117 -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 +9 -0
- investing_algorithm_framework/services/trade_service/trade_service.py +1099 -0
- 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.25.6.dist-info/METADATA +535 -0
- investing_algorithm_framework-7.25.6.dist-info/RECORD +268 -0
- {investing_algorithm_framework-1.5.dist-info → investing_algorithm_framework-7.25.6.dist-info}/WHEEL +1 -2
- investing_algorithm_framework-7.25.6.dist-info/entry_points.txt +3 -0
- investing_algorithm_framework/app/algorithm.py +0 -630
- investing_algorithm_framework/domain/models/backtest_profile.py +0 -414
- investing_algorithm_framework/domain/models/market_data/__init__.py +0 -11
- investing_algorithm_framework/domain/models/market_data/asset_price.py +0 -50
- investing_algorithm_framework/domain/models/market_data/ohlcv.py +0 -105
- investing_algorithm_framework/domain/models/market_data/order_book.py +0 -63
- investing_algorithm_framework/domain/models/market_data/ticker.py +0 -92
- investing_algorithm_framework/domain/models/order/order_fee.py +0 -45
- investing_algorithm_framework/domain/models/trade.py +0 -78
- 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/singleton.py +0 -9
- investing_algorithm_framework/domain/utils/backtesting.py +0 -82
- investing_algorithm_framework/infrastructure/models/order/order_fee.py +0 -21
- investing_algorithm_framework/infrastructure/repositories/order_fee_repository.py +0 -15
- investing_algorithm_framework/infrastructure/services/market_backtest_service.py +0 -360
- investing_algorithm_framework/infrastructure/services/market_service.py +0 -410
- investing_algorithm_framework/infrastructure/services/performance_service.py +0 -192
- investing_algorithm_framework/services/backtest_service.py +0 -268
- investing_algorithm_framework/services/market_data_service.py +0 -77
- investing_algorithm_framework/services/order_backtest_service.py +0 -122
- investing_algorithm_framework/services/order_service.py +0 -752
- investing_algorithm_framework/services/portfolio_service.py +0 -164
- investing_algorithm_framework/services/portfolio_snapshot_service.py +0 -68
- investing_algorithm_framework/services/position_cost_service.py +0 -5
- investing_algorithm_framework/services/position_service.py +0 -63
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -225
- investing_algorithm_framework-1.5.dist-info/AUTHORS.md +0 -8
- investing_algorithm_framework-1.5.dist-info/METADATA +0 -230
- investing_algorithm_framework-1.5.dist-info/RECORD +0 -119
- investing_algorithm_framework-1.5.dist-info/top_level.txt +0 -1
- /investing_algorithm_framework/{infrastructure/services/performance_backtest_service.py → app/reporting/tables/stop_loss_table.py} +0 -0
- /investing_algorithm_framework/services/{position_snapshot_service.py → positions/position_snapshot_service.py} +0 -0
- {investing_algorithm_framework-1.5.dist-info → investing_algorithm_framework-7.25.6.dist-info}/LICENSE +0 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_standard_deviation_downside_returns(snapshots):
|
|
6
|
+
"""
|
|
7
|
+
Calculate the standard deviation of downside returns from the net size
|
|
8
|
+
of the reports.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
report (BacktestReport): The report containing the equity curve.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
float: The standard deviation of downside returns.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
if len(snapshots) < 2:
|
|
18
|
+
return 0.0 # Not enough data
|
|
19
|
+
|
|
20
|
+
# Create DataFrame of net_size over time
|
|
21
|
+
data = [(s.total_value, s.created_at) for s in snapshots]
|
|
22
|
+
df = pd.DataFrame(data, columns=["total_value", "created_at"])
|
|
23
|
+
df['created_at'] = pd.to_datetime(df['created_at'])
|
|
24
|
+
df = df.sort_values('created_at').drop_duplicates('created_at').copy()
|
|
25
|
+
|
|
26
|
+
# Compute percentage returns
|
|
27
|
+
df['return'] = df['total_value'].pct_change()
|
|
28
|
+
df = df.dropna()
|
|
29
|
+
|
|
30
|
+
if df.empty:
|
|
31
|
+
return 0.0
|
|
32
|
+
|
|
33
|
+
# Filter downside returns
|
|
34
|
+
downside_returns = df['return'][df['return'] < 0]
|
|
35
|
+
|
|
36
|
+
if downside_returns.empty:
|
|
37
|
+
return 0.0
|
|
38
|
+
|
|
39
|
+
# Compute standard deviation of downside returns
|
|
40
|
+
downside_std = downside_returns.std(ddof=1) # ddof=1 for sample std dev
|
|
41
|
+
|
|
42
|
+
# Handle edge cases
|
|
43
|
+
if np.isnan(downside_std):
|
|
44
|
+
return 0.0
|
|
45
|
+
|
|
46
|
+
return downside_std
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_standard_deviation_returns(snapshots):
|
|
50
|
+
"""
|
|
51
|
+
Calculate the standard deviation of returns from the net size
|
|
52
|
+
of the reports.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
report (BacktestReport): The report containing the equity curve.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
float: The standard deviation of downside returns.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
if len(snapshots) < 2:
|
|
62
|
+
return 0.0 # Not enough data
|
|
63
|
+
|
|
64
|
+
# Create DataFrame of net_size over time
|
|
65
|
+
data = [(s.total_value, s.created_at) for s in snapshots]
|
|
66
|
+
df = pd.DataFrame(data, columns=["total_value", "created_at"])
|
|
67
|
+
df['created_at'] = pd.to_datetime(df['created_at'])
|
|
68
|
+
df = df.sort_values('created_at').drop_duplicates('created_at').copy()
|
|
69
|
+
|
|
70
|
+
# Compute percentage returns
|
|
71
|
+
df['return'] = df['total_value'].pct_change()
|
|
72
|
+
df = df.dropna()
|
|
73
|
+
|
|
74
|
+
if df.empty:
|
|
75
|
+
return 0.0
|
|
76
|
+
|
|
77
|
+
# Filter downside returns
|
|
78
|
+
df_returns = df['return']
|
|
79
|
+
|
|
80
|
+
if df_returns.empty:
|
|
81
|
+
return 0.0
|
|
82
|
+
|
|
83
|
+
std = df_returns.std(ddof=1) # ddof=1 for sample std dev
|
|
84
|
+
|
|
85
|
+
# Handle edge cases
|
|
86
|
+
if np.isnan(std):
|
|
87
|
+
return 0.0
|
|
88
|
+
|
|
89
|
+
return std
|
|
90
|
+
|
|
91
|
+
def get_daily_returns_std(snapshots):
|
|
92
|
+
"""
|
|
93
|
+
Calculate the standard deviation of daily returns from a list of snapshots.
|
|
94
|
+
Resamples data to daily frequency using end-of-day values.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
snapshots (List[PortfolioSnapshot]): Snapshots with total_value and created_at.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
float: Standard deviation of daily returns.
|
|
101
|
+
"""
|
|
102
|
+
if len(snapshots) < 2:
|
|
103
|
+
return 0.0 # Not enough data
|
|
104
|
+
|
|
105
|
+
# Create DataFrame from snapshots
|
|
106
|
+
data = [(s.created_at, s.total_value) for s in snapshots]
|
|
107
|
+
df = pd.DataFrame(data, columns=["created_at", "total_value"])
|
|
108
|
+
df["created_at"] = pd.to_datetime(df["created_at"])
|
|
109
|
+
df = df.drop_duplicates("created_at").set_index("created_at")
|
|
110
|
+
df = df.sort_index()
|
|
111
|
+
# Resample to daily frequency (end of day)
|
|
112
|
+
daily_df = df.resample("D").last().ffill().dropna()
|
|
113
|
+
|
|
114
|
+
# Calculate daily returns
|
|
115
|
+
daily_df["return"] = daily_df["total_value"].pct_change().dropna()
|
|
116
|
+
|
|
117
|
+
if daily_df["return"].empty:
|
|
118
|
+
return 0.0
|
|
119
|
+
|
|
120
|
+
return daily_df["return"].std()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def get_downside_std_of_daily_returns(snapshots):
|
|
124
|
+
"""
|
|
125
|
+
Calculate the downside standard deviation of daily returns from a list of snapshots.
|
|
126
|
+
Resamples data to daily frequency using end-of-day values.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
snapshots (List[PortfolioSnapshot]): Snapshots with total_value and created_at.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
float: Downside standard deviation of daily returns.
|
|
133
|
+
"""
|
|
134
|
+
if len(snapshots) < 2:
|
|
135
|
+
return 0.0 # Not enough data
|
|
136
|
+
|
|
137
|
+
# Create DataFrame from snapshots
|
|
138
|
+
data = [(s.created_at, s.total_value) for s in snapshots]
|
|
139
|
+
df = pd.DataFrame(data, columns=["created_at", "total_value"])
|
|
140
|
+
df["created_at"] = pd.to_datetime(df["created_at"])
|
|
141
|
+
df = df.drop_duplicates("created_at").set_index("created_at")
|
|
142
|
+
df = df.sort_index()
|
|
143
|
+
|
|
144
|
+
# Resample to daily frequency (end of day)
|
|
145
|
+
daily_df = df.resample("D").last().dropna()
|
|
146
|
+
|
|
147
|
+
# Calculate daily returns
|
|
148
|
+
daily_df["return"] = daily_df["total_value"].pct_change().dropna()
|
|
149
|
+
|
|
150
|
+
# Filter only negative returns for downside deviation
|
|
151
|
+
negative_returns = daily_df["return"][daily_df["return"] < 0]
|
|
152
|
+
|
|
153
|
+
if negative_returns.empty:
|
|
154
|
+
return 0.0
|
|
155
|
+
|
|
156
|
+
return negative_returns.std()
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
from typing import List, Tuple
|
|
2
|
+
|
|
3
|
+
from investing_algorithm_framework.domain import Trade, TradeStatus, \
|
|
4
|
+
OperationalException, BacktestRun
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_positive_trades(
|
|
8
|
+
trades: List[Trade]
|
|
9
|
+
) -> Tuple[int, float]:
|
|
10
|
+
"""
|
|
11
|
+
Calculate the number and percentage of positive trades.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
trades (List[Trade]): List of Trade objects.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Tuple[int, float]: A tuple containing the number of positive trades
|
|
18
|
+
and the percentage of positive trades.
|
|
19
|
+
"""
|
|
20
|
+
if trades is None or len(trades) == 0:
|
|
21
|
+
return 0, 0.0
|
|
22
|
+
|
|
23
|
+
closed_trades = [
|
|
24
|
+
trade for trade in trades if TradeStatus.CLOSED.equals(trade.status)
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
positive_trades = [
|
|
28
|
+
trade for trade in closed_trades if trade.net_gain_absolute > 0
|
|
29
|
+
]
|
|
30
|
+
number_of_positive_trades = len(positive_trades)
|
|
31
|
+
percentage_positive_trades = (
|
|
32
|
+
(number_of_positive_trades / len(closed_trades)) * 100.0
|
|
33
|
+
if len(closed_trades) > 0 else 0.0
|
|
34
|
+
)
|
|
35
|
+
return number_of_positive_trades, percentage_positive_trades
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_negative_trades(
|
|
39
|
+
trades: List[Trade]
|
|
40
|
+
) -> Tuple[int, float]:
|
|
41
|
+
"""
|
|
42
|
+
Calculate the number and percentage of negative trades.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
trades (List[Trade]): List of Trade objects.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Tuple[int, float]: A tuple containing the number of negative trades
|
|
49
|
+
and the percentage ofr negative trades.
|
|
50
|
+
"""
|
|
51
|
+
if trades is None or len(trades) == 0:
|
|
52
|
+
return 0, 0.0
|
|
53
|
+
|
|
54
|
+
closed_trades = [
|
|
55
|
+
trade for trade in trades if TradeStatus.CLOSED.equals(trade.status)
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
negative_trades = [
|
|
59
|
+
trade for trade in closed_trades if trade.net_gain_absolute < 0
|
|
60
|
+
]
|
|
61
|
+
number_of_negative_trades = len(negative_trades)
|
|
62
|
+
percentage_negative_trades = (
|
|
63
|
+
(number_of_negative_trades / len(closed_trades)) * 100.0
|
|
64
|
+
if len(closed_trades) > 0 else 0.0
|
|
65
|
+
)
|
|
66
|
+
return number_of_negative_trades, percentage_negative_trades
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_number_of_trades(
|
|
70
|
+
trades: List[Trade]
|
|
71
|
+
) -> int:
|
|
72
|
+
"""
|
|
73
|
+
Calculate the total number of trades.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
trades (List[Trade]): List of Trade objects.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
int: The total number of trades.
|
|
80
|
+
"""
|
|
81
|
+
if trades is None:
|
|
82
|
+
return 0
|
|
83
|
+
|
|
84
|
+
return len(trades)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_number_of_open_trades(
|
|
88
|
+
trades: List[Trade]
|
|
89
|
+
) -> int:
|
|
90
|
+
"""
|
|
91
|
+
Calculate the number of open trades.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
trades (List[Trade]): List of Trade objects.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
int: The number of open trades.
|
|
98
|
+
"""
|
|
99
|
+
if trades is None:
|
|
100
|
+
return 0
|
|
101
|
+
|
|
102
|
+
open_trades = [
|
|
103
|
+
trade for trade in trades if TradeStatus.OPEN.equals(trade.status)
|
|
104
|
+
]
|
|
105
|
+
return len(open_trades)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def get_number_of_closed_trades(
|
|
109
|
+
trades: List[Trade]
|
|
110
|
+
) -> int:
|
|
111
|
+
"""
|
|
112
|
+
Calculate the number of closed trades.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
trades (List[Trade]): List of Trade objects.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
int: The number of closed trades.
|
|
119
|
+
"""
|
|
120
|
+
if trades is None:
|
|
121
|
+
return 0
|
|
122
|
+
|
|
123
|
+
closed_trades = [
|
|
124
|
+
trade for trade in trades if TradeStatus.CLOSED.equals(trade.status)
|
|
125
|
+
]
|
|
126
|
+
return len(closed_trades)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def get_average_trade_duration(
|
|
130
|
+
trades: List[Trade]
|
|
131
|
+
) -> float:
|
|
132
|
+
"""
|
|
133
|
+
Calculate the average duration of closed trades in hours.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
trades (List[Trade]): List of Trade objects.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
float: The average trade duration in hours.
|
|
140
|
+
"""
|
|
141
|
+
if trades is None:
|
|
142
|
+
return 0.0
|
|
143
|
+
|
|
144
|
+
total_duration = 0.0
|
|
145
|
+
|
|
146
|
+
for trade in trades:
|
|
147
|
+
if TradeStatus.CLOSED.equals(trade.status):
|
|
148
|
+
total_duration += (trade.closed_at - trade.opened_at)\
|
|
149
|
+
.total_seconds() / 3600
|
|
150
|
+
|
|
151
|
+
number_of_trades = get_number_of_closed_trades(trades)
|
|
152
|
+
return total_duration / number_of_trades if number_of_trades > 0 else 0.0
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def get_current_average_trade_duration(
|
|
156
|
+
trades: List[Trade], backtest_run: BacktestRun
|
|
157
|
+
) -> float:
|
|
158
|
+
"""
|
|
159
|
+
Calculate the average duration of currently closed and open trades
|
|
160
|
+
in hours.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
trades (List[Trade]): List of Trade objects.
|
|
164
|
+
backtest_run (BacktestRun): The backtest run containing trades.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
float: The average trade duration in hours.
|
|
168
|
+
"""
|
|
169
|
+
if trades is None:
|
|
170
|
+
return 0.0
|
|
171
|
+
|
|
172
|
+
total_duration = 0.0
|
|
173
|
+
|
|
174
|
+
for trade in trades:
|
|
175
|
+
|
|
176
|
+
if TradeStatus.CLOSED.equals(trade.status):
|
|
177
|
+
total_duration += (trade.closed_at - trade.opened_at)\
|
|
178
|
+
.total_seconds() / 3600
|
|
179
|
+
else:
|
|
180
|
+
total_duration += (
|
|
181
|
+
backtest_run.backtest_end_date - trade.opened_at
|
|
182
|
+
).total_seconds() / 3600
|
|
183
|
+
|
|
184
|
+
number_of_trades = len(trades)
|
|
185
|
+
return total_duration / number_of_trades if number_of_trades > 0 else 0.0
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def get_average_trade_size(
|
|
189
|
+
trades: List[Trade]
|
|
190
|
+
) -> float:
|
|
191
|
+
"""
|
|
192
|
+
Calculate the average trade size based on the amount
|
|
193
|
+
and open price of each trade.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
trades (List[Trade]): List of Trade objects.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
float: The average trade size.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
if trades is None:
|
|
203
|
+
return 0.0
|
|
204
|
+
|
|
205
|
+
total_trade_size = 0.0
|
|
206
|
+
|
|
207
|
+
for trade in trades:
|
|
208
|
+
total_trade_size += trade.amount * trade.open_price
|
|
209
|
+
|
|
210
|
+
number_of_trades = get_number_of_trades(trades)
|
|
211
|
+
return total_trade_size / number_of_trades if number_of_trades > 0 else 0.0
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def get_average_trade_return(trades: List[Trade]) -> Tuple[float, float]:
|
|
215
|
+
"""
|
|
216
|
+
Calculate the average return (absolute PnL) and
|
|
217
|
+
average return percentage (per trade) of closed trades.
|
|
218
|
+
"""
|
|
219
|
+
if not trades or len(trades) == 0:
|
|
220
|
+
return 0.0, 0.0
|
|
221
|
+
|
|
222
|
+
closed_trades = [t for t in trades if TradeStatus.CLOSED.equals(t.status)]
|
|
223
|
+
|
|
224
|
+
if not closed_trades:
|
|
225
|
+
return 0.0, 0.0
|
|
226
|
+
|
|
227
|
+
total_return = sum(t.net_gain_absolute for t in closed_trades)
|
|
228
|
+
average_return = total_return / len(closed_trades)
|
|
229
|
+
|
|
230
|
+
percentage_returns = [
|
|
231
|
+
(t.net_gain_absolute / t.cost) for t in closed_trades if t.cost > 0
|
|
232
|
+
]
|
|
233
|
+
average_return_percentage = (
|
|
234
|
+
sum(percentage_returns) / len(percentage_returns)
|
|
235
|
+
if percentage_returns else 0.0
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
return average_return, average_return_percentage
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def get_current_average_trade_return(
|
|
242
|
+
trades: List[Trade]
|
|
243
|
+
) -> Tuple[float, float]:
|
|
244
|
+
"""
|
|
245
|
+
Calculate the average return (absolute PnL) and
|
|
246
|
+
average return percentage (per trade) of closed and open trades.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
trades (List[Trade]): List of trades.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Tuple[float, float]: The average return
|
|
253
|
+
percentage of the average return
|
|
254
|
+
"""
|
|
255
|
+
if not trades or len(trades) == 0:
|
|
256
|
+
return 0.0, 0.0
|
|
257
|
+
|
|
258
|
+
total_return = sum(t.net_gain_absolute for t in trades)
|
|
259
|
+
average_return = total_return / len(trades)
|
|
260
|
+
|
|
261
|
+
percentage_returns = [
|
|
262
|
+
(t.net_gain_absolute / t.cost) for t in trades if t.cost > 0
|
|
263
|
+
]
|
|
264
|
+
average_return_percentage = (
|
|
265
|
+
sum(percentage_returns) / len(percentage_returns)
|
|
266
|
+
if percentage_returns else 0.0
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
return average_return, average_return_percentage
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def get_average_trade_gain(trades: List[Trade]) -> Tuple[float, float]:
|
|
273
|
+
"""
|
|
274
|
+
Calculate the average gain from a list of trades.
|
|
275
|
+
|
|
276
|
+
The average gain is calculated as the mean of all positive returns.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
trades (List[Trade]): List of trades.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Tuple[float, float]: The average gain and average gain percentage
|
|
283
|
+
"""
|
|
284
|
+
if trades is None or len(trades) == 0:
|
|
285
|
+
return 0.0, 0.0
|
|
286
|
+
|
|
287
|
+
gains = [t.net_gain_absolute for t in trades if t.net_gain_absolute > 0]
|
|
288
|
+
|
|
289
|
+
if not gains:
|
|
290
|
+
return 0.0, 0.0
|
|
291
|
+
|
|
292
|
+
average_gain = sum(gains) / len(gains)
|
|
293
|
+
|
|
294
|
+
# Updated percentage calculation to match other functions
|
|
295
|
+
percentage_returns = [
|
|
296
|
+
(t.net_gain_absolute / t.cost) for t in trades
|
|
297
|
+
if t.net_gain_absolute > 0 and t.cost > 0
|
|
298
|
+
]
|
|
299
|
+
average_gain_percentage = (
|
|
300
|
+
sum(percentage_returns) / len(percentage_returns)
|
|
301
|
+
if percentage_returns else 0.0
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
return average_gain, average_gain_percentage
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def get_current_average_trade_gain(trades: List[Trade]) -> Tuple[float, float]:
|
|
308
|
+
"""
|
|
309
|
+
Calculate the average gain from a list of trades,
|
|
310
|
+
including both closed and open trades.
|
|
311
|
+
|
|
312
|
+
The average gain is calculated as the mean of all positive returns.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
trades (List[Trade]): List of trades.
|
|
316
|
+
Returns:
|
|
317
|
+
Tuple[float, float]: The average gain and average gain percentage
|
|
318
|
+
"""
|
|
319
|
+
if trades is None or len(trades) == 0:
|
|
320
|
+
return 0.0, 0.0
|
|
321
|
+
|
|
322
|
+
gains = [t.net_gain_absolute for t in trades if t.net_gain_absolute > 0]
|
|
323
|
+
|
|
324
|
+
if not gains:
|
|
325
|
+
return 0.0, 0.0
|
|
326
|
+
|
|
327
|
+
average_gain = sum(gains) / len(gains)
|
|
328
|
+
|
|
329
|
+
# Updated percentage calculation to match other functions
|
|
330
|
+
percentage_returns = [
|
|
331
|
+
(t.net_gain_absolute / t.cost) for t in trades
|
|
332
|
+
if t.net_gain_absolute > 0 and t.cost > 0
|
|
333
|
+
]
|
|
334
|
+
average_gain_percentage = (
|
|
335
|
+
sum(percentage_returns) / len(percentage_returns)
|
|
336
|
+
if percentage_returns else 0.0
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
return average_gain, average_gain_percentage
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def get_average_trade_loss(trades: List[Trade]) -> Tuple[float, float]:
|
|
343
|
+
"""
|
|
344
|
+
Calculate the average loss from a list of trades.
|
|
345
|
+
|
|
346
|
+
The average loss is calculated as the mean of all negative returns.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
trades (List[Trade]): List of trades.
|
|
350
|
+
Returns:
|
|
351
|
+
Tuple[float, float]: The average loss
|
|
352
|
+
percentage of the average loss
|
|
353
|
+
"""
|
|
354
|
+
if trades is None or len(trades) == 0:
|
|
355
|
+
return 0.0, 0.0
|
|
356
|
+
|
|
357
|
+
closed_trades = [t for t in trades if TradeStatus.CLOSED.equals(t.status)]
|
|
358
|
+
losing_trades = [t for t in closed_trades if t.net_gain < 0]
|
|
359
|
+
|
|
360
|
+
if not losing_trades or len(losing_trades) == 0:
|
|
361
|
+
return 0.0, 0.0
|
|
362
|
+
|
|
363
|
+
losses = [t.net_gain_absolute for t in losing_trades]
|
|
364
|
+
average_loss = sum(losses) / len(losses)
|
|
365
|
+
percentage_returns = [
|
|
366
|
+
(t.net_gain_absolute / t.cost) for t in losing_trades if t.cost > 0
|
|
367
|
+
]
|
|
368
|
+
average_return_percentage = (
|
|
369
|
+
sum(percentage_returns) / len(percentage_returns)
|
|
370
|
+
if percentage_returns else 0.0
|
|
371
|
+
)
|
|
372
|
+
return average_loss, average_return_percentage
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def get_current_average_trade_loss(
|
|
376
|
+
trades: List[Trade]
|
|
377
|
+
) -> Tuple[float, float]:
|
|
378
|
+
"""
|
|
379
|
+
Calculate the average loss from a list of trades,
|
|
380
|
+
including both closed and open trades.
|
|
381
|
+
|
|
382
|
+
The average loss is calculated as the mean of all negative returns.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
trades (List[Trade]): List of trades.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
Tuple[float, float]: The average loss
|
|
389
|
+
percentage of the average loss
|
|
390
|
+
"""
|
|
391
|
+
if trades is None or len(trades) == 0:
|
|
392
|
+
return 0.0, 0.0
|
|
393
|
+
|
|
394
|
+
losing_trades = [t for t in trades if t.net_gain_absolute < 0]
|
|
395
|
+
|
|
396
|
+
if not losing_trades or len(losing_trades) == 0:
|
|
397
|
+
return 0.0, 0.0
|
|
398
|
+
|
|
399
|
+
losses = [t.net_gain_absolute for t in losing_trades]
|
|
400
|
+
average_loss = sum(losses) / len(losses)
|
|
401
|
+
percentage_returns = [
|
|
402
|
+
(t.net_gain_absolute / t.cost) for t in losing_trades if t.cost > 0
|
|
403
|
+
]
|
|
404
|
+
average_return_percentage = (
|
|
405
|
+
sum(percentage_returns) / len(percentage_returns)
|
|
406
|
+
if percentage_returns else 0.0
|
|
407
|
+
)
|
|
408
|
+
return average_loss, average_return_percentage
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def get_median_trade_return(trades: List[Trade]) -> Tuple[float, float]:
|
|
412
|
+
"""
|
|
413
|
+
Calculate the median return from a list of trades.
|
|
414
|
+
|
|
415
|
+
The median return is calculated as the median of all returns.
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
trades (List[Trade]): List of trades.
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
Tuple[float, float]: The median return
|
|
422
|
+
percentage of the median return
|
|
423
|
+
"""
|
|
424
|
+
|
|
425
|
+
if not trades:
|
|
426
|
+
return 0.0, 0.0
|
|
427
|
+
|
|
428
|
+
sorted_returns = sorted(t.net_gain_absolute for t in trades)
|
|
429
|
+
n = len(sorted_returns)
|
|
430
|
+
mid = n // 2
|
|
431
|
+
|
|
432
|
+
if n % 2 == 0:
|
|
433
|
+
median_return = (sorted_returns[mid - 1] + sorted_returns[mid]) / 2
|
|
434
|
+
else:
|
|
435
|
+
median_return = sorted_returns[mid]
|
|
436
|
+
|
|
437
|
+
cost = sum(t.cost for t in trades)
|
|
438
|
+
percentage = (median_return / cost) if cost > 0 else 0.0
|
|
439
|
+
return median_return, percentage
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def get_best_trade(trades: List[Trade]) -> Trade:
|
|
443
|
+
"""
|
|
444
|
+
Get the trade with the highest net gain.
|
|
445
|
+
|
|
446
|
+
Args:
|
|
447
|
+
trades (List[Trade]): List of trades.
|
|
448
|
+
|
|
449
|
+
Returns:
|
|
450
|
+
Trade: The trade with the highest net gain.
|
|
451
|
+
"""
|
|
452
|
+
|
|
453
|
+
if not trades:
|
|
454
|
+
return None
|
|
455
|
+
|
|
456
|
+
return max(trades, key=lambda t: t.net_gain_absolute)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def get_worst_trade(trades: List[Trade]) -> Trade:
|
|
460
|
+
"""
|
|
461
|
+
Get the trade with the lowest net gain (worst trade).
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
trades (List[Trade]): List of trades.
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
Trade: The trade with the lowest net gain.
|
|
468
|
+
"""
|
|
469
|
+
|
|
470
|
+
if not trades:
|
|
471
|
+
return None
|
|
472
|
+
|
|
473
|
+
return min(trades, key=lambda t: t.net_gain)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|