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,177 @@
|
|
|
1
|
+
"""
|
|
2
|
+
| Metric | High Value Means... | Weakness if Used Alone |
|
|
3
|
+
| ------------------ | --------------------------- | ----------------------------------- |
|
|
4
|
+
| **Win Rate** | Many trades are profitable | Doesn't say how big wins/losses are |
|
|
5
|
+
| **Win/Loss Ratio** | Big wins relative to losses | Doesn’t say how *often* you win |
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Example of Non-Overlap:
|
|
9
|
+
Strategy A: 90% win rate, but average win is $1, average loss is $10 → not profitable.
|
|
10
|
+
|
|
11
|
+
Strategy B: 30% win rate, but average win is $300, average loss is $50 → highly profitable.
|
|
12
|
+
|
|
13
|
+
| Win Rate | Win/Loss Ratio | Comment |
|
|
14
|
+
| ----------------- | -------------- | ---------------------------------- |
|
|
15
|
+
| High (>60%) | <1 | Can still be profitable |
|
|
16
|
+
| Moderate (40-60%) | \~1 or >1 | Ideal sweet spot |
|
|
17
|
+
| Low (<40%) | >1 | Possible if big wins offset losses |
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
Practical Example:
|
|
21
|
+
* Win rate 40% with win/loss ratio 2: Good — you win less often but your
|
|
22
|
+
wins are twice as big.
|
|
23
|
+
* Win rate 60% with win/loss ratio 0.7: Also good — you win often
|
|
24
|
+
but your wins are smaller than losses.
|
|
25
|
+
|
|
26
|
+
What’s “good”?
|
|
27
|
+
* Typical win/loss ratio ranges from 0.5 to 3 depending on strategy style.
|
|
28
|
+
* Many profitable traders target win/loss ratio between 1.5 and 2.5.
|
|
29
|
+
* Very aggressive strategies might have a lower win rate but
|
|
30
|
+
higher win/loss ratio.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from typing import List
|
|
34
|
+
from investing_algorithm_framework.domain import TradeStatus, Trade
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_win_rate(trades: List[Trade]) -> float:
|
|
38
|
+
"""
|
|
39
|
+
Calculate the win rate of the portfolio based on the backtest report.
|
|
40
|
+
|
|
41
|
+
Win Rate is defined as the percentage of trades that were profitable.
|
|
42
|
+
The percentage of trades that are profitable.
|
|
43
|
+
|
|
44
|
+
Formula:
|
|
45
|
+
Win Rate = Number of Profitable Trades / Total Number of Trades
|
|
46
|
+
|
|
47
|
+
Example: If 60 out of 100 trades are profitable, the win rate is 60%.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
trades (List[Trade]): List of trades from the backtest report.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
float: The win rate as a percentage (e.g., o.75 for 75% win rate).
|
|
54
|
+
"""
|
|
55
|
+
trades = [
|
|
56
|
+
trade for trade in trades if TradeStatus.CLOSED.equals(trade.status)
|
|
57
|
+
]
|
|
58
|
+
positive_trades = sum(1 for trade in trades if trade.net_gain > 0)
|
|
59
|
+
total_trades = len(trades)
|
|
60
|
+
|
|
61
|
+
if total_trades == 0:
|
|
62
|
+
return 0.0
|
|
63
|
+
|
|
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
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def get_win_loss_ratio(trades: List[Trade]) -> float:
|
|
95
|
+
"""
|
|
96
|
+
Calculate the win/loss ratio of the portfolio based on the backtest report.
|
|
97
|
+
|
|
98
|
+
Win/Loss Ratio is defined as the average profit of winning trades divided by
|
|
99
|
+
the average loss of losing trades.
|
|
100
|
+
|
|
101
|
+
Formula:
|
|
102
|
+
Win/Loss Ratio = Average Profit of Winning Trades
|
|
103
|
+
/ Average Loss of Losing Trades
|
|
104
|
+
|
|
105
|
+
Example: If the average profit of winning trades is $200 and the
|
|
106
|
+
average loss of losing trades is $100, the win/loss ratio is 2.0.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
trades (List[Trade]): List of trades from the backtest report.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
float: The win/loss ratio.
|
|
113
|
+
"""
|
|
114
|
+
trades = [
|
|
115
|
+
trade for trade in trades if TradeStatus.CLOSED.equals(trade.status)
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
if not trades:
|
|
119
|
+
return 0.0
|
|
120
|
+
|
|
121
|
+
# Separate winning and losing trades
|
|
122
|
+
winning_trades = [t for t in trades if t.net_gain > 0]
|
|
123
|
+
losing_trades = [t for t in trades if t.net_gain < 0]
|
|
124
|
+
|
|
125
|
+
if not winning_trades or not losing_trades:
|
|
126
|
+
return 0.0
|
|
127
|
+
|
|
128
|
+
# Compute averages
|
|
129
|
+
avg_win = sum(t.net_gain for t in winning_trades) / len(winning_trades)
|
|
130
|
+
avg_loss = abs(
|
|
131
|
+
sum(t.net_gain for t in losing_trades) / len(losing_trades))
|
|
132
|
+
|
|
133
|
+
# Avoid division by zero
|
|
134
|
+
if avg_loss == 0:
|
|
135
|
+
return float('inf')
|
|
136
|
+
|
|
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
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import polars as pl
|
|
4
|
+
|
|
5
|
+
from investing_algorithm_framework.domain import INDEX_DATETIME, \
|
|
6
|
+
OrderStatus, OrderSide, Order, DataType
|
|
7
|
+
from .order_service import OrderService
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger("investing_algorithm_framework")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OrderBacktestService(OrderService):
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
order_repository,
|
|
17
|
+
trade_service,
|
|
18
|
+
position_service,
|
|
19
|
+
portfolio_repository,
|
|
20
|
+
portfolio_configuration_service,
|
|
21
|
+
portfolio_snapshot_service,
|
|
22
|
+
configuration_service,
|
|
23
|
+
):
|
|
24
|
+
super().__init__(
|
|
25
|
+
configuration_service=configuration_service,
|
|
26
|
+
order_repository=order_repository,
|
|
27
|
+
position_service=position_service,
|
|
28
|
+
portfolio_repository=portfolio_repository,
|
|
29
|
+
portfolio_configuration_service=portfolio_configuration_service,
|
|
30
|
+
portfolio_snapshot_service=portfolio_snapshot_service,
|
|
31
|
+
trade_service=trade_service,
|
|
32
|
+
)
|
|
33
|
+
self.configuration_service = configuration_service
|
|
34
|
+
|
|
35
|
+
def create(self, data, execute=True, validate=True, sync=True) -> Order:
|
|
36
|
+
"""
|
|
37
|
+
Override the create method to set the created_at and
|
|
38
|
+
updated_at attributes to the current backtest time.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
data (dict): Dictionary containing the order data
|
|
42
|
+
execute (bool): Flag to execute the order
|
|
43
|
+
validate (bool): Flag to validate the order
|
|
44
|
+
sync (bool): Flag to sync the order
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Order: Created order object
|
|
48
|
+
"""
|
|
49
|
+
config = self.configuration_service.get_config()
|
|
50
|
+
# Make sure the created_at is set to the current backtest time
|
|
51
|
+
data["created_at"] = config[INDEX_DATETIME]
|
|
52
|
+
data["updated_at"] = config[INDEX_DATETIME]
|
|
53
|
+
# Call super to have standard behavior
|
|
54
|
+
return super(OrderBacktestService, self)\
|
|
55
|
+
.create(data, execute, validate, sync)
|
|
56
|
+
|
|
57
|
+
def execute_order(self, order, portfolio):
|
|
58
|
+
order.status = OrderStatus.OPEN.value
|
|
59
|
+
order.remaining = order.get_amount()
|
|
60
|
+
order.filled = 0
|
|
61
|
+
order.updated_at = self.configuration_service.config[
|
|
62
|
+
INDEX_DATETIME
|
|
63
|
+
]
|
|
64
|
+
return order
|
|
65
|
+
|
|
66
|
+
def check_pending_orders(self, market_data):
|
|
67
|
+
"""
|
|
68
|
+
Function to check if any pending orders have executed. It querys the
|
|
69
|
+
open orders and checks if the order has executed based on the OHLCV
|
|
70
|
+
data. If the order has executed, the order status is set to CLOSED
|
|
71
|
+
and the filled amount is set to the order amount.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
market_data (dict): Dictionary containing the market data
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
None
|
|
78
|
+
"""
|
|
79
|
+
pending_orders = self.get_all({"status": OrderStatus.OPEN.value})
|
|
80
|
+
meta_data = market_data["metadata"]
|
|
81
|
+
|
|
82
|
+
for order in pending_orders:
|
|
83
|
+
ohlcv_meta_data = meta_data[DataType.OHLCV]
|
|
84
|
+
|
|
85
|
+
if order.get_symbol() not in ohlcv_meta_data:
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
timeframes = ohlcv_meta_data[order.get_symbol()].keys()
|
|
89
|
+
sorted_timeframes = sorted(timeframes)
|
|
90
|
+
most_granular_interval = sorted_timeframes[0]
|
|
91
|
+
identifier = (
|
|
92
|
+
ohlcv_meta_data[order.get_symbol()][most_granular_interval]
|
|
93
|
+
)
|
|
94
|
+
data = market_data[identifier]
|
|
95
|
+
|
|
96
|
+
if self.has_executed(order, data):
|
|
97
|
+
self.update(
|
|
98
|
+
order.id,
|
|
99
|
+
{
|
|
100
|
+
"status": OrderStatus.CLOSED.value,
|
|
101
|
+
"filled": order.get_amount(),
|
|
102
|
+
"remaining": 0,
|
|
103
|
+
"updated_at": self.configuration_service
|
|
104
|
+
.config[INDEX_DATETIME]
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def cancel_order(self, order):
|
|
109
|
+
self.check_pending_orders()
|
|
110
|
+
order = self.order_repository.get(order.id)
|
|
111
|
+
|
|
112
|
+
if order is not None:
|
|
113
|
+
|
|
114
|
+
if OrderStatus.OPEN.equals(order.status):
|
|
115
|
+
self.update(
|
|
116
|
+
order.id,
|
|
117
|
+
{
|
|
118
|
+
"status": OrderStatus.CANCELED.value,
|
|
119
|
+
"remaining": 0,
|
|
120
|
+
"updated_at": self.configuration_service
|
|
121
|
+
.config[INDEX_DATETIME]
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def has_executed(self, order, ohlcv_data_frame: pl.DataFrame):
|
|
126
|
+
"""
|
|
127
|
+
Check if the order has executed based on the OHLCV data.
|
|
128
|
+
|
|
129
|
+
A buy order is executed if the low price drops below or equals the
|
|
130
|
+
order price. Example: If the order price is 1000 and the low price
|
|
131
|
+
drops below or equals 1000, the order is executed. This simulates the
|
|
132
|
+
situation where a buyer is willing to pay a higher price than the
|
|
133
|
+
lowest price in the ohlcv data.
|
|
134
|
+
|
|
135
|
+
A sell order is executed if the high price goes above or equals the
|
|
136
|
+
order price. Example: If the order price is 1000 and the high price
|
|
137
|
+
goes above or equals 1000, the order is executed. This simulates the
|
|
138
|
+
situation where a seller is willing to accept a higher price for its
|
|
139
|
+
sell order.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
order: Order object
|
|
143
|
+
ohlcv_data_frame: OHLCV data frame
|
|
144
|
+
True if the order has executed, False otherwise
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
bool: True if the order has executed, False otherwise
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
# Extract attributes from the order object
|
|
151
|
+
created_at = order.get_created_at()
|
|
152
|
+
order_side = order.get_order_side()
|
|
153
|
+
order_price = order.get_price()
|
|
154
|
+
ohlcv_data_after_order = ohlcv_data_frame.filter(
|
|
155
|
+
pl.col('Datetime') >= created_at
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Check if the order execution conditions are met
|
|
159
|
+
if OrderSide.BUY.equals(order_side):
|
|
160
|
+
# Check if the low price drops below or equals the order price
|
|
161
|
+
if (ohlcv_data_after_order['Low'] <= order_price).any():
|
|
162
|
+
return True
|
|
163
|
+
elif OrderSide.SELL.equals(order_side):
|
|
164
|
+
# Check if the high price goes above or equals the order price
|
|
165
|
+
if (ohlcv_data_after_order['High'] >= order_price).any():
|
|
166
|
+
return True
|
|
167
|
+
|
|
168
|
+
# If conditions are not met, return False
|
|
169
|
+
return False
|
|
170
|
+
|
|
171
|
+
def create_snapshot(self, portfolio_id, created_at=None):
|
|
172
|
+
|
|
173
|
+
if created_at is None:
|
|
174
|
+
created_at = self.configuration_service \
|
|
175
|
+
.config[INDEX_DATETIME]
|
|
176
|
+
|
|
177
|
+
super(OrderBacktestService, self)\
|
|
178
|
+
.create_snapshot(portfolio_id, created_at=created_at)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
from investing_algorithm_framework.domain import OrderExecutor, \
|
|
5
|
+
ImproperlyConfigured
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OrderExecutorLookup:
|
|
9
|
+
"""
|
|
10
|
+
Efficient lookup for order executors based on market in O(1) time.
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
order_executors (List[OrderExecutor]): List of order executors
|
|
14
|
+
order_executor_lookup (dict): Dictionary to store the lookup
|
|
15
|
+
for order executors based on market.
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self, order_executors=[]):
|
|
18
|
+
self.order_executors = order_executors
|
|
19
|
+
|
|
20
|
+
# These will be our lookup tables
|
|
21
|
+
self.order_executor_lookup = defaultdict()
|
|
22
|
+
|
|
23
|
+
def add_order_executor(self, order_executor: OrderExecutor):
|
|
24
|
+
"""
|
|
25
|
+
Add an order executor to the lookup.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
order_executor (OrderExecutor): The order executor to be added.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
None
|
|
32
|
+
"""
|
|
33
|
+
self.order_executors.append(order_executor)
|
|
34
|
+
|
|
35
|
+
def register_order_executor_for_market(self, market) -> None:
|
|
36
|
+
"""
|
|
37
|
+
Register an order executor for a specific market.
|
|
38
|
+
This method will create a lookup table for efficient access to
|
|
39
|
+
order executors based on market. It will use the
|
|
40
|
+
order executors that are currently registered in the
|
|
41
|
+
order_executors list. The lookup table will be a dictionary
|
|
42
|
+
where the key is the market and the value is the portfolio provider.
|
|
43
|
+
|
|
44
|
+
This method will also check if the portfolio provider supports
|
|
45
|
+
the market. If no portfolio provider is found for the market,
|
|
46
|
+
it will raise an ImproperlyConfigured exception.
|
|
47
|
+
|
|
48
|
+
If multiple order executors are found for the market,
|
|
49
|
+
it will sort them by priority and pick the best one.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
market:
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
None
|
|
56
|
+
"""
|
|
57
|
+
matches = []
|
|
58
|
+
|
|
59
|
+
for order_executor in self.order_executors:
|
|
60
|
+
|
|
61
|
+
if order_executor.supports_market(market):
|
|
62
|
+
matches.append(order_executor)
|
|
63
|
+
|
|
64
|
+
if len(matches) == 0:
|
|
65
|
+
raise ImproperlyConfigured(
|
|
66
|
+
f"No portfolio provider found for market "
|
|
67
|
+
f"{market}. Cannot configure portfolio."
|
|
68
|
+
f" Please make sure that you have registered a portfolio "
|
|
69
|
+
f"provider for the market you are trying to use"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Sort by priority and pick the best one
|
|
73
|
+
best_provider = sorted(matches, key=lambda x: x.priority)[0]
|
|
74
|
+
self.order_executor_lookup[market] = best_provider
|
|
75
|
+
|
|
76
|
+
def get_order_executor(self, market: str):
|
|
77
|
+
"""
|
|
78
|
+
Get the order executor for a specific market.
|
|
79
|
+
This method will return the order executor for the market
|
|
80
|
+
that was registered in the lookup table. If no order executor
|
|
81
|
+
was found for the market, it will return None.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
market:
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
OrderExecutor: The order executor for the market.
|
|
88
|
+
"""
|
|
89
|
+
return self.order_executor_lookup.get(market, None)
|
|
90
|
+
|
|
91
|
+
def get_all(self) -> List[OrderExecutor]:
|
|
92
|
+
"""
|
|
93
|
+
Get all order executors.
|
|
94
|
+
This method will return all order executors that are currently
|
|
95
|
+
registered in the order_executors list.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
List[OrderExecutor]: A list of all order executors.
|
|
99
|
+
"""
|
|
100
|
+
return self.order_executors
|
|
101
|
+
|
|
102
|
+
def reset(self):
|
|
103
|
+
"""
|
|
104
|
+
Function to reset the order executor lookup table
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
None
|
|
108
|
+
"""
|
|
109
|
+
self.order_executor_lookup = defaultdict()
|
|
110
|
+
self.order_executors = []
|