investing-algorithm-framework 6.9.1__py3-none-any.whl → 7.19.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of investing-algorithm-framework might be problematic. Click here for more details.
- investing_algorithm_framework/__init__.py +147 -44
- investing_algorithm_framework/app/__init__.py +23 -6
- investing_algorithm_framework/app/algorithm/algorithm.py +5 -41
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +17 -10
- investing_algorithm_framework/app/analysis/__init__.py +15 -0
- investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
- investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
- investing_algorithm_framework/app/analysis/permutation.py +116 -0
- investing_algorithm_framework/app/analysis/ranking.py +297 -0
- investing_algorithm_framework/app/app.py +1322 -707
- investing_algorithm_framework/app/context.py +196 -88
- investing_algorithm_framework/app/eventloop.py +590 -0
- investing_algorithm_framework/app/reporting/__init__.py +16 -5
- investing_algorithm_framework/app/reporting/ascii.py +57 -202
- investing_algorithm_framework/app/reporting/backtest_report.py +284 -170
- investing_algorithm_framework/app/reporting/charts/__init__.py +10 -2
- investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +11 -26
- investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
- investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
- investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +1 -1
- investing_algorithm_framework/app/reporting/generate.py +100 -114
- investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +40 -32
- investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +34 -27
- investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +23 -19
- investing_algorithm_framework/app/reporting/tables/trades_table.py +1 -1
- investing_algorithm_framework/app/reporting/tables/utils.py +1 -0
- investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +10 -16
- investing_algorithm_framework/app/strategy.py +315 -175
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/cli/cli.py +30 -12
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +131 -34
- investing_algorithm_framework/cli/initialize_app.py +20 -1
- investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +18 -6
- investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
- investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -2
- investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +1 -1
- investing_algorithm_framework/create_app.py +3 -5
- investing_algorithm_framework/dependency_container.py +25 -39
- investing_algorithm_framework/domain/__init__.py +45 -38
- investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
- investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
- investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
- investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
- investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
- investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
- investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
- investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
- investing_algorithm_framework/domain/config.py +27 -0
- investing_algorithm_framework/domain/constants.py +6 -34
- investing_algorithm_framework/domain/data_provider.py +200 -56
- investing_algorithm_framework/domain/exceptions.py +34 -1
- investing_algorithm_framework/domain/models/__init__.py +10 -19
- investing_algorithm_framework/domain/models/base_model.py +0 -6
- investing_algorithm_framework/domain/models/data/__init__.py +7 -0
- investing_algorithm_framework/domain/models/data/data_source.py +214 -0
- investing_algorithm_framework/domain/models/{market_data_type.py → data/data_type.py} +7 -7
- investing_algorithm_framework/domain/models/market/market_credential.py +6 -0
- investing_algorithm_framework/domain/models/order/order.py +34 -13
- investing_algorithm_framework/domain/models/order/order_status.py +1 -1
- investing_algorithm_framework/domain/models/order/order_type.py +1 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +14 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +5 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +51 -11
- investing_algorithm_framework/domain/models/position/__init__.py +2 -1
- investing_algorithm_framework/domain/models/position/position.py +9 -0
- investing_algorithm_framework/domain/models/position/position_size.py +41 -0
- investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
- investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
- investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
- investing_algorithm_framework/domain/models/snapshot_interval.py +0 -1
- investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
- investing_algorithm_framework/domain/models/time_frame.py +7 -0
- investing_algorithm_framework/domain/models/time_interval.py +33 -0
- investing_algorithm_framework/domain/models/time_unit.py +63 -1
- investing_algorithm_framework/domain/models/trade/__init__.py +0 -2
- investing_algorithm_framework/domain/models/trade/trade.py +56 -32
- investing_algorithm_framework/domain/models/trade/trade_status.py +8 -2
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +106 -41
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +161 -99
- investing_algorithm_framework/domain/order_executor.py +19 -0
- investing_algorithm_framework/domain/portfolio_provider.py +20 -1
- investing_algorithm_framework/domain/services/__init__.py +0 -13
- investing_algorithm_framework/domain/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +5 -1
- investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
- investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
- investing_algorithm_framework/domain/utils/polars.py +17 -14
- investing_algorithm_framework/download_data.py +40 -10
- investing_algorithm_framework/infrastructure/__init__.py +13 -25
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +7 -4
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +811 -546
- investing_algorithm_framework/infrastructure/data_providers/csv.py +433 -122
- investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
- investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
- investing_algorithm_framework/infrastructure/database/sql_alchemy.py +81 -0
- investing_algorithm_framework/infrastructure/models/__init__.py +0 -13
- investing_algorithm_framework/infrastructure/models/order/order.py +9 -3
- investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +27 -8
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +21 -7
- investing_algorithm_framework/infrastructure/order_executors/__init__.py +2 -0
- investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +16 -2
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +2 -2
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +6 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +6 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +0 -4
- investing_algorithm_framework/services/__init__.py +105 -8
- investing_algorithm_framework/services/backtesting/backtest_service.py +536 -476
- investing_algorithm_framework/services/configuration_service.py +14 -4
- investing_algorithm_framework/services/data_providers/__init__.py +5 -0
- investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/__init__.py +48 -17
- investing_algorithm_framework/{app/reporting → services}/metrics/drawdown.py +10 -10
- investing_algorithm_framework/{app/reporting → services}/metrics/equity_curve.py +2 -2
- investing_algorithm_framework/{app/reporting → services}/metrics/exposure.py +60 -2
- investing_algorithm_framework/services/metrics/generate.py +358 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/profit_factor.py +36 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/recovery.py +2 -2
- investing_algorithm_framework/{app/reporting → services}/metrics/returns.py +146 -147
- investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
- investing_algorithm_framework/{app/reporting/metrics/sharp_ratio.py → services/metrics/sharpe_ratio.py} +6 -10
- investing_algorithm_framework/{app/reporting → services}/metrics/sortino_ratio.py +3 -7
- investing_algorithm_framework/services/metrics/trades.py +500 -0
- investing_algorithm_framework/services/metrics/volatility.py +97 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/win_rate.py +70 -3
- investing_algorithm_framework/services/order_service/order_backtest_service.py +21 -31
- investing_algorithm_framework/services/order_service/order_service.py +9 -71
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +0 -2
- investing_algorithm_framework/services/portfolios/portfolio_service.py +3 -13
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +62 -96
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +0 -3
- investing_algorithm_framework/services/repository_service.py +5 -2
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
- investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
- investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
- investing_algorithm_framework/services/trade_service/__init__.py +7 -1
- investing_algorithm_framework/services/trade_service/trade_service.py +51 -29
- investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
- investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
- investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/RECORD +159 -148
- investing_algorithm_framework/app/reporting/evaluation.py +0 -243
- investing_algorithm_framework/app/reporting/metrics/risk_free_rate.py +0 -8
- investing_algorithm_framework/app/reporting/metrics/volatility.py +0 -69
- investing_algorithm_framework/cli/templates/requirements_azure_function.txt.template +0 -3
- investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -9
- investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -47
- investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
- investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -0
- investing_algorithm_framework/domain/models/backtesting/backtest_results.py +0 -440
- investing_algorithm_framework/domain/models/data_source.py +0 -21
- investing_algorithm_framework/domain/models/date_range.py +0 -64
- investing_algorithm_framework/domain/models/trade/trade_risk_type.py +0 -34
- investing_algorithm_framework/domain/models/trading_data_types.py +0 -48
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
- investing_algorithm_framework/domain/services/market_data_sources.py +0 -543
- investing_algorithm_framework/domain/services/market_service.py +0 -153
- investing_algorithm_framework/domain/services/observable.py +0 -51
- investing_algorithm_framework/domain/services/observer.py +0 -19
- investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -16
- investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -746
- investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -270
- investing_algorithm_framework/infrastructure/models/market_data_sources/pandas.py +0 -312
- investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
- investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -471
- investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
- investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
- investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -322
- investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -10
- investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -269
- investing_algorithm_framework/services/market_data_source_service/data_provider_service.py +0 -350
- investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -377
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -296
- investing_algorithm_framework-6.9.1.dist-info/METADATA +0 -440
- /investing_algorithm_framework/{app/reporting → services}/metrics/alpha.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/beta.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/cagr.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/calmar_ratio.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/mean_daily_return.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/price_efficiency.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/standard_deviation.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/treynor_ratio.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/ulcer.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/value_at_risk.py +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,500 @@
|
|
|
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
|
+
raise OperationalException(
|
|
22
|
+
"Trades list is empty or None, cannot compute positive trades."
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
closed_trades = [
|
|
26
|
+
trade for trade in trades if TradeStatus.CLOSED.equals(trade.status)
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
positive_trades = [
|
|
30
|
+
trade for trade in closed_trades if trade.net_gain_absolute > 0
|
|
31
|
+
]
|
|
32
|
+
number_of_positive_trades = len(positive_trades)
|
|
33
|
+
percentage_positive_trades = (
|
|
34
|
+
(number_of_positive_trades / len(closed_trades)) * 100.0
|
|
35
|
+
if len(closed_trades) > 0 else 0.0
|
|
36
|
+
)
|
|
37
|
+
return number_of_positive_trades, percentage_positive_trades
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_negative_trades(
|
|
41
|
+
trades: List[Trade]
|
|
42
|
+
) -> Tuple[int, float]:
|
|
43
|
+
"""
|
|
44
|
+
Calculate the number and percentage of negative trades.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
trades (List[Trade]): List of Trade objects.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Tuple[int, float]: A tuple containing the number of negative trades
|
|
51
|
+
and the percentage of negative trades.
|
|
52
|
+
"""
|
|
53
|
+
if trades is None or len(trades) == 0:
|
|
54
|
+
raise OperationalException(
|
|
55
|
+
"Trades list is empty or None, cannot compute negative trades."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
closed_trades = [
|
|
59
|
+
trade for trade in trades if TradeStatus.CLOSED.equals(trade.status)
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
negative_trades = [
|
|
63
|
+
trade for trade in closed_trades if trade.net_gain_absolute < 0
|
|
64
|
+
]
|
|
65
|
+
number_of_negative_trades = len(negative_trades)
|
|
66
|
+
percentage_negative_trades = (
|
|
67
|
+
(number_of_negative_trades / len(closed_trades)) * 100.0
|
|
68
|
+
if len(closed_trades) > 0 else 0.0
|
|
69
|
+
)
|
|
70
|
+
return number_of_negative_trades, percentage_negative_trades
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_number_of_trades(
|
|
74
|
+
trades: List[Trade]
|
|
75
|
+
) -> int:
|
|
76
|
+
"""
|
|
77
|
+
Calculate the total number of trades.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
trades (List[Trade]): List of Trade objects.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
int: The total number of trades.
|
|
84
|
+
"""
|
|
85
|
+
if trades is None or len(trades) == 0:
|
|
86
|
+
raise OperationalException(
|
|
87
|
+
"Trades list is None, cannot compute number of trades."
|
|
88
|
+
)
|
|
89
|
+
return len(trades)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def get_number_of_open_trades(
|
|
93
|
+
trades: List[Trade]
|
|
94
|
+
) -> int:
|
|
95
|
+
"""
|
|
96
|
+
Calculate the number of open trades.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
trades (List[Trade]): List of Trade objects.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
int: The number of open trades.
|
|
103
|
+
"""
|
|
104
|
+
if trades is None or len(trades) == 0:
|
|
105
|
+
raise OperationalException(
|
|
106
|
+
"Trades list is None, cannot compute number of open trades."
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
open_trades = [
|
|
110
|
+
trade for trade in trades if TradeStatus.OPEN.equals(trade.status)
|
|
111
|
+
]
|
|
112
|
+
return len(open_trades)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def get_number_of_closed_trades(
|
|
116
|
+
trades: List[Trade]
|
|
117
|
+
) -> int:
|
|
118
|
+
"""
|
|
119
|
+
Calculate the number of closed trades.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
trades (List[Trade]): List of Trade objects.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
int: The number of closed trades.
|
|
126
|
+
"""
|
|
127
|
+
if trades is None or len(trades) == 0:
|
|
128
|
+
raise OperationalException(
|
|
129
|
+
"Trades list is None, cannot compute number of closed trades."
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
closed_trades = [
|
|
133
|
+
trade for trade in trades if TradeStatus.CLOSED.equals(trade.status)
|
|
134
|
+
]
|
|
135
|
+
return len(closed_trades)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def get_average_trade_duration(
|
|
139
|
+
trades: List[Trade]
|
|
140
|
+
) -> float:
|
|
141
|
+
"""
|
|
142
|
+
Calculate the average duration of closed trades in hours.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
trades (List[Trade]): List of Trade objects.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
float: The average trade duration in hours.
|
|
149
|
+
"""
|
|
150
|
+
if trades is None or len(trades) == 0:
|
|
151
|
+
raise OperationalException(
|
|
152
|
+
"Trades list is None, cannot compute average trade duration."
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
total_duration = 0.0
|
|
156
|
+
|
|
157
|
+
for trade in trades:
|
|
158
|
+
if TradeStatus.CLOSED.equals(trade.status):
|
|
159
|
+
total_duration += (trade.closed_at - trade.opened_at)\
|
|
160
|
+
.total_seconds() / 3600
|
|
161
|
+
|
|
162
|
+
number_of_trades = get_number_of_closed_trades(trades)
|
|
163
|
+
return total_duration / number_of_trades if number_of_trades > 0 else 0.0
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def get_current_average_trade_duration(
|
|
167
|
+
trades: List[Trade], backtest_run: BacktestRun
|
|
168
|
+
) -> float:
|
|
169
|
+
"""
|
|
170
|
+
Calculate the average duration of currently closed and open trades
|
|
171
|
+
in hours.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
trades (List[Trade]): List of Trade objects.
|
|
175
|
+
backtest_run (BacktestRun): The backtest run containing trades.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
float: The average trade duration in hours.
|
|
179
|
+
"""
|
|
180
|
+
if trades is None or len(trades) == 0:
|
|
181
|
+
raise OperationalException(
|
|
182
|
+
"Trades list is None, cannot compute average trade duration."
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
total_duration = 0.0
|
|
186
|
+
|
|
187
|
+
for trade in trades:
|
|
188
|
+
|
|
189
|
+
if TradeStatus.CLOSED.equals(trade.status):
|
|
190
|
+
total_duration += (trade.closed_at - trade.opened_at)\
|
|
191
|
+
.total_seconds() / 3600
|
|
192
|
+
else:
|
|
193
|
+
total_duration += (
|
|
194
|
+
backtest_run.backtest_end_date - trade.opened_at
|
|
195
|
+
).total_seconds() / 3600
|
|
196
|
+
|
|
197
|
+
number_of_trades = len(trades)
|
|
198
|
+
return total_duration / number_of_trades if number_of_trades > 0 else 0.0
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def get_average_trade_size(
|
|
202
|
+
trades: List[Trade]
|
|
203
|
+
) -> float:
|
|
204
|
+
"""
|
|
205
|
+
Calculate the average trade size based on the amount
|
|
206
|
+
and open price of each trade.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
trades (List[Trade]): List of Trade objects.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
float: The average trade size.
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
if trades is None or len(trades) == 0:
|
|
216
|
+
raise OperationalException(
|
|
217
|
+
"Trades list is None, cannot compute average trade size."
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
total_trade_size = 0.0
|
|
221
|
+
|
|
222
|
+
for trade in trades:
|
|
223
|
+
total_trade_size += trade.amount * trade.open_price
|
|
224
|
+
|
|
225
|
+
number_of_trades = get_number_of_trades(trades)
|
|
226
|
+
return total_trade_size / number_of_trades if number_of_trades > 0 else 0.0
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def get_average_trade_return(trades: List[Trade]) -> Tuple[float, float]:
|
|
230
|
+
"""
|
|
231
|
+
Calculate the average return (absolute PnL) and
|
|
232
|
+
average return percentage (per trade) of closed trades.
|
|
233
|
+
"""
|
|
234
|
+
if not trades or len(trades) == 0:
|
|
235
|
+
raise OperationalException(
|
|
236
|
+
"Trades list is empty, cannot compute average return."
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
closed_trades = [t for t in trades if TradeStatus.CLOSED.equals(t.status)]
|
|
240
|
+
|
|
241
|
+
if not closed_trades:
|
|
242
|
+
return 0.0, 0.0
|
|
243
|
+
|
|
244
|
+
total_return = sum(t.net_gain_absolute for t in closed_trades)
|
|
245
|
+
average_return = total_return / len(closed_trades)
|
|
246
|
+
|
|
247
|
+
percentage_returns = [
|
|
248
|
+
(t.net_gain_absolute / t.cost) for t in closed_trades if t.cost > 0
|
|
249
|
+
]
|
|
250
|
+
average_return_percentage = (
|
|
251
|
+
sum(percentage_returns) / len(percentage_returns)
|
|
252
|
+
if percentage_returns else 0.0
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
return average_return, average_return_percentage
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def get_current_average_trade_return(
|
|
259
|
+
trades: List[Trade]
|
|
260
|
+
) -> Tuple[float, float]:
|
|
261
|
+
"""
|
|
262
|
+
Calculate the average return (absolute PnL) and
|
|
263
|
+
average return percentage (per trade) of closed and open trades.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
trades (List[Trade]): List of trades.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Tuple[float, float]: The average return
|
|
270
|
+
percentage of the average return
|
|
271
|
+
"""
|
|
272
|
+
if not trades or len(trades) == 0:
|
|
273
|
+
raise OperationalException(
|
|
274
|
+
"Trades list is empty, cannot compute average return."
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
total_return = sum(t.net_gain_absolute for t in trades)
|
|
278
|
+
average_return = total_return / len(trades)
|
|
279
|
+
|
|
280
|
+
percentage_returns = [
|
|
281
|
+
(t.net_gain_absolute / t.cost) for t in trades if t.cost > 0
|
|
282
|
+
]
|
|
283
|
+
average_return_percentage = (
|
|
284
|
+
sum(percentage_returns) / len(percentage_returns)
|
|
285
|
+
if percentage_returns else 0.0
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
return average_return, average_return_percentage
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def get_average_trade_gain(trades: List[Trade]) -> Tuple[float, float]:
|
|
292
|
+
"""
|
|
293
|
+
Calculate the average gain from a list of trades.
|
|
294
|
+
|
|
295
|
+
The average gain is calculated as the mean of all positive returns.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
trades (List[Trade]): List of trades.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
Tuple[float, float]: The average gain and average gain percentage
|
|
302
|
+
"""
|
|
303
|
+
if trades is None or len(trades) == 0:
|
|
304
|
+
raise OperationalException(
|
|
305
|
+
"Trades list is empty or None, cannot calculate average gain."
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
gains = [t.net_gain_absolute for t in trades if t.net_gain_absolute > 0]
|
|
309
|
+
|
|
310
|
+
if not gains:
|
|
311
|
+
return 0.0, 0.0
|
|
312
|
+
|
|
313
|
+
average_gain = sum(gains) / len(gains)
|
|
314
|
+
|
|
315
|
+
# Updated percentage calculation to match other functions
|
|
316
|
+
percentage_returns = [
|
|
317
|
+
(t.net_gain_absolute / t.cost) for t in trades
|
|
318
|
+
if t.net_gain_absolute > 0 and t.cost > 0
|
|
319
|
+
]
|
|
320
|
+
average_gain_percentage = (
|
|
321
|
+
sum(percentage_returns) / len(percentage_returns)
|
|
322
|
+
if percentage_returns else 0.0
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
return average_gain, average_gain_percentage
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def get_current_average_trade_gain(trades: List[Trade]) -> Tuple[float, float]:
|
|
329
|
+
"""
|
|
330
|
+
Calculate the average gain from a list of trades,
|
|
331
|
+
including both closed and open trades.
|
|
332
|
+
|
|
333
|
+
The average gain is calculated as the mean of all positive returns.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
trades (List[Trade]): List of trades.
|
|
337
|
+
Returns:
|
|
338
|
+
Tuple[float, float]: The average gain and average gain percentage
|
|
339
|
+
"""
|
|
340
|
+
if trades is None or len(trades) == 0:
|
|
341
|
+
raise OperationalException(
|
|
342
|
+
"Trades list is empty or None, cannot calculate average gain."
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
gains = [t.net_gain_absolute for t in trades if t.net_gain_absolute > 0]
|
|
346
|
+
|
|
347
|
+
if not gains:
|
|
348
|
+
return 0.0, 0.0
|
|
349
|
+
|
|
350
|
+
average_gain = sum(gains) / len(gains)
|
|
351
|
+
|
|
352
|
+
# Updated percentage calculation to match other functions
|
|
353
|
+
percentage_returns = [
|
|
354
|
+
(t.net_gain_absolute / t.cost) for t in trades
|
|
355
|
+
if t.net_gain_absolute > 0 and t.cost > 0
|
|
356
|
+
]
|
|
357
|
+
average_gain_percentage = (
|
|
358
|
+
sum(percentage_returns) / len(percentage_returns)
|
|
359
|
+
if percentage_returns else 0.0
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
return average_gain, average_gain_percentage
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def get_average_trade_loss(trades: List[Trade]) -> Tuple[float, float]:
|
|
366
|
+
"""
|
|
367
|
+
Calculate the average loss from a list of trades.
|
|
368
|
+
|
|
369
|
+
The average loss is calculated as the mean of all negative returns.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
trades (List[Trade]): List of trades.
|
|
373
|
+
Returns:
|
|
374
|
+
Tuple[float, float]: The average loss
|
|
375
|
+
percentage of the average loss
|
|
376
|
+
"""
|
|
377
|
+
if trades is None or len(trades) == 0:
|
|
378
|
+
raise OperationalException(
|
|
379
|
+
"Trades list is empty or None, cannot calculate average loss."
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
closed_trades = [t for t in trades if TradeStatus.CLOSED.equals(t.status)]
|
|
383
|
+
losing_trades = [t for t in closed_trades if t.net_gain < 0]
|
|
384
|
+
|
|
385
|
+
if not losing_trades or len(losing_trades) == 0:
|
|
386
|
+
return 0.0, 0.0
|
|
387
|
+
|
|
388
|
+
losses = [t.net_gain_absolute for t in losing_trades]
|
|
389
|
+
average_loss = sum(losses) / len(losses)
|
|
390
|
+
percentage_returns = [
|
|
391
|
+
(t.net_gain_absolute / t.cost) for t in losing_trades if t.cost > 0
|
|
392
|
+
]
|
|
393
|
+
average_return_percentage = (
|
|
394
|
+
sum(percentage_returns) / len(percentage_returns)
|
|
395
|
+
if percentage_returns else 0.0
|
|
396
|
+
)
|
|
397
|
+
return average_loss, average_return_percentage
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def get_current_average_trade_loss(
|
|
401
|
+
trades: List[Trade]
|
|
402
|
+
) -> Tuple[float, float]:
|
|
403
|
+
"""
|
|
404
|
+
Calculate the average loss from a list of trades,
|
|
405
|
+
including both closed and open trades.
|
|
406
|
+
|
|
407
|
+
The average loss is calculated as the mean of all negative returns.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
trades (List[Trade]): List of trades.
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
Tuple[float, float]: The average loss
|
|
414
|
+
percentage of the average loss
|
|
415
|
+
"""
|
|
416
|
+
if trades is None or len(trades) == 0:
|
|
417
|
+
raise OperationalException(
|
|
418
|
+
"Trades list is empty or None, cannot calculate average loss."
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
losing_trades = [t for t in trades if t.net_gain_absolute < 0]
|
|
422
|
+
|
|
423
|
+
if not losing_trades or len(losing_trades) == 0:
|
|
424
|
+
return 0.0, 0.0
|
|
425
|
+
|
|
426
|
+
losses = [t.net_gain_absolute for t in losing_trades]
|
|
427
|
+
average_loss = sum(losses) / len(losses)
|
|
428
|
+
percentage_returns = [
|
|
429
|
+
(t.net_gain_absolute / t.cost) for t in losing_trades if t.cost > 0
|
|
430
|
+
]
|
|
431
|
+
average_return_percentage = (
|
|
432
|
+
sum(percentage_returns) / len(percentage_returns)
|
|
433
|
+
if percentage_returns else 0.0
|
|
434
|
+
)
|
|
435
|
+
return average_loss, average_return_percentage
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def get_median_trade_return(trades: List[Trade]) -> Tuple[float, float]:
|
|
439
|
+
"""
|
|
440
|
+
Calculate the median return from a list of trades.
|
|
441
|
+
|
|
442
|
+
The median return is calculated as the median of all returns.
|
|
443
|
+
|
|
444
|
+
Args:
|
|
445
|
+
trades (List[Trade]): List of trades.
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
Tuple[float, float]: The median return
|
|
449
|
+
percentage of the median return
|
|
450
|
+
"""
|
|
451
|
+
|
|
452
|
+
if not trades:
|
|
453
|
+
return 0.0, 0.0
|
|
454
|
+
|
|
455
|
+
sorted_returns = sorted(t.net_gain_absolute for t in trades)
|
|
456
|
+
n = len(sorted_returns)
|
|
457
|
+
mid = n // 2
|
|
458
|
+
|
|
459
|
+
if n % 2 == 0:
|
|
460
|
+
median_return = (sorted_returns[mid - 1] + sorted_returns[mid]) / 2
|
|
461
|
+
else:
|
|
462
|
+
median_return = sorted_returns[mid]
|
|
463
|
+
|
|
464
|
+
cost = sum(t.cost for t in trades)
|
|
465
|
+
percentage = (median_return / cost) if cost > 0 else 0.0
|
|
466
|
+
return median_return, percentage
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def get_best_trade(trades: List[Trade]) -> Trade:
|
|
470
|
+
"""
|
|
471
|
+
Get the trade with the highest net gain.
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
trades (List[Trade]): List of trades.
|
|
475
|
+
|
|
476
|
+
Returns:
|
|
477
|
+
Trade: The trade with the highest net gain.
|
|
478
|
+
"""
|
|
479
|
+
|
|
480
|
+
if not trades:
|
|
481
|
+
return None
|
|
482
|
+
|
|
483
|
+
return max(trades, key=lambda t: t.net_gain_absolute)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def get_worst_trade(trades: List[Trade]) -> Trade:
|
|
487
|
+
"""
|
|
488
|
+
Get the trade with the lowest net gain (worst trade).
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
trades (List[Trade]): List of trades.
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
Trade: The trade with the lowest net gain.
|
|
495
|
+
"""
|
|
496
|
+
|
|
497
|
+
if not trades:
|
|
498
|
+
return None
|
|
499
|
+
|
|
500
|
+
return min(trades, key=lambda t: t.net_gain)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Volatility is a statistical measure of the dispersion of returns for a
|
|
3
|
+
given portfolio. In finance, it is commonly used as a proxy for risk.
|
|
4
|
+
This function calculates the standard deviation of daily log returns and
|
|
5
|
+
annualizes it, giving an estimate of how much the portfolio's value
|
|
6
|
+
fluctuates on a yearly basis.
|
|
7
|
+
|
|
8
|
+
| **Annual Volatility** | **Risk Level (Standalone)** | **Context Matters: Sharpe Ratio Impact** | **Comments** |
|
|
9
|
+
| --------------------- | --------------------------- | ---------------------------------------- | ----------- |
|
|
10
|
+
| **< 5%** | Very Low Risk | Sharpe > 2.0 = Excellent<br>Sharpe < 0.5 = Poor | Low volatility is great unless returns are negative |
|
|
11
|
+
| **5% – 10%** | Low Risk | Sharpe > 1.0 = Good<br>Sharpe < 0.3 = Mediocre | Typical for conservative portfolios |
|
|
12
|
+
| **10% – 15%** | Moderate Risk | Sharpe > 0.8 = Good<br>Sharpe < 0.2 = Risky | S&P 500 benchmark; quality matters |
|
|
13
|
+
| **15% – 25%** | High Risk | Sharpe > 0.6 = Acceptable<br>Sharpe < 0.0 = Avoid | **Example: 30% CAGR + 23% vol = Sharpe ~1.3 = Excellent** |
|
|
14
|
+
| **> 25%** | Very High Risk | Sharpe > 0.4 = Maybe acceptable<br>Sharpe < 0.0 = Dangerous | Only viable with strong positive returns |
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Key takeaway: Don't interpret volatility in isolation. Always calculate
|
|
18
|
+
and compare the Sharpe Ratio to assess true strategy quality.
|
|
19
|
+
Your 30% CAGR with 23% volatility is exceptional because the return far outweighs the risk taken.
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from typing import List
|
|
24
|
+
|
|
25
|
+
import pandas as pd
|
|
26
|
+
import numpy as np
|
|
27
|
+
|
|
28
|
+
from investing_algorithm_framework.domain import PortfolioSnapshot
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_annual_volatility(
|
|
32
|
+
snapshots: List[PortfolioSnapshot],
|
|
33
|
+
trading_days_per_year=365
|
|
34
|
+
) -> float:
|
|
35
|
+
"""
|
|
36
|
+
Calculate the annualized volatility of portfolio net values.
|
|
37
|
+
|
|
38
|
+
!Important Note:
|
|
39
|
+
|
|
40
|
+
Volatility measures variability, not direction. For example:
|
|
41
|
+
|
|
42
|
+
A standard deviation of 0.238 (23.8%) means returns swing
|
|
43
|
+
wildly around their average, but it doesn't tell you if that average
|
|
44
|
+
is positive or negative.
|
|
45
|
+
|
|
46
|
+
Two scenarios with the same 23.8% volatility:
|
|
47
|
+
Mean return = +15% per year, Std = 23.8%
|
|
48
|
+
16% chance of losing >8.8% (15% - 23.8%)
|
|
49
|
+
16% chance of gaining >38.8% (15% + 23.8%)
|
|
50
|
+
This is excellent — high growth with swings
|
|
51
|
+
|
|
52
|
+
Mean return = -5% per year, Std = 23.8%
|
|
53
|
+
16% chance of losing >28.8% (-5% - 23.8%)
|
|
54
|
+
16% chance of gaining >18.8% (-5% + 23.8%)
|
|
55
|
+
This is terrible — losing money with high risk
|
|
56
|
+
|
|
57
|
+
To assess if "always good returns with high std" is perfect, you need
|
|
58
|
+
to consider risk-adjusted metrics like the Sharpe Ratio:
|
|
59
|
+
Sharpe Ratio = (Mean Return - Risk-Free Rate) / Volatility
|
|
60
|
+
Higher is better; tells you return per unit of risk taken
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
snapshots (List[PortfolioSnapshot]): List of portfolio snapshots
|
|
64
|
+
from the backtest report.
|
|
65
|
+
trading_days_per_year (int): Number of trading days in a year.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Float: Annualized volatility as a float
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
if len(snapshots) < 2:
|
|
72
|
+
return 0.0
|
|
73
|
+
|
|
74
|
+
# Build DataFrame from snapshots
|
|
75
|
+
records = [
|
|
76
|
+
(snapshot.total_value, snapshot.created_at) for snapshot in snapshots
|
|
77
|
+
]
|
|
78
|
+
df = pd.DataFrame(records, columns=['total_value', 'created_at'])
|
|
79
|
+
df['created_at'] = pd.to_datetime(df['created_at'])
|
|
80
|
+
df = df.set_index('created_at').sort_index().drop_duplicates()
|
|
81
|
+
|
|
82
|
+
# Resample to daily frequency, taking the last value of each day
|
|
83
|
+
df_daily = df.resample('D').last()
|
|
84
|
+
df_daily = df_daily.dropna()
|
|
85
|
+
|
|
86
|
+
if len(df_daily) < 2:
|
|
87
|
+
return 0.0
|
|
88
|
+
|
|
89
|
+
# Calculate log returns on daily data
|
|
90
|
+
df_daily['log_return'] = np.log(df_daily['total_value'] / df_daily['total_value'].shift(1))
|
|
91
|
+
df_daily = df_daily.dropna()
|
|
92
|
+
|
|
93
|
+
# Calculate daily volatility (standard deviation of daily returns)
|
|
94
|
+
daily_volatility = df_daily['log_return'].std()
|
|
95
|
+
|
|
96
|
+
# Annualize using trading days per year
|
|
97
|
+
return daily_volatility * np.sqrt(trading_days_per_year)
|
|
@@ -42,7 +42,7 @@ def get_win_rate(trades: List[Trade]) -> float:
|
|
|
42
42
|
The percentage of trades that are profitable.
|
|
43
43
|
|
|
44
44
|
Formula:
|
|
45
|
-
Win Rate =
|
|
45
|
+
Win Rate = Number of Profitable Trades / Total Number of Trades
|
|
46
46
|
|
|
47
47
|
Example: If 60 out of 100 trades are profitable, the win rate is 60%.
|
|
48
48
|
|
|
@@ -50,7 +50,7 @@ def get_win_rate(trades: List[Trade]) -> float:
|
|
|
50
50
|
trades (List[Trade]): List of trades from the backtest report.
|
|
51
51
|
|
|
52
52
|
Returns:
|
|
53
|
-
float: The win rate as a percentage (e.g., 75
|
|
53
|
+
float: The win rate as a percentage (e.g., o.75 for 75% win rate).
|
|
54
54
|
"""
|
|
55
55
|
trades = [
|
|
56
56
|
trade for trade in trades if TradeStatus.CLOSED.equals(trade.status)
|
|
@@ -61,7 +61,34 @@ def get_win_rate(trades: List[Trade]) -> float:
|
|
|
61
61
|
if total_trades == 0:
|
|
62
62
|
return 0.0
|
|
63
63
|
|
|
64
|
-
return
|
|
64
|
+
return positive_trades / total_trades
|
|
65
|
+
|
|
66
|
+
def get_current_win_rate(trades: List[Trade]) -> float:
|
|
67
|
+
"""
|
|
68
|
+
Calculate the current win rate of the portfolio based on a list
|
|
69
|
+
of recent trades.
|
|
70
|
+
|
|
71
|
+
Current Win Rate is defined as the percentage of the most recent trades
|
|
72
|
+
that were profitable. This metric also includes trades that are still open.
|
|
73
|
+
|
|
74
|
+
Formula:
|
|
75
|
+
Current Win Rate = Number of Profitable Recent Trades
|
|
76
|
+
/ Total Number of Recent Trades
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
trades (List[Trade]): List of recent trades.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
float: The current win rate as a percentage (e.g., 0.75 for
|
|
83
|
+
75% win rate).
|
|
84
|
+
"""
|
|
85
|
+
if not trades:
|
|
86
|
+
return 0.0
|
|
87
|
+
|
|
88
|
+
positive_trades = sum(1 for trade in trades if trade.net_gain_absolute > 0)
|
|
89
|
+
total_trades = len(trades)
|
|
90
|
+
|
|
91
|
+
return positive_trades / total_trades
|
|
65
92
|
|
|
66
93
|
|
|
67
94
|
def get_win_loss_ratio(trades: List[Trade]) -> float:
|
|
@@ -108,3 +135,43 @@ def get_win_loss_ratio(trades: List[Trade]) -> float:
|
|
|
108
135
|
return float('inf')
|
|
109
136
|
|
|
110
137
|
return avg_win / avg_loss
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_current_win_loss_ratio(trades: List[Trade]) -> float:
|
|
141
|
+
"""
|
|
142
|
+
Calculate the current win/loss ratio of the portfolio based on a list
|
|
143
|
+
of recent trades.
|
|
144
|
+
|
|
145
|
+
Current Win/Loss Ratio is defined as the average profit of winning
|
|
146
|
+
recent trades divided by the average loss of losing recent trades.
|
|
147
|
+
This metric also includes trades that are still open.
|
|
148
|
+
|
|
149
|
+
Formula:
|
|
150
|
+
Current Win/Loss Ratio = Average Profit of Winning Recent Trades
|
|
151
|
+
/ Average Loss of Losing Recent Trades
|
|
152
|
+
Args:
|
|
153
|
+
trades (List[Trade]): List of recent trades.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
float: The current win/loss ratio.
|
|
157
|
+
"""
|
|
158
|
+
if not trades:
|
|
159
|
+
return 0.0
|
|
160
|
+
|
|
161
|
+
# Separate winning and losing trades
|
|
162
|
+
winning_trades = [t for t in trades if t.net_gain_absolute > 0]
|
|
163
|
+
losing_trades = [t for t in trades if t.net_gain_absolute < 0]
|
|
164
|
+
|
|
165
|
+
if not winning_trades or not losing_trades:
|
|
166
|
+
return 0.0
|
|
167
|
+
|
|
168
|
+
# Compute averages
|
|
169
|
+
avg_win = sum(t.net_gain_absolute for t in winning_trades) / len(winning_trades)
|
|
170
|
+
avg_loss = abs(
|
|
171
|
+
sum(t.net_gain_absolute for t in losing_trades) / len(losing_trades))
|
|
172
|
+
|
|
173
|
+
# Avoid division by zero
|
|
174
|
+
if avg_loss == 0:
|
|
175
|
+
return float('inf')
|
|
176
|
+
|
|
177
|
+
return avg_win / avg_loss
|