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,470 @@
1
+ import os
2
+ from pathlib import Path
3
+ from dataclasses import dataclass, field
4
+ from logging import getLogger
5
+ from typing import Tuple, List, Dict
6
+ from datetime import datetime, date
7
+ import json
8
+ import pandas as pd
9
+
10
+ from investing_algorithm_framework.domain.models import Trade
11
+
12
+
13
+ logger = getLogger(__name__)
14
+
15
+
16
+ @dataclass
17
+ class BacktestMetrics:
18
+ """
19
+ Represents the result of a backtest, including metrics such as
20
+ total return, annualized return, volatility, Sharpe ratio,
21
+ and maximum drawdown.
22
+
23
+ Attributes:
24
+ backtest_date_range_name (str): The name of the date range
25
+ used for the backtest.
26
+ backtest_start_date (datetime): The start date of the backtest.
27
+ backtest_end_date (datetime): The end date of the backtest.
28
+ trading_symbol (str): The trading symbol used in the backtest.
29
+ initial_unallocated (float): The initial unallocated cash
30
+ at the start of the backtest.
31
+ final_value (float): The final value of the portfolio at the end
32
+ of the backtest.
33
+ equity_curve (List[Tuple[datetime, float]]): A list of
34
+ tuples representing the equity curve, where each tuple
35
+ contains a date and the corresponding portfolio value.
36
+ total_growth (float): The growth of the portfolio over the
37
+ backtest period.
38
+ total_growth_percentage (float): The percentage growth of the portfolio
39
+ over the backtest period.
40
+ total_net_gain (float): The total return of the backtest.
41
+ total_net_gain_percentage (float): The total return percentage
42
+ total_loss (float): The total loss of the backtest.
43
+ total_loss_percentage (float): The total loss percentage
44
+ cagr (float): The compound annual growth rate of the backtest.
45
+ sharpe_ratio (float): The Sharpe ratio of the backtest, indicating
46
+ risk-adjusted return.
47
+ rolling_sharpe_ratio (List[Tuple[datetime, float]): A list of rolling
48
+ Sharpe ratios over the backtest period.
49
+ sortino_ratio (float): The Sortino ratio of the backtest, focusing
50
+ on downside risk.
51
+ calmar_ratio (float): The Calmar ratio of the backtest, comparing
52
+ CAGR to maximum drawdown.
53
+ profit_factor (float): The profit factor of the backtest, calculated
54
+ as total profit divided by total loss.
55
+ annual_volatility (float): The annualized volatility of the
56
+ portfolio returns.
57
+ monthly_returns (List[Tuple[datetime, float]]): A list of monthly
58
+ returns during the backtest.
59
+ yearly_returns (List[Tuple[datetime, float]]): A list of yearly returns
60
+ during the backtest.
61
+ drawdown_series (List[Tuple[datetime, float]]): A list of drawdown
62
+ values over the backtest period.
63
+ max_drawdown (float): The maximum drawdown observed during
64
+ the backtest.
65
+ max_drawdown_absolute (float): The maximum absolute drawdown
66
+ observed during the backtest.
67
+ max_daily_drawdown (float): The maximum daily drawdown
68
+ observed during the backtest.
69
+ max_drawdown_duration (int): The duration of the maximum
70
+ drawdown in days.
71
+ trades_per_year (float): The average number of trades
72
+ executed per year.
73
+ trade_per_day (float): The average number of trades executed per day.
74
+ exposure_ratio (float): The exposure ratio, indicating the
75
+ average exposure of the portfolio.
76
+ cumulative_exposure (float): The cumulative exposure, indicating the
77
+ total exposure of the portfolio over the backtest period.
78
+ average_trade_size (float): The average size of trades executed
79
+ during the backtest.
80
+ average_trade_loss (float): The average loss from losing trades.
81
+ average_trade_loss_percentage (float): The average loss percentage
82
+ from losing trades.
83
+ average_trade_gain (float): The average gain from winning trades.
84
+ average_trade_gain_percentage (float): The average gain percentage
85
+ from winning trades.
86
+ average_trade_return (float): The average return from all trades.
87
+ average_trade_return_percentage (float): The average return percentage
88
+ from all trades.
89
+ median_trade_return (float): The median return from all trades.
90
+ median_trade_return_percentage (float): The median return percentage
91
+ from all trades.
92
+ number_of_positive_trades (int): The total number of profitable trades
93
+ executed during the backtest.
94
+ number_of_negative_trades (int): The total number of unprofitable
95
+ trades executed during the backtest.
96
+ best_trade (float): A string representation of the best trade,
97
+ including net gain and percentage.
98
+ worst_trade (float): A string representation of the worst trade,
99
+ including net loss and percentage.
100
+ average_trade_duration (float): The average duration of
101
+ trades in hours.
102
+ number_of_trades (int): The total number of trades executed
103
+ during the backtest.
104
+ number_of_trades_closed (int): The total number of trades close
105
+ during the backtest.
106
+ number_of_trades_opened (int): The total number of trades opened
107
+ during the backtest.
108
+ number_of_trades_open_at_end (int): The number of trades
109
+ still open at the end of the backtest.
110
+ win_rate (float): The win rate of the trades, expressed
111
+ as a percentage.
112
+ current_win_rate (float): The current win rate of the trades,
113
+ including open trades.
114
+ win_loss_ratio (float): The ratio of winning trades
115
+ to losing trades.
116
+ current_win_loss_ratio (float): The current ratio of winning
117
+ trades to losing trades, including open trades.
118
+ percentage_winning_months (float): The percentage of months
119
+ with positive returns.
120
+ percentage_winning_years (float): The percentage of years with
121
+ positive returns.
122
+ percentage_positive_trades (float): The percentage of trades that
123
+ were profitable.
124
+ percentage_negative_trades (float): The percentage of trades that
125
+ were unprofitable.
126
+ average_monthly_return (float): The average monthly return
127
+ of the portfolio.
128
+ average_monthly_return_losing_months (float): The average monthly
129
+ return during losing months.
130
+ average_monthly_return_winning_months (float): The average monthly
131
+ return during winning months.
132
+ best_month (datetime): A string representation of the best month,
133
+ including return and date.
134
+ best_year (datetime): A string representation of the best year,
135
+ including return and date.
136
+ worst_month (datetime): A string representation of the worst month,
137
+ including return and date.
138
+ worst_year (datetime): A string representation of the worst year,
139
+ including return and date.
140
+ metadata (Dict[str, str]): A dictionary to store any additional
141
+ metadata related to the backtest.
142
+ """
143
+ backtest_start_date: datetime
144
+ backtest_end_date: datetime
145
+ backtest_date_range_name: str = ""
146
+ trading_symbol: str = ""
147
+ initial_unallocated: float = 0.0
148
+ equity_curve: List[Tuple[float, datetime]] = field(default_factory=list)
149
+ total_growth: float = 0.0
150
+ total_growth_percentage: float = 0.0
151
+ total_net_gain: float = 0.0
152
+ total_net_gain_percentage: float = 0.0
153
+ total_loss: float = 0.0
154
+ total_loss_percentage: float = 0.0
155
+ final_value: float = 0.0
156
+ cumulative_return: float = 0.0
157
+ cumulative_return_series: List[Tuple[float, datetime]] = \
158
+ field(default_factory=list)
159
+ cagr: float = 0.0
160
+ sharpe_ratio: float = 0.0
161
+ rolling_sharpe_ratio: List[Tuple[float, datetime]] = \
162
+ field(default_factory=list)
163
+ sortino_ratio: float = 0.0
164
+ calmar_ratio: float = 0.0
165
+ profit_factor: float = 0.0
166
+ gross_profit: float = None
167
+ gross_loss: float = None
168
+ annual_volatility: float = 0.0
169
+ monthly_returns: List[Tuple[float, datetime]] = field(default_factory=list)
170
+ yearly_returns: List[Tuple[float, date]] = field(default_factory=list)
171
+ drawdown_series: List[Tuple[float, datetime]] = field(default_factory=list)
172
+ max_drawdown: float = 0.0
173
+ max_drawdown_absolute: float = 0.0
174
+ max_daily_drawdown: float = 0.0
175
+ max_drawdown_duration: int = 0
176
+ trades_per_year: float = 0.0
177
+ trade_per_day: float = 0.0
178
+ exposure_ratio: float = 0.0
179
+ cumulative_exposure: float = 0.0
180
+ best_trade: Trade = None
181
+ worst_trade: Trade = None
182
+ number_of_positive_trades: int = 0
183
+ percentage_positive_trades: float = 0.0
184
+ number_of_negative_trades: int = 0
185
+ percentage_negative_trades: float = 0.0
186
+ average_trade_duration: float = 0.0
187
+ average_trade_size: float = 0.0
188
+ average_trade_loss: float = 0.0
189
+ average_trade_loss_percentage: float = 0.0
190
+ average_trade_gain: float = 0.0
191
+ average_trade_gain_percentage: float = 0.0
192
+ average_trade_return: float = 0.0
193
+ average_trade_return_percentage: float = 0.0
194
+ current_average_trade_gain: float = 0.0
195
+ current_average_trade_gain_percentage: float = 0.0
196
+ current_average_trade_return: float = 0.0
197
+ current_average_trade_return_percentage: float = 0.0
198
+ current_average_trade_duration: float = 0.0
199
+ current_average_trade_loss: float = 0.0
200
+ current_average_trade_loss_percentage: float = 0.0
201
+ median_trade_return: float = 0.0
202
+ median_trade_return_percentage: float = 0.0
203
+ number_of_trades: int = 0
204
+ number_of_trades_closed: int = 0
205
+ number_of_trades_opened: int = 0
206
+ number_of_trades_open_at_end: int = 0
207
+ win_rate: float = 0.0
208
+ current_win_rate: float = 0.0
209
+ win_loss_ratio: float = 0.0
210
+ current_win_loss_ratio: float = 0.0
211
+ percentage_winning_months: float = 0.0
212
+ percentage_winning_years: float = 0.0
213
+ average_monthly_return: float = 0.0
214
+ average_monthly_return_losing_months: float = 0.0
215
+ average_monthly_return_winning_months: float = 0.0
216
+ best_month: Tuple[float, datetime] = None
217
+ best_year: Tuple[float, date] = None
218
+ worst_month: Tuple[float, datetime] = None
219
+ worst_year: Tuple[float, date] = None
220
+ total_number_of_days: int = None
221
+ metadata: Dict[str, str] = field(default_factory=dict)
222
+
223
+ def __post_init__(self):
224
+ self.total_number_of_days = (self.backtest_end_date -
225
+ self.backtest_start_date).days
226
+
227
+ def to_dict(self) -> dict:
228
+ """
229
+ Convert the BacktestMetrics instance to a dictionary.
230
+ Ensures all datetime values are serialized to ISO format, but
231
+ leaves strings unchanged.
232
+
233
+ Returns:
234
+ dict: A dictionary representation of the BacktestMetrics instance.
235
+ """
236
+
237
+ def ensure_iso(value):
238
+ return value.isoformat() \
239
+ if hasattr(value, "isoformat") else value
240
+
241
+ return {
242
+ "backtest_start_date": ensure_iso(self.backtest_start_date),
243
+ "backtest_end_date": ensure_iso(self.backtest_end_date),
244
+ "backtest_date_range_name": self.backtest_date_range_name,
245
+ "trading_symbol": self.trading_symbol,
246
+ "initial_unallocated": self.initial_unallocated,
247
+ "equity_curve": [(value, ensure_iso(date))
248
+ for value, date in self.equity_curve],
249
+ "final_value": self.final_value,
250
+ "total_net_gain": self.total_net_gain,
251
+ "total_net_gain_percentage": self.total_net_gain_percentage,
252
+ "total_growth": self.total_growth,
253
+ "total_growth_percentage": self.total_growth_percentage,
254
+ "total_loss": self.total_loss,
255
+ "total_loss_percentage": self.total_loss_percentage,
256
+ "cumulative_return": self.cumulative_return,
257
+ "cumulative_return_series": [(value, ensure_iso(date))
258
+ for value, date in
259
+ self.cumulative_return_series],
260
+ "cagr": self.cagr,
261
+ "sharpe_ratio": self.sharpe_ratio,
262
+ "rolling_sharpe_ratio": [(value, ensure_iso(date))
263
+ for value, date in
264
+ self.rolling_sharpe_ratio],
265
+ "sortino_ratio": self.sortino_ratio,
266
+ "calmar_ratio": self.calmar_ratio,
267
+ "profit_factor": self.profit_factor,
268
+ "annual_volatility": self.annual_volatility,
269
+ "monthly_returns": [(value, ensure_iso(date))
270
+ for value, date in self.monthly_returns],
271
+ "yearly_returns": [(value, ensure_iso(date))
272
+ for value, date in self.yearly_returns],
273
+ "drawdown_series": [(value, ensure_iso(date))
274
+ for value, date in self.drawdown_series],
275
+ "max_drawdown": self.max_drawdown,
276
+ "max_drawdown_absolute": self.max_drawdown_absolute,
277
+ "max_daily_drawdown": self.max_daily_drawdown,
278
+ "max_drawdown_duration": self.max_drawdown_duration,
279
+ "trades_per_year": self.trades_per_year,
280
+ "trade_per_day": self.trade_per_day,
281
+ "exposure_ratio": self.exposure_ratio,
282
+ "cumulative_exposure": self.cumulative_exposure,
283
+ "average_trade_gain": self.average_trade_gain,
284
+ "average_trade_gain_percentage":
285
+ self.average_trade_gain_percentage,
286
+ "average_trade_loss": self.average_trade_loss,
287
+ "average_trade_loss_percentage":
288
+ self.average_trade_loss_percentage,
289
+ "average_trade_return": self.average_trade_return,
290
+ "average_trade_return_percentage":
291
+ self.average_trade_return_percentage,
292
+ "median_trade_return": self.median_trade_return,
293
+ "median_trade_return_percentage":
294
+ self.median_trade_return_percentage,
295
+ "number_of_positive_trades": self.number_of_positive_trades,
296
+ "percentage_positive_trades": self.percentage_positive_trades,
297
+ "number_of_negative_trades": self.number_of_negative_trades,
298
+ "percentage_negative_trades": self.percentage_negative_trades,
299
+ "best_trade": self.best_trade.to_dict()
300
+ if self.best_trade else None,
301
+ "worst_trade": self.worst_trade.to_dict()
302
+ if self.worst_trade else None,
303
+ "average_trade_duration": self.average_trade_duration,
304
+ "average_trade_size": self.average_trade_size,
305
+ "number_of_trades": self.number_of_trades,
306
+ "number_of_trades_closed": self.number_of_trades_closed,
307
+ "number_of_trades_opened": self.number_of_trades_opened,
308
+ "win_rate": self.win_rate,
309
+ "current_win_rate": self.current_win_rate,
310
+ "win_loss_ratio": self.win_loss_ratio,
311
+ "current_win_loss_ratio": self.current_win_loss_ratio,
312
+ "percentage_winning_months": self.percentage_winning_months,
313
+ "percentage_winning_years": self.percentage_winning_years,
314
+ "average_monthly_return": self.average_monthly_return,
315
+ "average_monthly_return_losing_months":
316
+ self.average_monthly_return_losing_months,
317
+ "average_monthly_return_winning_months":
318
+ self.average_monthly_return_winning_months,
319
+ "best_month": self.best_month,
320
+ "best_year": self.best_year,
321
+ "worst_month": self.worst_month,
322
+ "worst_year": self.worst_year
323
+ }
324
+
325
+ def save(self, file_path: str | Path) -> None:
326
+ """
327
+ Save the backtest metrics to a file in JSON format. The metrics will
328
+ always be saved in a file named `metrics.json`
329
+
330
+ Args:
331
+ file_path (str): The directory where the metrics
332
+ file will be saved.
333
+ """
334
+ with open(file_path, 'w') as file:
335
+ json.dump(self.to_dict(), file, indent=4, default=str)
336
+
337
+ @staticmethod
338
+ def _parse_tuple_list_datetime(
339
+ data: List[List]
340
+ ) -> List[Tuple[float, datetime]]:
341
+ """
342
+ Parse a list of [value, datetime_string]
343
+ into List[Tuple[float, datetime]]
344
+ """
345
+ return [
346
+ (float(value), datetime.fromisoformat(date_str))
347
+ for value, date_str in data
348
+ ]
349
+
350
+ @staticmethod
351
+ def _parse_tuple_list_date(data: List[List]) -> List[Tuple[float, date]]:
352
+ """
353
+ Parse a list of [value, date_string] into List[Tuple[float, date]]
354
+ """
355
+ return [
356
+ (float(value), datetime.fromisoformat(date_str).date())
357
+ for value, date_str in data
358
+ ]
359
+
360
+ @staticmethod
361
+ def _parse_tuple_datetime(data) -> Tuple[float, datetime]:
362
+ """Parse a [value, datetime_string] into Tuple[float, datetime]"""
363
+ if data is None:
364
+ return None, None
365
+
366
+ # Check if the value is NaN, None, or the string '<NA>'
367
+ if pd.isna(data[0]) or data[0] is None or data[0] == '<NA>':
368
+ value = pd.NA
369
+ else:
370
+ value = float(data[0])
371
+
372
+ # Parse the datetime string
373
+ if data[1] is None or pd.isna(data[1]) or data[1] == '<NA>':
374
+ date_value = None
375
+ else:
376
+ # Convert the string to a datetime object
377
+ date_value = datetime.fromisoformat(data[1])
378
+
379
+ return (value, date_value)
380
+
381
+ @staticmethod
382
+ def _parse_tuple_date(data) -> Tuple[float, date]:
383
+ """Parse a [value, date_string] into Tuple[float, date]"""
384
+ if data is None:
385
+ return None, None
386
+
387
+ # Check if the value is NaN, None, or the string '<NA>'
388
+ if pd.isna(data[0]) or data[0] is None or data[0] == '<NA>':
389
+ value = pd.NA
390
+ else:
391
+ value = float(data[0])
392
+
393
+ # Parse the date string
394
+ if data[1] is None or pd.isna(data[1]) or data[1] == '<NA>':
395
+ date = None
396
+ else:
397
+ date = datetime.fromisoformat(data[1]).date()
398
+
399
+ return (value, date)
400
+
401
+ @staticmethod
402
+ def open(file_path: str | Path) -> 'BacktestMetrics':
403
+ """
404
+ Open a backtest metrics file from a directory and
405
+ return a BacktestMetrics instance.
406
+
407
+ Args:
408
+ file_path (str): The path to the metrics file.
409
+
410
+ Returns:
411
+ BacktestMetrics: An instance of BacktestMetrics
412
+ loaded from the file.
413
+ """
414
+ if not os.path.exists(file_path):
415
+ raise FileNotFoundError(f"Metrics file not found at {file_path}")
416
+
417
+ with open(file_path, 'r') as file:
418
+ data = json.load(file)
419
+
420
+ # Parse datetime fields
421
+ data['backtest_start_date'] = datetime.fromisoformat(
422
+ data['backtest_start_date']
423
+ )
424
+ data['backtest_end_date'] = datetime.fromisoformat(
425
+ data['backtest_end_date']
426
+ )
427
+
428
+ # Parse tuple lists with datetime
429
+ data['equity_curve'] = BacktestMetrics._parse_tuple_list_datetime(
430
+ data.get('equity_curve', [])
431
+ )
432
+ data['rolling_sharpe_ratio'] = BacktestMetrics\
433
+ ._parse_tuple_list_datetime(data.get('rolling_sharpe_ratio', []))
434
+ data['monthly_returns'] = BacktestMetrics\
435
+ ._parse_tuple_list_datetime(data.get('monthly_returns', []))
436
+ data['drawdown_series'] = BacktestMetrics\
437
+ ._parse_tuple_list_datetime(data.get('drawdown_series', []))
438
+
439
+ # Parse tuple lists with date
440
+ data['yearly_returns'] = BacktestMetrics\
441
+ ._parse_tuple_list_date(data.get('yearly_returns', []))
442
+
443
+ # Parse single tuples
444
+ data['best_month'] = BacktestMetrics\
445
+ ._parse_tuple_datetime(data.get('best_month'))
446
+ data['worst_month'] = BacktestMetrics\
447
+ ._parse_tuple_datetime(data.get('worst_month'))
448
+ data['best_year'] = BacktestMetrics\
449
+ ._parse_tuple_date(data.get('best_year'))
450
+ data['worst_year'] = BacktestMetrics\
451
+ ._parse_tuple_date(data.get('worst_year'))
452
+
453
+ # Parse Trade objects if they exist
454
+ if data.get('best_trade'):
455
+ data['best_trade'] = Trade.from_dict(data['best_trade'])
456
+ if data.get('worst_trade'):
457
+ data['worst_trade'] = Trade.from_dict(data['worst_trade'])
458
+
459
+ return BacktestMetrics(**data)
460
+
461
+ def __repr__(self):
462
+ """
463
+ Return a string representation of the Backtest instance.
464
+
465
+ Returns:
466
+ str: A string representation of the Backtest instance.
467
+ """
468
+ return json.dumps(
469
+ self.to_dict(), indent=4, sort_keys=True, default=str
470
+ )