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.
- investing_algorithm_framework/__init__.py +5 -3
- investing_algorithm_framework/app/__init__.py +4 -2
- investing_algorithm_framework/app/analysis/__init__.py +5 -1
- investing_algorithm_framework/app/analysis/backtest_utils.py +80 -0
- investing_algorithm_framework/app/app.py +39 -15
- investing_algorithm_framework/domain/backtesting/backtest.py +58 -14
- investing_algorithm_framework/domain/backtesting/backtest_run.py +31 -7
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +3 -4
- investing_algorithm_framework/domain/data_provider.py +5 -0
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +35 -1
- investing_algorithm_framework/infrastructure/data_providers/csv.py +42 -4
- investing_algorithm_framework/infrastructure/data_providers/pandas.py +37 -3
- investing_algorithm_framework/services/data_providers/data_provider_service.py +1 -1
- {investing_algorithm_framework-7.16.20.dist-info → investing_algorithm_framework-7.18.0.dist-info}/METADATA +1 -1
- {investing_algorithm_framework-7.16.20.dist-info → investing_algorithm_framework-7.18.0.dist-info}/RECORD +18 -17
- {investing_algorithm_framework-7.16.20.dist-info → investing_algorithm_framework-7.18.0.dist-info}/LICENSE +0 -0
- {investing_algorithm_framework-7.16.20.dist-info → investing_algorithm_framework-7.18.0.dist-info}/WHEEL +0 -0
- {investing_algorithm_framework-7.16.20.dist-info → investing_algorithm_framework-7.18.0.dist-info}/entry_points.txt +0 -0
|
@@ -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
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"] =
|
|
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
|
-
|
|
277
|
+
backtest_index_date (datetime): The date for which to fetch
|
|
277
278
|
backtest data.
|
|
278
|
-
|
|
279
|
+
backtest_start_date (datetime): The start date for the
|
|
279
280
|
backtest data.
|
|
280
|
-
|
|
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
|
-
|
|
299
|
+
backtest_index_date (datetime): The date for which to fetch
|
|
299
300
|
backtest data.
|
|
300
|
-
|
|
301
|
+
backtest_start_date (datetime): The start date for the
|
|
301
302
|
backtest data.
|
|
302
|
-
|
|
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,13 +1,14 @@
|
|
|
1
|
-
investing_algorithm_framework/__init__.py,sha256=
|
|
2
|
-
investing_algorithm_framework/app/__init__.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
161
|
-
investing_algorithm_framework/infrastructure/data_providers/csv.py,sha256=
|
|
162
|
-
investing_algorithm_framework/infrastructure/data_providers/pandas.py,sha256=
|
|
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=
|
|
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.
|
|
256
|
-
investing_algorithm_framework-7.
|
|
257
|
-
investing_algorithm_framework-7.
|
|
258
|
-
investing_algorithm_framework-7.
|
|
259
|
-
investing_algorithm_framework-7.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|