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,91 @@
|
|
|
1
|
+
from typing import Dict, List, Any, Union
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def create_markdown_table(data: List[Union[Dict[str, Any], Any]]):
|
|
5
|
+
"""
|
|
6
|
+
Create a markdown table with evenly spaced columns for nice display
|
|
7
|
+
in notebook output cells.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
data (List[dict] or List[object]): List of dictionaries or objects
|
|
11
|
+
containing data.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
str: Markdown formatted table with evenly spaced columns.
|
|
15
|
+
"""
|
|
16
|
+
if not data or len(data) == 0:
|
|
17
|
+
return ("| No Data Available |\n"
|
|
18
|
+
"|-------------------|\n"
|
|
19
|
+
"| No records found |\n")
|
|
20
|
+
|
|
21
|
+
# Determine if data contains dicts or objects
|
|
22
|
+
is_dict = isinstance(data[0], dict)
|
|
23
|
+
|
|
24
|
+
# Get columns from data
|
|
25
|
+
if is_dict:
|
|
26
|
+
columns = list(data[0].keys())
|
|
27
|
+
else:
|
|
28
|
+
# For objects, get all attributes (excluding private ones)
|
|
29
|
+
columns = [
|
|
30
|
+
attr for attr in dir(data[0])
|
|
31
|
+
if not attr.startswith('_')
|
|
32
|
+
and not callable(getattr(data[0], attr))
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
# Generate header titles
|
|
36
|
+
header_titles = [col.replace("_", " ").title() for col in columns]
|
|
37
|
+
|
|
38
|
+
# Collect and format all row data
|
|
39
|
+
all_rows_data = []
|
|
40
|
+
for item in data:
|
|
41
|
+
row_values = []
|
|
42
|
+
for col in columns:
|
|
43
|
+
# Get value
|
|
44
|
+
if is_dict:
|
|
45
|
+
value = item.get(col)
|
|
46
|
+
else:
|
|
47
|
+
value = getattr(item, col, None)
|
|
48
|
+
|
|
49
|
+
# Format value
|
|
50
|
+
if value is None:
|
|
51
|
+
formatted_value = "N/A"
|
|
52
|
+
elif isinstance(value, float):
|
|
53
|
+
formatted_value = f"{value:.2f}"
|
|
54
|
+
else:
|
|
55
|
+
formatted_value = str(value)
|
|
56
|
+
|
|
57
|
+
row_values.append(formatted_value)
|
|
58
|
+
all_rows_data.append(row_values)
|
|
59
|
+
|
|
60
|
+
# Calculate column widths based on both headers and data
|
|
61
|
+
col_widths = []
|
|
62
|
+
for i, header in enumerate(header_titles):
|
|
63
|
+
max_width = len(header)
|
|
64
|
+
for row in all_rows_data:
|
|
65
|
+
if i < len(row):
|
|
66
|
+
max_width = max(max_width, len(row[i]))
|
|
67
|
+
col_widths.append(max_width)
|
|
68
|
+
|
|
69
|
+
# Build markdown table
|
|
70
|
+
markdown = ""
|
|
71
|
+
|
|
72
|
+
# Header with padding
|
|
73
|
+
header_parts = [
|
|
74
|
+
title.ljust(width)
|
|
75
|
+
for title, width in zip(header_titles, col_widths)
|
|
76
|
+
]
|
|
77
|
+
markdown += "| " + " | ".join(header_parts) + " |\n"
|
|
78
|
+
|
|
79
|
+
# Separator
|
|
80
|
+
separator_parts = ["-" * width for width in col_widths]
|
|
81
|
+
markdown += "| " + " | ".join(separator_parts) + " |\n"
|
|
82
|
+
|
|
83
|
+
# Data rows
|
|
84
|
+
for row_values in all_rows_data:
|
|
85
|
+
row_parts = [
|
|
86
|
+
value.ljust(width)
|
|
87
|
+
for value, width in zip(row_values, col_widths)
|
|
88
|
+
]
|
|
89
|
+
markdown += "| " + " | ".join(row_parts) + " |\n"
|
|
90
|
+
|
|
91
|
+
return markdown
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from typing import List
|
|
3
|
+
from statistics import mean
|
|
4
|
+
|
|
5
|
+
from investing_algorithm_framework.domain import BacktestEvaluationFocus, \
|
|
6
|
+
BacktestDateRange, Backtest, BacktestMetrics, OperationalException
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def normalize(value, min_val, max_val):
|
|
10
|
+
"""
|
|
11
|
+
Normalize a value to a range [0, 1].
|
|
12
|
+
"""
|
|
13
|
+
if value is None or math.isnan(value) or math.isinf(value):
|
|
14
|
+
return 0
|
|
15
|
+
if min_val == max_val:
|
|
16
|
+
return 0
|
|
17
|
+
return (value - min_val) / (max_val - min_val)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def compute_score(metrics, weights, ranges):
|
|
21
|
+
"""
|
|
22
|
+
Compute a weighted score for the given metrics.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
metrics: The metrics to evaluate.
|
|
26
|
+
weights: The weights to apply to each metric.
|
|
27
|
+
ranges: The min/max ranges for each metric.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
float: The computed score.
|
|
31
|
+
"""
|
|
32
|
+
score = 0
|
|
33
|
+
for key, weight in weights.items():
|
|
34
|
+
if not hasattr(metrics, key):
|
|
35
|
+
continue
|
|
36
|
+
value = getattr(metrics, key)
|
|
37
|
+
# Skip non-numeric values (e.g., Trade objects
|
|
38
|
+
# for best_trade/worst_trade)
|
|
39
|
+
if not isinstance(value, (int, float)):
|
|
40
|
+
continue
|
|
41
|
+
if value is None or math.isnan(value) or math.isinf(value):
|
|
42
|
+
continue
|
|
43
|
+
if key in ranges:
|
|
44
|
+
value = normalize(value, ranges[key][0], ranges[key][1])
|
|
45
|
+
score += weight * value
|
|
46
|
+
return score
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def create_weights(
|
|
50
|
+
focus: BacktestEvaluationFocus | str | None = None,
|
|
51
|
+
custom_weights: dict | None = None,
|
|
52
|
+
) -> dict:
|
|
53
|
+
"""
|
|
54
|
+
Utility to generate weights dicts for ranking backtests.
|
|
55
|
+
|
|
56
|
+
This function does not assign weights to every possible performance
|
|
57
|
+
metric. Instead, it focuses on a curated subset of commonly relevant
|
|
58
|
+
ones (profitability, win rate, trade frequency, and risk-adjusted returns).
|
|
59
|
+
The rationale is to avoid overfitting ranking logic to noisy or redundant
|
|
60
|
+
statistics (e.g., monthly return breakdowns, best/worst trade), while
|
|
61
|
+
keeping the weighting system simple and interpretable.
|
|
62
|
+
Users who need fine-grained control can pass `custom_weights` to fully
|
|
63
|
+
override defaults.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
focus (BacktestEvaluationFocus | str | None): The focus for ranking.
|
|
67
|
+
custom_weights (dict): Full override for weights (all metrics).
|
|
68
|
+
If provided, it takes precedence over presets.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
dict: A dictionary of weights for ranking backtests.
|
|
72
|
+
"""
|
|
73
|
+
if focus is None:
|
|
74
|
+
focus = BacktestEvaluationFocus.BALANCED
|
|
75
|
+
|
|
76
|
+
weights = focus.get_weights()
|
|
77
|
+
|
|
78
|
+
# if full custom dict is given → override everything
|
|
79
|
+
if custom_weights is not None:
|
|
80
|
+
weights = {**weights, **custom_weights}
|
|
81
|
+
|
|
82
|
+
return weights
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def rank_results(
|
|
86
|
+
backtests: List[Backtest],
|
|
87
|
+
focus=None,
|
|
88
|
+
weights=None,
|
|
89
|
+
filter_fn=None,
|
|
90
|
+
backtest_date_range: BacktestDateRange = None
|
|
91
|
+
) -> List[Backtest]:
|
|
92
|
+
"""
|
|
93
|
+
Rank backtest results based on specified focus, weights, and filters.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
backtests (List[Backtest]): List of backtest results to rank.
|
|
97
|
+
focus (str, optional): Focus for ranking. If None,
|
|
98
|
+
uses default weights. Options: "balanced", "profit",
|
|
99
|
+
"frequency", "risk_adjusted".
|
|
100
|
+
weights (dict, optional): Custom weights for ranking metrics.
|
|
101
|
+
If None, uses default weights based on focus.
|
|
102
|
+
filter_fn (callable | dict, optional): A filter to apply to
|
|
103
|
+
backtests before ranking.
|
|
104
|
+
- If callable: receives metrics and should return True/False.
|
|
105
|
+
- If dict: mapping {metric_name: condition_fn},
|
|
106
|
+
all conditions must pass.
|
|
107
|
+
backtest_date_range (BacktestDateRange, optional): If provided,
|
|
108
|
+
only backtests matching this date range are considered.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
List[Backtest]: Sorted list of backtests based on computed scores.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
if weights is None:
|
|
115
|
+
weights = create_weights(focus=focus)
|
|
116
|
+
|
|
117
|
+
# Pair backtests with their metrics
|
|
118
|
+
paired = []
|
|
119
|
+
for backtest in backtests:
|
|
120
|
+
if backtest_date_range is not None:
|
|
121
|
+
metrics = backtest.get_backtest_metrics(backtest_date_range)
|
|
122
|
+
else:
|
|
123
|
+
metrics = backtest.backtest_summary
|
|
124
|
+
|
|
125
|
+
if metrics is not None:
|
|
126
|
+
paired.append((backtest, metrics))
|
|
127
|
+
|
|
128
|
+
# Apply filtering on metrics
|
|
129
|
+
if filter_fn is not None:
|
|
130
|
+
if callable(filter_fn):
|
|
131
|
+
paired = [
|
|
132
|
+
(bt, m) for bt, m in paired if filter_fn(m)
|
|
133
|
+
]
|
|
134
|
+
elif isinstance(filter_fn, dict):
|
|
135
|
+
paired = [
|
|
136
|
+
(bt, m) for bt, m in paired
|
|
137
|
+
if all(
|
|
138
|
+
cond(getattr(m, key, None))
|
|
139
|
+
for key, cond in filter_fn.items()
|
|
140
|
+
)
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
# Compute normalization ranges
|
|
144
|
+
ranges = {}
|
|
145
|
+
for key in weights:
|
|
146
|
+
values = [
|
|
147
|
+
getattr(m, key, None) for _, m in paired
|
|
148
|
+
]
|
|
149
|
+
values = [
|
|
150
|
+
v for v in values
|
|
151
|
+
if isinstance(v, (int, float)) and v is not None
|
|
152
|
+
and not math.isnan(v) and not math.isinf(v)
|
|
153
|
+
]
|
|
154
|
+
if values:
|
|
155
|
+
ranges[key] = (min(values), max(values))
|
|
156
|
+
|
|
157
|
+
# Sort Backtests by score
|
|
158
|
+
ranked = sorted(
|
|
159
|
+
paired,
|
|
160
|
+
key=lambda bm: compute_score(bm[1], weights, ranges),
|
|
161
|
+
reverse=True
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
return [bt for bt, _ in ranked]
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def combine_backtest_metrics(
|
|
168
|
+
backtest_metrics: List[BacktestMetrics]
|
|
169
|
+
) -> BacktestMetrics:
|
|
170
|
+
"""
|
|
171
|
+
Combine backtest metrics from multiple backtests into a single list.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
backtest_metrics (List[BacktestMetrics]): List of backtest
|
|
175
|
+
metrics to combine.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
BacktestMetrics: Combined list of backtest metrics.
|
|
179
|
+
"""
|
|
180
|
+
if not backtest_metrics:
|
|
181
|
+
raise OperationalException("No BacktestMetrics provided")
|
|
182
|
+
|
|
183
|
+
# Helper to take mean safely
|
|
184
|
+
|
|
185
|
+
def safe_mean(values):
|
|
186
|
+
vals = [v for v in values if v is not None]
|
|
187
|
+
return mean(vals) if vals else 0.0
|
|
188
|
+
|
|
189
|
+
# Dates
|
|
190
|
+
|
|
191
|
+
start_date = min(m.backtest_start_date for m in backtest_metrics)
|
|
192
|
+
end_date = max(m.backtest_end_date for m in backtest_metrics)
|
|
193
|
+
|
|
194
|
+
# Aggregate
|
|
195
|
+
return BacktestMetrics(
|
|
196
|
+
backtest_start_date=start_date,
|
|
197
|
+
backtest_end_date=end_date,
|
|
198
|
+
equity_curve=[], # leave empty to avoid misleading curves
|
|
199
|
+
total_growth=safe_mean([m.total_growth for m in backtest_metrics]),
|
|
200
|
+
total_growth_percentage=safe_mean(
|
|
201
|
+
[m.total_growth_percentage for m in backtest_metrics]),
|
|
202
|
+
total_net_gain=safe_mean([m.total_net_gain for m in backtest_metrics]),
|
|
203
|
+
total_net_gain_percentage=safe_mean(
|
|
204
|
+
[m.total_net_gain_percentage for m in backtest_metrics]),
|
|
205
|
+
final_value=safe_mean([m.final_value for m in backtest_metrics]),
|
|
206
|
+
cagr=safe_mean([m.cagr for m in backtest_metrics]),
|
|
207
|
+
sharpe_ratio=safe_mean([m.sharpe_ratio for m in backtest_metrics]),
|
|
208
|
+
rolling_sharpe_ratio=[],
|
|
209
|
+
sortino_ratio=safe_mean([m.sortino_ratio for m in backtest_metrics]),
|
|
210
|
+
calmar_ratio=safe_mean([m.calmar_ratio for m in backtest_metrics]),
|
|
211
|
+
profit_factor=safe_mean([m.profit_factor for m in backtest_metrics]),
|
|
212
|
+
gross_profit=sum(m.gross_profit or 0 for m in backtest_metrics),
|
|
213
|
+
gross_loss=sum(m.gross_loss or 0 for m in backtest_metrics),
|
|
214
|
+
annual_volatility=safe_mean(
|
|
215
|
+
[m.annual_volatility for m in backtest_metrics]),
|
|
216
|
+
monthly_returns=[],
|
|
217
|
+
yearly_returns=[],
|
|
218
|
+
drawdown_series=[],
|
|
219
|
+
max_drawdown=max(m.max_drawdown for m in backtest_metrics),
|
|
220
|
+
max_drawdown_absolute=max(
|
|
221
|
+
m.max_drawdown_absolute for m in backtest_metrics),
|
|
222
|
+
max_daily_drawdown=max(m.max_daily_drawdown for m in backtest_metrics),
|
|
223
|
+
max_drawdown_duration=max(
|
|
224
|
+
m.max_drawdown_duration for m in backtest_metrics),
|
|
225
|
+
trades_per_year=safe_mean(
|
|
226
|
+
[m.trades_per_year for m in backtest_metrics]
|
|
227
|
+
),
|
|
228
|
+
trade_per_day=safe_mean([m.trade_per_day for m in backtest_metrics]),
|
|
229
|
+
exposure_ratio=safe_mean(
|
|
230
|
+
[m.exposure_ratio for m in backtest_metrics]
|
|
231
|
+
),
|
|
232
|
+
average_trade_gain=safe_mean(
|
|
233
|
+
[m.average_trade_gain for m in backtest_metrics]),
|
|
234
|
+
average_trade_gain_percentage=(
|
|
235
|
+
safe_mean(
|
|
236
|
+
[m.average_trade_gain_percentage for m in backtest_metrics]
|
|
237
|
+
)
|
|
238
|
+
),
|
|
239
|
+
average_trade_loss=safe_mean(
|
|
240
|
+
[m.average_trade_loss for m in backtest_metrics]),
|
|
241
|
+
average_trade_loss_percentage=(
|
|
242
|
+
safe_mean(
|
|
243
|
+
[m.average_trade_loss_percentage for m in backtest_metrics]
|
|
244
|
+
)
|
|
245
|
+
),
|
|
246
|
+
median_trade_return=safe_mean(
|
|
247
|
+
[m.median_trade_return for m in backtest_metrics]),
|
|
248
|
+
median_trade_return_percentage=(
|
|
249
|
+
safe_mean(
|
|
250
|
+
[m.median_trade_return_percentage for m in backtest_metrics]
|
|
251
|
+
)
|
|
252
|
+
),
|
|
253
|
+
best_trade=max((
|
|
254
|
+
m.best_trade for m in backtest_metrics if m.best_trade),
|
|
255
|
+
key=lambda t: t.net_gain if t else float('-inf'),
|
|
256
|
+
default=None
|
|
257
|
+
),
|
|
258
|
+
worst_trade=min(
|
|
259
|
+
(m.worst_trade for m in backtest_metrics if m.worst_trade),
|
|
260
|
+
key=lambda t: t.net_gain if t else float('inf'),
|
|
261
|
+
default=None
|
|
262
|
+
),
|
|
263
|
+
average_trade_duration=safe_mean(
|
|
264
|
+
[m.average_trade_duration for m in backtest_metrics]),
|
|
265
|
+
number_of_trades=sum(m.number_of_trades for m in backtest_metrics),
|
|
266
|
+
win_rate=safe_mean([m.win_rate for m in backtest_metrics]),
|
|
267
|
+
win_loss_ratio=safe_mean([m.win_loss_ratio for m in backtest_metrics]),
|
|
268
|
+
percentage_winning_months=safe_mean(
|
|
269
|
+
[m.percentage_winning_months for m in backtest_metrics]),
|
|
270
|
+
percentage_winning_years=safe_mean(
|
|
271
|
+
[m.percentage_winning_years for m in backtest_metrics]),
|
|
272
|
+
average_monthly_return=safe_mean(
|
|
273
|
+
[m.average_monthly_return for m in backtest_metrics]),
|
|
274
|
+
average_monthly_return_losing_months=safe_mean(
|
|
275
|
+
[m.average_monthly_return_losing_months for m in backtest_metrics]
|
|
276
|
+
),
|
|
277
|
+
average_monthly_return_winning_months=safe_mean(
|
|
278
|
+
[m.average_monthly_return_winning_months for m in backtest_metrics]
|
|
279
|
+
),
|
|
280
|
+
best_month=max(
|
|
281
|
+
(m.best_month for m in backtest_metrics if m.best_month),
|
|
282
|
+
key=lambda x: x[0] if x else float('-inf'),
|
|
283
|
+
default=None
|
|
284
|
+
),
|
|
285
|
+
best_year=max((m.best_year for m in backtest_metrics if m.best_year),
|
|
286
|
+
key=lambda x: x[0] if x else float('-inf'),
|
|
287
|
+
default=None),
|
|
288
|
+
worst_month=min(
|
|
289
|
+
(m.worst_month for m in backtest_metrics if m.worst_month),
|
|
290
|
+
key=lambda x: x[0] if x else float('inf'),
|
|
291
|
+
default=None
|
|
292
|
+
),
|
|
293
|
+
worst_year=min(
|
|
294
|
+
(m.worst_year for m in backtest_metrics if m.worst_year),
|
|
295
|
+
key=lambda x: x[0] if x else float('inf'),
|
|
296
|
+
default=None
|
|
297
|
+
),
|
|
298
|
+
)
|
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
from investing_algorithm_framework.app.app import App
|
|
2
|
-
from investing_algorithm_framework.app.web import create_flask_app
|
|
3
|
-
from investing_algorithm_framework.app.strategy import TradingStrategy
|
|
1
|
+
from investing_algorithm_framework.app.app import App, AppHook
|
|
4
2
|
from investing_algorithm_framework.app.stateless import StatelessAction
|
|
3
|
+
from investing_algorithm_framework.app.strategy import TradingStrategy
|
|
5
4
|
from investing_algorithm_framework.app.task import Task
|
|
5
|
+
from investing_algorithm_framework.app.web import create_flask_app
|
|
6
6
|
from .algorithm import Algorithm
|
|
7
|
+
from .context import Context
|
|
8
|
+
from .reporting import add_html_report, \
|
|
9
|
+
BacktestReport, pretty_print_backtest, pretty_print_trades, \
|
|
10
|
+
pretty_print_positions, pretty_print_orders, \
|
|
11
|
+
get_equity_curve_with_drawdown_chart, \
|
|
12
|
+
get_rolling_sharpe_ratio_chart, \
|
|
13
|
+
get_monthly_returns_heatmap_chart, \
|
|
14
|
+
get_yearly_returns_bar_chart, get_equity_curve_chart, \
|
|
15
|
+
get_ohlcv_data_completeness_chart, get_entry_and_exit_signals
|
|
16
|
+
|
|
7
17
|
|
|
8
18
|
__all__ = [
|
|
9
19
|
"Algorithm",
|
|
@@ -11,5 +21,20 @@ __all__ = [
|
|
|
11
21
|
"create_flask_app",
|
|
12
22
|
"TradingStrategy",
|
|
13
23
|
"StatelessAction",
|
|
14
|
-
"Task"
|
|
24
|
+
"Task",
|
|
25
|
+
"AppHook",
|
|
26
|
+
"Context",
|
|
27
|
+
"add_html_report",
|
|
28
|
+
"BacktestReport",
|
|
29
|
+
"pretty_print_backtest",
|
|
30
|
+
"pretty_print_trades",
|
|
31
|
+
"pretty_print_positions",
|
|
32
|
+
"pretty_print_orders",
|
|
33
|
+
"get_equity_curve_with_drawdown_chart",
|
|
34
|
+
"get_rolling_sharpe_ratio_chart",
|
|
35
|
+
"get_monthly_returns_heatmap_chart",
|
|
36
|
+
"get_yearly_returns_bar_chart",
|
|
37
|
+
"get_ohlcv_data_completeness_chart",
|
|
38
|
+
"get_entry_and_exit_signals",
|
|
39
|
+
"get_equity_curve_chart",
|
|
15
40
|
]
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import logging
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from investing_algorithm_framework.app.app_hook import AppHook
|
|
6
|
+
from investing_algorithm_framework.app.strategy import TradingStrategy
|
|
7
|
+
from investing_algorithm_framework.domain import OperationalException, \
|
|
8
|
+
DataSource
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("investing_algorithm_framework")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Algorithm:
|
|
14
|
+
"""
|
|
15
|
+
Class to represent an algorithm. An algorithm is a collection of
|
|
16
|
+
strategies that are executed in a specific order.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
algorithm_id: The unique identifier of the algorithm. This id
|
|
20
|
+
should be a string and also will be used for all the
|
|
21
|
+
registered strategies within the algorithm.
|
|
22
|
+
_description: The description of the algorithm. It should be a string.
|
|
23
|
+
_strategies: A list of strategies that are part of the algorithm.
|
|
24
|
+
_tasks: A list of tasks that are part of the algorithm.
|
|
25
|
+
_data_sources: A list of data sources that are part of the algorithm.
|
|
26
|
+
_on_strategy_run_hooks: A list of hooks that will be called when a
|
|
27
|
+
"""
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
algorithm_id: str = None,
|
|
31
|
+
description: str = None,
|
|
32
|
+
strategy=None,
|
|
33
|
+
strategies=None,
|
|
34
|
+
tasks: List = None,
|
|
35
|
+
data_sources: List[DataSource] = None,
|
|
36
|
+
on_strategy_run_hooks=None,
|
|
37
|
+
metadata=None
|
|
38
|
+
):
|
|
39
|
+
self.algorithm_id = algorithm_id
|
|
40
|
+
self._context = {}
|
|
41
|
+
self._description = None
|
|
42
|
+
|
|
43
|
+
if description is not None:
|
|
44
|
+
self._description = description
|
|
45
|
+
|
|
46
|
+
self._strategies = []
|
|
47
|
+
self._tasks = []
|
|
48
|
+
self._data_sources = []
|
|
49
|
+
self._on_strategy_run_hooks = []
|
|
50
|
+
self.metadata = metadata
|
|
51
|
+
|
|
52
|
+
if data_sources is not None:
|
|
53
|
+
self._data_sources = data_sources
|
|
54
|
+
|
|
55
|
+
if strategies is not None:
|
|
56
|
+
self.strategies = strategies
|
|
57
|
+
|
|
58
|
+
if tasks is not None:
|
|
59
|
+
self.tasks = tasks
|
|
60
|
+
|
|
61
|
+
if strategy is not None:
|
|
62
|
+
self.add_strategy(strategy, throw_exception=True)
|
|
63
|
+
|
|
64
|
+
if on_strategy_run_hooks is not None:
|
|
65
|
+
for hook in on_strategy_run_hooks:
|
|
66
|
+
self.add_on_strategy_run_hook(hook)
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def data_sources(self):
|
|
70
|
+
return self._data_sources
|
|
71
|
+
|
|
72
|
+
@data_sources.setter
|
|
73
|
+
def data_sources(self, data_sources):
|
|
74
|
+
self._data_sources = data_sources
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def description(self):
|
|
78
|
+
"""
|
|
79
|
+
Function to get the description of the algorithm
|
|
80
|
+
"""
|
|
81
|
+
return self._description
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def strategies(self):
|
|
85
|
+
return self._strategies
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def on_strategy_run_hooks(self):
|
|
89
|
+
return self._on_strategy_run_hooks
|
|
90
|
+
|
|
91
|
+
@strategies.setter
|
|
92
|
+
def strategies(self, strategies):
|
|
93
|
+
|
|
94
|
+
for strategy in strategies:
|
|
95
|
+
self.add_strategy(strategy)
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def tasks(self):
|
|
99
|
+
return self._tasks
|
|
100
|
+
|
|
101
|
+
@tasks.setter
|
|
102
|
+
def tasks(self, tasks):
|
|
103
|
+
|
|
104
|
+
for task in tasks:
|
|
105
|
+
self.add_task(task)
|
|
106
|
+
|
|
107
|
+
def get_strategy(self, strategy_id):
|
|
108
|
+
for strategy in self._strategies:
|
|
109
|
+
if strategy.worker_id == strategy_id:
|
|
110
|
+
return strategy
|
|
111
|
+
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
def get_strategies(self):
|
|
115
|
+
|
|
116
|
+
if self._strategies is None:
|
|
117
|
+
raise OperationalException(
|
|
118
|
+
"No strategies have been added to the algorithm"
|
|
119
|
+
)
|
|
120
|
+
return self._strategies
|
|
121
|
+
|
|
122
|
+
def add_strategy(self, strategy, throw_exception=True) -> None:
|
|
123
|
+
"""
|
|
124
|
+
Function to add a strategy to the algorithm. The strategy should be an
|
|
125
|
+
instance of TradingStrategy or a subclass based on the TradingStrategy
|
|
126
|
+
class.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
strategy: Instance of TradingStrategy
|
|
130
|
+
throw_exception: Flag to allow for throwing an exception when
|
|
131
|
+
the provided strategy is not inline with what the application
|
|
132
|
+
expects.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
None
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
if inspect.isclass(strategy):
|
|
139
|
+
|
|
140
|
+
if not issubclass(strategy, TradingStrategy):
|
|
141
|
+
raise OperationalException(
|
|
142
|
+
"The strategy must be a subclass of TradingStrategy"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
strategy = strategy()
|
|
146
|
+
|
|
147
|
+
if not isinstance(strategy, TradingStrategy):
|
|
148
|
+
|
|
149
|
+
if throw_exception:
|
|
150
|
+
raise OperationalException(
|
|
151
|
+
"Strategy should be an instance of TradingStrategy"
|
|
152
|
+
)
|
|
153
|
+
else:
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
strategy_ids = []
|
|
157
|
+
|
|
158
|
+
for s in self._strategies:
|
|
159
|
+
strategy_ids.append(s.strategy_id)
|
|
160
|
+
|
|
161
|
+
# Check for duplicate strategy IDs
|
|
162
|
+
if strategy.strategy_id in strategy_ids:
|
|
163
|
+
raise OperationalException(
|
|
164
|
+
"Can't add strategy, there already exists a strategy "
|
|
165
|
+
"with the same id in the algorithm"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
strategy.algorithm_id = self.algorithm_id
|
|
169
|
+
self._strategies.append(strategy)
|
|
170
|
+
|
|
171
|
+
def add_task(self, task):
|
|
172
|
+
if inspect.isclass(task):
|
|
173
|
+
task = task()
|
|
174
|
+
|
|
175
|
+
self._tasks.append(task)
|
|
176
|
+
|
|
177
|
+
def add_on_strategy_run_hook(self, app_hook):
|
|
178
|
+
"""
|
|
179
|
+
Function to add a hook that will be called when a strategy is run.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
app_hook: The hook function to be added.
|
|
183
|
+
"""
|
|
184
|
+
# Check if the app_hook inherits from AppHook
|
|
185
|
+
if inspect.isclass(app_hook) and not issubclass(app_hook, AppHook):
|
|
186
|
+
raise OperationalException(
|
|
187
|
+
"App hook should be an instance of AppHook"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if inspect.isclass(app_hook):
|
|
191
|
+
app_hook = app_hook()
|
|
192
|
+
|
|
193
|
+
self._on_strategy_run_hooks.append(app_hook)
|