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,746 +0,0 @@
1
- import logging
2
- import os
3
- from datetime import timedelta, datetime, timezone
4
-
5
- import polars
6
- from dateutil import parser
7
-
8
- from investing_algorithm_framework.domain import RESOURCE_DIRECTORY, \
9
- BACKTEST_DATA_DIRECTORY_NAME, DATETIME_FORMAT_BACKTESTING, \
10
- OperationalException, DATETIME_FORMAT, OHLCVMarketDataSource, \
11
- BacktestMarketDataSource, OrderBookMarketDataSource, \
12
- TickerMarketDataSource, TimeFrame, sync_timezones
13
- from investing_algorithm_framework.infrastructure.services import \
14
- CCXTMarketService
15
-
16
- logger = logging.getLogger("investing_algorithm_framework")
17
-
18
-
19
- class CCXTOHLCVBacktestMarketDataSource(
20
- OHLCVMarketDataSource, BacktestMarketDataSource
21
- ):
22
- """
23
- CCXTOHLCVBacktestMarketDataSource implementation using ccxt to download
24
- all data sources.
25
-
26
- This class will determine the start and end date of the data range by
27
- taking the backtest start date (e.g. 01-01-2024) and the backtest
28
- end date (e.g. 31-12-2024) in combination with the difference between
29
- start and end date. The reason for this is that the data source needs
30
- to have data on the first run (e.g. an algorithm starting on
31
- 01-01-2024 that requires 2h data for the last 17 days will
32
- need to have pulled data from 15-12-2023)
33
-
34
- To achieve this, a backtest_data_start_date attribute is used. This
35
- attribute is indexed on this calculated date.
36
- """
37
- backtest_data_directory = None
38
- backtest_data_end_date = None
39
- total_minutes_time_frame = None
40
- column_names = ["Datetime", "Open", "High", "Low", "Close", "Volume"]
41
-
42
- def __init__(
43
- self,
44
- identifier,
45
- market,
46
- symbol,
47
- time_frame,
48
- window_size=None,
49
- ):
50
- super().__init__(
51
- identifier=identifier,
52
- market=market,
53
- symbol=symbol,
54
- time_frame=time_frame,
55
- window_size=window_size,
56
- )
57
- self.data = None
58
- self._start_date_data_source = None
59
- self._end_date_data_source = None
60
- self.backtest_end_index = self.window_size
61
- self.backtest_start_index = 0
62
- self.window_cache = {}
63
-
64
- def prepare_data(
65
- self,
66
- config,
67
- backtest_start_date,
68
- backtest_end_date,
69
- ):
70
- """
71
- Prepare data implementation of ccxt based ohlcv backtest market
72
- data source
73
-
74
- This implementation will check if the data source already exists before
75
- pulling all the data. This optimization will prevent downloading
76
- of unnecessary resources.
77
-
78
- When downloading the data it will use the ccxt library.
79
-
80
- Args:
81
- config (dict): the configuration of the data source
82
- backtest_start_date (datetime): the start date of the backtest
83
- backtest_end_date (datetime): the end date of the backtest
84
- time_frame (string): the time frame of the data
85
- window_size (int): the total amount of candle sticks that need to
86
- be returned
87
-
88
- Returns:
89
- None
90
- """
91
-
92
- if config is None:
93
- config = self.config
94
-
95
- # Calculating the backtest data start date
96
- backtest_data_start_date = \
97
- backtest_start_date - timedelta(
98
- minutes=(
99
- (self.window_size + 1) *
100
- TimeFrame.from_value(self.time_frame).amount_of_minutes
101
- )
102
- )
103
-
104
- self.backtest_data_start_date = backtest_data_start_date\
105
- .replace(microsecond=0)
106
- self.backtest_data_end_date = backtest_end_date.replace(microsecond=0)
107
-
108
- # Creating the backtest data directory and file
109
- self.backtest_data_directory = os.path.join(
110
- config[RESOURCE_DIRECTORY], config[BACKTEST_DATA_DIRECTORY_NAME]
111
- )
112
-
113
- if not os.path.isdir(self.backtest_data_directory):
114
- os.mkdir(self.backtest_data_directory)
115
-
116
- file_path = self._create_file_path()
117
-
118
- if not self._data_source_exists(file_path):
119
- if not os.path.isfile(file_path):
120
- try:
121
- with open(file_path, 'w') as _:
122
- pass
123
- except Exception as e:
124
- logger.error(e)
125
- raise OperationalException(
126
- f"Could not create backtest data file {file_path}"
127
- )
128
-
129
- # Get the OHLCV data from the ccxt market service
130
- market_service = CCXTMarketService(
131
- market_credential_service=self.market_credential_service,
132
- )
133
- market_service.config = config
134
- ohlcv = market_service.get_ohlcv(
135
- symbol=self.symbol,
136
- time_frame=self.time_frame,
137
- from_timestamp=backtest_data_start_date,
138
- to_timestamp=backtest_end_date,
139
- market=self.market
140
- )
141
-
142
- if len(ohlcv) == 0:
143
- raise OperationalException(
144
- f"No data found for {self.symbol} " +
145
- f"for date range: {backtest_data_start_date} " +
146
- f"to {backtest_end_date}. Please make sure that " +
147
- "the market has data for this date range."
148
- )
149
- self.write_data_to_file_path(file_path, ohlcv)
150
-
151
- self.load_data()
152
- self._precompute_sliding_windows() # Precompute sliding windows!
153
-
154
- def _precompute_sliding_windows(self):
155
- """
156
- Precompute all sliding windows for fast retrieval.
157
-
158
- A sliding window is calculated as a subset of the data. It will
159
- take for each timestamp in the data a window of size `window_size`
160
- and stores it in a cache with the last timestamp of the window.
161
- """
162
- self.window_cache = {}
163
- timestamps = self.data["Datetime"].to_list()
164
-
165
- for i in range(len(timestamps) - self.window_size + 1):
166
- # Use last timestamp as key
167
- end_time = timestamps[i + self.window_size - 1]
168
-
169
- # Convert end_time datetime object to UTC
170
- if isinstance(end_time, str):
171
- end_time = parser.parse(end_time)
172
- elif isinstance(end_time, datetime):
173
- end_time = end_time
174
-
175
- # end_time = end_time.replace(tzinfo=timezone.utc)
176
- self.window_cache[end_time] = self.data.slice(i, self.window_size)
177
-
178
- def load_data(self):
179
- file_path = self._create_file_path()
180
- self.data = polars.read_csv(
181
- file_path,
182
- schema_overrides={"Datetime": polars.Datetime},
183
- low_memory=True
184
- ) # Faster parsing
185
- first_row = self.data.head(1)
186
- last_row = self.data.tail(1)
187
-
188
- self._start_date_data_source = first_row["Datetime"][0]
189
- self._end_date_data_source = last_row["Datetime"][0]
190
-
191
- def _create_file_path(self):
192
- """
193
- Function to create a filename in the following format:
194
- OHLCV_{symbol}_{market}_{time_frame}_{start_date}_{end_date}.csv
195
- """
196
- symbol_string = self.symbol.replace("/", "-")
197
- time_frame_string = self.time_frame.replace("_", "")
198
- backtest_data_start_date = \
199
- self.backtest_data_start_date.strftime(DATETIME_FORMAT_BACKTESTING)
200
- backtest_data_end_date = \
201
- self.backtest_data_end_date.strftime(DATETIME_FORMAT_BACKTESTING)
202
- return os.path.join(
203
- self.backtest_data_directory,
204
- os.path.join(
205
- f"OHLCV_"
206
- f"{symbol_string}_"
207
- f"{self.market}_"
208
- f"{time_frame_string}_"
209
- f"{backtest_data_start_date}_"
210
- f"{backtest_data_end_date}.csv"
211
- )
212
- )
213
-
214
- def get_data(
215
- self,
216
- date,
217
- config=None,
218
- ):
219
- """
220
- Get data implementation of ccxt based ohlcv backtest market data
221
- source. This implementation will use polars to load and filter the
222
- data.
223
- """
224
-
225
- data = self.window_cache.get(date)
226
- if data is not None:
227
- return data
228
-
229
- # Find closest previous timestamp
230
- sorted_timestamps = sorted(self.window_cache.keys())
231
-
232
- closest_date = None
233
- for ts in reversed(sorted_timestamps):
234
- date = sync_timezones(ts, date)
235
-
236
- if ts <= date:
237
- closest_date = ts
238
- break
239
-
240
- return self.window_cache.get(closest_date) if closest_date else None
241
-
242
- def to_backtest_market_data_source(self) -> BacktestMarketDataSource:
243
- # Ignore this method for now
244
- pass
245
-
246
- def empty(self):
247
- return False
248
-
249
- @property
250
- def file_name(self):
251
- return self._create_file_path().split("/")[-1]
252
-
253
- def write_data_to_file_path(self, data_file, data: polars.DataFrame):
254
- data.write_csv(data_file)
255
-
256
-
257
- class CCXTTickerBacktestMarketDataSource(
258
- TickerMarketDataSource, BacktestMarketDataSource
259
- ):
260
- """
261
- CCXTTickerBacktestMarketDataSource implementation using ccxt to download
262
- all data sources.
263
-
264
- This class will determine the start and end date of the data range by
265
- taking the start date of the backtest minus 1 day and the end date of the
266
- backtest. The reason for this is that the data source needs
267
- to have data on the first run (e.g. an algorithm starting on
268
- 01-01-2024 that requires ticker data will need to have pulled data from
269
- 01-01-2024 - amount of minutes of the provided time_frame)
270
-
271
- To achieve this, a backtest_data_start_date attribute is used. This
272
- attribute is indexed on this calculated date.
273
- """
274
- backtest_data_directory = None
275
- backtest_data_start_date = None
276
- backtest_data_end_date = None
277
- time_frame = None
278
- column_names = ["Datetime", "Open", "High", "Low", "Close", "Volume"]
279
-
280
- def __init__(
281
- self,
282
- identifier,
283
- market,
284
- symbol=None,
285
- time_frame=None,
286
- ):
287
- super().__init__(
288
- identifier=identifier,
289
- market=market,
290
- symbol=symbol,
291
- )
292
-
293
- if time_frame is not None:
294
- self.time_frame = time_frame
295
-
296
- if self.time_frame is None:
297
- raise OperationalException(
298
- "time_frame should be set for "
299
- "CCXTTickerBacktestMarketDataSource"
300
- )
301
-
302
- def prepare_data(
303
- self,
304
- config,
305
- backtest_start_date,
306
- backtest_end_date,
307
- ):
308
- """
309
- Prepare data implementation of ccxt based ticker backtest market
310
- data source
311
-
312
- This implementation will check if the data source already exists before
313
- pulling all the data. This optimization will prevent downloading
314
- of unnecessary resources.
315
-
316
- When downloading the data it will use the ccxt library.
317
- """
318
-
319
- if config is None:
320
- config = self.config
321
-
322
- total_minutes = TimeFrame.from_string(self.time_frame)\
323
- .amount_of_minutes
324
- self.backtest_data_start_date = \
325
- backtest_start_date - timedelta(minutes=total_minutes)
326
- self.backtest_data_end_date = backtest_end_date
327
-
328
- # Creating the backtest data directory and file
329
- self.backtest_data_directory = os.path.join(
330
- config[RESOURCE_DIRECTORY], config[BACKTEST_DATA_DIRECTORY_NAME]
331
- )
332
-
333
- if not os.path.isdir(self.backtest_data_directory):
334
- os.mkdir(self.backtest_data_directory)
335
-
336
- file_path = self._create_file_path()
337
-
338
- if not os.path.isfile(file_path):
339
- try:
340
- with open(file_path, 'w') as _:
341
- pass
342
- except Exception as e:
343
- logger.error(e)
344
- raise OperationalException(
345
- f"Could not create backtest data file {file_path}"
346
- )
347
-
348
- # Check if the data source already exists, if not download the data
349
- if not self._data_source_exists(file_path):
350
- if not os.path.isfile(file_path):
351
- try:
352
- with open(file_path, 'w') as _:
353
- pass
354
- except Exception as e:
355
- logger.error(e)
356
- raise OperationalException(
357
- f"Could not create backtest data file {file_path}"
358
- )
359
-
360
- # Get the OHLCV data from the ccxt market service
361
- market_service = CCXTMarketService(
362
- market_credential_service=self.market_credential_service
363
- )
364
- market_service.config = config
365
- ohlcv = market_service.get_ohlcv(
366
- symbol=self.symbol,
367
- time_frame=self.time_frame,
368
- from_timestamp=self.backtest_data_start_date,
369
- to_timestamp=backtest_end_date,
370
- market=self.market
371
- )
372
- self.write_data_to_file_path(file_path, ohlcv)
373
-
374
- def _create_file_path(self):
375
-
376
- if self.symbol is None or self.market is None:
377
- return None
378
-
379
- symbol_string = self.symbol.replace("/", "-")
380
- market_string = self.market.replace("/", "-")
381
- backtest_data_start_date = \
382
- self.backtest_data_start_date.strftime(DATETIME_FORMAT_BACKTESTING)
383
- backtest_data_end_date = \
384
- self.backtest_data_end_date.strftime(DATETIME_FORMAT_BACKTESTING)
385
- return os.path.join(
386
- self.backtest_data_directory,
387
- os.path.join(
388
- f"TICKER_"
389
- f"{symbol_string}_"
390
- f"{market_string}_"
391
- f"{backtest_data_start_date}_"
392
- f"{backtest_data_end_date}.csv"
393
- )
394
- )
395
-
396
- def to_backtest_market_data_source(self) -> BacktestMarketDataSource:
397
- # Ignore this method for now
398
- pass
399
-
400
- def empty(self):
401
- return False
402
-
403
- def get_data(
404
- self,
405
- date,
406
- config,
407
- ):
408
- """
409
- Get data implementation of ccxt based ticker backtest market data
410
- source
411
- """
412
- file_path = self._create_file_path()
413
-
414
- # Filter the data based on the backtest index date and the end date
415
- df = polars.read_csv(file_path)
416
- filtered_df = df.filter(
417
- (df['Datetime'] >= date.strftime(DATETIME_FORMAT))
418
- )
419
-
420
- # If nothing is found, get all dates before the index date
421
- if len(filtered_df) == 0:
422
- filtered_df = df.filter(
423
- (df['Datetime'] <= date.strftime(
424
- DATETIME_FORMAT))
425
- )
426
- first_row = filtered_df.tail(1)[0]
427
- else:
428
- first_row = filtered_df.head(1)[0]
429
-
430
- first_row_datetime = parser.parse(first_row["Datetime"][0])
431
-
432
- return {
433
- "symbol": self.symbol,
434
- "bid": float(first_row["Close"][0]),
435
- "ask": float(first_row["Close"][0]),
436
- "datetime": first_row_datetime,
437
- }
438
-
439
- def write_data_to_file_path(self, data_file, data: polars.DataFrame):
440
- data.write_csv(data_file)
441
-
442
-
443
- class CCXTOHLCVMarketDataSource(OHLCVMarketDataSource):
444
- """
445
- CCXTOHLCVMarketDataSource implementation of OHLCVMarketDataSource using
446
- ccxt to download all ohlcv data sources.
447
- """
448
-
449
- def get_data(
450
- self,
451
- start_date: datetime = None,
452
- end_date: datetime = None,
453
- config=None,
454
- ):
455
- """
456
- Implementation of get_data for CCXTOHLCVMarketDataSource.
457
- This implementation uses the CCXTMarketService to get the OHLCV data.
458
-
459
- Args:
460
- start_date: datetime (optional) - the start date of the data. The
461
- first candle stick should close to this date.
462
- end_date: datetime (optional) - the end date of the data. The last
463
- candle stick should close to this date.
464
- storage_path: string (optional) - the storage path specifies the
465
- directory where the data is written to or read from.
466
- If set the data provider will write all its downloaded data
467
- to this location. Also, it will check if the
468
- data already exists at the storage location. If this is the
469
- case it will return this.
470
-
471
- Returns
472
- polars.DataFrame with the OHLCV data
473
- """
474
- market_service = CCXTMarketService(
475
- market_credential_service=self.market_credential_service,
476
- )
477
-
478
- # Add config if present
479
- if self.config is not None:
480
- market_service.config = self.config
481
-
482
- # Calculate the start and end dates
483
- if start_date is None or end_date is None:
484
-
485
- if start_date is None:
486
-
487
- if end_date is None:
488
- end_date = datetime.now(tz=timezone.utc)
489
-
490
- if self.window_size is None:
491
- raise OperationalException(
492
- "Window_size should be defined before the " +
493
- "get_data method can be called. Make sure to set " +
494
- "the window_size attribute on your " +
495
- "CCXTOHLCVMarketDataSource or provide a start_date " +
496
- "and end_date to the get_data method."
497
- )
498
-
499
- start_date = self.create_start_date(
500
- end_date=end_date,
501
- time_frame=self.time_frame,
502
- window_size=self.window_size
503
- )
504
- else:
505
- end_date = self.create_end_date(
506
- start_date=start_date,
507
- time_frame=self.time_frame,
508
- window_size=self.window_size
509
- )
510
-
511
- storage_path = self.get_storage_path()
512
-
513
- logger.info(
514
- f"Getting OHLCV data for {self.symbol} " +
515
- f"from {start_date} to {end_date}"
516
- )
517
- data = None
518
-
519
- if storage_path is not None:
520
- # Check if data is already in storage
521
- data = self._get_data_from_storage(
522
- storage_path=storage_path,
523
- symbol=self.symbol,
524
- time_frame=self.time_frame,
525
- from_timestamp=start_date,
526
- to_timestamp=end_date,
527
- market=self.market,
528
- )
529
-
530
- if data is None:
531
- # Get the OHLCV data from the ccxt market service
532
- data = market_service.get_ohlcv(
533
- symbol=self.symbol,
534
- time_frame=self.time_frame,
535
- from_timestamp=start_date,
536
- to_timestamp=end_date,
537
- market=self.market
538
- )
539
-
540
- # if storage path is set, write the data to the storage path
541
- if storage_path is not None:
542
- self.write_data_to_storage(
543
- data=data,
544
- storage_path=storage_path,
545
- symbol=self.symbol,
546
- time_frame=self.time_frame,
547
- from_timestamp=start_date,
548
- to_timestamp=end_date,
549
- market=self.market,
550
- data_type="OHLCV"
551
- )
552
-
553
- return data
554
-
555
- def to_backtest_market_data_source(self) -> BacktestMarketDataSource:
556
-
557
- if self.window_size is None:
558
- raise OperationalException(
559
- "Window_size should be defined before the " +
560
- "CCXTOHLCVMarketDataSource can be converted to " +
561
- "a backtest market data source. Make sure to set " +
562
- "the window_size attribute on your CCXTOHLCVMarketDataSource"
563
- )
564
-
565
- return CCXTOHLCVBacktestMarketDataSource(
566
- identifier=self.identifier,
567
- market=self.market,
568
- symbol=self.symbol,
569
- time_frame=self.time_frame,
570
- window_size=self.window_size
571
- )
572
-
573
- def _get_data_from_storage(
574
- self,
575
- storage_path,
576
- symbol,
577
- time_frame,
578
- from_timestamp,
579
- to_timestamp,
580
- market
581
- ):
582
- """
583
- Function to get data from the storage path:
584
-
585
- Parameters:
586
- storage_path: string - the storage path where the
587
- data should be in.
588
-
589
- Return:
590
- Polars dataframe.
591
- """
592
-
593
- if not os.path.isdir(storage_path):
594
- return None
595
-
596
- if not os.path.isdir(storage_path):
597
- return None
598
-
599
- for filename in os.listdir(storage_path):
600
- path = os.path.join(storage_path, filename)
601
-
602
- if os.path.isfile(path) or path.split('.')[-1] != ".csv":
603
- continue
604
-
605
- file_name_symbol = self.get_file_name_symbol(path)
606
- file_name_market = self.get_file_name_market(path)
607
- file_name_time_frame = self.get_file_name_time_frame(path)
608
- file_name_start_date = self.get_file_name_start_date(path)
609
- file_name_end_date = self.get_file_name_end_date(path)
610
-
611
- if file_name_symbol == symbol \
612
- and file_name_market == market \
613
- and file_name_time_frame == time_frame \
614
- and file_name_start_date >= from_timestamp \
615
- and file_name_end_date <= to_timestamp:
616
- return polars.read_csv(path)
617
-
618
- return None
619
-
620
- def write_data_to_storage(
621
- self,
622
- data: polars.DataFrame,
623
- storage_path,
624
- symbol,
625
- time_frame,
626
- from_timestamp,
627
- to_timestamp,
628
- market,
629
- data_type="OHLCV"
630
- ):
631
- """
632
- Function to write data to the storage path:
633
-
634
- Parameters:
635
- data: polars.DataFrame - the data that should be written to the
636
- storage path.
637
- storage_path: string - the storage path where the data should
638
- be written to.
639
- symbol: string - the symbol of the data.
640
- time_frame: string - the time_frame of the data.
641
- from_timestamp: datetime - the start date of the data.
642
- to_timestamp: datetime - the end date of the data.
643
- market: string - the market of the data.
644
-
645
- Return:
646
- None
647
- """
648
-
649
- if not os.path.isdir(storage_path):
650
- os.mkdir(storage_path)
651
-
652
- file_path = self.create_storage_file_path(
653
- storage_path=storage_path,
654
- symbol=symbol,
655
- time_frame=time_frame,
656
- start_datetime=from_timestamp,
657
- end_datetime=to_timestamp,
658
- market=market,
659
- data_type=data_type
660
- )
661
-
662
- if os.path.isfile(file_path):
663
- return
664
-
665
- else:
666
- try:
667
- with open(file_path, 'w') as _:
668
- pass
669
- except Exception as e:
670
- logger.error(e)
671
- raise OperationalException(
672
- f"Could not create data file {file_path}"
673
- )
674
-
675
- data.write_csv(file_path)
676
-
677
-
678
- class CCXTOrderBookMarketDataSource(OrderBookMarketDataSource):
679
-
680
- def get_data(
681
- self,
682
- start_date: datetime = None,
683
- end_date: datetime = None,
684
- config=None,
685
- ):
686
- market_service = CCXTMarketService(
687
- market_credential_service=self.market_credential_service
688
- )
689
- market_service.config = self.config
690
- return market_service.get_order_book(
691
- symbol=self.symbol, market=self.market
692
- )
693
-
694
- def to_backtest_market_data_source(self) -> BacktestMarketDataSource:
695
- pass
696
-
697
-
698
- class CCXTTickerMarketDataSource(TickerMarketDataSource):
699
-
700
- def __init__(
701
- self,
702
- market,
703
- identifier=None,
704
- symbol=None,
705
- backtest_time_frame=None,
706
- ):
707
- super().__init__(
708
- identifier=identifier,
709
- market=market,
710
- symbol=symbol,
711
- )
712
- self._backtest_time_frame = backtest_time_frame
713
-
714
- def get_data(
715
- self,
716
- start_date: datetime = None,
717
- end_date: datetime = None,
718
- config=None,
719
- ):
720
- market_service = CCXTMarketService(
721
- market_credential_service=self.market_credential_service
722
- )
723
- market_service.config = self.config
724
- market = self.market
725
-
726
- market_service.market = market
727
- symbol = self.symbol
728
- return market_service.get_ticker(symbol=symbol, market=market)
729
-
730
- def to_backtest_market_data_source(self) -> BacktestMarketDataSource:
731
-
732
- if self._backtest_time_frame is None:
733
- raise OperationalException(
734
- "Backtest time frame should be defined before the " +
735
- "CCXTTickerMarketDataSource can be converted to " +
736
- "a backtest market data source. Make sure to set " +
737
- "the backtest_time_frame attribute on your " +
738
- "CCXTTickerMarketDataSource"
739
- )
740
-
741
- return CCXTTickerBacktestMarketDataSource(
742
- identifier=self.identifier,
743
- market=self.market,
744
- symbol=self.symbol,
745
- time_frame=self._backtest_time_frame,
746
- )