investing-algorithm-framework 3.7.0__py3-none-any.whl → 7.19.15__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 (256) hide show
  1. investing_algorithm_framework/__init__.py +168 -45
  2. investing_algorithm_framework/app/__init__.py +32 -1
  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 +1933 -589
  12. investing_algorithm_framework/app/app_hook.py +28 -0
  13. investing_algorithm_framework/app/context.py +1725 -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/action_handlers/__init__.py +4 -2
  37. investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
  38. investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +1 -1
  39. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
  40. investing_algorithm_framework/app/strategy.py +664 -84
  41. investing_algorithm_framework/app/task.py +5 -3
  42. investing_algorithm_framework/app/web/__init__.py +2 -1
  43. investing_algorithm_framework/app/web/create_app.py +4 -2
  44. investing_algorithm_framework/cli/__init__.py +0 -0
  45. investing_algorithm_framework/cli/cli.py +226 -0
  46. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
  47. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  48. investing_algorithm_framework/cli/initialize_app.py +603 -0
  49. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  50. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  51. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  52. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  53. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  54. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  55. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  56. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  57. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  58. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  59. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  60. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  61. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  62. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  63. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  64. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  65. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  66. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  67. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  68. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  69. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  70. investing_algorithm_framework/create_app.py +40 -6
  71. investing_algorithm_framework/dependency_container.py +72 -56
  72. investing_algorithm_framework/domain/__init__.py +71 -47
  73. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  74. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  75. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  76. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  77. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  78. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  79. investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
  80. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  81. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  82. investing_algorithm_framework/domain/config.py +59 -91
  83. investing_algorithm_framework/domain/constants.py +13 -38
  84. investing_algorithm_framework/domain/data_provider.py +334 -0
  85. investing_algorithm_framework/domain/data_structures.py +3 -2
  86. investing_algorithm_framework/domain/exceptions.py +51 -1
  87. investing_algorithm_framework/domain/models/__init__.py +17 -12
  88. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  89. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  90. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  91. investing_algorithm_framework/domain/models/event.py +35 -0
  92. investing_algorithm_framework/domain/models/market/market_credential.py +55 -1
  93. investing_algorithm_framework/domain/models/order/order.py +77 -83
  94. investing_algorithm_framework/domain/models/order/order_status.py +2 -2
  95. investing_algorithm_framework/domain/models/order/order_type.py +1 -3
  96. investing_algorithm_framework/domain/models/portfolio/portfolio.py +81 -3
  97. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +26 -3
  98. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +108 -11
  99. investing_algorithm_framework/domain/models/position/__init__.py +2 -1
  100. investing_algorithm_framework/domain/models/position/position.py +12 -0
  101. investing_algorithm_framework/domain/models/position/position_size.py +41 -0
  102. investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
  103. investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
  104. investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
  105. investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
  106. investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
  107. investing_algorithm_framework/domain/models/time_frame.py +37 -0
  108. investing_algorithm_framework/domain/models/time_interval.py +33 -0
  109. investing_algorithm_framework/domain/models/time_unit.py +66 -2
  110. investing_algorithm_framework/domain/models/trade/__init__.py +8 -1
  111. investing_algorithm_framework/domain/models/trade/trade.py +295 -171
  112. investing_algorithm_framework/domain/models/trade/trade_status.py +9 -2
  113. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
  114. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
  115. investing_algorithm_framework/domain/order_executor.py +112 -0
  116. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  117. investing_algorithm_framework/domain/services/__init__.py +2 -9
  118. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +0 -6
  119. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  120. investing_algorithm_framework/domain/strategy.py +1 -29
  121. investing_algorithm_framework/domain/utils/__init__.py +12 -7
  122. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  123. investing_algorithm_framework/domain/utils/dates.py +57 -0
  124. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  125. investing_algorithm_framework/domain/utils/polars.py +53 -0
  126. investing_algorithm_framework/domain/utils/random.py +29 -0
  127. investing_algorithm_framework/download_data.py +108 -0
  128. investing_algorithm_framework/infrastructure/__init__.py +31 -18
  129. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  130. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
  131. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  132. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  133. investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
  134. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +86 -12
  135. investing_algorithm_framework/infrastructure/models/__init__.py +6 -11
  136. investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -1
  137. investing_algorithm_framework/infrastructure/models/order/order.py +35 -49
  138. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  139. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  140. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +1 -1
  141. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +8 -0
  142. investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +17 -5
  143. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  144. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  145. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
  146. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
  147. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  148. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  149. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  150. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  151. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  152. investing_algorithm_framework/infrastructure/repositories/__init__.py +8 -0
  153. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  154. investing_algorithm_framework/infrastructure/repositories/order_repository.py +5 -0
  155. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +1 -1
  156. investing_algorithm_framework/infrastructure/repositories/position_repository.py +11 -0
  157. investing_algorithm_framework/infrastructure/repositories/repository.py +81 -27
  158. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  159. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
  160. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
  161. investing_algorithm_framework/infrastructure/services/__init__.py +4 -4
  162. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  163. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
  164. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  165. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  166. investing_algorithm_framework/services/__init__.py +113 -16
  167. investing_algorithm_framework/services/backtesting/__init__.py +0 -7
  168. investing_algorithm_framework/services/backtesting/backtest_service.py +566 -359
  169. investing_algorithm_framework/services/configuration_service.py +77 -11
  170. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  171. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  172. investing_algorithm_framework/services/market_credential_service.py +16 -1
  173. investing_algorithm_framework/services/metrics/__init__.py +114 -0
  174. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  175. investing_algorithm_framework/services/metrics/beta.py +0 -0
  176. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  177. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  178. investing_algorithm_framework/services/metrics/drawdown.py +181 -0
  179. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  180. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  181. investing_algorithm_framework/services/metrics/generate.py +358 -0
  182. investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
  183. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  184. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  185. investing_algorithm_framework/services/metrics/returns.py +452 -0
  186. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  187. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  188. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  189. investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
  190. investing_algorithm_framework/services/metrics/trades.py +500 -0
  191. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  192. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  193. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  194. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  195. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  196. investing_algorithm_framework/services/order_service/__init__.py +3 -1
  197. investing_algorithm_framework/services/order_service/order_backtest_service.py +76 -89
  198. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  199. investing_algorithm_framework/services/order_service/order_service.py +407 -326
  200. investing_algorithm_framework/services/portfolios/__init__.py +3 -1
  201. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +37 -3
  202. investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +22 -8
  203. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  204. investing_algorithm_framework/services/portfolios/portfolio_service.py +96 -28
  205. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +97 -28
  206. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +116 -313
  207. investing_algorithm_framework/services/positions/__init__.py +7 -0
  208. investing_algorithm_framework/services/positions/position_service.py +210 -0
  209. investing_algorithm_framework/services/repository_service.py +8 -2
  210. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  211. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
  212. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
  213. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
  214. investing_algorithm_framework/services/trade_service/__init__.py +7 -1
  215. investing_algorithm_framework/services/trade_service/trade_service.py +1013 -315
  216. investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
  217. investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
  218. investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
  219. investing_algorithm_framework-7.19.15.dist-info/RECORD +263 -0
  220. investing_algorithm_framework-7.19.15.dist-info/entry_points.txt +3 -0
  221. investing_algorithm_framework/app/algorithm.py +0 -1105
  222. investing_algorithm_framework/domain/graphs.py +0 -382
  223. investing_algorithm_framework/domain/metrics/__init__.py +0 -6
  224. investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -11
  225. investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -43
  226. investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
  227. investing_algorithm_framework/domain/models/backtesting/backtest_report.py +0 -580
  228. investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -243
  229. investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
  230. investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
  231. investing_algorithm_framework/domain/services/market_data_sources.py +0 -344
  232. investing_algorithm_framework/domain/services/market_service.py +0 -153
  233. investing_algorithm_framework/domain/singleton.py +0 -9
  234. investing_algorithm_framework/domain/utils/backtesting.py +0 -472
  235. investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -12
  236. investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -559
  237. investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -254
  238. investing_algorithm_framework/infrastructure/models/market_data_sources/us_treasury_yield.py +0 -47
  239. investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
  240. investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -455
  241. investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
  242. investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
  243. investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -350
  244. investing_algorithm_framework/services/backtesting/backtest_report_writer_service.py +0 -53
  245. investing_algorithm_framework/services/backtesting/graphs.py +0 -61
  246. investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -8
  247. investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -150
  248. investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -189
  249. investing_algorithm_framework/services/position_service.py +0 -31
  250. investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -264
  251. investing_algorithm_framework-3.7.0.dist-info/METADATA +0 -339
  252. investing_algorithm_framework-3.7.0.dist-info/RECORD +0 -147
  253. /investing_algorithm_framework/{domain → services}/metrics/price_efficiency.py +0 -0
  254. /investing_algorithm_framework/services/{position_snapshot_service.py → positions/position_snapshot_service.py} +0 -0
  255. {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
  256. {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
@@ -0,0 +1,214 @@
1
+ from dataclasses import dataclass
2
+ from datetime import datetime, timezone, timedelta
3
+ from typing import Union
4
+
5
+ from dateutil import parser
6
+
7
+ from investing_algorithm_framework.domain.models.time_frame import TimeFrame
8
+ from .data_type import DataType
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class DataSource:
13
+ """
14
+ Base class for data sources.
15
+ """
16
+ identifier: str = None
17
+ data_provider_identifier: str = None
18
+ data_type: Union[DataType, str] = None
19
+ symbol: str = None
20
+ window_size: int = None
21
+ time_frame: Union[TimeFrame, str] = None
22
+ market: str = None
23
+ storage_path: str = None
24
+ pandas: bool = False
25
+ date: Union[datetime, None] = None
26
+ start_date: Union[datetime, None] = None
27
+ end_date: Union[datetime, None] = None
28
+ save: bool = False
29
+
30
+ def __post_init__(self):
31
+ # Convert data_type and time_frame to their respective enums if needed
32
+ if isinstance(self.data_type, str):
33
+ object.__setattr__(self, 'data_type',
34
+ DataType.from_string(self.data_type))
35
+
36
+ if isinstance(self.time_frame, str):
37
+ object.__setattr__(self, 'time_frame',
38
+ TimeFrame.from_string(self.time_frame))
39
+
40
+ start_date = self.start_date
41
+ end_date = self.end_date
42
+
43
+ # Parse the start_date if it is a string and
44
+ # make sure its set to timezone utc
45
+ if start_date is None:
46
+
47
+ if isinstance(self.start_date, str):
48
+ start_date = parser.parse(start_date)
49
+
50
+ if start_date is not None:
51
+ object.__setattr__(
52
+ self,
53
+ 'start_date',
54
+ start_date.replace(tzinfo=timezone.utc)
55
+ )
56
+
57
+ # Parse the end_date if it is a string and
58
+ # make sure its set to timezone utc
59
+ if end_date is None:
60
+
61
+ if isinstance(self.end_date, str):
62
+ end_date = parser.parse(end_date)
63
+
64
+ if end_date is not None:
65
+ object.__setattr__(
66
+ self,
67
+ 'end_date',
68
+ end_date.replace(tzinfo=timezone.utc)
69
+ )
70
+
71
+ if self.market is not None:
72
+ object.__setattr__(self, 'market', self.market.upper())
73
+
74
+ if self.symbol is not None:
75
+ object.__setattr__(self, 'symbol', self.symbol.upper())
76
+
77
+ def get_identifier(self):
78
+ """
79
+ Returns the identifier or creates a unique identifier for the
80
+ data source based on its attributes.
81
+ """
82
+
83
+ if self.identifier is not None:
84
+ return self.identifier
85
+
86
+ if DataType.OHLCV.equals(self.data_type):
87
+ return (f"{self.data_type.value}_{self.market}_"
88
+ f"{self.symbol}_{self.time_frame.value}")
89
+
90
+ elif DataType.CUSTOM.equals(self.data_type):
91
+ identifier = "CUSTOM"
92
+
93
+ if self.symbol is not None:
94
+ identifier += f"_{self.symbol}"
95
+
96
+ if self.time_frame is not None:
97
+ identifier += f"_{self.time_frame.value}"
98
+
99
+ if self.market is not None:
100
+ identifier += f"_{self.market}"
101
+
102
+ if self.window_size is not None:
103
+ identifier += f"_{self.window_size}"
104
+
105
+ return identifier
106
+
107
+ def to_dict(self):
108
+ """
109
+ Converts the DataSource instance to a dictionary.
110
+ """
111
+ non_null_attributes = {
112
+ key: value for key, value in self.__dict__.items()
113
+ if value is not None
114
+ }
115
+ # Convert DataType and TimeFrame to their string representations
116
+ if self.data_type is not None:
117
+ non_null_attributes['data_type'] = self.data_type.value
118
+ if self.time_frame is not None:
119
+ non_null_attributes['time_frame'] = self.time_frame.value
120
+
121
+ return non_null_attributes
122
+
123
+ def __repr__(self):
124
+ return (
125
+ f"DataSource(identifier={self.identifier}, "
126
+ f"data_provider_identifier={self.data_provider_identifier}, "
127
+ f"data_type={self.data_type}, symbol={self.symbol}, "
128
+ f"window_size={self.window_size}, time_frame={self.time_frame}, "
129
+ f"market={self.market}, storage_path={self.storage_path}, "
130
+ f"pandas={self.pandas}, date={self.date}, "
131
+ f"start_date={self.start_date}, end_date={self.end_date}, "
132
+ f"save={self.save})"
133
+ )
134
+
135
+ def __eq__(self, other):
136
+ """
137
+ Compares two DataSource instances for equality.
138
+
139
+ OHLCV data sources are considered equal if they have:
140
+ - The same data_type (OHLCV), symbol, time_frame, and market.
141
+ - If no market and timeframe is specified, then
142
+ they are considered equal for the same symbol
143
+ and data_type.
144
+ """
145
+ if DataType.OHLCV.equals(self.data_type):
146
+
147
+ if other.time_frame is None and other.window_size is None:
148
+ return (self.data_type == other.data_type and
149
+ self.symbol == other.symbol)
150
+ elif self.time_frame is None and self.window_size is None:
151
+ return (self.data_type == other.data_type and
152
+ self.symbol == other.symbol)
153
+
154
+ return (self.time_frame == other.time_frame and
155
+ self.market == other.market and
156
+ self.symbol == other.symbol and
157
+ self.data_type == other.data_type)
158
+
159
+ elif DataType.CUSTOM.equals(self.data_type):
160
+ return (self.data_type == other.data_type and
161
+ self.symbol == other.symbol and
162
+ self.window_size == other.window_size and
163
+ self.time_frame == other.time_frame and
164
+ self.market == other.market)
165
+
166
+ elif DataType.TICKER.equals(self.data_type):
167
+ return (self.data_type == other.data_type and
168
+ self.symbol == other.symbol and
169
+ self.market == other.market)
170
+
171
+ return False
172
+
173
+ def create_start_date_data(self, index_date: datetime) -> datetime:
174
+
175
+ if self.window_size is None or self.time_frame is None:
176
+ return index_date
177
+
178
+ return index_date - \
179
+ (self.window_size * timedelta(
180
+ minutes=self.time_frame.amount_of_minutes
181
+ ))
182
+
183
+ def get_number_of_required_data_points(
184
+ self, start_date: datetime, end_date: datetime
185
+ ) -> int:
186
+ """
187
+ Returns the number of data points required based on the given
188
+ attributes of the data source. If the required number of data points
189
+ can't be determined, it returns None.
190
+
191
+ E.g., for OHLCV data source, it
192
+ calculates the number of data points needed between the
193
+ start_date and end_date based on the time frame.
194
+
195
+ Args:
196
+ start_date (datetime): The start date for the data points.
197
+ end_date (datetime): The end date for the data points.
198
+
199
+ Returns:
200
+ int: The number of required data points, or None if it can't
201
+ be determined.
202
+ """
203
+
204
+ if self.time_frame is None:
205
+ return None
206
+
207
+ delta = end_date - start_date
208
+ total_minutes = delta.total_seconds() / 60
209
+ data_points = total_minutes / self.time_frame.amount_of_minutes
210
+
211
+ if self.window_size is not None:
212
+ data_points += self.window_size
213
+
214
+ return int(data_points)
@@ -0,0 +1,46 @@
1
+ from enum import Enum
2
+
3
+
4
+ class DataType(Enum):
5
+ OHLCV = "OHLCV"
6
+ TICKER = "TICKER"
7
+ ORDER_BOOK = "ORDER_BOOK"
8
+ CUSTOM = "CUSTOM"
9
+
10
+ @staticmethod
11
+ def from_string(value: str):
12
+
13
+ if isinstance(value, str):
14
+
15
+ for entry in DataType:
16
+
17
+ if value.upper() == entry.value:
18
+ return entry
19
+
20
+ raise ValueError(
21
+ f"Could not convert {value} to DataType"
22
+ )
23
+
24
+ @staticmethod
25
+ def from_value(value):
26
+
27
+ if isinstance(value, str):
28
+ return DataType.from_string(value)
29
+
30
+ if isinstance(value, DataType):
31
+
32
+ for entry in DataType:
33
+
34
+ if value == entry:
35
+ return entry
36
+
37
+ raise ValueError(
38
+ f"Could not convert {value} to TimeFrame"
39
+ )
40
+
41
+ def equals(self, other):
42
+
43
+ if isinstance(other, Enum):
44
+ return self.value == other.value
45
+ else:
46
+ return DataType.from_string(other) == self
@@ -0,0 +1,35 @@
1
+ from enum import Enum
2
+
3
+
4
+ class Event(Enum):
5
+ PORTFOLIO_CREATED = "PORTFOLIO_CREATED"
6
+ ORDER_CREATED = "ORDER_CREATED"
7
+ TRADE_CLOSED = "TRADE_CLOSED"
8
+ STRATEGY_RUN = "STRATEGY_RUN"
9
+
10
+ @staticmethod
11
+ def from_string(value: str):
12
+
13
+ if isinstance(value, str):
14
+ for status in Event:
15
+
16
+ if value.upper() == status.value:
17
+ return status
18
+
19
+ raise ValueError("Could not convert value to Event")
20
+
21
+ @staticmethod
22
+ def from_value(value):
23
+
24
+ if isinstance(value, Event):
25
+ for status in Event:
26
+
27
+ if value == status:
28
+ return status
29
+ elif isinstance(value, str):
30
+ return Event.from_string(value)
31
+
32
+ raise ValueError(f"Could not convert value {value} to Event")
33
+
34
+ def equals(self, other):
35
+ return Event.from_value(other) == self
@@ -1,9 +1,63 @@
1
+ import os
2
+ import logging
3
+
4
+ from investing_algorithm_framework.domain.exceptions import \
5
+ OperationalException
6
+
7
+ logger = logging.getLogger("investing_algorithm_framework")
8
+
9
+
1
10
  class MarketCredential:
2
- def __init__(self, api_key: str, secret_key: str, market: str):
11
+ """
12
+ Market credential model to store the api key and secret key for a market.
13
+ """
14
+ def __init__(
15
+ self, market: str, api_key: str = None, secret_key: str = None
16
+ ):
3
17
  self._api_key = api_key
4
18
  self._secret_key = secret_key
5
19
  self._market = market
6
20
 
21
+ def initialize(self):
22
+ """
23
+ Internal helper to initialize the market credential.
24
+ """
25
+ logger.info(f"Initializing market credential for {self.market}")
26
+
27
+ if self.api_key is None:
28
+ logger.info(
29
+ "Reading api key from environment variable"
30
+ f" {self.market.upper()}_API_KEY"
31
+ )
32
+
33
+ # Check if environment variable is set
34
+ environment_variable = f"{self.market.upper()}_API_KEY"
35
+ self._api_key = os.getenv(environment_variable)
36
+
37
+ if self.api_key is None:
38
+ raise OperationalException(
39
+ f"Market credential for market {self.market}"
40
+ " requires an api key, either"
41
+ " as an argument or as an environment variable"
42
+ f" named as {self._market.upper()}_API_KEY"
43
+ )
44
+
45
+ if self.secret_key is None:
46
+ logger.info(
47
+ "Reading secret key from environment variable"
48
+ f" {self.market.upper()}_SECRET_KEY"
49
+ )
50
+
51
+ # Check if environment variable is set
52
+ environment_variable = f"{self.market.upper()}_SECRET_KEY"
53
+ self._secret_key = os.getenv(environment_variable)
54
+
55
+ if self.api_key is not None:
56
+ self._api_key = self.api_key.strip()
57
+
58
+ if self.secret_key is not None:
59
+ self._secret_key = self.secret_key.strip()
60
+
7
61
  def get_api_key(self):
8
62
  return self.api_key
9
63
 
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ from datetime import datetime, timezone
2
3
 
3
4
  from dateutil.parser import parse
4
5
 
@@ -23,26 +24,24 @@ class Order(BaseModel):
23
24
  self,
24
25
  order_type,
25
26
  order_side,
26
- status,
27
27
  amount,
28
+ status: OrderStatus | None | str = OrderStatus.CREATED.value,
28
29
  target_symbol=None,
29
30
  trading_symbol=None,
30
31
  price=None,
31
- net_gain=0,
32
32
  created_at=None,
33
33
  updated_at=None,
34
- trade_closed_at=None,
35
- trade_closed_price=None,
36
- trade_closed_amount=None,
37
34
  external_id=None,
35
+ cost=None,
38
36
  filled=None,
39
37
  remaining=None,
40
- cost=None,
41
38
  fee=None,
42
39
  position_id=None,
43
40
  order_fee=None,
44
41
  order_fee_currency=None,
45
- order_fee_rate=None
42
+ order_fee_rate=None,
43
+ id=None,
44
+ metadata=None,
46
45
  ):
47
46
  if target_symbol is None:
48
47
  raise OperationalException("Target symbol is not specified")
@@ -62,6 +61,15 @@ class Order(BaseModel):
62
61
  if status is None:
63
62
  raise OperationalException("Status is not set")
64
63
 
64
+ self.created_at = created_at
65
+ self.updated_at = updated_at
66
+
67
+ if self.created_at is None:
68
+ self.created_at = datetime.now(tz=timezone.utc)
69
+
70
+ if self.updated_at is None:
71
+ self.updated_at = datetime.now(tz=timezone.utc)
72
+
65
73
  self.external_id = external_id
66
74
  self.price = price
67
75
  self.order_side = OrderSide.from_value(order_side).value
@@ -69,20 +77,16 @@ class Order(BaseModel):
69
77
  self.status = OrderStatus.from_value(status).value
70
78
  self.position_id = position_id
71
79
  self.amount = amount
72
- self.net_gain = net_gain
73
- self.trade_closed_at = trade_closed_at
74
- self.trade_closed_price = trade_closed_price
75
- self.trade_closed_amount = trade_closed_amount
76
- self.created_at = created_at
77
- self.updated_at = updated_at
78
80
  self.filled = filled
79
81
  self.remaining = remaining
80
- self.cost = cost
81
82
  self.fee = fee
82
83
  self._available_amount = self.filled
83
84
  self.order_fee = order_fee
84
85
  self.order_fee_currency = order_fee_currency
85
86
  self.order_fee_rate = order_fee_rate
87
+ self.id = id
88
+ self.cost = cost
89
+ self.metadata = metadata if metadata is not None else {}
86
90
 
87
91
  def get_id(self):
88
92
  return self.id
@@ -147,38 +151,6 @@ class Order(BaseModel):
147
151
  def set_external_id(self, external_id):
148
152
  self.external_id = external_id
149
153
 
150
- def get_net_gain(self):
151
-
152
- if self.net_gain is None:
153
- return 0
154
-
155
- return self.net_gain
156
-
157
- def set_net_gain(self, net_gain):
158
- self.net_gain = net_gain
159
-
160
- def get_trade_closed_at(self):
161
- return self.trade_closed_at
162
-
163
- def set_trade_closed_at(self, trade_closed_at):
164
- self.trade_closed_at = trade_closed_at
165
-
166
- def get_trade_closed_price(self):
167
- return self.trade_closed_price
168
-
169
- def set_trade_closed_price(self, trade_closed_price):
170
- self.trade_closed_price = trade_closed_price
171
-
172
- def get_trade_closed_amount(self):
173
-
174
- if self.trade_closed_amount is not None:
175
- return self.trade_closed_amount
176
-
177
- return 0
178
-
179
- def set_trade_closed_amount(self, trade_closed_amount):
180
- self.trade_closed_amount = trade_closed_amount
181
-
182
154
  def get_created_at(self):
183
155
  return self.created_at
184
156
 
@@ -204,23 +176,13 @@ class Order(BaseModel):
204
176
  def get_remaining(self):
205
177
 
206
178
  if self.remaining is None:
207
- return self.amount
179
+ return self.get_amount() - self.get_filled()
208
180
 
209
181
  return self.remaining
210
182
 
211
183
  def set_remaining(self, remaining):
212
184
  self.remaining = remaining
213
185
 
214
- def get_cost(self):
215
-
216
- if self.cost is None:
217
- return 0
218
-
219
- return self.cost
220
-
221
- def set_cost(self, cost):
222
- self.cost = cost
223
-
224
186
  def get_fee(self):
225
187
  return self.fee
226
188
 
@@ -228,7 +190,13 @@ class Order(BaseModel):
228
190
  self.fee = order_fee
229
191
 
230
192
  def get_symbol(self):
231
- return self.get_target_symbol() + "/" + self.get_trading_symbol()
193
+ return (self.get_target_symbol().upper() + "/"
194
+ + self.get_trading_symbol().upper())
195
+
196
+ @property
197
+ def symbol(self):
198
+ return (self.get_target_symbol().upper() + "/"
199
+ + self.get_trading_symbol().upper())
232
200
 
233
201
  def get_available_amount(self):
234
202
 
@@ -249,20 +217,33 @@ class Order(BaseModel):
249
217
  self.set_available_amount(available_amount)
250
218
 
251
219
  def to_dict(self, datetime_format=None):
220
+ """
221
+ Convert the Order object to a dictionary
252
222
 
253
- if datetime_format is not None:
254
- created_at = self.created_at.strftime(datetime_format) \
255
- if self.created_at else None
256
- updated_at = self.updated_at.strftime(datetime_format) \
257
- if self.updated_at else None
258
- trade_closed_at = self.trade_closed_at.strftime(datetime_format) \
259
- if self.trade_closed_at else None
260
- else:
261
- created_at = self.created_at
262
- updated_at = self.updated_at
263
- trade_closed_at = self.trade_closed_at
223
+ Args:
224
+ datetime_format (str): The format to use for the datetime fields.
225
+ If None, the datetime fields will be returned as is.
226
+ Defaults to None.
227
+
228
+ Returns:
229
+ dict: A dictionary representation of the Order object.
230
+ """
231
+
232
+ def ensure_iso(value):
233
+ if hasattr(value, "isoformat"):
234
+ if value.tzinfo is None:
235
+ value = value.replace(tzinfo=timezone.utc)
236
+ return value.isoformat()
237
+ return value
238
+
239
+ created_at = ensure_iso(self.created_at) if self.created_at else None
240
+ updated_at = ensure_iso(self.updated_at) if self.updated_at else None
241
+
242
+ # Ensure status is a string
243
+ self.status = OrderStatus.from_value(self.status).value
264
244
 
265
245
  return {
246
+ "id": self.id,
266
247
  "external_id": self.external_id,
267
248
  "target_symbol": self.target_symbol,
268
249
  "trading_symbol": self.trading_symbol,
@@ -271,17 +252,15 @@ class Order(BaseModel):
271
252
  "status": self.status,
272
253
  "price": self.price,
273
254
  "amount": self.amount,
274
- "net_gain": self.net_gain,
275
- "trade_closed_at": trade_closed_at,
276
- "trade_closed_price": self.trade_closed_price,
277
255
  "created_at": created_at,
278
256
  "updated_at": updated_at,
257
+ "cost": self.cost,
279
258
  "filled": self.filled,
280
259
  "remaining": self.remaining,
281
- "cost": self.cost,
282
260
  "order_fee_currency": self.order_fee_currency,
283
261
  "order_fee_rate": self.order_fee_rate,
284
262
  "order_fee": self.order_fee,
263
+ "metadata": self.metadata if hasattr(self, 'metadata') else {},
285
264
  }
286
265
 
287
266
  @staticmethod
@@ -303,8 +282,9 @@ class Order(BaseModel):
303
282
  if updated_at is not None:
304
283
  updated_at = parse(updated_at)
305
284
 
306
- return Order(
307
- external_id=data.get("id", None),
285
+ order = Order(
286
+ id=data.get("id", None),
287
+ external_id=data.get("external_id", None),
308
288
  target_symbol=target_symbol,
309
289
  trading_symbol=trading_symbol,
310
290
  price=data.get("price", None),
@@ -314,14 +294,16 @@ class Order(BaseModel):
314
294
  order_side=data.get("order_side", None),
315
295
  filled=data.get("filled", None),
316
296
  remaining=data.get("remaining", None),
317
- cost=data.get("cost", None),
318
297
  fee=data.get("fee", None),
298
+ cost=data.get("cost", None),
319
299
  created_at=created_at,
320
300
  updated_at=updated_at,
321
301
  order_fee=data.get("order_fee", None),
322
302
  order_fee_currency=data.get("order_fee_currency", None),
323
303
  order_fee_rate=data.get("order_fee_rate", None),
304
+ metadata=data.get("metadata", {}),
324
305
  )
306
+ return order
325
307
 
326
308
  @staticmethod
327
309
  def from_ccxt_order(ccxt_order):
@@ -343,6 +325,13 @@ class Order(BaseModel):
343
325
  order_fee_currency = ccxt_fee.get("currency", None)
344
326
  order_fee_rate = ccxt_fee.get("rate", None)
345
327
 
328
+ created_at = ccxt_order.get("datetime", None)
329
+ created_at = parse(created_at) if created_at else None
330
+
331
+ # Make sure that the created_at is a utc datetime object
332
+ if created_at and created_at.tzinfo is None:
333
+ created_at = created_at.replace(tzinfo=timezone.utc)
334
+
346
335
  return Order(
347
336
  external_id=ccxt_order.get("id", None),
348
337
  target_symbol=target_symbol,
@@ -350,15 +339,15 @@ class Order(BaseModel):
350
339
  price=ccxt_order.get("price", None),
351
340
  amount=ccxt_order.get("amount", None),
352
341
  status=status,
342
+ cost=ccxt_order.get("cost", None),
353
343
  order_type=ccxt_order.get("type", None),
354
344
  order_side=ccxt_order.get("side", None),
355
345
  filled=ccxt_order.get("filled", None),
356
346
  remaining=ccxt_order.get("remaining", None),
357
- cost=ccxt_order.get("cost", None),
358
347
  order_fee=order_fee,
359
348
  order_fee_currency=order_fee_currency,
360
349
  order_fee_rate=order_fee_rate,
361
- created_at=parse(ccxt_order.get("datetime", None))
350
+ created_at=created_at
362
351
  )
363
352
 
364
353
  def __repr__(self):
@@ -372,7 +361,6 @@ class Order(BaseModel):
372
361
  id=id_value,
373
362
  price=self.get_price(),
374
363
  amount=self.get_amount(),
375
- net_gain=self.get_net_gain(),
376
364
  external_id=self.external_id,
377
365
  status=self.status,
378
366
  target_symbol=self.target_symbol,
@@ -381,10 +369,16 @@ class Order(BaseModel):
381
369
  order_type=self.order_type,
382
370
  filled=self.get_filled(),
383
371
  remaining=self.get_remaining(),
384
- cost=self.get_cost(),
385
- trade_closed_at=self.get_trade_closed_at(),
386
- trade_closed_price=self.get_trade_closed_price(),
387
- trade_closed_amount=self.get_trade_closed_amount(),
388
372
  created_at=self.get_created_at(),
389
373
  updated_at=self.get_updated_at(),
390
374
  )
375
+
376
+ def get_size(self):
377
+ """
378
+ Get the size of the order
379
+
380
+ Returns:
381
+ float: The size of the order
382
+ """
383
+ return self.get_amount() * self.get_price() \
384
+ if self.get_price() is not None else 0
@@ -18,7 +18,7 @@ class OrderStatus(Enum):
18
18
  if value.upper() == order_type.value:
19
19
  return order_type
20
20
 
21
- raise ValueError("Could not convert value to OrderStatus")
21
+ raise ValueError(f"Could not convert value {value} to OrderStatus")
22
22
 
23
23
  @staticmethod
24
24
  def from_value(value):
@@ -31,7 +31,7 @@ class OrderStatus(Enum):
31
31
  elif isinstance(value, str):
32
32
  return OrderStatus.from_string(value)
33
33
 
34
- raise ValueError("Could not convert value to OrderStatus")
34
+ raise ValueError(f"Could not convert value: {value} to OrderStatus")
35
35
 
36
36
  def equals(self, other):
37
37
  return OrderStatus.from_value(other) == self