investing-algorithm-framework 1.3.1__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.
Files changed (282) hide show
  1. investing_algorithm_framework/__init__.py +195 -16
  2. investing_algorithm_framework/analysis/__init__.py +16 -0
  3. investing_algorithm_framework/analysis/backtest_data_ranges.py +202 -0
  4. investing_algorithm_framework/analysis/data.py +170 -0
  5. investing_algorithm_framework/analysis/markdown.py +91 -0
  6. investing_algorithm_framework/analysis/ranking.py +298 -0
  7. investing_algorithm_framework/app/__init__.py +31 -4
  8. investing_algorithm_framework/app/algorithm/__init__.py +7 -0
  9. investing_algorithm_framework/app/algorithm/algorithm.py +193 -0
  10. investing_algorithm_framework/app/algorithm/algorithm_factory.py +118 -0
  11. investing_algorithm_framework/app/app.py +2233 -264
  12. investing_algorithm_framework/app/app_hook.py +28 -0
  13. investing_algorithm_framework/app/context.py +1724 -0
  14. investing_algorithm_framework/app/eventloop.py +620 -0
  15. investing_algorithm_framework/app/reporting/__init__.py +27 -0
  16. investing_algorithm_framework/app/reporting/ascii.py +921 -0
  17. investing_algorithm_framework/app/reporting/backtest_report.py +349 -0
  18. investing_algorithm_framework/app/reporting/charts/__init__.py +19 -0
  19. investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
  20. investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
  21. investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +74 -0
  22. investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
  23. investing_algorithm_framework/app/reporting/charts/monthly_returns_heatmap.py +70 -0
  24. investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
  25. investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +79 -0
  26. investing_algorithm_framework/app/reporting/charts/yearly_returns_barchart.py +55 -0
  27. investing_algorithm_framework/app/reporting/generate.py +185 -0
  28. investing_algorithm_framework/app/reporting/tables/__init__.py +11 -0
  29. investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +217 -0
  30. investing_algorithm_framework/app/reporting/tables/stop_loss_table.py +0 -0
  31. investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
  32. investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
  33. investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
  34. investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
  35. investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
  36. investing_algorithm_framework/app/stateless/action_handlers/__init__.py +6 -3
  37. investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
  38. investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +2 -1
  39. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
  40. investing_algorithm_framework/app/stateless/exception_handler.py +1 -1
  41. investing_algorithm_framework/app/strategy.py +873 -52
  42. investing_algorithm_framework/app/task.py +5 -3
  43. investing_algorithm_framework/app/web/__init__.py +2 -1
  44. investing_algorithm_framework/app/web/controllers/__init__.py +2 -2
  45. investing_algorithm_framework/app/web/controllers/orders.py +4 -3
  46. investing_algorithm_framework/app/web/controllers/portfolio.py +1 -1
  47. investing_algorithm_framework/app/web/controllers/positions.py +3 -3
  48. investing_algorithm_framework/app/web/create_app.py +4 -2
  49. investing_algorithm_framework/app/web/error_handler.py +1 -1
  50. investing_algorithm_framework/app/web/schemas/order.py +2 -2
  51. investing_algorithm_framework/app/web/schemas/position.py +1 -0
  52. investing_algorithm_framework/cli/__init__.py +0 -0
  53. investing_algorithm_framework/cli/cli.py +231 -0
  54. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
  55. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  56. investing_algorithm_framework/cli/initialize_app.py +603 -0
  57. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  58. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  59. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  60. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  61. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  62. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  63. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  64. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  65. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  66. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  67. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  68. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  69. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  70. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  71. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  72. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  73. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  74. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  75. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  76. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  77. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  78. investing_algorithm_framework/cli/validate_backtest_checkpoints.py +197 -0
  79. investing_algorithm_framework/create_app.py +43 -9
  80. investing_algorithm_framework/dependency_container.py +121 -33
  81. investing_algorithm_framework/domain/__init__.py +109 -22
  82. investing_algorithm_framework/domain/algorithm_id.py +69 -0
  83. investing_algorithm_framework/domain/backtesting/__init__.py +25 -0
  84. investing_algorithm_framework/domain/backtesting/backtest.py +548 -0
  85. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +113 -0
  86. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +241 -0
  87. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +470 -0
  88. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  89. investing_algorithm_framework/domain/backtesting/backtest_run.py +663 -0
  90. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  91. investing_algorithm_framework/domain/backtesting/backtest_utils.py +198 -0
  92. investing_algorithm_framework/domain/backtesting/combine_backtests.py +392 -0
  93. investing_algorithm_framework/domain/config.py +60 -138
  94. investing_algorithm_framework/domain/constants.py +23 -34
  95. investing_algorithm_framework/domain/data_provider.py +334 -0
  96. investing_algorithm_framework/domain/data_structures.py +42 -0
  97. investing_algorithm_framework/domain/decimal_parsing.py +40 -0
  98. investing_algorithm_framework/domain/exceptions.py +51 -1
  99. investing_algorithm_framework/domain/models/__init__.py +29 -14
  100. investing_algorithm_framework/domain/models/app_mode.py +34 -0
  101. investing_algorithm_framework/domain/models/base_model.py +3 -1
  102. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  103. investing_algorithm_framework/domain/models/data/data_source.py +222 -0
  104. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  105. investing_algorithm_framework/domain/models/event.py +35 -0
  106. investing_algorithm_framework/domain/models/market/__init__.py +5 -0
  107. investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
  108. investing_algorithm_framework/domain/models/order/__init__.py +3 -4
  109. investing_algorithm_framework/domain/models/order/order.py +243 -86
  110. investing_algorithm_framework/domain/models/order/order_status.py +2 -2
  111. investing_algorithm_framework/domain/models/order/order_type.py +1 -3
  112. investing_algorithm_framework/domain/models/portfolio/__init__.py +7 -2
  113. investing_algorithm_framework/domain/models/portfolio/portfolio.py +134 -1
  114. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +37 -37
  115. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
  116. investing_algorithm_framework/domain/models/position/__init__.py +3 -2
  117. investing_algorithm_framework/domain/models/position/position.py +29 -0
  118. investing_algorithm_framework/domain/models/position/position_size.py +41 -0
  119. investing_algorithm_framework/domain/models/position/{position_cost.py → position_snapshot.py} +16 -8
  120. investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
  121. investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
  122. investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
  123. investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
  124. investing_algorithm_framework/domain/models/strategy_profile.py +33 -0
  125. investing_algorithm_framework/domain/models/time_frame.py +94 -98
  126. investing_algorithm_framework/domain/models/time_interval.py +33 -0
  127. investing_algorithm_framework/domain/models/time_unit.py +111 -2
  128. investing_algorithm_framework/domain/models/tracing/__init__.py +0 -0
  129. investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
  130. investing_algorithm_framework/domain/models/trade/__init__.py +11 -0
  131. investing_algorithm_framework/domain/models/trade/trade.py +389 -0
  132. investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
  133. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
  134. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
  135. investing_algorithm_framework/domain/order_executor.py +112 -0
  136. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  137. investing_algorithm_framework/domain/services/__init__.py +11 -0
  138. investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
  139. investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
  140. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
  141. investing_algorithm_framework/domain/services/rounding_service.py +27 -0
  142. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  143. investing_algorithm_framework/domain/strategy.py +1 -29
  144. investing_algorithm_framework/domain/utils/__init__.py +16 -4
  145. investing_algorithm_framework/domain/utils/csv.py +22 -0
  146. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  147. investing_algorithm_framework/domain/utils/dates.py +57 -0
  148. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  149. investing_algorithm_framework/domain/utils/polars.py +53 -0
  150. investing_algorithm_framework/domain/utils/random.py +29 -0
  151. investing_algorithm_framework/download_data.py +244 -0
  152. investing_algorithm_framework/infrastructure/__init__.py +39 -11
  153. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  154. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1152 -0
  155. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  156. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  157. investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
  158. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +87 -13
  159. investing_algorithm_framework/infrastructure/models/__init__.py +13 -4
  160. investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
  161. investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -2
  162. investing_algorithm_framework/infrastructure/models/order/order.py +73 -73
  163. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  164. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  165. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +3 -2
  166. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
  167. investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +57 -3
  168. investing_algorithm_framework/infrastructure/models/position/__init__.py +2 -2
  169. investing_algorithm_framework/infrastructure/models/position/position.py +16 -11
  170. investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +23 -0
  171. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  172. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  173. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
  174. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
  175. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  176. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  177. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  178. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  179. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  180. investing_algorithm_framework/infrastructure/repositories/__init__.py +13 -5
  181. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  182. investing_algorithm_framework/infrastructure/repositories/order_repository.py +32 -19
  183. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +2 -2
  184. investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
  185. investing_algorithm_framework/infrastructure/repositories/position_repository.py +47 -4
  186. investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
  187. investing_algorithm_framework/infrastructure/repositories/repository.py +85 -31
  188. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  189. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
  190. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
  191. investing_algorithm_framework/infrastructure/services/__init__.py +9 -2
  192. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  193. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +193 -0
  194. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  195. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  196. investing_algorithm_framework/infrastructure/services/backtesting/__init__.py +9 -0
  197. investing_algorithm_framework/infrastructure/services/backtesting/backtest_service.py +2596 -0
  198. investing_algorithm_framework/infrastructure/services/backtesting/event_backtest_service.py +285 -0
  199. investing_algorithm_framework/infrastructure/services/backtesting/vector_backtest_service.py +468 -0
  200. investing_algorithm_framework/services/__init__.py +127 -10
  201. investing_algorithm_framework/services/configuration_service.py +95 -0
  202. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  203. investing_algorithm_framework/services/data_providers/data_provider_service.py +1058 -0
  204. investing_algorithm_framework/services/market_credential_service.py +40 -0
  205. investing_algorithm_framework/services/metrics/__init__.py +119 -0
  206. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  207. investing_algorithm_framework/services/metrics/beta.py +0 -0
  208. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  209. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  210. investing_algorithm_framework/services/metrics/drawdown.py +218 -0
  211. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  212. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  213. investing_algorithm_framework/services/metrics/generate.py +358 -0
  214. investing_algorithm_framework/services/metrics/mean_daily_return.py +84 -0
  215. investing_algorithm_framework/services/metrics/price_efficiency.py +57 -0
  216. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  217. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  218. investing_algorithm_framework/services/metrics/returns.py +452 -0
  219. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  220. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  221. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  222. investing_algorithm_framework/services/metrics/standard_deviation.py +156 -0
  223. investing_algorithm_framework/services/metrics/trades.py +473 -0
  224. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  225. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  226. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  227. investing_algorithm_framework/services/metrics/volatility.py +118 -0
  228. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  229. investing_algorithm_framework/services/order_service/__init__.py +9 -0
  230. investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
  231. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  232. investing_algorithm_framework/services/order_service/order_service.py +826 -0
  233. investing_algorithm_framework/services/portfolios/__init__.py +16 -0
  234. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
  235. investing_algorithm_framework/services/{portfolio_configuration_service.py → portfolios/portfolio_configuration_service.py} +27 -12
  236. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  237. investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
  238. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
  239. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
  240. investing_algorithm_framework/services/positions/__init__.py +7 -0
  241. investing_algorithm_framework/services/positions/position_service.py +210 -0
  242. investing_algorithm_framework/services/positions/position_snapshot_service.py +18 -0
  243. investing_algorithm_framework/services/repository_service.py +8 -2
  244. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  245. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +117 -0
  246. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
  247. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
  248. investing_algorithm_framework/services/trade_service/__init__.py +9 -0
  249. investing_algorithm_framework/services/trade_service/trade_service.py +1099 -0
  250. investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
  251. investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
  252. investing_algorithm_framework-7.25.6.dist-info/METADATA +535 -0
  253. investing_algorithm_framework-7.25.6.dist-info/RECORD +268 -0
  254. {investing_algorithm_framework-1.3.1.dist-info → investing_algorithm_framework-7.25.6.dist-info}/WHEEL +1 -2
  255. investing_algorithm_framework-7.25.6.dist-info/entry_points.txt +3 -0
  256. investing_algorithm_framework/app/algorithm.py +0 -410
  257. investing_algorithm_framework/domain/models/market_data/__init__.py +0 -11
  258. investing_algorithm_framework/domain/models/market_data/asset_price.py +0 -50
  259. investing_algorithm_framework/domain/models/market_data/ohlcv.py +0 -76
  260. investing_algorithm_framework/domain/models/market_data/order_book.py +0 -63
  261. investing_algorithm_framework/domain/models/market_data/ticker.py +0 -92
  262. investing_algorithm_framework/domain/models/order/order_fee.py +0 -45
  263. investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
  264. investing_algorithm_framework/domain/models/trading_time_frame.py +0 -205
  265. investing_algorithm_framework/domain/singleton.py +0 -9
  266. investing_algorithm_framework/infrastructure/models/order/order_fee.py +0 -21
  267. investing_algorithm_framework/infrastructure/models/position/position_cost.py +0 -32
  268. investing_algorithm_framework/infrastructure/repositories/order_fee_repository.py +0 -15
  269. investing_algorithm_framework/infrastructure/repositories/position_cost_repository.py +0 -16
  270. investing_algorithm_framework/infrastructure/services/market_service.py +0 -422
  271. investing_algorithm_framework/services/market_data_service.py +0 -75
  272. investing_algorithm_framework/services/order_service.py +0 -464
  273. investing_algorithm_framework/services/portfolio_service.py +0 -105
  274. investing_algorithm_framework/services/position_cost_service.py +0 -5
  275. investing_algorithm_framework/services/position_service.py +0 -50
  276. investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -219
  277. investing_algorithm_framework/setup_logging.py +0 -40
  278. investing_algorithm_framework-1.3.1.dist-info/AUTHORS.md +0 -8
  279. investing_algorithm_framework-1.3.1.dist-info/METADATA +0 -172
  280. investing_algorithm_framework-1.3.1.dist-info/RECORD +0 -103
  281. investing_algorithm_framework-1.3.1.dist-info/top_level.txt +0 -1
  282. {investing_algorithm_framework-1.3.1.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,13 +1,40 @@
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
+ 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
+
6
17
 
7
18
  __all__ = [
19
+ "Algorithm",
8
20
  "App",
9
21
  "create_flask_app",
10
22
  "TradingStrategy",
11
23
  "StatelessAction",
12
- "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",
13
40
  ]
@@ -0,0 +1,7 @@
1
+ from .algorithm_factory import AlgorithmFactory
2
+ from .algorithm import Algorithm
3
+
4
+ __all__ = [
5
+ "AlgorithmFactory",
6
+ "Algorithm"
7
+ ]
@@ -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)