investing-algorithm-framework 7.19.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of investing-algorithm-framework might be problematic. Click here for more details.

Files changed (260) hide show
  1. investing_algorithm_framework/__init__.py +197 -0
  2. investing_algorithm_framework/app/__init__.py +47 -0
  3. investing_algorithm_framework/app/algorithm/__init__.py +7 -0
  4. investing_algorithm_framework/app/algorithm/algorithm.py +239 -0
  5. investing_algorithm_framework/app/algorithm/algorithm_factory.py +114 -0
  6. investing_algorithm_framework/app/analysis/__init__.py +15 -0
  7. investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
  8. investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
  9. investing_algorithm_framework/app/analysis/permutation.py +116 -0
  10. investing_algorithm_framework/app/analysis/ranking.py +297 -0
  11. investing_algorithm_framework/app/app.py +2204 -0
  12. investing_algorithm_framework/app/app_hook.py +28 -0
  13. investing_algorithm_framework/app/context.py +1667 -0
  14. investing_algorithm_framework/app/eventloop.py +590 -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/__init__.py +35 -0
  37. investing_algorithm_framework/app/stateless/action_handlers/__init__.py +84 -0
  38. investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +8 -0
  39. investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +15 -0
  40. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +40 -0
  41. investing_algorithm_framework/app/stateless/exception_handler.py +40 -0
  42. investing_algorithm_framework/app/strategy.py +675 -0
  43. investing_algorithm_framework/app/task.py +41 -0
  44. investing_algorithm_framework/app/web/__init__.py +5 -0
  45. investing_algorithm_framework/app/web/controllers/__init__.py +13 -0
  46. investing_algorithm_framework/app/web/controllers/orders.py +20 -0
  47. investing_algorithm_framework/app/web/controllers/portfolio.py +20 -0
  48. investing_algorithm_framework/app/web/controllers/positions.py +18 -0
  49. investing_algorithm_framework/app/web/create_app.py +20 -0
  50. investing_algorithm_framework/app/web/error_handler.py +59 -0
  51. investing_algorithm_framework/app/web/responses.py +20 -0
  52. investing_algorithm_framework/app/web/run_strategies.py +4 -0
  53. investing_algorithm_framework/app/web/schemas/__init__.py +12 -0
  54. investing_algorithm_framework/app/web/schemas/order.py +12 -0
  55. investing_algorithm_framework/app/web/schemas/portfolio.py +22 -0
  56. investing_algorithm_framework/app/web/schemas/position.py +15 -0
  57. investing_algorithm_framework/app/web/setup_cors.py +6 -0
  58. investing_algorithm_framework/cli/__init__.py +0 -0
  59. investing_algorithm_framework/cli/cli.py +207 -0
  60. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +499 -0
  61. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  62. investing_algorithm_framework/cli/initialize_app.py +603 -0
  63. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  64. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  65. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  66. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  67. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  68. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  69. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  70. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  71. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  72. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  73. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  74. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  75. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  76. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  77. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  78. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  79. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  80. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  81. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  82. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  83. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  84. investing_algorithm_framework/create_app.py +54 -0
  85. investing_algorithm_framework/dependency_container.py +155 -0
  86. investing_algorithm_framework/domain/__init__.py +148 -0
  87. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  88. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  89. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  90. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  91. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  92. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  93. investing_algorithm_framework/domain/backtesting/backtest_run.py +435 -0
  94. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  95. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  96. investing_algorithm_framework/domain/config.py +111 -0
  97. investing_algorithm_framework/domain/constants.py +83 -0
  98. investing_algorithm_framework/domain/data_provider.py +334 -0
  99. investing_algorithm_framework/domain/data_structures.py +42 -0
  100. investing_algorithm_framework/domain/decimal_parsing.py +40 -0
  101. investing_algorithm_framework/domain/exceptions.py +112 -0
  102. investing_algorithm_framework/domain/models/__init__.py +43 -0
  103. investing_algorithm_framework/domain/models/app_mode.py +34 -0
  104. investing_algorithm_framework/domain/models/base_model.py +25 -0
  105. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  106. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  107. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  108. investing_algorithm_framework/domain/models/event.py +35 -0
  109. investing_algorithm_framework/domain/models/market/__init__.py +5 -0
  110. investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
  111. investing_algorithm_framework/domain/models/order/__init__.py +6 -0
  112. investing_algorithm_framework/domain/models/order/order.py +384 -0
  113. investing_algorithm_framework/domain/models/order/order_side.py +36 -0
  114. investing_algorithm_framework/domain/models/order/order_status.py +37 -0
  115. investing_algorithm_framework/domain/models/order/order_type.py +30 -0
  116. investing_algorithm_framework/domain/models/portfolio/__init__.py +9 -0
  117. investing_algorithm_framework/domain/models/portfolio/portfolio.py +169 -0
  118. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +93 -0
  119. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
  120. investing_algorithm_framework/domain/models/position/__init__.py +4 -0
  121. investing_algorithm_framework/domain/models/position/position.py +68 -0
  122. investing_algorithm_framework/domain/models/position/position_snapshot.py +47 -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 +153 -0
  126. investing_algorithm_framework/domain/models/time_interval.py +124 -0
  127. investing_algorithm_framework/domain/models/time_unit.py +149 -0
  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 +13 -0
  131. investing_algorithm_framework/domain/models/trade/trade.py +388 -0
  132. investing_algorithm_framework/domain/models/trade/trade_risk_type.py +34 -0
  133. investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
  134. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +267 -0
  135. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +303 -0
  136. investing_algorithm_framework/domain/order_executor.py +112 -0
  137. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  138. investing_algorithm_framework/domain/positions/__init__.py +4 -0
  139. investing_algorithm_framework/domain/positions/position_size.py +41 -0
  140. investing_algorithm_framework/domain/services/__init__.py +11 -0
  141. investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
  142. investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
  143. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
  144. investing_algorithm_framework/domain/services/rounding_service.py +27 -0
  145. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  146. investing_algorithm_framework/domain/stateless_actions.py +7 -0
  147. investing_algorithm_framework/domain/strategy.py +44 -0
  148. investing_algorithm_framework/domain/utils/__init__.py +27 -0
  149. investing_algorithm_framework/domain/utils/csv.py +104 -0
  150. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  151. investing_algorithm_framework/domain/utils/dates.py +57 -0
  152. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  153. investing_algorithm_framework/domain/utils/polars.py +53 -0
  154. investing_algorithm_framework/domain/utils/random.py +41 -0
  155. investing_algorithm_framework/domain/utils/signatures.py +17 -0
  156. investing_algorithm_framework/domain/utils/stoppable_thread.py +26 -0
  157. investing_algorithm_framework/domain/utils/synchronized.py +12 -0
  158. investing_algorithm_framework/download_data.py +108 -0
  159. investing_algorithm_framework/infrastructure/__init__.py +50 -0
  160. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  161. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
  162. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  163. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  164. investing_algorithm_framework/infrastructure/database/__init__.py +10 -0
  165. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +120 -0
  166. investing_algorithm_framework/infrastructure/models/__init__.py +16 -0
  167. investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
  168. investing_algorithm_framework/infrastructure/models/model_extension.py +6 -0
  169. investing_algorithm_framework/infrastructure/models/order/__init__.py +4 -0
  170. investing_algorithm_framework/infrastructure/models/order/order.py +124 -0
  171. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  172. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  173. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +4 -0
  174. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
  175. investing_algorithm_framework/infrastructure/models/portfolio/sql_portfolio.py +114 -0
  176. investing_algorithm_framework/infrastructure/models/position/__init__.py +4 -0
  177. investing_algorithm_framework/infrastructure/models/position/position.py +63 -0
  178. investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +23 -0
  179. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  180. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  181. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +40 -0
  182. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +41 -0
  183. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  184. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  185. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  186. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  187. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  188. investing_algorithm_framework/infrastructure/repositories/__init__.py +21 -0
  189. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  190. investing_algorithm_framework/infrastructure/repositories/order_repository.py +96 -0
  191. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +30 -0
  192. investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
  193. investing_algorithm_framework/infrastructure/repositories/position_repository.py +66 -0
  194. investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
  195. investing_algorithm_framework/infrastructure/repositories/repository.py +299 -0
  196. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  197. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +23 -0
  198. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +23 -0
  199. investing_algorithm_framework/infrastructure/services/__init__.py +7 -0
  200. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  201. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
  202. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  203. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  204. investing_algorithm_framework/services/__init__.py +132 -0
  205. investing_algorithm_framework/services/backtesting/__init__.py +5 -0
  206. investing_algorithm_framework/services/backtesting/backtest_service.py +651 -0
  207. investing_algorithm_framework/services/configuration_service.py +96 -0
  208. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  209. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  210. investing_algorithm_framework/services/market_credential_service.py +40 -0
  211. investing_algorithm_framework/services/metrics/__init__.py +114 -0
  212. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  213. investing_algorithm_framework/services/metrics/beta.py +0 -0
  214. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  215. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  216. investing_algorithm_framework/services/metrics/drawdown.py +181 -0
  217. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  218. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  219. investing_algorithm_framework/services/metrics/generate.py +358 -0
  220. investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
  221. investing_algorithm_framework/services/metrics/price_efficiency.py +57 -0
  222. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  223. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  224. investing_algorithm_framework/services/metrics/returns.py +452 -0
  225. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  226. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  227. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  228. investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
  229. investing_algorithm_framework/services/metrics/trades.py +500 -0
  230. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  231. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  232. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  233. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  234. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  235. investing_algorithm_framework/services/order_service/__init__.py +9 -0
  236. investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
  237. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  238. investing_algorithm_framework/services/order_service/order_service.py +826 -0
  239. investing_algorithm_framework/services/portfolios/__init__.py +16 -0
  240. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
  241. investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +75 -0
  242. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  243. investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
  244. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
  245. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
  246. investing_algorithm_framework/services/positions/__init__.py +7 -0
  247. investing_algorithm_framework/services/positions/position_service.py +210 -0
  248. investing_algorithm_framework/services/positions/position_snapshot_service.py +18 -0
  249. investing_algorithm_framework/services/repository_service.py +40 -0
  250. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  251. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +132 -0
  252. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +66 -0
  253. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +41 -0
  254. investing_algorithm_framework/services/trade_service/__init__.py +3 -0
  255. investing_algorithm_framework/services/trade_service/trade_service.py +1083 -0
  256. investing_algorithm_framework-7.19.14.dist-info/LICENSE +201 -0
  257. investing_algorithm_framework-7.19.14.dist-info/METADATA +459 -0
  258. investing_algorithm_framework-7.19.14.dist-info/RECORD +260 -0
  259. investing_algorithm_framework-7.19.14.dist-info/WHEEL +4 -0
  260. investing_algorithm_framework-7.19.14.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,921 @@
1
+ import os
2
+ from datetime import datetime
3
+ from typing import List
4
+
5
+ from tabulate import tabulate
6
+
7
+ from investing_algorithm_framework.domain import DATETIME_FORMAT, \
8
+ BacktestDateRange, TradeStatus, OrderSide, TradeRiskType, Backtest
9
+ from investing_algorithm_framework.domain.constants import \
10
+ DATETIME_FORMAT_BACKTESTING
11
+
12
+ COLOR_RED = '\033[91m'
13
+ COLOR_PURPLE = '\033[95m'
14
+ COLOR_RESET = '\033[0m'
15
+ COLOR_GREEN = '\033[92m'
16
+ COLOR_YELLOW = '\033[93m'
17
+ BACKTEST_REPORT_FILE_NAME_PATTERN = (
18
+ r"^report_\w+_backtest-start-date_\d{4}-\d{2}-\d{2}:\d{2}:\d{2}_"
19
+ r"backtest-end-date_\d{4}-\d{2}-\d{2}:\d{2}:\d{2}_"
20
+ r"created-at_\d{4}-\d{2}-\d{2}:\d{2}:\d{2}\.json$"
21
+ )
22
+
23
+
24
+ def format_date(date) -> str:
25
+ """
26
+ Format the date to the format YYYY-MM-DD HH:MM:SS
27
+ """
28
+
29
+ if date is None:
30
+ return ""
31
+
32
+ if isinstance(date, datetime):
33
+ return date.strftime("%Y-%m-%d %H:%M")
34
+ else:
35
+ date = datetime.strptime(date, "%Y-%m-%d %H:%M:%S")
36
+ return date.strftime("%Y-%m-%d %H:%M")
37
+
38
+ def is_positive(number) -> bool:
39
+ """
40
+ Check if a number is positive.
41
+
42
+ param number: The number
43
+ :return: True if the number is positive, False otherwise
44
+ """
45
+ number = float(number)
46
+ return number > 0
47
+
48
+
49
+ def pretty_print_profit_evaluation(
50
+ reports, price_precision=2, percentage_precision=4
51
+ ):
52
+ profit_table = {}
53
+ profit_table["Algorithm name"] = [
54
+ report.name for report in reports
55
+ ]
56
+ profit_table["Profit"] = [
57
+ f"{float(report.total_net_gain):.{price_precision}f} {report.trading_symbol}"
58
+ for report in reports
59
+ ]
60
+ profit_table["Profit percentage"] = [
61
+ f"{float(report.total_net_gain_percentage):.{percentage_precision}f}%" for report in reports
62
+ ]
63
+ profit_table["Percentage positive trades"] = [
64
+ f"{float(report.percentage_positive_trades):.{0}f}%"
65
+ for report in reports
66
+ ]
67
+ profit_table["Date range"] = [
68
+ f"{report.backtest_date_range.name} {report.backtest_date_range.start_date} - {report.backtest_date_range.end_date}"
69
+ for report in reports
70
+ ]
71
+ profit_table["Total value"] = [
72
+ f"{float(report.total_value):.{price_precision}f}" for report in reports
73
+ ]
74
+ print(tabulate(profit_table, headers="keys", tablefmt="rounded_grid"))
75
+
76
+
77
+ def pretty_print_growth_evaluation(
78
+ reports,
79
+ price_precision=2,
80
+ percentage_precision=4
81
+ ):
82
+ growth_table = {}
83
+ growth_table["Algorithm name"] = [
84
+ report.name for report in reports
85
+ ]
86
+ growth_table["Growth"] = [
87
+ f"{float(report.growth):.{price_precision}f} {report.trading_symbol}" for report in reports
88
+ ]
89
+ growth_table["Growth percentage"] = [
90
+ f"{float(report.growth_percentage):.{percentage_precision}f}%" for report in reports
91
+ ]
92
+ growth_table["Percentage positive trades"] = [
93
+ f"{float(report.percentage_positive_trades):.{0}f}%"
94
+ for report in reports
95
+ ]
96
+ growth_table["Date range"] = [
97
+ f"{report.backtest_date_range.name} {report.backtest_date_range.start_date} - {report.backtest_date_range.end_date}"
98
+ for report in reports
99
+ ]
100
+ growth_table["Total value"] = [
101
+ f"{float(report.total_value):.{price_precision}f}" for report in reports
102
+ ]
103
+ print(
104
+ tabulate(growth_table, headers="keys", tablefmt="rounded_grid")
105
+ )
106
+
107
+ def pretty_print_stop_losses(
108
+ backtest,
109
+ backtest_date_range: BacktestDateRange = None,
110
+ triggered_only=False,
111
+ amount_precision=4,
112
+ price_precision=2,
113
+ time_precision=1,
114
+ percentage_precision=0
115
+ ):
116
+ print(f"{COLOR_YELLOW}Stop losses overview{COLOR_RESET}")
117
+ stop_loss_table = {}
118
+ results = backtest.get_backtest_run(backtest_date_range)
119
+ trades = results.get_trades()
120
+ selection = []
121
+
122
+ def get_sold_amount(stop_loss):
123
+ if stop_loss["sold_amount"] > 0:
124
+ return f"{float(stop_loss['sold_amount']):.{amount_precision}f} {stop_loss['target_symbol']}"
125
+
126
+ return ""
127
+
128
+ def get_status(stop_loss):
129
+
130
+ if stop_loss.sold_amount == 0:
131
+ return "NOT TRIGGERED"
132
+
133
+ if stop_loss.sold_amount == stop_loss.sell_amount:
134
+ return "TRIGGERED"
135
+
136
+ if stop_loss.sold_amount < stop_loss.sell_amount:
137
+ return "PARTIALLY TRIGGERED"
138
+
139
+ def get_high_water_mark(stop_loss):
140
+ if stop_loss["high_water_mark"] is not None:
141
+ return f"{float(stop_loss['high_water_mark']):.{price_precision}f} {stop_loss['trading_symbol']} {format_date(stop_loss['high_water_mark_date'])}"
142
+
143
+ return ""
144
+
145
+ def get_stop_loss_price(take_profit):
146
+
147
+ if TradeRiskType.TRAILING.equals(take_profit["trade_risk_type"]):
148
+ initial_price = take_profit["open_price"]
149
+ percentage = take_profit["percentage"]
150
+ initial_stop_loss_price = \
151
+ initial_price * (1 - (percentage / 100))
152
+ return f"{float(take_profit['stop_loss_price']):.{price_precision}f} ({(initial_stop_loss_price):.{price_precision}f}) ({take_profit['percentage']})% {take_profit['trading_symbol']}"
153
+ else:
154
+ return f"{float(take_profit['stop_loss_price']):.{price_precision}f}({take_profit['percentage']})% {take_profit['trading_symbol']}"
155
+
156
+ if triggered_only:
157
+ for trade in trades:
158
+
159
+ if trade.stop_losses is not None:
160
+ selection += [
161
+ {
162
+ "symbol": trade.symbol,
163
+ "target_symbol": trade.target_symbol,
164
+ "trading_symbol": trade.trading_symbol,
165
+ "status": get_status(stop_loss),
166
+ "trade_id": stop_loss.trade_id,
167
+ "trade_risk_type": stop_loss.trade_risk_type,
168
+ "percentage": stop_loss.percentage,
169
+ "open_price": stop_loss.open_price,
170
+ "sell_percentage": stop_loss.sell_percentage,
171
+ "high_water_mark": stop_loss.high_water_mark,
172
+ "high_water_mark_date": stop_loss.high_water_mark_date,
173
+ "stop_loss_price": stop_loss.stop_loss_price,
174
+ "sell_amount": stop_loss.sell_amount,
175
+ "sold_amount": stop_loss.sold_amount,
176
+ "active": stop_loss.active,
177
+ "sell_prices": stop_loss.sell_prices
178
+ } for stop_loss in trade.stop_losses if stop_loss.sold_amount > 0
179
+ ]
180
+ else:
181
+ for trade in trades:
182
+
183
+ if trade.stop_losses is not None:
184
+ for stop_loss in trade.stop_losses:
185
+ data = {
186
+ "symbol": trade.symbol,
187
+ "target_symbol": trade.target_symbol,
188
+ "trading_symbol": trade.trading_symbol,
189
+ "status": get_status(stop_loss),
190
+ "trade_id": stop_loss.trade_id,
191
+ "trade_risk_type": stop_loss.trade_risk_type,
192
+ "percentage": stop_loss.percentage,
193
+ "open_price": stop_loss.open_price,
194
+ "sell_percentage": stop_loss.sell_percentage,
195
+ "stop_loss_price": stop_loss.stop_loss_price,
196
+ "sell_amount": stop_loss.sell_amount,
197
+ "sold_amount": stop_loss.sold_amount,
198
+ "active": stop_loss.active,
199
+ }
200
+
201
+ if hasattr(stop_loss, "sell_prices"):
202
+ data["sell_prices"] = stop_loss.sell_prices
203
+ else:
204
+ data["sell_prices"] = None
205
+
206
+ if hasattr(stop_loss, "high_water_mark"):
207
+ data["high_water_mark"] = stop_loss.high_water_mark
208
+
209
+ if hasattr(stop_loss, "high_water_mark_date"):
210
+ data["high_water_mark_date"] = \
211
+ stop_loss.high_water_mark_date
212
+ else:
213
+ data["high_water_mark_date"] = None
214
+ else:
215
+ data["high_water_mark"] = None
216
+ data["high_water_mark_date"] = None
217
+ selection.append(data)
218
+
219
+ stop_loss_table["Trade (Trade id)"] = [
220
+ f"{stop_loss['symbol'] + ' (' + str(stop_loss['trade_id']) + ')'}"
221
+ for stop_loss in selection
222
+ ]
223
+ stop_loss_table["Status"] = [
224
+ f"{stop_loss['status']}"
225
+ for stop_loss in selection
226
+ ]
227
+ stop_loss_table["Active"] = [
228
+ f"{stop_loss['active']}"
229
+ for stop_loss in selection
230
+ ]
231
+ stop_loss_table["Type"] = [
232
+ f"{stop_loss['trade_risk_type']}" for stop_loss in selection
233
+ ]
234
+ stop_loss_table["Stop Loss (Initial Stop Loss)"] = [
235
+ get_stop_loss_price(stop_loss) for stop_loss in selection
236
+ ]
237
+ stop_loss_table["Open price"] = [
238
+ f"{float(stop_loss['open_price']):.{price_precision}f} {stop_loss['trading_symbol']}" for stop_loss in selection if stop_loss['open_price'] is not None
239
+ ]
240
+ stop_loss_table["Sell price's"] = [
241
+ f"{stop_loss['sell_prices']}" for stop_loss in selection if stop_loss['sell_prices'] is not None
242
+ ]
243
+ stop_loss_table["High water mark"] = [
244
+ f"{get_high_water_mark(stop_loss)}" for stop_loss in selection
245
+ ]
246
+ stop_loss_table["Percentage"] = [
247
+ f"{float(stop_loss['sell_percentage'])}%" for stop_loss in selection
248
+ ]
249
+ stop_loss_table["Size"] = [
250
+ f"{float(stop_loss['sell_amount']):.{price_precision}f} {stop_loss['target_symbol']}" for stop_loss in selection
251
+ ]
252
+ stop_loss_table["Sold amount"] = [
253
+ get_sold_amount(stop_loss) for stop_loss in selection
254
+ ]
255
+ print(tabulate(stop_loss_table, headers="keys", tablefmt="rounded_grid"))
256
+
257
+
258
+ def pretty_print_take_profits(
259
+ backtest: Backtest,
260
+ backtest_date_range: BacktestDateRange = None,
261
+ triggered_only=False,
262
+ amount_precision=4,
263
+ price_precision=2,
264
+ time_precision=1,
265
+ percentage_precision=0
266
+ ):
267
+ print(f"{COLOR_YELLOW}Take profits overview{COLOR_RESET}")
268
+ take_profit_table = {}
269
+ results = backtest.get_backtest_run(backtest_date_range)
270
+ trades = results.get_trades()
271
+ selection = []
272
+
273
+ def get_high_water_mark(take_profit):
274
+ if take_profit["high_water_mark"] is not None:
275
+ return f"{float(take_profit['high_water_mark']):.{price_precision}f} {take_profit['trading_symbol']} ({format_date(take_profit['high_water_mark_date'])})"
276
+
277
+ return ""
278
+
279
+ def get_sold_amount(take_profit):
280
+ if take_profit["sold_amount"] > 0:
281
+ return f"{float(take_profit['sold_amount']):.{amount_precision}f} {take_profit['target_symbol']}"
282
+
283
+ return ""
284
+
285
+ def get_take_profit_price(take_profit):
286
+
287
+ if TradeRiskType.TRAILING.equals(take_profit["trade_risk_type"]):
288
+ initial_price = take_profit["open_price"]
289
+ percentage = take_profit["percentage"]
290
+ initial_take_profit_price = \
291
+ initial_price * (1 + (percentage / 100))
292
+ return f"{float(take_profit['take_profit_price']):.{price_precision}f} ({(initial_take_profit_price):.{price_precision}f}) ({take_profit['percentage']})% {take_profit['trading_symbol']}"
293
+ else:
294
+ return f"{float(take_profit['take_profit_price']):.{price_precision}f}({take_profit['percentage']})% {take_profit['trading_symbol']}"
295
+
296
+ def get_status(take_profit):
297
+
298
+ if take_profit.sold_amount == 0:
299
+ return "NOT TRIGGERED"
300
+
301
+ if take_profit.sold_amount == take_profit.sell_amount:
302
+ return "TRIGGERED"
303
+
304
+ if take_profit.sold_amount < take_profit.sell_amount:
305
+ return "PARTIALLY TRIGGERED"
306
+
307
+ if triggered_only:
308
+ for trade in trades:
309
+
310
+ if trade.take_profits is not None:
311
+ selection += [
312
+ {
313
+ "symbol": trade.symbol,
314
+ "target_symbol": trade.target_symbol,
315
+ "trading_symbol": trade.trading_symbol,
316
+ "status": get_status(take_profit),
317
+ "trade_id": take_profit.trade_id,
318
+ "trade_risk_type": take_profit.trade_risk_type,
319
+ "percentage": take_profit.percentage,
320
+ "open_price": take_profit.open_price,
321
+ "sell_percentage": take_profit.sell_percentage,
322
+ "high_water_mark": take_profit.high_water_mark,
323
+ "high_water_mark_date": \
324
+ take_profit.high_water_mark_date,
325
+ "take_profit_price": take_profit.take_profit_price,
326
+ "sell_amount": take_profit.sell_amount,
327
+ "sold_amount": take_profit.sold_amount,
328
+ "active": take_profit.active,
329
+ "sell_prices": take_profit.sell_prices
330
+ } for take_profit in trade.take_profits if take_profit.sold_amount > 0
331
+ ]
332
+ else:
333
+ for trade in trades:
334
+
335
+ if trade.take_profits is not None:
336
+
337
+ for take_profit in trade.take_profits:
338
+ data = {
339
+ "symbol": trade.symbol,
340
+ "target_symbol": trade.target_symbol,
341
+ "trading_symbol": trade.trading_symbol,
342
+ "status": get_status(take_profit),
343
+ "trade_id": take_profit.trade_id,
344
+ "trade_risk_type": take_profit.trade_risk_type,
345
+ "percentage": take_profit.percentage,
346
+ "open_price": take_profit.open_price,
347
+ "sell_percentage": take_profit.sell_percentage,
348
+ "take_profit_price": take_profit.take_profit_price,
349
+ "sell_amount": take_profit.sell_amount,
350
+ "sold_amount": take_profit.sold_amount,
351
+ "active": take_profit.active
352
+ }
353
+
354
+ if hasattr(take_profit, "sell_prices"):
355
+ data["sell_prices"] = take_profit.sell_prices
356
+ else:
357
+ data["sell_prices"] = None
358
+
359
+ if hasattr(take_profit, "high_water_mark"):
360
+ data["high_water_mark"] = take_profit.high_water_mark
361
+
362
+ if hasattr(take_profit, "high_water_mark_date"):
363
+ data["high_water_mark_date"] = \
364
+ take_profit.high_water_mark_date
365
+ else:
366
+ data["high_water_mark_date"] = None
367
+ else:
368
+ data["high_water_mark"] = None
369
+ data["high_water_mark_date"] = None
370
+
371
+ selection.append(data)
372
+
373
+ take_profit_table["Trade (Trade id)"] = [
374
+ f"{stop_loss['symbol'] + ' (' + str(stop_loss['trade_id']) + ')'}"
375
+ for stop_loss in selection
376
+ ]
377
+ take_profit_table["Status"] = [
378
+ f"{stop_loss['status']}"
379
+ for stop_loss in selection
380
+ ]
381
+ take_profit_table["Active"] = [
382
+ f"{stop_loss['active']}"
383
+ for stop_loss in selection
384
+ ]
385
+ take_profit_table["Type"] = [
386
+ f"{stop_loss['trade_risk_type']}" for stop_loss
387
+ in selection
388
+ ]
389
+ take_profit_table["Take profit (Initial Take Profit)"] = [
390
+ get_take_profit_price(stop_loss) for stop_loss in selection
391
+ ]
392
+ take_profit_table["Open price"] = [
393
+ f"{float(stop_loss['open_price']):.{price_precision}f} {stop_loss['trading_symbol']}" for stop_loss in selection if stop_loss['open_price'] is not None
394
+ ]
395
+ take_profit_table["Sell price's"] = [
396
+ f"{stop_loss['sell_prices']}" for stop_loss in selection
397
+ ]
398
+ # Print nothing if high water mark is None
399
+ take_profit_table["High water mark"] = [
400
+ f"{get_high_water_mark(stop_loss)}" for stop_loss in selection
401
+ ]
402
+ take_profit_table["Percentage"] = [
403
+ f"{float(stop_loss['sell_percentage'])}%" for stop_loss in selection
404
+ ]
405
+ take_profit_table["Size"] = [
406
+ f"{float(stop_loss['sell_amount']):.{amount_precision}f} {stop_loss['target_symbol']}" for stop_loss in selection
407
+ ]
408
+ take_profit_table["Sold amount"] = [
409
+ get_sold_amount(stop_loss) for stop_loss in selection
410
+ ]
411
+ print(tabulate(take_profit_table, headers="keys", tablefmt="rounded_grid"))
412
+
413
+
414
+ def pretty_print_date_ranges(date_ranges: List[BacktestDateRange]) -> None:
415
+ """
416
+ Pretty print the date ranges to the console.
417
+
418
+ param date_ranges: The date ranges
419
+ """
420
+ print(f"{COLOR_YELLOW}Date ranges of backtests:{COLOR_RESET}")
421
+ for date_range in date_ranges:
422
+ start_date = date_range.start_date
423
+ end_date = date_range.end_date
424
+
425
+ if isinstance(start_date, datetime):
426
+ start_date = start_date.strftime(DATETIME_FORMAT)
427
+
428
+ if isinstance(end_date, datetime):
429
+ end_date = end_date.strftime(DATETIME_FORMAT)
430
+
431
+ if date_range.name is not None:
432
+ print(f"{COLOR_GREEN}{date_range.name}: {start_date} - {end_date}{COLOR_RESET}")
433
+ else:
434
+ print(f"{COLOR_GREEN}{start_date} - {end_date}{COLOR_RESET}")
435
+
436
+
437
+ def pretty_print_price_efficiency(reports, precision=4):
438
+ """
439
+ Pretty print the price efficiency of the backtest reports evaluation
440
+ to the console.
441
+ """
442
+ # Get all symbols of the reports
443
+ print(f"{COLOR_YELLOW}Price noise{COLOR_RESET}")
444
+ rows = []
445
+
446
+ for report in reports:
447
+
448
+ if report.metrics is not None and "efficiency_ratio" in report.metrics:
449
+ price_efficiency = report.metrics["efficiency_ratio"]
450
+
451
+ for symbol in price_efficiency:
452
+ row = {}
453
+ row["Symbol"] = symbol
454
+ row["Efficiency ratio / Noise"] = f"{float(price_efficiency[symbol]):.{precision}f}"
455
+ row["Date"] = f"{report.backtest_start_date} - {report.backtest_end_date}"
456
+
457
+ if report.backtest_date_range.name is not None:
458
+ row["Date"] = f"{report.backtest_date_range.name} " \
459
+ f"{report.backtest_date_range.start_date}" \
460
+ f" - {report.backtest_date_range.end_date}"
461
+ else:
462
+ row["Date"] = f"{report.backtest_start_date} - " \
463
+ f"{report.backtest_end_date}"
464
+
465
+ rows.append(row)
466
+
467
+ # Remove all duplicate rows with the same symbol and date range
468
+ unique_rows = []
469
+
470
+ # Initialize an empty set to track unique (symbol, date) pairs
471
+ seen = set()
472
+ # Initialize a list to store the filtered dictionaries
473
+ filtered_data = []
474
+
475
+ # Iterate through each dictionary in the list
476
+ for entry in rows:
477
+ # Extract the (symbol, date) pair
478
+ pair = (entry["Symbol"], entry["Date"])
479
+ # Check if the pair is already in the set
480
+ if pair not in seen:
481
+ # If not, add the pair to the set and
482
+ # the entry to the filtered list
483
+ seen.add(pair)
484
+ filtered_data.append(entry)
485
+
486
+ print(
487
+ tabulate(
488
+ filtered_data,
489
+ headers="keys",
490
+ tablefmt="rounded_grid"
491
+ )
492
+ )
493
+
494
+
495
+ def pretty_print_orders(
496
+ backtest: Backtest,
497
+ backtest_date_range: BacktestDateRange = None,
498
+ target_symbol = None,
499
+ order_status = None,
500
+ amount_precesion=4,
501
+ price_precision=2,
502
+ time_precision=1,
503
+ percentage_precision=2
504
+ ) -> None:
505
+ """
506
+ Pretty print the orders of the backtest report to the console.
507
+
508
+ Args:
509
+ backtest: The backtest
510
+ backtest_date_range: The date range of the backtest
511
+ target_symbol: The target symbol of the orders
512
+ order_status: The status of the orders
513
+ amount_precesion: The precision of the amount
514
+ price_precision: The precision of the price
515
+ time_precision: The precision of the time
516
+ percentage_precision: The precision of the percentage
517
+
518
+ Returns:
519
+ None
520
+ """
521
+ run = backtest.get_backtest_run(backtest_date_range)
522
+ selection = run.get_orders(
523
+ target_symbol=target_symbol,
524
+ order_status=order_status
525
+ )
526
+
527
+ print(f"{COLOR_YELLOW}Orders overview{COLOR_RESET}")
528
+ orders_table = {}
529
+ orders_table["Pair (Order id)"] = [
530
+ f"{order.target_symbol}/{order.trading_symbol} ({order.id})"
531
+ for order in selection
532
+ ]
533
+ orders_table["Status"] = [
534
+ order.status for order in selection
535
+ ]
536
+ orders_table["Side"] = [
537
+ order.order_side for order in selection
538
+ ]
539
+ orders_table["Size"] = [
540
+ f"{float(order.get_size()):.{amount_precesion}f} {order.trading_symbol}"
541
+ for order in selection
542
+ ]
543
+ orders_table["Price"] = [
544
+ f"{float(order.price):.{amount_precesion}f} {order.trading_symbol}"
545
+ for order in selection
546
+ ]
547
+ orders_table["Amount"] = [
548
+ f"{float(order.amount):.{amount_precesion}f} {order.target_symbol}"
549
+ for order in selection
550
+ ]
551
+ orders_table["Filled"] = [
552
+ f"{float(order.filled):.{amount_precesion}f} {order.target_symbol}"
553
+ for order in selection
554
+ ]
555
+ orders_table["Created at"] = [
556
+ order.created_at.strftime("%Y-%m-%d %H:%M") for order in selection if order.created_at is not None
557
+ ]
558
+ print(tabulate(orders_table, headers="keys", tablefmt="rounded_grid"))
559
+
560
+
561
+ def pretty_print_positions(
562
+ backtest,
563
+ backtest_date_range: BacktestDateRange = None,
564
+ symbol = None,
565
+ amount_precision=4,
566
+ price_precision=2,
567
+ time_precision=1,
568
+ percentage_precision=2
569
+ ) -> None:
570
+ """
571
+ Pretty print the positions of the backtest report to the console.
572
+
573
+ Args:
574
+ backtest: The backtest report
575
+ backtest_date_range: The date range of the backtest
576
+ amount_precision: The precision of the amount
577
+ price_precision: The precision of the price
578
+ time_precision: The precision of the time
579
+ percentage_precision: The precision of the percentage
580
+
581
+ Returns:
582
+ None
583
+ """
584
+ results = backtest.get_backtest_run(backtest_date_range)
585
+ selection = results.get_positions(symbol=symbol)
586
+
587
+ print(f"{COLOR_YELLOW}Positions overview{COLOR_RESET}")
588
+ position_table = {}
589
+ position_table["Position"] = [
590
+ position.symbol for position in selection
591
+ ]
592
+ position_table["Amount"] = [
593
+ f"{float(position.amount):.{amount_precision}f}" for position in
594
+ selection
595
+ ]
596
+ position_table["Pending buy amount"] = [
597
+ f"{float(position.amount_pending_buy):.{amount_precision}f}"
598
+ for position in selection
599
+ ]
600
+ position_table["Pending sell amount"] = [
601
+ f"{float(position.amount_pending_sell):.{amount_precision}f}"
602
+ for position in selection
603
+ ]
604
+ position_table[f"Cost ({results.trading_symbol})"] = [
605
+ f"{float(position.cost):.{price_precision}f}"
606
+ for position in selection
607
+ ]
608
+ position_table[f"Value ({results.trading_symbol})"] = [
609
+ f"{float(position.value):.{price_precision}f} {results.trading_symbol}"
610
+ for position in selection
611
+ ]
612
+ position_table["Percentage of portfolio"] = [
613
+ f"{float(position.percentage_of_portfolio):.{percentage_precision}f}%"
614
+ for position in selection
615
+ ]
616
+ position_table[f"Growth ({results.trading_symbol})"] = [
617
+ f"{float(position.growth):.{price_precision}f} {results.trading_symbol}"
618
+ for position in selection
619
+ ]
620
+ position_table["Growth_percentage"] = [
621
+ f"{float(position.growth_percentage):.{percentage_precision}f}%"
622
+ for position in selection
623
+ ]
624
+ print(
625
+ tabulate(position_table, headers="keys", tablefmt="rounded_grid")
626
+ )
627
+
628
+
629
+ def pretty_print_trades(
630
+ backtest: Backtest,
631
+ backtest_date_range: BacktestDateRange = None,
632
+ target_symbol = None,
633
+ status = None,
634
+ amount_precision=4,
635
+ price_precision=2,
636
+ time_precision=1,
637
+ percentage_precision=2
638
+ ):
639
+ """
640
+ Pretty print the trades of the backtest report to the console.
641
+
642
+ Args:
643
+ backtest: The backtest report
644
+ backtest_date_range: The date range of the backtest
645
+ target_symbol: The target symbol of the trades
646
+ status: The status of the trades
647
+ amount_precision: The precision of the amount
648
+ price_precision: The precision of the price
649
+ time_precision: The precision of the time
650
+ percentage_precision: The precision of the percentage
651
+
652
+ Returns:
653
+ None
654
+ """
655
+ run = backtest.get_backtest_run(backtest_date_range)
656
+ selection = run.get_trades(
657
+ target_symbol=target_symbol,
658
+ trade_status=status
659
+ )
660
+
661
+ def get_status(trade):
662
+ status = "OPEN"
663
+
664
+ if TradeStatus.CLOSED.equals(trade.status):
665
+ status = "CLOSED"
666
+
667
+ if has_triggered_stop_losses(trade):
668
+ status += ", SL"
669
+
670
+ if has_triggered_take_profits(trade):
671
+ status += ", TP"
672
+
673
+ return status
674
+
675
+ def get_close_prices(trade):
676
+
677
+ sell_orders = [
678
+ order for order in trade.orders
679
+ if OrderSide.SELL.equals(order.order_side)
680
+ ]
681
+ text = ""
682
+ number_of_sell_orders = 0
683
+
684
+ for sell_order in sell_orders:
685
+
686
+ if number_of_sell_orders > 0:
687
+ text += ", "
688
+
689
+ text += f"{float(sell_order.price):.{price_precision}f}"
690
+ number_of_sell_orders += 1
691
+
692
+ return text
693
+
694
+ def has_triggered_take_profits(trade):
695
+
696
+ if trade.take_profits is None:
697
+ return False
698
+
699
+ triggered = [
700
+ take_profit for take_profit in trade.take_profits if take_profit.sold_amount != 0
701
+ ]
702
+
703
+ return len(triggered) > 0
704
+
705
+ def has_triggered_stop_losses(trade):
706
+
707
+ if trade.stop_losses is None:
708
+ return False
709
+
710
+ triggered = [
711
+ stop_loss for stop_loss in trade.stop_losses if stop_loss.sold_amount != 0
712
+ ]
713
+ return len(triggered) > 0
714
+
715
+ def get_high_water_mark(trade):
716
+ if trade.high_water_mark is not None:
717
+ return f"{float(trade.high_water_mark):.{price_precision}f} {trade.trading_symbol} ({format_date(trade.high_water_mark_datetime)})"
718
+
719
+ return ""
720
+
721
+ print(f"{COLOR_YELLOW}Trades overview{COLOR_RESET}")
722
+ trades_table = {}
723
+ trades_table["Pair (Trade id)"] = [
724
+ f"{trade.target_symbol}/{trade.trading_symbol} ({trade.id})"
725
+ for trade in selection
726
+ ]
727
+ trades_table["Status"] = [
728
+ get_status(trade) for trade in selection
729
+ ]
730
+ trades_table["Amount (remaining)"] = [
731
+ f"{float(trade.amount):.{amount_precision}f} ({float(trade.remaining):.{amount_precision}f}) {trade.target_symbol}"
732
+ for trade in selection
733
+ ]
734
+ trades_table[f"Net gain ({run.trading_symbol})"] = [
735
+ f"{float(trade.net_gain):.{price_precision}f}"
736
+ for trade in selection
737
+ ]
738
+ trades_table["Open date"] = [
739
+ trade.opened_at.strftime("%Y-%m-%d %H:%M") for trade in selection if trade.opened_at is not None
740
+ ]
741
+ trades_table["Close date"] = [
742
+ trade.closed_at.strftime("%Y-%m-%d %H:%M") for trade in selection if trade.closed_at is not None
743
+ ]
744
+ trades_table["Duration"] = [
745
+ f"{trade.duration:.{time_precision}f} hours" for trade in selection
746
+ ]
747
+ # Add (unrealized) to the net gain if the trade is still open
748
+ trades_table[f"Net gain ({run.trading_symbol})"] = [
749
+ f"{float(trade.net_gain_absolute):.{price_precision}f} ({float(trade.net_gain_percentage):.{percentage_precision}f}%)" + (" (unrealized)" if not TradeStatus.CLOSED.equals(trade.status) else "")
750
+ for trade in selection
751
+ ]
752
+ trades_table[f"Open price ({run.trading_symbol})"] = [
753
+ f"{trade.open_price:.{price_precision}f}" for trade in selection
754
+ ]
755
+ trades_table[
756
+ f"Close price's ({run.trading_symbol})"
757
+ ] = [
758
+ get_close_prices(trade) for trade in selection
759
+ ]
760
+ trades_table["High water mark"] = [
761
+ get_high_water_mark(trade) for trade in selection
762
+ ]
763
+ print(tabulate(trades_table, headers="keys", tablefmt="rounded_grid"))
764
+
765
+
766
+ def print_number_of_runs(report):
767
+
768
+ if report.number_of_runs == 1:
769
+
770
+ if report.inteval > 1:
771
+ print(f"Strategy ran every {report.interval} {report.time_unit} "
772
+ f"for a total of {report.number_of_runs} time")
773
+
774
+
775
+ def pretty_print_backtest(
776
+ backtest: Backtest,
777
+ backtest_date_range: BacktestDateRange = None,
778
+ show_positions=True,
779
+ show_trades=True,
780
+ show_stop_losses=True,
781
+ show_triggered_stop_losses_only=False,
782
+ show_take_profits=True,
783
+ show_triggered_take_profits_only=False,
784
+ amount_precision=4,
785
+ price_precision=2,
786
+ time_precision=1,
787
+ percentage_precision=2
788
+ ):
789
+ """
790
+ Pretty print the backtest report to the console.
791
+
792
+ Args:
793
+ backtest: Backtest - the backtest report
794
+ show_positions: bool - show the positions
795
+ show_trades: bool - show the trades
796
+ show_stop_losses: bool - show the stop losses
797
+ show_triggered_stop_losses_only: bool - show only the triggered stop losses
798
+ show_take_profits: bool - show the take profits
799
+ show_triggered_take_profits_only: bool - show only the triggered take profits
800
+ amount_precision: int - the amount precision
801
+ price_precision: int - the price precision
802
+ time_precision: int - the time precision
803
+ percentage_precision: int - the percentage precision
804
+
805
+ Returns:
806
+ None
807
+ """
808
+ backtest_results = backtest.get_backtest_run(backtest_date_range)
809
+ backtest_metrics = backtest.get_backtest_metrics(backtest_date_range)
810
+ ascii_art = f"""
811
+ :%%%#+- .=*#%%% {COLOR_GREEN}Backtest report{COLOR_RESET}
812
+ *%%%%%%%+------=*%%%%%%%- {COLOR_GREEN}---------------------------{COLOR_RESET}
813
+ *%%%%%%%%%%%%%%%%%%%%%%%- {COLOR_YELLOW}Start date:{COLOR_RESET}{COLOR_GREEN} {backtest_results.backtest_start_date}{COLOR_RESET}
814
+ .%%%%%%%%%%%%%%%%%%%%%%# {COLOR_YELLOW}End date:{COLOR_RESET}{COLOR_GREEN} {backtest_results.backtest_end_date}{COLOR_RESET}
815
+ #%%%####%%%%%%%%**#%%%+ {COLOR_YELLOW}Number of days:{COLOR_RESET}{COLOR_GREEN}{COLOR_RESET}{COLOR_GREEN} {backtest_results.number_of_days}{COLOR_RESET}
816
+ .:-+*%%%%- {COLOR_PURPLE}-+..#{COLOR_RESET}%%%+.{COLOR_PURPLE}+- +{COLOR_RESET}%%%#*=-: {COLOR_YELLOW}Number of runs:{COLOR_RESET}{COLOR_GREEN} {backtest_results.number_of_runs}{COLOR_RESET}
817
+ .:-=*%%%%. {COLOR_PURPLE}+={COLOR_RESET} .%%# {COLOR_PURPLE}-+.-{COLOR_RESET}%%%%=-:.. {COLOR_YELLOW}Number of orders:{COLOR_RESET}{COLOR_GREEN} {backtest_results.number_of_orders}{COLOR_RESET}
818
+ .:=+#%%%%%*###%%%%#*+#%%%%%%*+-: {COLOR_YELLOW}Initial balance:{COLOR_RESET}{COLOR_GREEN} {backtest_results.initial_unallocated}{COLOR_RESET}
819
+ +%%%%%%%%%%%%%%%%%%%= {COLOR_YELLOW}Final balance:{COLOR_RESET}{COLOR_GREEN} {float(backtest_metrics.final_value):.{price_precision}f}{COLOR_RESET}
820
+ :++ .=#%%%%%%%%%%%%%*- {COLOR_YELLOW}Total net gain:{COLOR_RESET}{COLOR_GREEN} {float(backtest_metrics.total_net_gain):.{price_precision}f} {float(backtest_metrics.total_net_gain_percentage):.{percentage_precision}f}%{COLOR_RESET}
821
+ :++: :+%%%%%%#-. {COLOR_YELLOW}Growth:{COLOR_RESET}{COLOR_GREEN} {float(backtest_metrics.growth):.{price_precision}f} {float(backtest_metrics.growth_percentage):.{percentage_precision}f}%{COLOR_RESET}
822
+ :++: .%%%%%#= {COLOR_YELLOW}Number of trades:{COLOR_RESET}{COLOR_GREEN} {backtest_results.number_of_trades}{COLOR_RESET}
823
+ :++: .%%%%%#= {COLOR_YELLOW}Number of trades closed:{COLOR_RESET}{COLOR_GREEN} {backtest_results.number_of_trades_closed}{COLOR_RESET}
824
+ :++: .#%%%%%#*= {COLOR_YELLOW}Number of trades open(end of backtest):{COLOR_RESET}{COLOR_GREEN} {backtest_results.number_of_trades_open}{COLOR_RESET}
825
+ :++- :%%%%%%%%%+= {COLOR_YELLOW}Percentage positive trades:{COLOR_RESET}{COLOR_GREEN} {float(backtest_metrics.percentage_positive_trades):.{percentage_precision}f}%{COLOR_RESET}
826
+ .++- -%%%%%%%%%%%+= {COLOR_YELLOW}Percentage negative trades:{COLOR_RESET}{COLOR_GREEN} {float(backtest_metrics.percentage_negative_trades):.{percentage_precision}f}%{COLOR_RESET}
827
+ .++- .%%%%%%%%%%%%%+= {COLOR_YELLOW}Average trade size:{COLOR_RESET}{COLOR_GREEN} {float(backtest_metrics.average_trade_size):.{price_precision}f} {backtest_results.trading_symbol}{COLOR_RESET}
828
+ .++- *%%%%%%%%%%%%%*+: {COLOR_YELLOW}Average trade duration:{COLOR_RESET}{COLOR_GREEN} {float(backtest_metrics.average_trade_duration):.{0}f} hours{COLOR_RESET}
829
+ .++- %%%%%%%%%%%%%%#+=
830
+ =++........:::%%%%%%%%%%%%%%*+-
831
+ .=++++++++++**#%%%%%%%%%%%%%++.
832
+ """
833
+
834
+ print(ascii_art)
835
+
836
+ if show_positions:
837
+ print(f"{COLOR_YELLOW}Positions overview{COLOR_RESET}")
838
+ pretty_print_positions(backtest)
839
+
840
+ if show_trades:
841
+ pretty_print_trades(
842
+ backtest=backtest,
843
+ amount_precision=amount_precision,
844
+ price_precision=price_precision,
845
+ time_precision=time_precision,
846
+ percentage_precision=percentage_precision
847
+ )
848
+
849
+ if show_stop_losses:
850
+ pretty_print_stop_losses(
851
+ backtest=backtest,
852
+ triggered_only=show_triggered_stop_losses_only,
853
+ amount_precision=amount_precision,
854
+ price_precision=price_precision,
855
+ time_precision=time_precision,
856
+ percentage_precision=percentage_precision
857
+ )
858
+
859
+ if show_take_profits:
860
+ pretty_print_take_profits(
861
+ backtest=backtest,
862
+ triggered_only=show_triggered_take_profits_only,
863
+ amount_precision=amount_precision,
864
+ price_precision=price_precision,
865
+ time_precision=time_precision,
866
+ percentage_precision=percentage_precision
867
+ )
868
+
869
+ def get_start_date_from_backtest_report_file(path: str) -> datetime:
870
+ """
871
+ Function to get the backtest start date from a backtest report file.
872
+
873
+ Parameters:
874
+ path (str): The path to the backtest report file
875
+
876
+ Returns:
877
+ datetime: The backtest start date
878
+ """
879
+
880
+ # Get the backtest start date from the file name
881
+ backtest_start_date = os.path.basename(path).split("_")[3]
882
+ # Parse the backtest start date
883
+ return datetime.strptime(
884
+ backtest_start_date, DATETIME_FORMAT_BACKTESTING
885
+ )
886
+
887
+
888
+ def get_end_date_from_backtest_report_file(path: str) -> datetime:
889
+ """
890
+ Function to get the backtest end date from a backtest report file.
891
+
892
+ Parameters:
893
+ path (str): The path to the backtest report file
894
+
895
+ Returns:
896
+ datetime: The backtest end date
897
+ """
898
+
899
+ # Get the backtest end date from the file name
900
+ backtest_end_date = os.path.basename(path).split("_")[5]
901
+ # Parse the backtest end date
902
+ return datetime.strptime(
903
+ backtest_end_date, DATETIME_FORMAT_BACKTESTING
904
+ )
905
+
906
+
907
+ def get_algorithm_name_from_backtest_report_file(path: str) -> str:
908
+ """
909
+ Function to get the algorithm name from a backtest report file.
910
+
911
+ Parameters:
912
+ path (str): The path to the backtest report file
913
+
914
+ Returns:
915
+ str: The algorithm name
916
+ """
917
+ # Get the word between "report_" and "_backtest_start_date"
918
+ # it can contain _
919
+ # Get the algorithm name from the file name
920
+ algorithm_name = os.path.basename(path).split("_")[1]
921
+ return algorithm_name