investing-algorithm-framework 7.16.20__py3-none-any.whl → 7.18.0__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.

@@ -1,9 +1,9 @@
1
1
  from .app import App, Algorithm, \
2
2
  TradingStrategy, StatelessAction, Task, AppHook, Context, \
3
- add_html_report, BacktestReport, \
3
+ add_html_report, BacktestReport, save_backtests_to_directory, \
4
4
  pretty_print_trades, pretty_print_positions, \
5
5
  pretty_print_orders, pretty_print_backtest, select_backtest_date_ranges, \
6
- get_equity_curve_with_drawdown_chart, \
6
+ get_equity_curve_with_drawdown_chart, load_backtests_from_directory, \
7
7
  get_rolling_sharpe_ratio_chart, rank_results, \
8
8
  get_monthly_returns_heatmap_chart, create_weights, \
9
9
  get_yearly_returns_bar_chart, get_entry_and_exit_signals, \
@@ -189,5 +189,7 @@ __all__ = [
189
189
  "get_negative_trades",
190
190
  "get_positive_trades",
191
191
  "get_number_of_trades",
192
- "BacktestRun"
192
+ "BacktestRun",
193
+ "load_backtests_from_directory",
194
+ "save_backtests_to_directory"
193
195
  ]
@@ -14,7 +14,7 @@ from .reporting import add_html_report, \
14
14
  get_yearly_returns_bar_chart, get_equity_curve_chart, \
15
15
  get_ohlcv_data_completeness_chart, get_entry_and_exit_signals
16
16
  from .analysis import select_backtest_date_ranges, rank_results, \
17
- create_weights
17
+ create_weights, load_backtests_from_directory, save_backtests_to_directory
18
18
 
19
19
 
20
20
  __all__ = [
@@ -41,5 +41,7 @@ __all__ = [
41
41
  "rank_results",
42
42
  "create_weights",
43
43
  "get_entry_and_exit_signals",
44
- "get_equity_curve_chart"
44
+ "get_equity_curve_chart",
45
+ "load_backtests_from_directory",
46
+ "save_backtests_to_directory"
45
47
  ]
@@ -1,11 +1,15 @@
1
1
  from .backtest_data_ranges import select_backtest_date_ranges
2
2
  from .ranking import rank_results, create_weights, combine_backtest_metrics
3
3
  from .permutation import create_ohlcv_permutation
4
+ from .backtest_utils import load_backtests_from_directory, \
5
+ save_backtests_to_directory
4
6
 
5
7
  __all__ = [
6
8
  "select_backtest_date_ranges",
7
9
  "rank_results",
8
10
  "create_weights",
9
11
  "create_ohlcv_permutation",
10
- "combine_backtest_metrics"
12
+ "combine_backtest_metrics",
13
+ "load_backtests_from_directory",
14
+ "save_backtests_to_directory"
11
15
  ]
@@ -0,0 +1,80 @@
1
+ import os
2
+ from pathlib import Path
3
+ from typing import List, Union
4
+ from logging import getLogger
5
+ from random import Random
6
+
7
+ from investing_algorithm_framework.domain import Backtest
8
+
9
+
10
+ logger = getLogger("investing_algorithm_framework")
11
+
12
+
13
+ def save_backtests_to_directory(
14
+ backtests: List[Backtest],
15
+ directory_path: Union[str, Path]
16
+ ) -> None:
17
+ """
18
+ Saves a list of Backtest objects to the specified directory.
19
+
20
+ Args:
21
+ backtests (List[Backtest]): List of Backtest objects to save.
22
+ directory_path (str): Path to the directory where backtests
23
+ will be saved.
24
+
25
+ Returns:
26
+ None
27
+ """
28
+
29
+ if not os.path.exists(directory_path):
30
+ os.makedirs(directory_path)
31
+
32
+ for backtest in backtests:
33
+ # Check if there is an ID in the backtest metadata
34
+ backtest_id = backtest.metadata.get('id')
35
+
36
+ if backtest_id is None:
37
+ logger.warning(
38
+ "Backtest is missing 'id' in metadata. "
39
+ "Generating a random ID as name for backtest file."
40
+ )
41
+ backtest_id = str(Random().randint(100000, 999999))
42
+
43
+ backtest.save(os.path.join(directory_path, backtest_id))
44
+
45
+
46
+ def load_backtests_from_directory(
47
+ directory_path: Union[str, Path]
48
+ ) -> List[Backtest]:
49
+ """
50
+ Loads Backtest objects from the specified directory.
51
+
52
+ Args:
53
+ directory_path (str): Path to the directory from which backtests
54
+ will be loaded.
55
+
56
+ Returns:
57
+ List[Backtest]: List of loaded Backtest objects.
58
+ """
59
+
60
+ backtests = []
61
+
62
+ if not os.path.exists(directory_path):
63
+ logger.warning(
64
+ f"Directory {directory_path} does not exist. "
65
+ "No backtests loaded."
66
+ )
67
+ return backtests
68
+
69
+ for file_name in os.listdir(directory_path):
70
+ file_path = os.path.join(directory_path, file_name)
71
+
72
+ try:
73
+ backtest = Backtest.open(file_path)
74
+ backtests.append(backtest)
75
+ except Exception as e:
76
+ logger.error(
77
+ f"Failed to load backtest from {file_path}: {e}"
78
+ )
79
+
80
+ return backtests
@@ -801,6 +801,7 @@ class App:
801
801
  show_progress: bool = True,
802
802
  market: Optional[str] = None,
803
803
  trading_symbol: Optional[str] = None,
804
+ continue_on_error: bool = False,
804
805
  ) -> List[Backtest]:
805
806
  """
806
807
  Run vectorized backtests for a set of strategies. The provided
@@ -848,6 +849,10 @@ class App:
848
849
  trading_symbol (str): The trading symbol to use for the backtest.
849
850
  This is used to create a portfolio configuration if no
850
851
  portfolio configuration is provided in the strategy.
852
+ continue_on_error (bool): Whether to continue running other
853
+ backtests if an error occurs in one of the backtests. If set
854
+ to True, the backtest will return an empty Backtest instance
855
+ in case of an error. If set to False, the error will be raised.
851
856
 
852
857
  Returns:
853
858
  List[Backtest]: List of Backtest instances for each strategy
@@ -956,7 +961,8 @@ class App:
956
961
  show_data_initialization_progress: bool = True,
957
962
  initial_amount: float = None,
958
963
  market: str = None,
959
- trading_symbol: str = None
964
+ trading_symbol: str = None,
965
+ continue_on_error: bool = False,
960
966
  ) -> Backtest:
961
967
  """
962
968
  Run vectorized backtests for a strategy. The provided
@@ -1008,6 +1014,10 @@ class App:
1008
1014
  that the portfolio will start with. If not provided,
1009
1015
  the initial amount from the portfolio configuration will
1010
1016
  be used.
1017
+ continue_on_error (bool): Whether to continue running other
1018
+ backtests if an error occurs in one of the backtests. If set
1019
+ to True, the backtest will return an empty Backtest instance
1020
+ in case of an error. If set to False, the error will be raised.
1011
1021
 
1012
1022
  Returns:
1013
1023
  Backtest: Instance of Backtest
@@ -1040,21 +1050,35 @@ class App:
1040
1050
 
1041
1051
  backtest_service = self.container.backtest_service()
1042
1052
  backtest_service.validate_strategy_for_vector_backtest(strategy)
1043
- run = backtest_service.create_vector_backtest(
1044
- strategy=strategy,
1045
- backtest_date_range=backtest_date_range,
1046
- risk_free_rate=risk_free_rate,
1047
- market=market,
1048
- trading_symbol=trading_symbol,
1049
- initial_amount=initial_amount
1050
- )
1051
- backtest = Backtest(
1052
- backtest_runs=[run],
1053
- risk_free_rate=risk_free_rate,
1054
- backtest_summary=generate_backtest_summary_metrics(
1055
- [run.backtest_metrics]
1053
+
1054
+ try:
1055
+ run = backtest_service.create_vector_backtest(
1056
+ strategy=strategy,
1057
+ backtest_date_range=backtest_date_range,
1058
+ risk_free_rate=risk_free_rate,
1059
+ market=market,
1060
+ trading_symbol=trading_symbol,
1061
+ initial_amount=initial_amount
1056
1062
  )
1057
- )
1063
+ backtest = Backtest(
1064
+ backtest_runs=[run],
1065
+ risk_free_rate=risk_free_rate,
1066
+ backtest_summary=generate_backtest_summary_metrics(
1067
+ [run.backtest_metrics]
1068
+ )
1069
+ )
1070
+ except Exception as e:
1071
+ logger.error(
1072
+ f"Error occurred during vector backtest for strategy "
1073
+ f"{strategy.strategy_id}: {str(e)}"
1074
+ )
1075
+ if continue_on_error:
1076
+ backtest = Backtest(
1077
+ backtest_runs=[],
1078
+ risk_free_rate=risk_free_rate,
1079
+ )
1080
+ else:
1081
+ raise e
1058
1082
 
1059
1083
  # Add the metadata to the backtest
1060
1084
  if metadata is None:
@@ -14,6 +14,7 @@ from .backtest_run import BacktestRun
14
14
  from .backtest_permutation_test import BacktestPermutationTest
15
15
  from .backtest_date_range import BacktestDateRange
16
16
  from .backtest_summary_metrics import BacktestSummaryMetrics
17
+ from .combine_backtests import generate_backtest_summary_metrics
17
18
 
18
19
 
19
20
  logger = getLogger(__name__)
@@ -177,13 +178,19 @@ class Backtest:
177
178
  }
178
179
 
179
180
  @staticmethod
180
- def open(directory_path: Union[str, Path]) -> 'Backtest':
181
+ def open(
182
+ directory_path: Union[str, Path],
183
+ backtest_date_ranges: List[BacktestDateRange] = None
184
+ ) -> 'Backtest':
181
185
  """
182
186
  Open a backtest report from a directory and return a Backtest instance.
183
187
 
184
188
  Args:
185
189
  directory_path (str): The path to the directory containing the
186
190
  backtest report files.
191
+ backtest_date_ranges (List[BacktestDateRange], optional): A list of
192
+ date ranges to filter the backtest runs. If provided, only
193
+ backtest runs matching these date ranges will be loaded.
187
194
 
188
195
  Returns:
189
196
  Backtest: An instance of Backtest with the loaded metrics
@@ -214,13 +221,59 @@ class Backtest:
214
221
  except json.JSONDecodeError as e:
215
222
  logger.error(f"Error decoding id JSON: {e}")
216
223
  id = None
217
- # Load combined backtest metrics if available
218
224
 
219
- summary_file = os.path.join(directory_path, "summary.json")
225
+ # Load all backtest runs
226
+ runs_dir = os.path.join(directory_path, "runs")
227
+
228
+ if os.path.isdir(runs_dir):
229
+ for dir_name in os.listdir(runs_dir):
230
+ run_path = os.path.join(runs_dir, dir_name)
231
+ if os.path.isdir(run_path):
232
+
233
+ if backtest_date_ranges is not None:
234
+ temp_run = BacktestRun.open(run_path)
235
+ match_found = False
236
+
237
+ for date_range in backtest_date_ranges:
238
+ if (
239
+ temp_run.backtest_start_date ==
240
+ date_range.start_date and
241
+ temp_run.backtest_end_date ==
242
+ date_range.end_date
243
+ ):
244
+
245
+ if date_range.name is not None:
246
+ if (
247
+ temp_run.backtest_date_range_name ==
248
+ date_range.name
249
+ ):
250
+ match_found = True
251
+ break
252
+ else:
253
+ match_found = True
254
+ break
255
+
256
+ if not match_found:
257
+ continue
258
+
259
+ backtest_runs.append(BacktestRun.open(run_path))
260
+
261
+ # Load combined backtests summary
262
+ if backtest_date_ranges is not None:
263
+ summary_file = os.path.join(directory_path, "summary.json")
264
+
265
+ if os.path.isfile(summary_file):
266
+ backtest_summary_metrics = \
267
+ BacktestSummaryMetrics.open(summary_file)
268
+ else:
269
+ # Generate new summary from loaded backtest runs
270
+ temp_metrics = []
271
+ for br in backtest_runs:
272
+ if br.backtest_metrics:
273
+ temp_metrics.append(br.backtest_metrics)
220
274
 
221
- if os.path.isfile(summary_file):
222
275
  backtest_summary_metrics = \
223
- BacktestSummaryMetrics.open(summary_file)
276
+ generate_backtest_summary_metrics(temp_metrics)
224
277
 
225
278
  # Load backtest permutation test metrics
226
279
  perm_test_dir = os.path.join(directory_path, "permutation_tests")
@@ -233,15 +286,6 @@ class Backtest:
233
286
  BacktestPermutationTest.open(perm_test_file)
234
287
  )
235
288
 
236
- # Load all backtest runs
237
- runs_dir = os.path.join(directory_path, "runs")
238
-
239
- if os.path.isdir(runs_dir):
240
- for dir_name in os.listdir(runs_dir):
241
- run_path = os.path.join(runs_dir, dir_name)
242
- if os.path.isdir(run_path):
243
- backtest_runs.append(BacktestRun.open(run_path))
244
-
245
289
  # Load metadata if available
246
290
  meta_file = os.path.join(directory_path, "metadata.json")
247
291
 
@@ -251,17 +251,41 @@ class BacktestRun:
251
251
  # Remove backtest_metrics to avoid redundancy
252
252
  data.pop("backtest_metrics", None)
253
253
 
254
- data["backtest_start_date"] = self.backtest_start_date.strftime(
254
+ # Ensure datetime objects are in UTC before formatting
255
+ backtest_start_date = self.backtest_start_date
256
+
257
+ if backtest_start_date.tzinfo is None:
258
+ # Naive datetime - treat as UTC
259
+ backtest_start_date = backtest_start_date.replace(
260
+ tzinfo=timezone.utc
261
+ )
262
+ else:
263
+ # Timezone-aware - convert to UTC
264
+ backtest_start_date = backtest_start_date.astimezone(
265
+ timezone.utc
266
+ )
267
+
268
+ backtest_end_date = self.backtest_end_date
269
+ if backtest_end_date.tzinfo is None:
270
+ backtest_end_date = backtest_end_date.replace(
271
+ tzinfo=timezone.utc
272
+ )
273
+ else:
274
+ backtest_end_date = backtest_end_date.astimezone(timezone.utc)
275
+
276
+ created_at = self.created_at
277
+ if created_at.tzinfo is None:
278
+ created_at = created_at.replace(tzinfo=timezone.utc)
279
+ else:
280
+ created_at = created_at.astimezone(timezone.utc)
281
+
282
+ data["backtest_start_date"] = backtest_start_date.strftime(
255
283
  "%Y-%m-%d %H:%M:%S"
256
284
  )
257
- data["backtest_end_date"] = self.backtest_end_date.strftime(
285
+ data["backtest_end_date"] = backtest_end_date.strftime(
258
286
  "%Y-%m-%d %H:%M:%S"
259
287
  )
260
-
261
- if self.created_at.tzinfo is None:
262
- self.created_at = self.created_at.replace(tzinfo=timezone.utc)
263
-
264
- data["created_at"] = self.created_at.strftime(
288
+ data["created_at"] = created_at.strftime(
265
289
  "%Y-%m-%d %H:%M:%S"
266
290
  )
267
291
  json.dump(data, f, default=str)
@@ -1,6 +1,5 @@
1
1
  from typing import List
2
2
 
3
- from .backtest import Backtest
4
3
  from .backtest_summary_metrics import BacktestSummaryMetrics
5
4
  from .backtest_metrics import BacktestMetrics
6
5
 
@@ -27,9 +26,7 @@ def safe_weighted_mean(values, weights):
27
26
  ) / total_weight if total_weight > 0 else None
28
27
 
29
28
 
30
- def combine_backtests(
31
- backtests: List[Backtest],
32
- ) -> Backtest:
29
+ def combine_backtests(backtests):
33
30
  """
34
31
  Combine multiple backtests into a single backtest by aggregating
35
32
  their results.
@@ -72,6 +69,8 @@ def combine_backtests(
72
69
  risk_free_rate = backtest.risk_free_rate
73
70
  break
74
71
 
72
+ from .backtest import Backtest
73
+
75
74
  backtest = Backtest(
76
75
  backtest_summary=summary,
77
76
  metadata=metadata,
@@ -240,6 +240,7 @@ class DataProvider(ABC):
240
240
  backtest_index_date: datetime,
241
241
  backtest_start_date: datetime = None,
242
242
  backtest_end_date: datetime = None,
243
+ data_source: DataSource = None,
243
244
  ) -> Any:
244
245
  """
245
246
  Fetches backtest data for a given datasource
@@ -251,6 +252,10 @@ class DataProvider(ABC):
251
252
  backtest data.
252
253
  backtest_end_date (datetime): The end date for the
253
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.
254
259
 
255
260
  Returns:
256
261
  Any: The data for the given symbol and date range.
@@ -386,7 +386,8 @@ class CCXTOHLCVDataProvider(DataProvider):
386
386
  self,
387
387
  backtest_index_date: datetime,
388
388
  backtest_start_date: datetime = None,
389
- backtest_end_date: datetime = None
389
+ backtest_end_date: datetime = None,
390
+ data_source: DataSource = None
390
391
  ) -> None:
391
392
  """
392
393
  Fetches backtest data for a given datasource
@@ -398,6 +399,8 @@ class CCXTOHLCVDataProvider(DataProvider):
398
399
  backtest data.
399
400
  backtest_end_date (datetime): The end date for the
400
401
  backtest data.
402
+ data_source (Optional[Datasource]): The data source for which to
403
+ fetch backtest data. Defaults to None.
401
404
 
402
405
  Returns:
403
406
  pl.DataFrame: The backtest data for the given datasource.
@@ -407,6 +410,17 @@ class CCXTOHLCVDataProvider(DataProvider):
407
410
  backtest_end_date is not None:
408
411
 
409
412
  if backtest_start_date < self._start_date_data_source:
413
+
414
+ if data_source is not None:
415
+ raise OperationalException(
416
+ f"Request data date {backtest_start_date} "
417
+ f"is before the range of "
418
+ f"the available data "
419
+ f"{self._start_date_data_source} "
420
+ f"- {self._end_date_data_source}."
421
+ f" for data source {data_source.identifier}."
422
+ )
423
+
410
424
  raise OperationalException(
411
425
  f"Request data date {backtest_start_date} "
412
426
  f"is before the range of "
@@ -416,6 +430,17 @@ class CCXTOHLCVDataProvider(DataProvider):
416
430
  )
417
431
 
418
432
  if backtest_end_date > self._end_date_data_source:
433
+
434
+ if data_source is not None:
435
+ raise OperationalException(
436
+ f"Request data date {backtest_end_date} "
437
+ f"is after the range of "
438
+ f"the available data "
439
+ f"{self._start_date_data_source} "
440
+ f"- {self._end_date_data_source}."
441
+ f" for data source {data_source.identifier}."
442
+ )
443
+
419
444
  raise OperationalException(
420
445
  f"Request data date {backtest_end_date} "
421
446
  f"is after the range of "
@@ -442,6 +467,15 @@ class CCXTOHLCVDataProvider(DataProvider):
442
467
  )
443
468
  data = self.window_cache[closest_key]
444
469
  except ValueError:
470
+
471
+ if data_source is not None:
472
+ raise OperationalException(
473
+ "No OHLCV data available for the "
474
+ f"date: {backtest_index_date} "
475
+ f"within the prepared backtest data "
476
+ f"for data source {data_source.identifier}. "
477
+ )
478
+
445
479
  raise OperationalException(
446
480
  "No OHLCV data available for the "
447
481
  f"date: {backtest_index_date} "
@@ -267,18 +267,25 @@ class CSVOHLCVDataProvider(DataProvider):
267
267
  self,
268
268
  backtest_index_date: datetime,
269
269
  backtest_start_date: datetime = None,
270
- backtest_end_date: datetime = None
270
+ backtest_end_date: datetime = None,
271
+ data_source: DataSource = None
271
272
  ) -> None:
272
273
  """
273
274
  Fetches backtest data for a given datasource
274
275
 
275
276
  Args:
276
- backtest_index_date (datetime): The date for which to fetch
277
+ backtest_index_date (datetime): The date for which to fetch
277
278
  backtest data.
278
- backtest_start_date (datetime): The start date for the
279
+ backtest_start_date (datetime): The start date for the
279
280
  backtest data.
280
- backtest_end_date (datetime): The end date for the
281
+ backtest_end_date (datetime): The end date for the
281
282
  backtest data.
283
+ data_source (Optional[DataSource]): The data source specification
284
+ that matches a data provider.
285
+
286
+ Raises:
287
+ OperationalException: If the requested backtest date range
288
+ is outside the available data range.
282
289
 
283
290
  Returns:
284
291
  pl.DataFrame: The backtest data for the given datasource.
@@ -287,6 +294,17 @@ class CSVOHLCVDataProvider(DataProvider):
287
294
  backtest_end_date is not None:
288
295
 
289
296
  if backtest_start_date < self._start_date_data_source:
297
+
298
+ if data_source is not None:
299
+ raise OperationalException(
300
+ f"Request data date {backtest_end_date} "
301
+ f"is after the range of "
302
+ f"the available data "
303
+ f"{self._start_date_data_source} "
304
+ f"- {self._end_date_data_source}."
305
+ f" for data source {data_source.identifier}."
306
+ )
307
+
290
308
  raise OperationalException(
291
309
  f"Request data date {backtest_start_date} "
292
310
  f"is before the range of "
@@ -296,6 +314,17 @@ class CSVOHLCVDataProvider(DataProvider):
296
314
  )
297
315
 
298
316
  if backtest_end_date > self._end_date_data_source:
317
+
318
+ if data_source is not None:
319
+ raise OperationalException(
320
+ f"Request data date {backtest_end_date} "
321
+ f"is after the range of "
322
+ f"the available data "
323
+ f"{self._start_date_data_source} "
324
+ f"- {self._end_date_data_source}."
325
+ f" for data source {data_source.identifier}."
326
+ )
327
+
299
328
  raise OperationalException(
300
329
  f"Request data date {backtest_end_date} "
301
330
  f"is after the range of "
@@ -322,6 +351,15 @@ class CSVOHLCVDataProvider(DataProvider):
322
351
  )
323
352
  data = self.window_cache[closest_key]
324
353
  except ValueError:
354
+
355
+ if data_source is not None:
356
+ raise OperationalException(
357
+ "No data available for the "
358
+ f"date: {backtest_index_date} "
359
+ "within the prepared backtest data "
360
+ f"for data source {data_source.identifier}."
361
+ )
362
+
325
363
  raise OperationalException(
326
364
  "No data available for the "
327
365
  f"date: {backtest_index_date} "
@@ -290,17 +290,20 @@ class PandasOHLCVDataProvider(DataProvider):
290
290
  backtest_index_date: datetime = None,
291
291
  backtest_start_date: datetime = None,
292
292
  backtest_end_date: datetime = None,
293
+ data_source: DataSource = None
293
294
  ) -> None:
294
295
  """
295
296
  Fetches backtest data for a given datasource
296
297
 
297
298
  Args:
298
- backtest_index_date (datetime): The date for which to fetch
299
+ backtest_index_date (datetime): The date for which to fetch
299
300
  backtest data.
300
- backtest_start_date (datetime): The start date for the
301
+ backtest_start_date (datetime): The start date for the
301
302
  backtest data.
302
- backtest_end_date (datetime): The end date for the
303
+ backtest_end_date (datetime): The end date for the
303
304
  backtest data.
305
+ data_source (Optional[DataSource]): The data source for which to
306
+ fetch backtest data.
304
307
 
305
308
  Returns:
306
309
  pl.DataFrame: The backtest data for the given datasource.
@@ -310,6 +313,17 @@ class PandasOHLCVDataProvider(DataProvider):
310
313
  backtest_end_date is not None:
311
314
 
312
315
  if backtest_start_date < self._start_date_data_source:
316
+
317
+ if data_source is not None:
318
+ raise OperationalException(
319
+ f"Request data date {backtest_end_date} "
320
+ f"is after the range of "
321
+ f"the available data "
322
+ f"{self._start_date_data_source} "
323
+ f"- {self._end_date_data_source}."
324
+ f" for data source {data_source.identifier}."
325
+ )
326
+
313
327
  raise OperationalException(
314
328
  f"Request data date {backtest_start_date} "
315
329
  f"is before the range of "
@@ -319,6 +333,17 @@ class PandasOHLCVDataProvider(DataProvider):
319
333
  )
320
334
 
321
335
  if backtest_end_date > self._end_date_data_source:
336
+
337
+ if data_source is not None:
338
+ raise OperationalException(
339
+ f"Request data date {backtest_end_date} "
340
+ f"is after the range of "
341
+ f"the available data "
342
+ f"{self._start_date_data_source} "
343
+ f"- {self._end_date_data_source}."
344
+ f" for data source {data_source.identifier}."
345
+ )
346
+
322
347
  raise OperationalException(
323
348
  f"Request data date {backtest_end_date} "
324
349
  f"is after the range of "
@@ -345,6 +370,15 @@ class PandasOHLCVDataProvider(DataProvider):
345
370
  )
346
371
  data = self.window_cache[closest_key]
347
372
  except ValueError:
373
+
374
+ if data_source is not None:
375
+ raise OperationalException(
376
+ "No data available for the "
377
+ f"date: {backtest_index_date} "
378
+ "within the prepared backtest data "
379
+ f"for data source {data_source.identifier}."
380
+ )
381
+
348
382
  raise OperationalException(
349
383
  "No data available for the "
350
384
  f"date: {backtest_index_date} "
@@ -156,7 +156,6 @@ class DataProviderIndex:
156
156
  f"provider for the defined datasource. If you are using a "
157
157
  "custom data provider, make sure it has a "
158
158
  "data_provider_identifier set"
159
-
160
159
  )
161
160
 
162
161
  # Sort by priority and pick the best one (lowest priority first)
@@ -553,6 +552,7 @@ class DataProviderService:
553
552
  backtest_index_date=backtest_index_date,
554
553
  backtest_start_date=start_date,
555
554
  backtest_end_date=end_date,
555
+ data_source=data_source
556
556
  )
557
557
 
558
558
  def get_vectorized_backtest_data(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: investing-algorithm-framework
3
- Version: 7.16.20
3
+ Version: 7.18.0
4
4
  Summary: A framework for creating trading bots
5
5
  Author: MDUYN
6
6
  Requires-Python: >=3.10
@@ -1,13 +1,14 @@
1
- investing_algorithm_framework/__init__.py,sha256=rYv3eJDD0QbOPXCvK-Ye8oml63MYM_QLqXrt6vFpHk8,6857
2
- investing_algorithm_framework/app/__init__.py,sha256=x683g8hvp5yERywt0CFWLavMcTDnCwVVUW_g3o5kyOc,1539
1
+ investing_algorithm_framework/__init__.py,sha256=ACNFUkjEf2UxGoBoci9JHeT6hGFISFyy4U-rLnZW8_E,6989
2
+ investing_algorithm_framework/app/__init__.py,sha256=BjVkBQvVuI7ovTpR2Bn8YOHL2p5vsX-LRqpe-aS3ImM,1671
3
3
  investing_algorithm_framework/app/algorithm/__init__.py,sha256=-a9o9bTfAhW9qSW-bKvlLQuMCf-YXxIztudo2TxMjCI,136
4
4
  investing_algorithm_framework/app/algorithm/algorithm.py,sha256=v8AZZ7hr5ZKJbavk242xCUpGHv3mKZ4sqfGV7BwPgdU,6854
5
5
  investing_algorithm_framework/app/algorithm/algorithm_factory.py,sha256=Z6El6ErAEEljVGEM0Hkd2VXSMM9qkSCfkp-Ht-WEy3w,3549
6
- investing_algorithm_framework/app/analysis/__init__.py,sha256=hstMgI9_FufhUqHHphBTSS_KgKpktB_aBBvu-61whU8,343
6
+ investing_algorithm_framework/app/analysis/__init__.py,sha256=hISKshN-FQchLBasWX5raMG-IXVJP43t5fXEdrL6M9U,508
7
7
  investing_algorithm_framework/app/analysis/backtest_data_ranges.py,sha256=pt8vUjhyqCN5JspihlzMWW4n5BULWcy0E9SlkEM2Fo4,3960
8
+ investing_algorithm_framework/app/analysis/backtest_utils.py,sha256=W_X1T8f1UOllUUkaO4ZiQlQ7dSEGi9TqVo_ROyv7XNE,2121
8
9
  investing_algorithm_framework/app/analysis/permutation.py,sha256=NHzyMQ9aCqLiLXyw1CpRZfITLzsRwHmMn8Uj8oV5_1E,3941
9
10
  investing_algorithm_framework/app/analysis/ranking.py,sha256=-XEWmU3rxLvkC9GOW6Zci7E3Y7H2xKwU3id8ljf0n9k,10888
10
- investing_algorithm_framework/app/app.py,sha256=6OCoiSM4HXhbR4fY_pFOrhkPR_oEifprnb1I8dP2dIM,82040
11
+ investing_algorithm_framework/app/app.py,sha256=saAfkfWJg-in7qKCOIpN1HljMU2YCuvdqSTWi4a-Ub0,83223
11
12
  investing_algorithm_framework/app/app_hook.py,sha256=cDiY4x2n06tljpx-fcbIM1oPnjTnEthibvqxUvfEppo,834
12
13
  investing_algorithm_framework/app/context.py,sha256=kWOBZq7E45xoAPbMfn9HPhDQmEcyCqBYWi-NJK5nMXk,58874
13
14
  investing_algorithm_framework/app/eventloop.py,sha256=w9zufVpiHrgsxuh4_AW2DXxOfI4LVcScNHTLfL9-Tws,21736
@@ -84,17 +85,17 @@ investing_algorithm_framework/create_app.py,sha256=HN_45Bza5Ro3o-v364m2mjSVjjZny
84
85
  investing_algorithm_framework/dependency_container.py,sha256=LIqK4fH2OdIKpvixzI2eSCXp6EtvEcWAJxJo-7a9qp4,6699
85
86
  investing_algorithm_framework/domain/__init__.py,sha256=PASQlRGcU_MDIwnLppanXGo-BUXdkhajvpRvcwqxdL0,4903
86
87
  investing_algorithm_framework/domain/backtesting/__init__.py,sha256=q-NejGHzE233w5jXPhSsuLpBZ_yl3m-qb2g6FnxZaps,699
87
- investing_algorithm_framework/domain/backtesting/backtest.py,sha256=mS7JdPTXhw5AQrQ-krXWtpNsBbVVYkxc1lBSoPhCqoQ,15617
88
+ investing_algorithm_framework/domain/backtesting/backtest.py,sha256=eRSaujcoggYdX3jmGO8KL4a2gOdYUeWH7Jx5vcLkeYg,17514
88
89
  investing_algorithm_framework/domain/backtesting/backtest_date_range.py,sha256=e_V7HMdtln4uu87jwwa_Yr7ZesgrpFqsEqtr0e0DTto,3186
89
90
  investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py,sha256=D__3I_TSxDVnGtlddmWt4wHcqut8MGyYMf1IfQZXYJ0,7547
90
91
  investing_algorithm_framework/domain/backtesting/backtest_metrics.py,sha256=gfiuNhT15UpY5l02onknf7D5wHJfeUKodlnG9FV5I1E,20120
91
92
  investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py,sha256=8JXdu3EgFh2f2Yc41OYwIBwlYtjFiumyAJUrN5kL078,6703
92
- investing_algorithm_framework/domain/backtesting/backtest_run.py,sha256=ffQgilEkyixGrGvKeCZF670OPoY3ljixlgsXPajHpZY,14310
93
+ investing_algorithm_framework/domain/backtesting/backtest_run.py,sha256=Pozrx4zUBDOHDn6jFY9sHUksMya3AGsgboIOUeBbKxk,15267
93
94
  investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py,sha256=Dt3gFz-MNmxtOhYxVPN8lI_7rXtE9PK10lULDFuCHlU,7131
94
- investing_algorithm_framework/domain/backtesting/combine_backtests.py,sha256=O2tv59Ak9Jy25w47Iz2b2pdtr2uDmkb8OUeoEMihq2o,9993
95
+ investing_algorithm_framework/domain/backtesting/combine_backtests.py,sha256=E6MHctsSaiQdDPLQtXSU9Exf_yYLUc2oAD3vKMeNM20,9963
95
96
  investing_algorithm_framework/domain/config.py,sha256=_VkaJvrdqIKAT3_l-Y8XTEKNEaw5uVIwQ7vxomuCpUw,3003
96
97
  investing_algorithm_framework/domain/constants.py,sha256=-ZU0z1DSgJaUQvkp_ELZslqD0D-scASz7uhdXRbR8Tc,2610
97
- investing_algorithm_framework/domain/data_provider.py,sha256=PJPbG6qWbHoGqJskVX8oCB-1FKdf67AZ21eDjcmSNGc,10739
98
+ investing_algorithm_framework/domain/data_provider.py,sha256=yMr7RWxHh6I9kSHsrwwukXi0k6UHLwmntIQGv0V0Q1w,11018
98
99
  investing_algorithm_framework/domain/data_structures.py,sha256=ePtdGhVaB16QLUvKQn5MiWM_TBOcBBTj5M0llW8tGEE,989
99
100
  investing_algorithm_framework/domain/decimal_parsing.py,sha256=NtMNkxZyWrFHxGKd6gLIDmWF88BYcTl8tYaAaKO1tlg,823
100
101
  investing_algorithm_framework/domain/exceptions.py,sha256=CjMPVqA5_hPoyeThH0vP-grGpmWXrKU_Tb19wWUJGqM,2359
@@ -157,9 +158,9 @@ investing_algorithm_framework/domain/utils/synchronized.py,sha256=YjvutHxMh5r1WE
157
158
  investing_algorithm_framework/download_data.py,sha256=VSnXiHOSyIXwUrFuXHlvJb-YMUrXBTQN2P2r4qcQsCI,3956
158
159
  investing_algorithm_framework/infrastructure/__init__.py,sha256=-ekyHXcQzGSIp3HYOxqV2W2Wt5JiLxazmkEE-jOV7qA,1774
159
160
  investing_algorithm_framework/infrastructure/data_providers/__init__.py,sha256=7uhvfzs5eAoARD_f36d7xmns9JSpQX843pyplQF6f0A,742
160
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py,sha256=nrtRObKCxDxlCb-ZnE-n40aXvmR1aimwMPWH-SRQzQk,36735
161
- investing_algorithm_framework/infrastructure/data_providers/csv.py,sha256=V7rKm41ixdhU2PXJ76dVCflH0qwOUffzNE_gRjkHGg0,16282
162
- investing_algorithm_framework/infrastructure/data_providers/pandas.py,sha256=JZy6l3xD5bLbJ8jbvlpb7NzyL-71OC7IjtSOFnqmjHo,17542
161
+ investing_algorithm_framework/infrastructure/data_providers/ccxt.py,sha256=sFyV3qHUK1rHLXMw6nt8yzdOnHwrPE6ImVC1rORjPe8,38235
162
+ investing_algorithm_framework/infrastructure/data_providers/csv.py,sha256=V1mpOYmBeC93HYQBUwgdH0Gj-57tIg5g9LOtxXSfXn4,17906
163
+ investing_algorithm_framework/infrastructure/data_providers/pandas.py,sha256=KIDYbStexDSbkmQzalTu36Qoxb68miR7Uzv_Y1acYt8,19015
163
164
  investing_algorithm_framework/infrastructure/database/__init__.py,sha256=Zf986kazO5O-NPC2nF0dmy0EAkwi93P-R_Amun-ig3w,214
164
165
  investing_algorithm_framework/infrastructure/database/sql_alchemy.py,sha256=kZpUaWQiGqtilsGpMkl7ZE8LygoWAU6t2HhwO6niimE,3509
165
166
  investing_algorithm_framework/infrastructure/models/__init__.py,sha256=Xtcy4dHqrItaTpSfPfD0ztz3rH56yE_viFEzrtV79H0,441
@@ -205,7 +206,7 @@ investing_algorithm_framework/services/backtesting/__init__.py,sha256=sD6JMQVuUT
205
206
  investing_algorithm_framework/services/backtesting/backtest_service.py,sha256=rWxOxAXhuywb0jx_j0XVChKBPU3CUV4vYF0T7oTtG2E,24841
206
207
  investing_algorithm_framework/services/configuration_service.py,sha256=BCgiBlrLjMjfU4afmjYaHu9gOWNmgaxhf6RBN2XJkw0,2853
207
208
  investing_algorithm_framework/services/data_providers/__init__.py,sha256=OHVccpIYGc-1B2AkCI_2Nhsb9KMaAUrng4DHhIbFD8Y,96
208
- investing_algorithm_framework/services/data_providers/data_provider_service.py,sha256=Tv5W38rshK7sG7XEhp7L-McdiNWAAlvU_1ScSdt1NCE,28420
209
+ investing_algorithm_framework/services/data_providers/data_provider_service.py,sha256=KOhxQEdU1VxE5MXl713Lo4lzv3J8kuSxCa025Ywyr20,28455
209
210
  investing_algorithm_framework/services/market_credential_service.py,sha256=syitQ61sECzK0i0Wd-Hc8xaTv4tpRYRFbCjyw9pWMeA,1197
210
211
  investing_algorithm_framework/services/metrics/__init__.py,sha256=kQaAw5r-hXCsaEY9fQ5Fch8CfjltdjKrGFn6gNSWWdA,4322
211
212
  investing_algorithm_framework/services/metrics/alpha.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -252,8 +253,8 @@ investing_algorithm_framework/services/trade_order_evaluator/default_trade_order
252
253
  investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py,sha256=pNnmgaKMR9RY6Kxq7xS0nURKoaQDe2ehrP5GfElkkcI,1328
253
254
  investing_algorithm_framework/services/trade_service/__init__.py,sha256=AcwPyJjDRdiREnl_MWMkDSc-V-ZjXtvpHD6eQT9mc1o,68
254
255
  investing_algorithm_framework/services/trade_service/trade_service.py,sha256=OtzIS5EebByGcqDvV2AFeBjXSarvrgubMXDaVKg6Rbw,41193
255
- investing_algorithm_framework-7.16.20.dist-info/LICENSE,sha256=wbVEDvoZiMPHufRY3sLEffvAr7GH5hOIngHF8y4HFQg,11343
256
- investing_algorithm_framework-7.16.20.dist-info/METADATA,sha256=rliJwEZyg3ys51tNtm4YGRyC9mFHIABzG90ZNDJx0J8,19636
257
- investing_algorithm_framework-7.16.20.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
258
- investing_algorithm_framework-7.16.20.dist-info/entry_points.txt,sha256=jrPF0YksDs27vYzEvj3tXLe3OGWU24EJA05z5xHqmq8,91
259
- investing_algorithm_framework-7.16.20.dist-info/RECORD,,
256
+ investing_algorithm_framework-7.18.0.dist-info/LICENSE,sha256=wbVEDvoZiMPHufRY3sLEffvAr7GH5hOIngHF8y4HFQg,11343
257
+ investing_algorithm_framework-7.18.0.dist-info/METADATA,sha256=_8KvJKynhbGR8VmlZzsG6tY6D2u_EzA8iApv5zCkTjE,19635
258
+ investing_algorithm_framework-7.18.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
259
+ investing_algorithm_framework-7.18.0.dist-info/entry_points.txt,sha256=jrPF0YksDs27vYzEvj3tXLe3OGWU24EJA05z5xHqmq8,91
260
+ investing_algorithm_framework-7.18.0.dist-info/RECORD,,