investing-algorithm-framework 6.9.1__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 (192) hide show
  1. investing_algorithm_framework/__init__.py +147 -44
  2. investing_algorithm_framework/app/__init__.py +23 -6
  3. investing_algorithm_framework/app/algorithm/algorithm.py +5 -41
  4. investing_algorithm_framework/app/algorithm/algorithm_factory.py +17 -10
  5. investing_algorithm_framework/app/analysis/__init__.py +15 -0
  6. investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
  7. investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
  8. investing_algorithm_framework/app/analysis/permutation.py +116 -0
  9. investing_algorithm_framework/app/analysis/ranking.py +297 -0
  10. investing_algorithm_framework/app/app.py +1322 -707
  11. investing_algorithm_framework/app/context.py +196 -88
  12. investing_algorithm_framework/app/eventloop.py +590 -0
  13. investing_algorithm_framework/app/reporting/__init__.py +16 -5
  14. investing_algorithm_framework/app/reporting/ascii.py +57 -202
  15. investing_algorithm_framework/app/reporting/backtest_report.py +284 -170
  16. investing_algorithm_framework/app/reporting/charts/__init__.py +10 -2
  17. investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
  18. investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
  19. investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +11 -26
  20. investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
  21. investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
  22. investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +1 -1
  23. investing_algorithm_framework/app/reporting/generate.py +100 -114
  24. investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +40 -32
  25. investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +34 -27
  26. investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +23 -19
  27. investing_algorithm_framework/app/reporting/tables/trades_table.py +1 -1
  28. investing_algorithm_framework/app/reporting/tables/utils.py +1 -0
  29. investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +10 -16
  30. investing_algorithm_framework/app/strategy.py +315 -175
  31. investing_algorithm_framework/app/task.py +5 -3
  32. investing_algorithm_framework/cli/cli.py +30 -12
  33. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +131 -34
  34. investing_algorithm_framework/cli/initialize_app.py +20 -1
  35. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +18 -6
  36. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  37. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  38. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -2
  39. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +1 -1
  40. investing_algorithm_framework/create_app.py +3 -5
  41. investing_algorithm_framework/dependency_container.py +25 -39
  42. investing_algorithm_framework/domain/__init__.py +45 -38
  43. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  44. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  45. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  46. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  47. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  48. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  49. investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
  50. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  51. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  52. investing_algorithm_framework/domain/config.py +27 -0
  53. investing_algorithm_framework/domain/constants.py +6 -34
  54. investing_algorithm_framework/domain/data_provider.py +200 -56
  55. investing_algorithm_framework/domain/exceptions.py +34 -1
  56. investing_algorithm_framework/domain/models/__init__.py +10 -19
  57. investing_algorithm_framework/domain/models/base_model.py +0 -6
  58. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  59. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  60. investing_algorithm_framework/domain/models/{market_data_type.py → data/data_type.py} +7 -7
  61. investing_algorithm_framework/domain/models/market/market_credential.py +6 -0
  62. investing_algorithm_framework/domain/models/order/order.py +34 -13
  63. investing_algorithm_framework/domain/models/order/order_status.py +1 -1
  64. investing_algorithm_framework/domain/models/order/order_type.py +1 -1
  65. investing_algorithm_framework/domain/models/portfolio/portfolio.py +14 -1
  66. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +5 -1
  67. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +51 -11
  68. investing_algorithm_framework/domain/models/position/__init__.py +2 -1
  69. investing_algorithm_framework/domain/models/position/position.py +9 -0
  70. investing_algorithm_framework/domain/models/position/position_size.py +41 -0
  71. investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
  72. investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
  73. investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
  74. investing_algorithm_framework/domain/models/snapshot_interval.py +0 -1
  75. investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
  76. investing_algorithm_framework/domain/models/time_frame.py +7 -0
  77. investing_algorithm_framework/domain/models/time_interval.py +33 -0
  78. investing_algorithm_framework/domain/models/time_unit.py +63 -1
  79. investing_algorithm_framework/domain/models/trade/__init__.py +0 -2
  80. investing_algorithm_framework/domain/models/trade/trade.py +56 -32
  81. investing_algorithm_framework/domain/models/trade/trade_status.py +8 -2
  82. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +106 -41
  83. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +161 -99
  84. investing_algorithm_framework/domain/order_executor.py +19 -0
  85. investing_algorithm_framework/domain/portfolio_provider.py +20 -1
  86. investing_algorithm_framework/domain/services/__init__.py +0 -13
  87. investing_algorithm_framework/domain/strategy.py +1 -29
  88. investing_algorithm_framework/domain/utils/__init__.py +5 -1
  89. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  90. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  91. investing_algorithm_framework/domain/utils/polars.py +17 -14
  92. investing_algorithm_framework/download_data.py +40 -10
  93. investing_algorithm_framework/infrastructure/__init__.py +13 -25
  94. investing_algorithm_framework/infrastructure/data_providers/__init__.py +7 -4
  95. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +811 -546
  96. investing_algorithm_framework/infrastructure/data_providers/csv.py +433 -122
  97. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  98. investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
  99. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +81 -0
  100. investing_algorithm_framework/infrastructure/models/__init__.py +0 -13
  101. investing_algorithm_framework/infrastructure/models/order/order.py +9 -3
  102. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +27 -8
  103. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +21 -7
  104. investing_algorithm_framework/infrastructure/order_executors/__init__.py +2 -0
  105. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  106. investing_algorithm_framework/infrastructure/repositories/repository.py +16 -2
  107. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +2 -2
  108. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +6 -0
  109. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +6 -0
  110. investing_algorithm_framework/infrastructure/services/__init__.py +0 -4
  111. investing_algorithm_framework/services/__init__.py +105 -8
  112. investing_algorithm_framework/services/backtesting/backtest_service.py +536 -476
  113. investing_algorithm_framework/services/configuration_service.py +14 -4
  114. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  115. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  116. investing_algorithm_framework/{app/reporting → services}/metrics/__init__.py +48 -17
  117. investing_algorithm_framework/{app/reporting → services}/metrics/drawdown.py +10 -10
  118. investing_algorithm_framework/{app/reporting → services}/metrics/equity_curve.py +2 -2
  119. investing_algorithm_framework/{app/reporting → services}/metrics/exposure.py +60 -2
  120. investing_algorithm_framework/services/metrics/generate.py +358 -0
  121. investing_algorithm_framework/{app/reporting → services}/metrics/profit_factor.py +36 -0
  122. investing_algorithm_framework/{app/reporting → services}/metrics/recovery.py +2 -2
  123. investing_algorithm_framework/{app/reporting → services}/metrics/returns.py +146 -147
  124. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  125. investing_algorithm_framework/{app/reporting/metrics/sharp_ratio.py → services/metrics/sharpe_ratio.py} +6 -10
  126. investing_algorithm_framework/{app/reporting → services}/metrics/sortino_ratio.py +3 -7
  127. investing_algorithm_framework/services/metrics/trades.py +500 -0
  128. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  129. investing_algorithm_framework/{app/reporting → services}/metrics/win_rate.py +70 -3
  130. investing_algorithm_framework/services/order_service/order_backtest_service.py +21 -31
  131. investing_algorithm_framework/services/order_service/order_service.py +9 -71
  132. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +0 -2
  133. investing_algorithm_framework/services/portfolios/portfolio_service.py +3 -13
  134. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +62 -96
  135. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +0 -3
  136. investing_algorithm_framework/services/repository_service.py +5 -2
  137. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  138. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
  139. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
  140. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
  141. investing_algorithm_framework/services/trade_service/__init__.py +7 -1
  142. investing_algorithm_framework/services/trade_service/trade_service.py +51 -29
  143. investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
  144. investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
  145. investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
  146. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/RECORD +159 -148
  147. investing_algorithm_framework/app/reporting/evaluation.py +0 -243
  148. investing_algorithm_framework/app/reporting/metrics/risk_free_rate.py +0 -8
  149. investing_algorithm_framework/app/reporting/metrics/volatility.py +0 -69
  150. investing_algorithm_framework/cli/templates/requirements_azure_function.txt.template +0 -3
  151. investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -9
  152. investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -47
  153. investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
  154. investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -0
  155. investing_algorithm_framework/domain/models/backtesting/backtest_results.py +0 -440
  156. investing_algorithm_framework/domain/models/data_source.py +0 -21
  157. investing_algorithm_framework/domain/models/date_range.py +0 -64
  158. investing_algorithm_framework/domain/models/trade/trade_risk_type.py +0 -34
  159. investing_algorithm_framework/domain/models/trading_data_types.py +0 -48
  160. investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
  161. investing_algorithm_framework/domain/services/market_data_sources.py +0 -543
  162. investing_algorithm_framework/domain/services/market_service.py +0 -153
  163. investing_algorithm_framework/domain/services/observable.py +0 -51
  164. investing_algorithm_framework/domain/services/observer.py +0 -19
  165. investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -16
  166. investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -746
  167. investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -270
  168. investing_algorithm_framework/infrastructure/models/market_data_sources/pandas.py +0 -312
  169. investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
  170. investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -471
  171. investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
  172. investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
  173. investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -322
  174. investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -10
  175. investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -269
  176. investing_algorithm_framework/services/market_data_source_service/data_provider_service.py +0 -350
  177. investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -377
  178. investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -296
  179. investing_algorithm_framework-6.9.1.dist-info/METADATA +0 -440
  180. /investing_algorithm_framework/{app/reporting → services}/metrics/alpha.py +0 -0
  181. /investing_algorithm_framework/{app/reporting → services}/metrics/beta.py +0 -0
  182. /investing_algorithm_framework/{app/reporting → services}/metrics/cagr.py +0 -0
  183. /investing_algorithm_framework/{app/reporting → services}/metrics/calmar_ratio.py +0 -0
  184. /investing_algorithm_framework/{app/reporting → services}/metrics/mean_daily_return.py +0 -0
  185. /investing_algorithm_framework/{app/reporting → services}/metrics/price_efficiency.py +0 -0
  186. /investing_algorithm_framework/{app/reporting → services}/metrics/standard_deviation.py +0 -0
  187. /investing_algorithm_framework/{app/reporting → services}/metrics/treynor_ratio.py +0 -0
  188. /investing_algorithm_framework/{app/reporting → services}/metrics/ulcer.py +0 -0
  189. /investing_algorithm_framework/{app/reporting → services}/metrics/value_at_risk.py +0 -0
  190. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
  191. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
  192. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/entry_points.txt +0 -0
@@ -1,9 +1,12 @@
1
- from typing import List
1
+ from typing import List, Any, Union
2
2
  from abc import ABC, abstractmethod
3
3
  from datetime import datetime
4
- from investing_algorithm_framework.domain.models.trading_data_types import \
5
- TradingDataType
4
+ from investing_algorithm_framework.domain.exceptions import \
5
+ ImproperlyConfigured
6
6
  from investing_algorithm_framework.domain.models.time_frame import TimeFrame
7
+ from investing_algorithm_framework.domain.models.data.data_type import DataType
8
+ from investing_algorithm_framework.domain.models.data.data_source import \
9
+ DataSource
7
10
 
8
11
 
9
12
  class DataProvider(ABC):
@@ -13,20 +16,16 @@ class DataProvider(ABC):
13
16
  algorithms.
14
17
 
15
18
  Attributes:
16
- data_type (str): The type of data to be
19
+ data_type (DataType): The type of data to be
17
20
  fetched (e.g., OHLCV, TICKER, CUSTOM_DATA).
18
- symbols (Optional[List[str]]): A list supported symbols that the
21
+ symbol (Optional[List[str]]): A list supported symbols that the
19
22
  data provider can provide data for. The framework will use this
20
23
  list when searching for a data provider for a specific symbol.
21
24
  Example: ["AAPL/USD", "GOOGL/USD", "MSFT/USD"]
22
- markets (Optional[List[str]]): A list supported markets that the
23
- data provider can provide data for. The framework will use this
24
- list when searching for a data provider for a specific market.
25
- Example: ["BINANCE", "COINBASE", "KRAKEN"]
26
25
  priority (int): The priority of the data provider. The lower the
27
26
  number, the higher the priority. The framework will use this
28
27
  priority when searching for a data provider for a specific symbol.
29
- Example: 0 is the highest priority, 1 is the second highest
28
+ Example: 0 is the highest priority, 1 is the second-highest
30
29
  priority, etc. This is useful when multiple data providers
31
30
  support the same symbol or market. The framework will use the
32
31
  data provider with the highest priority.
@@ -40,53 +39,102 @@ class DataProvider(ABC):
40
39
  for the data. This is useful for data providers that support
41
40
  saving data to a file
42
41
  """
42
+ data_type: DataType = None
43
+ data_provider_identifier: str = None
43
44
 
44
45
  def __init__(
45
46
  self,
46
- data_type: str,
47
+ data_provider_identifier: str = None,
48
+ data_type: str = None,
47
49
  symbol: str = None,
48
50
  market: str = None,
49
- markets: list = None,
50
51
  priority: int = 0,
51
52
  time_frame=None,
52
53
  window_size=None,
53
54
  storage_path=None,
55
+ storage_directory=None,
56
+ config=None,
54
57
  ):
55
58
  """
56
- Initializes the DataProvider with data type, symbols, and markets.
59
+ Initializes the DataProvider. The data provider should be defined
60
+ with a data_type and a data_provider_identifier. The data_type
61
+ should be a valid DataType, and the data_provider_identifier
62
+ should be a unique identifier for the data provider.
63
+
64
+ Args:
65
+ data_provider_identifier (str): The unique identifier for the
66
+ data provider. This is used to identify the data provider
67
+ in the framework. It should be a unique string that identifies
68
+ the data provider. Example: "binance",
69
+ "coinbase", "custom_feed_data"
70
+ data_type (str): The type of data to be fetched
71
+ (e.g., "OHLCV", "TICKER", "CUSTOM_DATA")
72
+ symbol (str): The symbol to fetch data for. This is optional and
73
+ can be set later. Example: "AAPL/USD", "BTC/USD"
74
+ market (str): The market to fetch data for.
75
+ This is optional and can be set later.
76
+ Example: "BINANCE", "COINBASE"
77
+ priority (int): The priority of the data provider. The lower the
78
+ number, the higher the priority. This is useful when multiple
79
+ data providers support the same symbol or market. The framework
80
+ will use the data provider with the highest priority.
81
+ Example: 0 is the highest priority, 1 is the second-highest
82
+ priority, etc.
83
+ time_frame (str): The time frame for the data. This is optional and
84
+ can be set later. This is useful for data providers
85
+ that support multiple time frames.
86
+ Example: "1m", "5m", "1h", "1d"
87
+ window_size (int): The window size for the data. This is optional
88
+ and can be set later. This is useful for data providers that
89
+ support multiple window sizes. Example: 100, 200, 500
90
+ storage_path (str): The path to the storage location for the data.
91
+ This is optional and can be set later. This is useful for data
92
+ providers that support saving data to a file.
93
+ Example: "/path/to/data"
57
94
  """
58
95
  self._data_type = None
59
96
  self._time_frame = None
60
97
 
61
98
  if data_type is not None:
62
- self._data_type = TradingDataType.from_value(data_type)
99
+ self.data_type = DataType.from_value(data_type)
63
100
 
64
101
  if time_frame is not None:
65
102
  self.time_frame = TimeFrame.from_value(time_frame)
66
103
 
104
+ if data_provider_identifier is not None:
105
+ self.data_provider_identifier = data_provider_identifier
106
+
67
107
  self.symbol = symbol
108
+
109
+ if self.symbol is not None:
110
+ self.symbol = self.symbol.upper()
111
+
68
112
  self.market = market
69
- self.markets = markets
113
+
114
+ if self.market is not None:
115
+ self.market = self.market.upper()
116
+
70
117
  self.priority = priority
118
+ self._config = config
71
119
  self.window_size = window_size
72
120
  self.storage_path = storage_path
121
+ self.storage_directory = storage_directory
73
122
  self._market_credentials = None
74
123
 
75
- @property
76
- def data_type(self):
77
- return self._data_type
124
+ # Check if the data provider is properly configured
125
+ if self.data_type is None:
126
+ raise ImproperlyConfigured(
127
+ "DataProvider must be initialized "
128
+ "with a data_type. Either pass it as a parameter in "
129
+ "the constructor or set it as a class attribute."
130
+ )
78
131
 
79
- @data_type.setter
80
- def data_type(self, value):
81
- self._data_type = TradingDataType.from_value(value)
82
-
83
- @property
84
- def time_frame(self):
85
- return self._time_frame
86
-
87
- @time_frame.setter
88
- def time_frame(self, value):
89
- self._time_frame = TimeFrame.from_value(value)
132
+ if self.data_provider_identifier is None:
133
+ raise ImproperlyConfigured(
134
+ "DataProvider must be initialized with a "
135
+ "data_provider_identifier. Either pass it as a parameter "
136
+ "in the constructor or set it as a class attribute."
137
+ )
90
138
 
91
139
  @property
92
140
  def market_credentials(self):
@@ -124,67 +172,163 @@ class DataProvider(ABC):
124
172
  @abstractmethod
125
173
  def has_data(
126
174
  self,
127
- data_type: str = None,
128
- symbol: str = None,
129
- market: str = None,
130
- time_frame: str = None,
175
+ data_source: DataSource,
131
176
  start_date: datetime = None,
132
177
  end_date: datetime = None,
133
- window_size=None,
134
178
  ) -> bool:
135
179
  """
136
180
  Checks if the data provider has data for the given parameters.
181
+
182
+ Args:
183
+ data_source (DataSource): The data source specification that
184
+ matches a data provider.
185
+ start_date (datetime): The start date for the data.
186
+ end_date (datetime): The end date for the data.
187
+
188
+ Returns:
189
+ bool: True if the data provider has data for the given parameters,
137
190
  """
138
191
  raise NotImplementedError("Subclasses should implement this method.")
139
192
 
140
193
  @abstractmethod
141
194
  def get_data(
142
195
  self,
143
- data_type: str = None,
144
196
  date: datetime = None,
145
- symbol: str = None,
146
- market: str = None,
147
- time_frame: str = None,
148
197
  start_date: datetime = None,
149
198
  end_date: datetime = None,
150
- storage_path=None,
151
- window_size=None,
152
- pandas=False,
153
- save: bool = True,
154
- ):
199
+ save: bool = False,
200
+ ) -> Any:
155
201
  """
156
202
  Fetches data for a given symbol and date range.
203
+
204
+ Args:
205
+ start_date (datetime): The start date for the data.
206
+ end_date (datetime): The end date for the data.
207
+ date (datetime): The specific date for which to fetch data.
208
+ save (bool): Whether to save the data to the storage path.
209
+
210
+ Returns:
211
+ Any: The data for the given symbol and date range.
212
+ This can be a DataFrame, a list, or any other data structure
213
+ depending on the implementation.
157
214
  """
158
215
  raise NotImplementedError("Subclasses should implement this method.")
159
216
 
160
217
  @abstractmethod
161
- def pre_pare_backtest_data(
218
+ def prepare_backtest_data(
162
219
  self,
163
220
  backtest_start_date,
164
221
  backtest_end_date,
165
- symbol: str = None,
166
- market: str = None,
167
- time_frame: str = None,
168
- window_size=None,
169
222
  ) -> None:
170
223
  """
171
224
  Prepares backtest data for a given symbol and date range.
225
+
226
+ Args:
227
+ backtest_start_date (datetime): The start date for the
228
+ backtest data.
229
+ backtest_end_date (datetime): The end date for the
230
+ backtest data.
231
+
232
+ Returns:
233
+ None
172
234
  """
173
235
  raise NotImplementedError("Subclasses should implement this method.")
174
236
 
175
237
  @abstractmethod
176
238
  def get_backtest_data(
177
239
  self,
178
- date: datetime = None,
179
- symbol: str = None,
180
- market: str = None,
181
- time_frame: str = None,
240
+ backtest_index_date: datetime,
182
241
  backtest_start_date: datetime = None,
183
242
  backtest_end_date: datetime = None,
184
- window_size=None,
185
- pandas=False,
186
- ) -> None:
243
+ data_source: DataSource = None,
244
+ ) -> Any:
187
245
  """
188
- Fetches backtest data for a given symbol and date range.
246
+ Fetches backtest data for a given datasource
247
+
248
+ Args:
249
+ backtest_index_date (datetime): The date for which to fetch
250
+ backtest data.
251
+ backtest_start_date (datetime): The start date for the
252
+ backtest data.
253
+ backtest_end_date (datetime): The end date for the
254
+ backtest data.
255
+ data_source (Optional[DataSource]): The data source
256
+ specification that is used to fetch the data.
257
+ This param is optional and can be used to
258
+ help identify errors in data fetching.
259
+
260
+ Returns:
261
+ Any: The data for the given symbol and date range.
262
+ This can be a DataFrame, a list, or any other data structure
263
+ depending on the implementation.
264
+ """
265
+ raise NotImplementedError("Subclasses should implement this method.")
266
+
267
+ @abstractmethod
268
+ def copy(self, data_source: DataSource) -> "DataProvider":
269
+ """
270
+ Returns a copy of the data provider instance based on a
271
+ given data source. The data source is previously matched
272
+ with the 'has_data' method. Then a new instance of the data
273
+ provider must be registered in the framework so that each
274
+ data source has its own instance of the data provider.
275
+
276
+ Args:
277
+ data_source (DataSource): The data source specification that
278
+ matches a data provider.
279
+
280
+ Returns:
281
+ DataProvider: A new instance of the data provider with the same
282
+ configuration.
283
+ """
284
+ raise NotImplementedError("Subclasses should implement this method.")
285
+
286
+ @abstractmethod
287
+ def get_number_of_data_points(
288
+ self,
289
+ start_date: datetime,
290
+ end_date: datetime,
291
+ ) -> int:
292
+ """
293
+ Returns the number of data points available between the
294
+ given start and end dates.
295
+
296
+ Args:
297
+ start_date (datetime): The start date for the data points.
298
+ end_date (datetime): The end date for the data points.
299
+ Returns:
300
+ int: The number of data points available between the
301
+ given start and end dates.
302
+ """
303
+ raise NotImplementedError("Subclasses should implement this method.")
304
+
305
+ @abstractmethod
306
+ def get_missing_data_dates(
307
+ self,
308
+ start_date: datetime,
309
+ end_date: datetime,
310
+ ) -> List[datetime]:
311
+ """
312
+ Returns a list of dates for which data is missing between the
313
+ given start and end dates.
314
+
315
+ Args:
316
+ start_date (datetime): The start date for checking missing data.
317
+ end_date (datetime): The end date for checking missing data.
318
+
319
+ Returns:
320
+ List[datetime]: A list of dates for which data is missing
321
+ between the given start and end dates.
322
+ """
323
+ raise NotImplementedError("Subclasses should implement this method.")
324
+
325
+ @abstractmethod
326
+ def get_data_source_file_path(self) -> Union[str, None]:
327
+ """
328
+ Returns the file path for the given data source if applicable.
329
+
330
+ Returns:
331
+ Union[str, None]: The file path for the data source or None
332
+ if not applicable.
189
333
  """
190
334
  raise NotImplementedError("Subclasses should implement this method.")
@@ -49,7 +49,14 @@ class ImproperlyConfigured(Exception):
49
49
  class OperationalException(Exception):
50
50
  """
51
51
  Class OperationalException: Exception class indicating a problem occurred
52
- during running of the investing_algorithm_framework
52
+ during running of the investing_algorithm_framework.
53
+
54
+ This exception is used to indicate that an error occurred during the
55
+ operation of the investing_algorithm_framework, such as during a backtest,
56
+ strategy execution, or any other operational aspect of the framework.
57
+
58
+ Attributes:
59
+ message (str): The error message to be returned in the response.
53
60
  """
54
61
  def __init__(self, message) -> None:
55
62
  super(OperationalException, self).__init__(message)
@@ -77,3 +84,29 @@ class NetworkError(Exception):
77
84
  "status": "error",
78
85
  "message": self.error_message
79
86
  }
87
+
88
+
89
+ class DataError(Exception):
90
+ """
91
+ Class DataError: Exception class indicating a problem occurred
92
+ during data retrieval or processing
93
+ """
94
+
95
+ def __init__(
96
+ self,
97
+ message,
98
+ data_source_file_path: str = None,
99
+ number_of_missing_data_points: int = None,
100
+ total_number_of_data_points: int = None,
101
+ ) -> None:
102
+ super(DataError, self).__init__(message)
103
+ self.error_message = message
104
+ self.data_source_file_path = data_source_file_path
105
+ self.number_of_missing_data_points = number_of_missing_data_points
106
+ self.total_number_of_data_points = total_number_of_data_points
107
+
108
+ def to_response(self):
109
+ return {
110
+ "status": "error",
111
+ "message": self.error_message
112
+ }
@@ -1,23 +1,17 @@
1
1
  from .app_mode import AppMode
2
- from .backtesting import BacktestResult, BacktestPosition, \
3
- BacktestDateRange
4
2
  from .market import MarketCredential
5
3
  from .order import OrderStatus, OrderSide, OrderType, Order
6
4
  from .portfolio import PortfolioConfiguration, Portfolio, PortfolioSnapshot
7
- from .position import Position, PositionSnapshot
5
+ from .position import Position, PositionSnapshot, PositionSize
8
6
  from .strategy_profile import StrategyProfile
9
7
  from .time_frame import TimeFrame
10
8
  from .time_interval import TimeInterval
11
9
  from .time_unit import TimeUnit
12
- from .trade import Trade, TradeStatus, TradeStopLoss, TradeTakeProfit, \
13
- TradeRiskType
14
- from .trading_data_types import TradingDataType
15
- from .trading_time_frame import TradingTimeFrame
16
- from .date_range import DateRange
17
- from .market_data_type import MarketDataType
18
- from .data_source import DataSource
10
+ from .trade import Trade, TradeStatus, TradeStopLoss, TradeTakeProfit
19
11
  from .snapshot_interval import SnapshotInterval
20
12
  from .event import Event
13
+ from .data import DataSource, DataType
14
+ from .risk_rules import TakeProfitRule, StopLossRule
21
15
 
22
16
  __all__ = [
23
17
  "OrderStatus",
@@ -27,28 +21,25 @@ __all__ = [
27
21
  "TimeFrame",
28
22
  "TimeInterval",
29
23
  "TimeUnit",
30
- "TradingTimeFrame",
31
- "TradingDataType",
32
24
  "PortfolioConfiguration",
33
25
  "Position",
34
26
  "Portfolio",
35
- "BacktestResult",
36
27
  "PositionSnapshot",
37
28
  "PortfolioSnapshot",
38
29
  "StrategyProfile",
39
- "BacktestPosition",
40
30
  "Trade",
41
31
  "MarketCredential",
42
32
  "TradeStatus",
43
- "BacktestReportsEvaluation",
33
+ "DataType",
34
+ "AppMode",
35
+ "DataSource",
44
36
  "AppMode",
45
- "BacktestDateRange",
46
- "DateRange",
47
- "MarketDataType",
48
37
  "TradeStopLoss",
49
38
  "TradeTakeProfit",
50
- "TradeRiskType",
51
39
  "DataSource",
52
40
  "SnapshotInterval",
53
41
  "Event",
42
+ "PositionSize",
43
+ "StopLossRule",
44
+ "TakeProfitRule",
54
45
  ]
@@ -23,9 +23,3 @@ class BaseModel:
23
23
 
24
24
  if value is not None:
25
25
  setattr(self, attr, value)
26
-
27
- @staticmethod
28
- def from_dict(data):
29
- instance = BaseModel()
30
- instance.update(data)
31
- return instance
@@ -0,0 +1,7 @@
1
+ from .data_source import DataSource
2
+ from .data_type import DataType
3
+
4
+ __all__ = [
5
+ "DataSource",
6
+ "DataType",
7
+ ]
@@ -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)