investing-algorithm-framework 1.5__py3-none-any.whl → 7.25.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- investing_algorithm_framework/__init__.py +192 -16
- investing_algorithm_framework/analysis/__init__.py +16 -0
- investing_algorithm_framework/analysis/backtest_data_ranges.py +202 -0
- investing_algorithm_framework/analysis/data.py +170 -0
- investing_algorithm_framework/analysis/markdown.py +91 -0
- investing_algorithm_framework/analysis/ranking.py +298 -0
- investing_algorithm_framework/app/__init__.py +29 -4
- investing_algorithm_framework/app/algorithm/__init__.py +7 -0
- investing_algorithm_framework/app/algorithm/algorithm.py +193 -0
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +118 -0
- investing_algorithm_framework/app/app.py +2220 -379
- investing_algorithm_framework/app/app_hook.py +28 -0
- investing_algorithm_framework/app/context.py +1724 -0
- investing_algorithm_framework/app/eventloop.py +620 -0
- investing_algorithm_framework/app/reporting/__init__.py +27 -0
- investing_algorithm_framework/app/reporting/ascii.py +921 -0
- investing_algorithm_framework/app/reporting/backtest_report.py +349 -0
- investing_algorithm_framework/app/reporting/charts/__init__.py +19 -0
- investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +74 -0
- investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
- investing_algorithm_framework/app/reporting/charts/monthly_returns_heatmap.py +70 -0
- investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
- investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +79 -0
- investing_algorithm_framework/app/reporting/charts/yearly_returns_barchart.py +55 -0
- investing_algorithm_framework/app/reporting/generate.py +185 -0
- investing_algorithm_framework/app/reporting/tables/__init__.py +11 -0
- investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +217 -0
- investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
- investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
- investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
- investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
- investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
- investing_algorithm_framework/app/stateless/action_handlers/__init__.py +6 -3
- investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +2 -1
- investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
- investing_algorithm_framework/app/strategy.py +867 -60
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/app/web/__init__.py +2 -1
- investing_algorithm_framework/app/web/controllers/__init__.py +2 -2
- investing_algorithm_framework/app/web/controllers/orders.py +3 -2
- investing_algorithm_framework/app/web/controllers/positions.py +2 -2
- investing_algorithm_framework/app/web/create_app.py +4 -2
- investing_algorithm_framework/app/web/schemas/position.py +1 -0
- investing_algorithm_framework/cli/__init__.py +0 -0
- investing_algorithm_framework/cli/cli.py +231 -0
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
- investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
- investing_algorithm_framework/cli/initialize_app.py +603 -0
- investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
- investing_algorithm_framework/cli/templates/app.py.template +18 -0
- investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
- investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
- investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
- investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
- investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
- investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
- investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
- investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
- investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
- investing_algorithm_framework/cli/templates/env.example.template +2 -0
- investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
- investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
- investing_algorithm_framework/cli/templates/readme.md.template +135 -0
- investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
- investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
- investing_algorithm_framework/cli/validate_backtest_checkpoints.py +197 -0
- investing_algorithm_framework/create_app.py +40 -7
- investing_algorithm_framework/dependency_container.py +100 -47
- investing_algorithm_framework/domain/__init__.py +97 -30
- investing_algorithm_framework/domain/algorithm_id.py +69 -0
- investing_algorithm_framework/domain/backtesting/__init__.py +25 -0
- investing_algorithm_framework/domain/backtesting/backtest.py +548 -0
- investing_algorithm_framework/domain/backtesting/backtest_date_range.py +113 -0
- investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +241 -0
- investing_algorithm_framework/domain/backtesting/backtest_metrics.py +470 -0
- investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
- investing_algorithm_framework/domain/backtesting/backtest_run.py +663 -0
- investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
- investing_algorithm_framework/domain/backtesting/backtest_utils.py +198 -0
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +392 -0
- investing_algorithm_framework/domain/config.py +59 -136
- investing_algorithm_framework/domain/constants.py +18 -37
- investing_algorithm_framework/domain/data_provider.py +334 -0
- investing_algorithm_framework/domain/data_structures.py +42 -0
- investing_algorithm_framework/domain/exceptions.py +51 -1
- investing_algorithm_framework/domain/models/__init__.py +26 -19
- investing_algorithm_framework/domain/models/app_mode.py +34 -0
- investing_algorithm_framework/domain/models/data/__init__.py +7 -0
- investing_algorithm_framework/domain/models/data/data_source.py +222 -0
- investing_algorithm_framework/domain/models/data/data_type.py +46 -0
- investing_algorithm_framework/domain/models/event.py +35 -0
- investing_algorithm_framework/domain/models/market/__init__.py +5 -0
- investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
- investing_algorithm_framework/domain/models/order/__init__.py +3 -4
- investing_algorithm_framework/domain/models/order/order.py +198 -65
- investing_algorithm_framework/domain/models/order/order_status.py +2 -2
- investing_algorithm_framework/domain/models/order/order_type.py +1 -3
- investing_algorithm_framework/domain/models/portfolio/__init__.py +6 -2
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +98 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +37 -43
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +108 -11
- investing_algorithm_framework/domain/models/position/__init__.py +2 -1
- investing_algorithm_framework/domain/models/position/position.py +20 -0
- investing_algorithm_framework/domain/models/position/position_size.py +41 -0
- investing_algorithm_framework/domain/models/position/position_snapshot.py +0 -2
- investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
- investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
- investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
- investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
- investing_algorithm_framework/domain/models/strategy_profile.py +19 -141
- investing_algorithm_framework/domain/models/time_frame.py +94 -98
- investing_algorithm_framework/domain/models/time_interval.py +33 -0
- investing_algorithm_framework/domain/models/time_unit.py +66 -2
- investing_algorithm_framework/domain/models/tracing/__init__.py +0 -0
- investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
- investing_algorithm_framework/domain/models/trade/__init__.py +11 -0
- investing_algorithm_framework/domain/models/trade/trade.py +389 -0
- investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
- investing_algorithm_framework/domain/order_executor.py +112 -0
- investing_algorithm_framework/domain/portfolio_provider.py +118 -0
- investing_algorithm_framework/domain/services/__init__.py +11 -0
- investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
- investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
- investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
- investing_algorithm_framework/domain/services/rounding_service.py +27 -0
- investing_algorithm_framework/domain/services/state_handler.py +38 -0
- investing_algorithm_framework/domain/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +15 -5
- investing_algorithm_framework/domain/utils/csv.py +22 -0
- investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
- investing_algorithm_framework/domain/utils/dates.py +57 -0
- investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
- investing_algorithm_framework/domain/utils/polars.py +53 -0
- investing_algorithm_framework/domain/utils/random.py +29 -0
- investing_algorithm_framework/download_data.py +244 -0
- investing_algorithm_framework/infrastructure/__init__.py +37 -11
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1152 -0
- investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
- investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
- investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
- investing_algorithm_framework/infrastructure/database/sql_alchemy.py +86 -12
- investing_algorithm_framework/infrastructure/models/__init__.py +7 -3
- investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -2
- investing_algorithm_framework/infrastructure/models/order/order.py +53 -53
- investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
- investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
- investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +1 -1
- investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +8 -2
- investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +17 -6
- investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +3 -1
- investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
- investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
- investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
- investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
- investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
- investing_algorithm_framework/infrastructure/repositories/__init__.py +10 -4
- investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
- investing_algorithm_framework/infrastructure/repositories/order_repository.py +16 -5
- investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +2 -2
- investing_algorithm_framework/infrastructure/repositories/position_repository.py +11 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +84 -30
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +9 -4
- investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
- investing_algorithm_framework/infrastructure/services/aws/state_handler.py +193 -0
- investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
- investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
- investing_algorithm_framework/infrastructure/services/backtesting/__init__.py +9 -0
- investing_algorithm_framework/infrastructure/services/backtesting/backtest_service.py +2596 -0
- investing_algorithm_framework/infrastructure/services/backtesting/event_backtest_service.py +285 -0
- investing_algorithm_framework/infrastructure/services/backtesting/vector_backtest_service.py +468 -0
- investing_algorithm_framework/services/__init__.py +123 -15
- investing_algorithm_framework/services/configuration_service.py +77 -11
- investing_algorithm_framework/services/data_providers/__init__.py +5 -0
- investing_algorithm_framework/services/data_providers/data_provider_service.py +1058 -0
- investing_algorithm_framework/services/market_credential_service.py +40 -0
- investing_algorithm_framework/services/metrics/__init__.py +119 -0
- investing_algorithm_framework/services/metrics/alpha.py +0 -0
- investing_algorithm_framework/services/metrics/beta.py +0 -0
- investing_algorithm_framework/services/metrics/cagr.py +60 -0
- investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
- investing_algorithm_framework/services/metrics/drawdown.py +218 -0
- investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
- investing_algorithm_framework/services/metrics/exposure.py +210 -0
- investing_algorithm_framework/services/metrics/generate.py +358 -0
- investing_algorithm_framework/services/metrics/mean_daily_return.py +84 -0
- investing_algorithm_framework/services/metrics/price_efficiency.py +57 -0
- investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
- investing_algorithm_framework/services/metrics/recovery.py +113 -0
- investing_algorithm_framework/services/metrics/returns.py +452 -0
- investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
- investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
- investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
- investing_algorithm_framework/services/metrics/standard_deviation.py +156 -0
- investing_algorithm_framework/services/metrics/trades.py +473 -0
- investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
- investing_algorithm_framework/services/metrics/ulcer.py +0 -0
- investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
- investing_algorithm_framework/services/metrics/volatility.py +118 -0
- investing_algorithm_framework/services/metrics/win_rate.py +177 -0
- investing_algorithm_framework/services/order_service/__init__.py +9 -0
- investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
- investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
- investing_algorithm_framework/services/order_service/order_service.py +826 -0
- investing_algorithm_framework/services/portfolios/__init__.py +16 -0
- investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
- investing_algorithm_framework/services/{portfolio_configuration_service.py → portfolios/portfolio_configuration_service.py} +27 -12
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
- investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
- investing_algorithm_framework/services/positions/__init__.py +7 -0
- investing_algorithm_framework/services/positions/position_service.py +210 -0
- investing_algorithm_framework/services/repository_service.py +8 -2
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +117 -0
- investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
- investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
- investing_algorithm_framework/services/trade_service/__init__.py +9 -0
- investing_algorithm_framework/services/trade_service/trade_service.py +1099 -0
- investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
- investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
- investing_algorithm_framework-7.25.6.dist-info/METADATA +535 -0
- investing_algorithm_framework-7.25.6.dist-info/RECORD +268 -0
- {investing_algorithm_framework-1.5.dist-info → investing_algorithm_framework-7.25.6.dist-info}/WHEEL +1 -2
- investing_algorithm_framework-7.25.6.dist-info/entry_points.txt +3 -0
- investing_algorithm_framework/app/algorithm.py +0 -630
- investing_algorithm_framework/domain/models/backtest_profile.py +0 -414
- investing_algorithm_framework/domain/models/market_data/__init__.py +0 -11
- investing_algorithm_framework/domain/models/market_data/asset_price.py +0 -50
- investing_algorithm_framework/domain/models/market_data/ohlcv.py +0 -105
- investing_algorithm_framework/domain/models/market_data/order_book.py +0 -63
- investing_algorithm_framework/domain/models/market_data/ticker.py +0 -92
- investing_algorithm_framework/domain/models/order/order_fee.py +0 -45
- investing_algorithm_framework/domain/models/trade.py +0 -78
- investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
- investing_algorithm_framework/domain/singleton.py +0 -9
- investing_algorithm_framework/domain/utils/backtesting.py +0 -82
- investing_algorithm_framework/infrastructure/models/order/order_fee.py +0 -21
- investing_algorithm_framework/infrastructure/repositories/order_fee_repository.py +0 -15
- investing_algorithm_framework/infrastructure/services/market_backtest_service.py +0 -360
- investing_algorithm_framework/infrastructure/services/market_service.py +0 -410
- investing_algorithm_framework/infrastructure/services/performance_service.py +0 -192
- investing_algorithm_framework/services/backtest_service.py +0 -268
- investing_algorithm_framework/services/market_data_service.py +0 -77
- investing_algorithm_framework/services/order_backtest_service.py +0 -122
- investing_algorithm_framework/services/order_service.py +0 -752
- investing_algorithm_framework/services/portfolio_service.py +0 -164
- investing_algorithm_framework/services/portfolio_snapshot_service.py +0 -68
- investing_algorithm_framework/services/position_cost_service.py +0 -5
- investing_algorithm_framework/services/position_service.py +0 -63
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -225
- investing_algorithm_framework-1.5.dist-info/AUTHORS.md +0 -8
- investing_algorithm_framework-1.5.dist-info/METADATA +0 -230
- investing_algorithm_framework-1.5.dist-info/RECORD +0 -119
- investing_algorithm_framework-1.5.dist-info/top_level.txt +0 -1
- /investing_algorithm_framework/{infrastructure/services/performance_backtest_service.py → app/reporting/tables/stop_loss_table.py} +0 -0
- /investing_algorithm_framework/services/{position_snapshot_service.py → positions/position_snapshot_service.py} +0 -0
- {investing_algorithm_framework-1.5.dist-info → investing_algorithm_framework-7.25.6.dist-info}/LICENSE +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
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
|
+
# Filter out non-positive values before calculating log returns
|
|
90
|
+
# Log returns are only valid for positive portfolio values
|
|
91
|
+
df_daily = df_daily[df_daily['total_value'] > 0]
|
|
92
|
+
|
|
93
|
+
if len(df_daily) < 2:
|
|
94
|
+
return 0.0
|
|
95
|
+
|
|
96
|
+
# Calculate log returns on daily data
|
|
97
|
+
# Add a small epsilon to avoid log(0) and replace inf/nan values
|
|
98
|
+
price_ratios = df_daily['total_value'] / df_daily['total_value'].shift(1)
|
|
99
|
+
|
|
100
|
+
# Filter out invalid price ratios (zero, negative, or NaN)
|
|
101
|
+
df_daily['log_return'] = np.log(price_ratios)
|
|
102
|
+
|
|
103
|
+
# Replace inf and -inf with NaN, then drop them
|
|
104
|
+
df_daily['log_return'] = df_daily['log_return'].replace([np.inf, -np.inf], np.nan)
|
|
105
|
+
df_daily = df_daily.dropna()
|
|
106
|
+
|
|
107
|
+
if len(df_daily) < 2:
|
|
108
|
+
return 0.0
|
|
109
|
+
|
|
110
|
+
# Calculate daily volatility (standard deviation of daily returns)
|
|
111
|
+
daily_volatility = df_daily['log_return'].std()
|
|
112
|
+
|
|
113
|
+
# Handle edge case where std might be NaN
|
|
114
|
+
if pd.isna(daily_volatility):
|
|
115
|
+
return 0.0
|
|
116
|
+
|
|
117
|
+
# Annualize using trading days per year
|
|
118
|
+
return daily_volatility * np.sqrt(trading_days_per_year)
|
|
@@ -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 = []
|