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,124 @@
1
+ from enum import Enum
2
+
3
+ from .time_frame import TimeFrame
4
+
5
+
6
+ class TimeInterval(Enum):
7
+ CURRENT = "CURRENT"
8
+ MINUTES_ONE = "MINUTES_ONE"
9
+ MINUTES_FIFTEEN = "MINUTES_FIFTEEN"
10
+ HOURS_ONE = "HOURS_ONE"
11
+ HOURS_FOUR = "HOURS_FOUR"
12
+ DAYS_ONE = "DAYS_ONE"
13
+
14
+ @staticmethod
15
+ def from_string(value: str):
16
+
17
+ if isinstance(value, str):
18
+
19
+ for entry in TimeInterval:
20
+
21
+ if value.upper() == entry.value:
22
+ return entry
23
+
24
+ raise ValueError(
25
+ f"Could not convert {value} to TimeInterval"
26
+ )
27
+
28
+ @staticmethod
29
+ def from_ohlcv_data_file(file_path: str):
30
+ """
31
+ Extracts the time interval from the file name of an OHLCV data file.
32
+ The file name should contain the time interval in the format
33
+ 'symbol_timeinterval.csv'.
34
+
35
+ Args:
36
+ file_path (str): The file path of the OHLCV data file.
37
+
38
+ Returns:
39
+ TimeInterval: The extracted time interval.
40
+ """
41
+ if not isinstance(file_path, str):
42
+ raise ValueError("File path must be a string.")
43
+
44
+ parts = file_path.split('_')
45
+ if len(parts) < 2:
46
+ raise ValueError(
47
+ "File name does not contain a valid time interval."
48
+ )
49
+
50
+ time_interval_str = parts[-1].split('.')[0].upper()
51
+ try:
52
+ return TimeInterval.from_string(time_interval_str)
53
+ except ValueError:
54
+ raise ValueError(
55
+ "Could not extract time interval from "
56
+ f"file name: {file_path}. "
57
+ "Expected format 'symbol_timeinterval.csv', "
58
+ f"got '{time_interval_str}'."
59
+ )
60
+
61
+ def equals(self, other):
62
+
63
+ if isinstance(other, Enum):
64
+ return self.value == other.value
65
+
66
+ else:
67
+ return TimeInterval.from_string(other) == self
68
+
69
+ @staticmethod
70
+ def from_time_frame(time_frame):
71
+
72
+ if TimeFrame.CURRENT.equals(time_frame):
73
+ return TimeInterval.CURRENT
74
+ elif TimeFrame.ONE_HOUR.equals(time_frame):
75
+ return TimeInterval.MINUTES_ONE
76
+ elif TimeFrame.ONE_DAY.equals(time_frame):
77
+ return TimeInterval.MINUTES_FIFTEEN
78
+ elif TimeFrame.ONE_WEEK.equals(time_frame):
79
+ return TimeInterval.HOURS_ONE
80
+ elif TimeFrame.ONE_MONTH.equals(time_frame):
81
+ return TimeInterval.HOURS_FOUR
82
+ elif TimeFrame.ONE_YEAR.equals(time_frame):
83
+ return TimeInterval.DAYS_ONE
84
+ else:
85
+ raise NotImplementedError(
86
+ f"Timeframe {time_frame} not implemented"
87
+ )
88
+
89
+ def amount_of_data_points(self):
90
+
91
+ if TimeInterval.CURRENT.equals(self):
92
+ return 1
93
+ elif TimeInterval.MINUTES_ONE.equals(self):
94
+ return 60
95
+ elif TimeInterval.MINUTES_FIFTEEN.equals(self):
96
+ return 96
97
+ elif TimeInterval.HOURS_ONE.equals(self):
98
+ return 168
99
+ elif TimeInterval.HOURS_FOUR.equals(self):
100
+ return 168
101
+ elif TimeInterval.DAYS_ONE.equals(self):
102
+ return 365
103
+ else:
104
+ raise NotImplementedError(f"Timeframe {self} not implemented")
105
+
106
+ @property
107
+ def time_frame(self):
108
+ from investing_algorithm_framework.domain.models.time_frame import \
109
+ TimeFrame
110
+
111
+ if TimeInterval.MINUTES_ONE.equals(self):
112
+ return TimeFrame.ONE_HOUR
113
+ elif TimeInterval.MINUTES_FIFTEEN.equals(self):
114
+ return TimeFrame.ONE_DAY
115
+ elif TimeInterval.HOURS_ONE.equals(self):
116
+ return TimeFrame.ONE_WEEK
117
+ elif TimeInterval.HOURS_FOUR.equals(self):
118
+ return TimeFrame.ONE_MONTH
119
+ elif TimeInterval.DAYS_ONE.equals(self):
120
+ return TimeFrame.ONE_YEAR
121
+ else:
122
+ raise NotImplementedError(
123
+ f"TimeInterval {self.value} not implemented"
124
+ )
@@ -0,0 +1,149 @@
1
+ from datetime import timedelta
2
+ from enum import Enum
3
+ from investing_algorithm_framework.domain.exceptions import \
4
+ OperationalException
5
+
6
+
7
+ class TimeUnit(Enum):
8
+ """
9
+ Enum class the represents a time unit such as
10
+ second, minute, hour or day. This can class
11
+ can be used to specify time specification within
12
+ the framework.
13
+ """
14
+ SECOND = "SECOND"
15
+ MINUTE = "MINUTE"
16
+ HOUR = "HOUR"
17
+ DAY = "DAY"
18
+
19
+ @staticmethod
20
+ def from_string(value: str):
21
+
22
+ if isinstance(value, str):
23
+
24
+ for entry in TimeUnit:
25
+
26
+ if value.upper() == entry.value:
27
+ return entry
28
+
29
+ raise OperationalException(
30
+ f"Could not convert string {value} to time unit"
31
+ )
32
+
33
+ raise OperationalException(
34
+ f"Could not convert value {value} to time unit," +
35
+ " please make sure that the value is either of type string or" +
36
+ f"TimeUnit. Its current type is {type(value)}"
37
+ )
38
+
39
+ @staticmethod
40
+ def from_ohlcv_data_file(file_path: str):
41
+ """
42
+ Extracts the time unit from the file name of an OHLCV data file.
43
+ The file name should contain the time unit in the
44
+ format 'symbol_timeunit.csv'.
45
+
46
+ Args:
47
+ file_path (str): The file path of the OHLCV data file.
48
+
49
+ Returns:
50
+ TimeUnit: The extracted time unit.
51
+ """
52
+ if not isinstance(file_path, str):
53
+ raise OperationalException(
54
+ "File path must be a string."
55
+ )
56
+
57
+ parts = file_path.split('_')
58
+ if len(parts) < 2:
59
+ raise OperationalException(
60
+ "File name does not contain a valid time unit."
61
+ )
62
+
63
+ time_unit_str = parts[-1].split('.')[0].upper()
64
+ try:
65
+ return TimeUnit.from_string(time_unit_str)
66
+ except ValueError:
67
+ raise OperationalException(
68
+ f"Could not extract time unit from file name: {file_path}. "
69
+ "Expected format 'symbol_timeunit.csv', "
70
+ f"got '{time_unit_str}'."
71
+ )
72
+
73
+ def equals(self, other):
74
+
75
+ if isinstance(other, Enum):
76
+ return self.value == other.value
77
+ else:
78
+ return TimeUnit.from_string(other) == self
79
+
80
+ @staticmethod
81
+ def from_value(value):
82
+
83
+ if isinstance(value, TimeUnit):
84
+
85
+ for entry in TimeUnit:
86
+
87
+ if entry == value:
88
+ return entry
89
+
90
+ return TimeUnit.from_string(value)
91
+
92
+ def create_date(self, start_date, interval):
93
+
94
+ if TimeUnit.SECOND.equals(self):
95
+ return timedelta(minutes=interval)
96
+ elif TimeUnit.MINUTE.equals(self):
97
+ return timedelta(minutes=interval)
98
+ elif TimeUnit.HOUR.equals(self):
99
+ return timedelta(hours=interval)
100
+ elif TimeUnit.DAY.equals(self):
101
+ return timedelta(days=interval)
102
+
103
+ raise ValueError(f"Unsupported time unit: {self}")
104
+
105
+ @property
106
+ def single_name(self):
107
+
108
+ if TimeUnit.SECOND.equals(self.value):
109
+ return "second"
110
+
111
+ if TimeUnit.MINUTE.equals(self.value):
112
+ return "minute"
113
+
114
+ if TimeUnit.HOUR.equals(self.value):
115
+ return "hour"
116
+
117
+ if TimeUnit.DAY.equals(self.value):
118
+ return "day"
119
+
120
+ @property
121
+ def plural_name(self):
122
+
123
+ if TimeUnit.SECOND.equals(self.value):
124
+ return "seconds"
125
+
126
+ if TimeUnit.MINUTE.equals(self.value):
127
+ return "minutes"
128
+
129
+ if TimeUnit.HOUR.equals(self.value):
130
+ return "hours"
131
+
132
+ if TimeUnit.DAY.equals(self.value):
133
+ return "days"
134
+
135
+ @property
136
+ def amount_of_minutes(self):
137
+ if TimeUnit.SECOND.equals(self):
138
+ return 1 / 60
139
+
140
+ if TimeUnit.MINUTE.equals(self):
141
+ return 1
142
+
143
+ if TimeUnit.HOUR.equals(self):
144
+ return 60
145
+
146
+ if TimeUnit.DAY.equals(self):
147
+ return 60 * 24
148
+
149
+ raise ValueError(f"Unsupported time unit: {self}")
@@ -0,0 +1,23 @@
1
+ class Trace:
2
+ """
3
+ Represents a trace of a trading strategy. A trace contains
4
+ data that has been generated by a trading strategy during its
5
+ execution.
6
+
7
+ The data can be used to analyze the performance of the trading after
8
+ it has been executed. Usually, the data contains metrics that
9
+ have been generated by the trading strategy during its execution,
10
+ and the signals that have been generated.
11
+ """
12
+
13
+ def __init__(
14
+ self,
15
+ strategy_id: str,
16
+ symbol: str,
17
+ data,
18
+ drop_duplicates=True
19
+ ):
20
+ self.strategy_id = strategy_id
21
+ self.symbol = symbol
22
+ self.data = data
23
+ self.drop_duplicates = drop_duplicates
@@ -0,0 +1,13 @@
1
+ from .trade import Trade
2
+ from .trade_status import TradeStatus
3
+ from .trade_stop_loss import TradeStopLoss
4
+ from .trade_take_profit import TradeTakeProfit
5
+ from .trade_risk_type import TradeRiskType
6
+
7
+ __all__ = [
8
+ "Trade",
9
+ "TradeStatus",
10
+ "TradeStopLoss",
11
+ "TradeTakeProfit",
12
+ "TradeRiskType",
13
+ ]
@@ -0,0 +1,388 @@
1
+ from dateutil.parser import parse
2
+ from datetime import timezone
3
+
4
+ from investing_algorithm_framework.domain.models.base_model import BaseModel
5
+ from investing_algorithm_framework.domain.models.order import OrderSide, Order
6
+ from investing_algorithm_framework.domain.models.trade.trade_status import \
7
+ TradeStatus
8
+ from investing_algorithm_framework.domain.models.trade.trade_stop_loss import \
9
+ TradeStopLoss
10
+ from investing_algorithm_framework.domain.models.trade\
11
+ .trade_take_profit import TradeTakeProfit
12
+
13
+
14
+ class Trade(BaseModel):
15
+ """
16
+ Trade model
17
+
18
+ A trade is a combination of a buy and sell order that has been opened or
19
+ closed.
20
+
21
+ A trade is considered opened when a buy order is executed and there is
22
+ no corresponding sell order. A trade is considered closed when a sell
23
+ order is executed and the amount of the sell order is equal or larger
24
+ to the amount of the buy order.
25
+
26
+ A single sell order can close multiple buy orders. Also, a single
27
+ buy order can be closed by multiple sell orders.
28
+
29
+ Attributes:
30
+ orders: str, the id of the buy order
31
+ target_symbol: str, the target symbol of the trade
32
+ trading_symbol: str, the trading symbol of the trade
33
+ closed_at: datetime, the datetime when the trade was closed
34
+ amount: float, the amount of the trade
35
+ available_amount: float, the available amount of the trade
36
+ remaining: float, the remaining amount that is not filled by the
37
+ buy order that opened the trade.
38
+ filled_amount: float, the filled amount of the trade by the buy
39
+ order that opened the trade.
40
+ net_gain: float, the net gain of the trade
41
+ last_reported_price: float, the last reported price of the trade
42
+ last_reported_price_datetime: datetime, the datetime when the last
43
+ reported price was reported
44
+ updated_at: datetime, the datetime when the trade was last updated
45
+ status: str, the status of the trade
46
+ metadata: dict, the metadata of the trade, this can be used to store
47
+ additional information about the trade.
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ id,
53
+ orders,
54
+ target_symbol,
55
+ trading_symbol,
56
+ closed_at,
57
+ opened_at,
58
+ open_price,
59
+ amount,
60
+ available_amount,
61
+ cost,
62
+ remaining,
63
+ filled_amount,
64
+ status,
65
+ net_gain=0,
66
+ last_reported_price=None,
67
+ last_reported_price_datetime=None,
68
+ high_water_mark=None,
69
+ high_water_mark_datetime=None,
70
+ updated_at=None,
71
+ stop_losses=None,
72
+ take_profits=None,
73
+ metadata=None,
74
+ ):
75
+ self.id = id
76
+ self.orders = orders
77
+ self.target_symbol = target_symbol
78
+ self.trading_symbol = trading_symbol
79
+ self.closed_at = closed_at
80
+ self.opened_at = opened_at
81
+ self.open_price = open_price
82
+ self.amount = amount
83
+ self.available_amount = available_amount
84
+ self.cost = cost
85
+ self.remaining = remaining
86
+ self.filled_amount = filled_amount
87
+ self.net_gain = net_gain
88
+ self.last_reported_price = last_reported_price
89
+ self.last_reported_price_datetime = last_reported_price_datetime
90
+ self.high_water_mark = high_water_mark
91
+ self.high_water_mark_datetime = high_water_mark_datetime
92
+ self.status = TradeStatus.from_value(status).value
93
+ self.updated_at = updated_at
94
+ self.stop_losses = stop_losses
95
+ self.take_profits = take_profits
96
+ self.metadata = metadata if metadata is not None else {}
97
+
98
+ def update(self, data):
99
+
100
+ if "status" in data:
101
+ self.status = TradeStatus.from_value(data["status"]).value
102
+
103
+ if TradeStatus.CLOSED.equals(self.status):
104
+
105
+ # Set all stop losses to inactive
106
+ if self.stop_losses is not None:
107
+ for stop_loss in self.stop_losses:
108
+ stop_loss.active = False
109
+
110
+ # set all take profits to inactive
111
+ if self.take_profits is not None:
112
+ for take_profit in self.take_profits:
113
+ take_profit.active = False
114
+
115
+ if "last_reported_price" in data:
116
+ self.last_reported_price = data["last_reported_price"]
117
+
118
+ if self.high_water_mark is None:
119
+ self.high_water_mark = data["last_reported_price"]
120
+ self.high_water_mark_datetime = \
121
+ data["last_reported_price_datetime"]
122
+ else:
123
+
124
+ if data["last_reported_price"] > self.high_water_mark:
125
+ self.high_water_mark = data["last_reported_price"]
126
+ self.high_water_mark_datetime = \
127
+ data["last_reported_price_datetime"]
128
+
129
+ return super().update(data)
130
+
131
+ @property
132
+ def closed_prices(self):
133
+ return [
134
+ order.price for order in self.orders
135
+ if order.order_side == OrderSide.SELL.value
136
+ ]
137
+
138
+ @property
139
+ def buy_order(self):
140
+
141
+ if self.orders is None:
142
+ return
143
+
144
+ return [
145
+ order for order in self.orders
146
+ if order.order_side == OrderSide.BUY.value
147
+ ][0]
148
+
149
+ @property
150
+ def symbol(self):
151
+ return f"{self.target_symbol.upper()}/{self.trading_symbol.upper()}"
152
+
153
+ @property
154
+ def duration(self):
155
+ """
156
+ Calculate the duration of the trade in hours.
157
+
158
+ Returns:
159
+ float: The duration of the trade in hours.
160
+ """
161
+ if TradeStatus.CLOSED.equals(self.status):
162
+ # Get the total hours between the closed and opened datetime
163
+ diff = self.closed_at - self.opened_at
164
+ return diff.total_seconds() / 3600
165
+
166
+ if self.opened_at is None:
167
+ return None
168
+
169
+ if self.updated_at is None:
170
+ return None
171
+
172
+ diff = self.updated_at - self.opened_at
173
+ return diff.total_seconds() / 3600
174
+
175
+ @property
176
+ def size(self):
177
+ return self.amount * self.open_price
178
+
179
+ @property
180
+ def change(self):
181
+ """
182
+ Property to calculate the change in value of the trade.
183
+
184
+ This is the difference between the current value of the trade
185
+ and the cost of the trade.
186
+ """
187
+ if TradeStatus.CLOSED.equals(self.status):
188
+ return self.net_gain
189
+
190
+ if self.last_reported_price is None:
191
+ return 0
192
+
193
+ if self.remaining is None or self.remaining == 0:
194
+ amount = self.amount
195
+ else:
196
+ amount = self.amount - self.remaining
197
+
198
+ cost = amount * self.open_price
199
+ gain = (amount * self.last_reported_price) - cost
200
+ return gain
201
+
202
+ @property
203
+ def net_gain_absolute(self):
204
+
205
+ if TradeStatus.CLOSED.equals(self.status):
206
+ return self.net_gain
207
+ else:
208
+ gain = 0
209
+
210
+ if self.last_reported_price is not None:
211
+ gain = (
212
+ self.available_amount *
213
+ (self.last_reported_price - self.open_price)
214
+ )
215
+
216
+ gain += self.net_gain
217
+ return gain
218
+
219
+ @property
220
+ def net_gain_percentage(self):
221
+
222
+ if TradeStatus.CLOSED.equals(self.status):
223
+
224
+ if self.cost != 0:
225
+ return (self.net_gain / self.cost) * 100
226
+
227
+ else:
228
+ gain = 0
229
+
230
+ if self.last_reported_price is not None:
231
+ gain = (
232
+ self.available_amount *
233
+ (self.last_reported_price - self.open_price)
234
+ )
235
+
236
+ gain += self.net_gain
237
+
238
+ if self.cost != 0:
239
+ return (gain / self.cost) * 100
240
+
241
+ return 0
242
+
243
+ @property
244
+ def percentage_change(self):
245
+
246
+ if TradeStatus.CLOSED.equals(self.status):
247
+
248
+ if self.cost != 0:
249
+ return (self.net_gain / self.cost) * 100
250
+
251
+ if self.last_reported_price is None:
252
+ return 0
253
+
254
+ cost = self.available_amount * self.open_price
255
+ gain = (self.available_amount * self.last_reported_price) - cost
256
+ gain += self.net_gain
257
+
258
+ if cost != 0:
259
+ return (gain / cost) * 100
260
+
261
+ return 0
262
+
263
+ def to_dict(self, datetime_format=None):
264
+ def ensure_iso(value):
265
+ if hasattr(value, "isoformat"):
266
+ if value.tzinfo is None:
267
+ value = value.replace(tzinfo=timezone.utc)
268
+ return value.isoformat()
269
+ return value
270
+
271
+ opened_at = ensure_iso(self.opened_at) if self.opened_at else None
272
+ closed_at = ensure_iso(self.closed_at) if self.closed_at else None
273
+ updated_at = ensure_iso(self.updated_at) if self.updated_at else None
274
+
275
+ # Ensure status is a string
276
+ self.status = TradeStatus.from_value(self.status).value
277
+
278
+ return {
279
+ "id": self.id,
280
+ "orders": [
281
+ order.to_dict(datetime_format=datetime_format)
282
+ for order in self.orders
283
+ ],
284
+ "target_symbol": self.target_symbol,
285
+ "trading_symbol": self.trading_symbol,
286
+ "status": self.status,
287
+ "amount": self.amount,
288
+ "remaining": self.remaining if self.remaining is not None else 0,
289
+ "open_price": self.open_price,
290
+ "last_reported_price": self.last_reported_price,
291
+ "opened_at": opened_at,
292
+ "closed_at": closed_at,
293
+ "updated_at": updated_at,
294
+ "net_gain": self.net_gain if self.net_gain is not None else 0,
295
+ "cost": self.cost if self.cost is not None else 0,
296
+ "stop_losses": [
297
+ stop_loss.to_dict(datetime_format=datetime_format)
298
+ for stop_loss in self.stop_losses
299
+ ] if self.stop_losses else None,
300
+ "take_profits": [
301
+ take_profit.to_dict(datetime_format=datetime_format)
302
+ for take_profit in self.take_profits
303
+ ] if self.take_profits else None,
304
+ "filled_amount": self.filled_amount,
305
+ "available_amount": self.available_amount,
306
+ "metadata": self.metadata if self.metadata else {},
307
+ }
308
+
309
+ @staticmethod
310
+ def from_dict(data):
311
+ opened_at = None
312
+ closed_at = None
313
+ updated_at = None
314
+ stop_losses = None
315
+ take_profits = None
316
+ orders = None
317
+
318
+ if "opened_at" in data and data["opened_at"] is not None:
319
+ opened_at = parse(data["opened_at"])
320
+
321
+ if "closed_at" in data and data["closed_at"] is not None:
322
+ closed_at = parse(data["closed_at"])
323
+
324
+ if "updated_at" in data and data["updated_at"] is not None:
325
+ updated_at = parse(data["updated_at"])
326
+
327
+ if "stop_losses" in data and data["stop_losses"] is not None:
328
+ stop_losses = [
329
+ TradeStopLoss.from_dict(stop_loss)
330
+ for stop_loss in data["stop_losses"]
331
+ ]
332
+
333
+ if "take_profits" in data and data["take_profits"] is not None:
334
+ take_profits = [
335
+ TradeTakeProfit.from_dict(take_profit)
336
+ for take_profit in data["take_profits"]
337
+ ]
338
+
339
+ if "orders" in data and data["orders"] is not None:
340
+ orders = [
341
+ Order.from_dict(order)
342
+ for order in data["orders"]
343
+ ]
344
+ return Trade(
345
+ id=data.get("id", None),
346
+ orders=orders,
347
+ target_symbol=data["target_symbol"],
348
+ trading_symbol=data["trading_symbol"],
349
+ amount=data["amount"],
350
+ open_price=data["open_price"],
351
+ opened_at=opened_at,
352
+ closed_at=closed_at,
353
+ filled_amount=data.get("filled_amount", 0),
354
+ available_amount=data.get("available_amount", 0),
355
+ remaining=data.get("remaining", 0),
356
+ net_gain=data.get("net_gain", 0),
357
+ last_reported_price=data.get("last_reported_price"),
358
+ status=TradeStatus.from_value(data["status"]).value,
359
+ cost=data.get("cost", 0),
360
+ updated_at=updated_at,
361
+ stop_losses=stop_losses,
362
+ take_profits=take_profits,
363
+ metadata=data.get("metadata", {}),
364
+ )
365
+
366
+ def __repr__(self):
367
+ return self.repr(
368
+ id=self.id,
369
+ symbol=self.symbol,
370
+ target_symbol=self.target_symbol,
371
+ trading_symbol=self.trading_symbol,
372
+ status=self.status,
373
+ amount=self.amount,
374
+ available_amount=self.available_amount,
375
+ filled_amount=self.filled_amount,
376
+ remaining=self.remaining,
377
+ open_price=self.open_price,
378
+ opened_at=self.opened_at,
379
+ closed_at=self.closed_at,
380
+ net_gain=self.net_gain,
381
+ last_reported_price=self.last_reported_price,
382
+ updated_at=self.updated_at,
383
+ metadata=self.metadata,
384
+ )
385
+
386
+ def __lt__(self, other):
387
+ # Define the less-than comparison based on created_at attribute
388
+ return self.opened_at < other.opened_at