investing-algorithm-framework 3.7.0__py3-none-any.whl → 7.19.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of investing-algorithm-framework might be problematic. Click here for more details.
- investing_algorithm_framework/__init__.py +168 -45
- investing_algorithm_framework/app/__init__.py +32 -1
- investing_algorithm_framework/app/algorithm/__init__.py +7 -0
- investing_algorithm_framework/app/algorithm/algorithm.py +239 -0
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +114 -0
- investing_algorithm_framework/app/analysis/__init__.py +15 -0
- investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
- investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
- investing_algorithm_framework/app/analysis/permutation.py +116 -0
- investing_algorithm_framework/app/analysis/ranking.py +297 -0
- investing_algorithm_framework/app/app.py +1933 -589
- investing_algorithm_framework/app/app_hook.py +28 -0
- investing_algorithm_framework/app/context.py +1725 -0
- investing_algorithm_framework/app/eventloop.py +590 -0
- investing_algorithm_framework/app/reporting/__init__.py +27 -0
- investing_algorithm_framework/app/reporting/ascii.py +921 -0
- investing_algorithm_framework/app/reporting/backtest_report.py +349 -0
- investing_algorithm_framework/app/reporting/charts/__init__.py +19 -0
- investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +74 -0
- investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
- investing_algorithm_framework/app/reporting/charts/monthly_returns_heatmap.py +70 -0
- investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
- investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +79 -0
- investing_algorithm_framework/app/reporting/charts/yearly_returns_barchart.py +55 -0
- investing_algorithm_framework/app/reporting/generate.py +185 -0
- investing_algorithm_framework/app/reporting/tables/__init__.py +11 -0
- investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +217 -0
- investing_algorithm_framework/app/reporting/tables/stop_loss_table.py +0 -0
- investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
- investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
- investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
- investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
- investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
- investing_algorithm_framework/app/stateless/action_handlers/__init__.py +4 -2
- investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
- investing_algorithm_framework/app/strategy.py +664 -84
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/app/web/__init__.py +2 -1
- investing_algorithm_framework/app/web/create_app.py +4 -2
- investing_algorithm_framework/cli/__init__.py +0 -0
- investing_algorithm_framework/cli/cli.py +226 -0
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
- investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
- investing_algorithm_framework/cli/initialize_app.py +603 -0
- investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
- investing_algorithm_framework/cli/templates/app.py.template +18 -0
- investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
- investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
- investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
- investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
- investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
- investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
- investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
- investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
- investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
- investing_algorithm_framework/cli/templates/env.example.template +2 -0
- investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
- investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
- investing_algorithm_framework/cli/templates/readme.md.template +135 -0
- investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
- investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
- investing_algorithm_framework/create_app.py +40 -6
- investing_algorithm_framework/dependency_container.py +72 -56
- investing_algorithm_framework/domain/__init__.py +71 -47
- investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
- investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
- investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
- investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
- investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
- investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
- investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
- investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
- investing_algorithm_framework/domain/config.py +59 -91
- investing_algorithm_framework/domain/constants.py +13 -38
- investing_algorithm_framework/domain/data_provider.py +334 -0
- investing_algorithm_framework/domain/data_structures.py +3 -2
- investing_algorithm_framework/domain/exceptions.py +51 -1
- investing_algorithm_framework/domain/models/__init__.py +17 -12
- investing_algorithm_framework/domain/models/data/__init__.py +7 -0
- investing_algorithm_framework/domain/models/data/data_source.py +214 -0
- investing_algorithm_framework/domain/models/data/data_type.py +46 -0
- investing_algorithm_framework/domain/models/event.py +35 -0
- investing_algorithm_framework/domain/models/market/market_credential.py +55 -1
- investing_algorithm_framework/domain/models/order/order.py +77 -83
- investing_algorithm_framework/domain/models/order/order_status.py +2 -2
- investing_algorithm_framework/domain/models/order/order_type.py +1 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +81 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +26 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +108 -11
- investing_algorithm_framework/domain/models/position/__init__.py +2 -1
- investing_algorithm_framework/domain/models/position/position.py +12 -0
- investing_algorithm_framework/domain/models/position/position_size.py +41 -0
- investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
- investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
- investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
- investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
- investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
- investing_algorithm_framework/domain/models/time_frame.py +37 -0
- investing_algorithm_framework/domain/models/time_interval.py +33 -0
- investing_algorithm_framework/domain/models/time_unit.py +66 -2
- investing_algorithm_framework/domain/models/trade/__init__.py +8 -1
- investing_algorithm_framework/domain/models/trade/trade.py +295 -171
- investing_algorithm_framework/domain/models/trade/trade_status.py +9 -2
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
- investing_algorithm_framework/domain/order_executor.py +112 -0
- investing_algorithm_framework/domain/portfolio_provider.py +118 -0
- investing_algorithm_framework/domain/services/__init__.py +2 -9
- investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +0 -6
- investing_algorithm_framework/domain/services/state_handler.py +38 -0
- investing_algorithm_framework/domain/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +12 -7
- investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
- investing_algorithm_framework/domain/utils/dates.py +57 -0
- investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
- investing_algorithm_framework/domain/utils/polars.py +53 -0
- investing_algorithm_framework/domain/utils/random.py +29 -0
- investing_algorithm_framework/download_data.py +108 -0
- investing_algorithm_framework/infrastructure/__init__.py +31 -18
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
- investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
- investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
- investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
- investing_algorithm_framework/infrastructure/database/sql_alchemy.py +86 -12
- investing_algorithm_framework/infrastructure/models/__init__.py +6 -11
- investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -1
- investing_algorithm_framework/infrastructure/models/order/order.py +35 -49
- investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
- investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
- investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +1 -1
- investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +8 -0
- investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +17 -5
- investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
- investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
- investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
- investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
- investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
- investing_algorithm_framework/infrastructure/repositories/__init__.py +8 -0
- investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
- investing_algorithm_framework/infrastructure/repositories/order_repository.py +5 -0
- investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +1 -1
- investing_algorithm_framework/infrastructure/repositories/position_repository.py +11 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +81 -27
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +4 -4
- investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
- investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
- investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
- investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
- investing_algorithm_framework/services/__init__.py +113 -16
- investing_algorithm_framework/services/backtesting/__init__.py +0 -7
- investing_algorithm_framework/services/backtesting/backtest_service.py +566 -359
- investing_algorithm_framework/services/configuration_service.py +77 -11
- investing_algorithm_framework/services/data_providers/__init__.py +5 -0
- investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
- investing_algorithm_framework/services/market_credential_service.py +16 -1
- investing_algorithm_framework/services/metrics/__init__.py +114 -0
- investing_algorithm_framework/services/metrics/alpha.py +0 -0
- investing_algorithm_framework/services/metrics/beta.py +0 -0
- investing_algorithm_framework/services/metrics/cagr.py +60 -0
- investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
- investing_algorithm_framework/services/metrics/drawdown.py +181 -0
- investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
- investing_algorithm_framework/services/metrics/exposure.py +210 -0
- investing_algorithm_framework/services/metrics/generate.py +358 -0
- investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
- investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
- investing_algorithm_framework/services/metrics/recovery.py +113 -0
- investing_algorithm_framework/services/metrics/returns.py +452 -0
- investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
- investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
- investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
- investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
- investing_algorithm_framework/services/metrics/trades.py +500 -0
- investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
- investing_algorithm_framework/services/metrics/ulcer.py +0 -0
- investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
- investing_algorithm_framework/services/metrics/volatility.py +97 -0
- investing_algorithm_framework/services/metrics/win_rate.py +177 -0
- investing_algorithm_framework/services/order_service/__init__.py +3 -1
- investing_algorithm_framework/services/order_service/order_backtest_service.py +76 -89
- investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
- investing_algorithm_framework/services/order_service/order_service.py +407 -326
- investing_algorithm_framework/services/portfolios/__init__.py +3 -1
- investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +37 -3
- investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +22 -8
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
- investing_algorithm_framework/services/portfolios/portfolio_service.py +96 -28
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +97 -28
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +116 -313
- investing_algorithm_framework/services/positions/__init__.py +7 -0
- investing_algorithm_framework/services/positions/position_service.py +210 -0
- investing_algorithm_framework/services/repository_service.py +8 -2
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
- investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
- investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
- investing_algorithm_framework/services/trade_service/__init__.py +7 -1
- investing_algorithm_framework/services/trade_service/trade_service.py +1013 -315
- investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
- investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
- investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
- investing_algorithm_framework-7.19.15.dist-info/RECORD +263 -0
- investing_algorithm_framework-7.19.15.dist-info/entry_points.txt +3 -0
- investing_algorithm_framework/app/algorithm.py +0 -1105
- investing_algorithm_framework/domain/graphs.py +0 -382
- investing_algorithm_framework/domain/metrics/__init__.py +0 -6
- investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -11
- investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -43
- investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
- investing_algorithm_framework/domain/models/backtesting/backtest_report.py +0 -580
- investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -243
- investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
- investing_algorithm_framework/domain/services/market_data_sources.py +0 -344
- investing_algorithm_framework/domain/services/market_service.py +0 -153
- investing_algorithm_framework/domain/singleton.py +0 -9
- investing_algorithm_framework/domain/utils/backtesting.py +0 -472
- investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -12
- investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -559
- investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -254
- investing_algorithm_framework/infrastructure/models/market_data_sources/us_treasury_yield.py +0 -47
- investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
- investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -455
- investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
- investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
- investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -350
- investing_algorithm_framework/services/backtesting/backtest_report_writer_service.py +0 -53
- investing_algorithm_framework/services/backtesting/graphs.py +0 -61
- investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -8
- investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -150
- investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -189
- investing_algorithm_framework/services/position_service.py +0 -31
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -264
- investing_algorithm_framework-3.7.0.dist-info/METADATA +0 -339
- investing_algorithm_framework-3.7.0.dist-info/RECORD +0 -147
- /investing_algorithm_framework/{domain → services}/metrics/price_efficiency.py +0 -0
- /investing_algorithm_framework/services/{position_snapshot_service.py → positions/position_snapshot_service.py} +0 -0
- {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
- {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
|
@@ -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
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from .order_backtest_service import OrderBacktestService
|
|
2
2
|
from .order_service import OrderService
|
|
3
|
+
from .order_executor_lookup import OrderExecutorLookup
|
|
3
4
|
|
|
4
5
|
__all__ = [
|
|
5
6
|
"OrderService",
|
|
6
|
-
"OrderBacktestService"
|
|
7
|
+
"OrderBacktestService",
|
|
8
|
+
"OrderExecutorLookup",
|
|
7
9
|
]
|
|
@@ -2,11 +2,8 @@ import logging
|
|
|
2
2
|
|
|
3
3
|
import polars as pl
|
|
4
4
|
|
|
5
|
-
from investing_algorithm_framework.domain import
|
|
6
|
-
OrderStatus,
|
|
7
|
-
OperationalException, OrderSide, Order
|
|
8
|
-
from investing_algorithm_framework.services.market_data_source_service \
|
|
9
|
-
import BacktestMarketDataSourceService
|
|
5
|
+
from investing_algorithm_framework.domain import INDEX_DATETIME, \
|
|
6
|
+
OrderStatus, OrderSide, Order, DataType
|
|
10
7
|
from .order_service import OrderService
|
|
11
8
|
|
|
12
9
|
logger = logging.getLogger("investing_algorithm_framework")
|
|
@@ -17,90 +14,86 @@ class OrderBacktestService(OrderService):
|
|
|
17
14
|
def __init__(
|
|
18
15
|
self,
|
|
19
16
|
order_repository,
|
|
20
|
-
|
|
17
|
+
trade_service,
|
|
18
|
+
position_service,
|
|
21
19
|
portfolio_repository,
|
|
22
20
|
portfolio_configuration_service,
|
|
23
21
|
portfolio_snapshot_service,
|
|
24
22
|
configuration_service,
|
|
25
|
-
market_data_source_service: BacktestMarketDataSourceService,
|
|
26
23
|
):
|
|
27
|
-
super(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
33
|
self.configuration_service = configuration_service
|
|
34
|
-
self._market_data_source_service: BacktestMarketDataSourceService = \
|
|
35
|
-
market_data_source_service
|
|
36
34
|
|
|
37
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()
|
|
38
50
|
# Make sure the created_at is set to the current backtest time
|
|
39
|
-
data["created_at"] =
|
|
40
|
-
|
|
51
|
+
data["created_at"] = config[INDEX_DATETIME]
|
|
52
|
+
data["updated_at"] = config[INDEX_DATETIME]
|
|
41
53
|
# Call super to have standard behavior
|
|
42
54
|
return super(OrderBacktestService, self)\
|
|
43
55
|
.create(data, execute, validate, sync)
|
|
44
56
|
|
|
45
|
-
def execute_order(self,
|
|
46
|
-
order =
|
|
47
|
-
order =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"updated_at": self.configuration_service
|
|
53
|
-
.config[BACKTESTING_INDEX_DATETIME]
|
|
54
|
-
}
|
|
55
|
-
)
|
|
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
|
+
]
|
|
56
64
|
return order
|
|
57
65
|
|
|
58
|
-
def check_pending_orders(self):
|
|
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
|
+
"""
|
|
59
79
|
pending_orders = self.get_all({"status": OrderStatus.OPEN.value})
|
|
60
|
-
|
|
61
|
-
config = self.configuration_service.get_config()
|
|
80
|
+
meta_data = market_data["metadata"]
|
|
62
81
|
|
|
63
82
|
for order in pending_orders:
|
|
64
|
-
|
|
65
|
-
f"/{order.trading_symbol.upper()}"
|
|
66
|
-
position = self.position_repository.get(order.position_id)
|
|
67
|
-
portfolio = self.portfolio_repository.get(position.portfolio_id)
|
|
68
|
-
timeframe = None
|
|
69
|
-
|
|
70
|
-
if BACKTESTING_PENDING_ORDER_CHECK_INTERVAL in \
|
|
71
|
-
self.configuration_service.config:
|
|
72
|
-
timeframe = self.configuration_service\
|
|
73
|
-
.config[BACKTESTING_PENDING_ORDER_CHECK_INTERVAL]
|
|
74
|
-
|
|
75
|
-
if not self._market_data_source_service\
|
|
76
|
-
.is_ohlcv_data_source_present(
|
|
77
|
-
symbol=symbol,
|
|
78
|
-
market=portfolio.market,
|
|
79
|
-
time_frame=timeframe
|
|
80
|
-
):
|
|
81
|
-
raise OperationalException(
|
|
82
|
-
f"OHLCV data source not found for {symbol} "
|
|
83
|
-
f"and market {portfolio.market} for order check "
|
|
84
|
-
f"time frame "
|
|
85
|
-
f"{config[BACKTESTING_PENDING_ORDER_CHECK_INTERVAL]}. "
|
|
86
|
-
f"Cannot check pending orders for symbol {symbol} "
|
|
87
|
-
f"with market {portfolio.market}. Please add a ohlcv data"
|
|
88
|
-
f"source for {symbol} and market {portfolio.market} with "
|
|
89
|
-
f"time frame "
|
|
90
|
-
f"{config[BACKTESTING_PENDING_ORDER_CHECK_INTERVAL]} "
|
|
91
|
-
)
|
|
83
|
+
ohlcv_meta_data = meta_data[DataType.OHLCV]
|
|
92
84
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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]
|
|
101
93
|
)
|
|
94
|
+
data = market_data[identifier]
|
|
102
95
|
|
|
103
|
-
if self.has_executed(order,
|
|
96
|
+
if self.has_executed(order, data):
|
|
104
97
|
self.update(
|
|
105
98
|
order.id,
|
|
106
99
|
{
|
|
@@ -108,10 +101,9 @@ class OrderBacktestService(OrderService):
|
|
|
108
101
|
"filled": order.get_amount(),
|
|
109
102
|
"remaining": 0,
|
|
110
103
|
"updated_at": self.configuration_service
|
|
111
|
-
.config[
|
|
104
|
+
.config[INDEX_DATETIME]
|
|
112
105
|
}
|
|
113
106
|
)
|
|
114
|
-
break
|
|
115
107
|
|
|
116
108
|
def cancel_order(self, order):
|
|
117
109
|
self.check_pending_orders()
|
|
@@ -126,11 +118,11 @@ class OrderBacktestService(OrderService):
|
|
|
126
118
|
"status": OrderStatus.CANCELED.value,
|
|
127
119
|
"remaining": 0,
|
|
128
120
|
"updated_at": self.configuration_service
|
|
129
|
-
.config[
|
|
121
|
+
.config[INDEX_DATETIME]
|
|
130
122
|
}
|
|
131
123
|
)
|
|
132
124
|
|
|
133
|
-
def has_executed(self, order, ohlcv_data_frame):
|
|
125
|
+
def has_executed(self, order, ohlcv_data_frame: pl.DataFrame):
|
|
134
126
|
"""
|
|
135
127
|
Check if the order has executed based on the OHLCV data.
|
|
136
128
|
|
|
@@ -138,7 +130,7 @@ class OrderBacktestService(OrderService):
|
|
|
138
130
|
order price. Example: If the order price is 1000 and the low price
|
|
139
131
|
drops below or equals 1000, the order is executed. This simulates the
|
|
140
132
|
situation where a buyer is willing to pay a higher price than the
|
|
141
|
-
|
|
133
|
+
lowest price in the ohlcv data.
|
|
142
134
|
|
|
143
135
|
A sell order is executed if the high price goes above or equals the
|
|
144
136
|
order price. Example: If the order price is 1000 and the high price
|
|
@@ -146,27 +138,22 @@ class OrderBacktestService(OrderService):
|
|
|
146
138
|
situation where a seller is willing to accept a higher price for its
|
|
147
139
|
sell order.
|
|
148
140
|
|
|
149
|
-
:
|
|
150
|
-
|
|
151
|
-
|
|
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
|
|
152
148
|
"""
|
|
153
149
|
|
|
154
150
|
# Extract attributes from the order object
|
|
155
151
|
created_at = order.get_created_at()
|
|
156
152
|
order_side = order.get_order_side()
|
|
157
153
|
order_price = order.get_price()
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
ohlcv_data_after_order = ohlcv_data_frame.filter(
|
|
162
|
-
pl.col('Datetime') >= created_at
|
|
163
|
-
)
|
|
164
|
-
else:
|
|
165
|
-
ohlcv_data_after_order = ohlcv_data_frame.filter(
|
|
166
|
-
pl.col('Datetime') >= created_at.strftime(
|
|
167
|
-
self.configuration_service.config["DATETIME_FORMAT"]
|
|
168
|
-
)
|
|
169
|
-
)
|
|
154
|
+
ohlcv_data_after_order = ohlcv_data_frame.filter(
|
|
155
|
+
pl.col('Datetime') >= created_at
|
|
156
|
+
)
|
|
170
157
|
|
|
171
158
|
# Check if the order execution conditions are met
|
|
172
159
|
if OrderSide.BUY.equals(order_side):
|
|
@@ -185,7 +172,7 @@ class OrderBacktestService(OrderService):
|
|
|
185
172
|
|
|
186
173
|
if created_at is None:
|
|
187
174
|
created_at = self.configuration_service \
|
|
188
|
-
.config[
|
|
175
|
+
.config[INDEX_DATETIME]
|
|
189
176
|
|
|
190
177
|
super(OrderBacktestService, self)\
|
|
191
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 = []
|