investing-algorithm-framework 7.17.0__py3-none-any.whl → 7.19.1__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,16 +1,16 @@
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, \
10
10
  get_ohlcv_data_completeness_chart, get_equity_curve_chart
11
11
  from .domain import ApiException, combine_backtests, PositionSize, \
12
12
  OrderType, OperationalException, OrderStatus, OrderSide, \
13
- TimeUnit, TimeInterval, Order, Portfolio, Backtest, \
13
+ TimeUnit, TimeInterval, Order, Portfolio, Backtest, DataError, \
14
14
  Position, TimeFrame, INDEX_DATETIME, MarketCredential, \
15
15
  PortfolioConfiguration, RESOURCE_DIRECTORY, AWS_LAMBDA_LOGGING_CONFIG, \
16
16
  Trade, SYMBOLS, RESERVED_BALANCES, APP_MODE, AppMode, DATETIME_FORMAT, \
@@ -189,5 +189,8 @@ __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",
195
+ "DataError"
193
196
  ]
@@ -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
@@ -5,6 +5,7 @@ import threading
5
5
  from datetime import datetime, timezone
6
6
  from typing import List, Optional, Any, Dict, Tuple
7
7
 
8
+ import pandas as pd
8
9
  from flask import Flask
9
10
 
10
11
  from investing_algorithm_framework.app.algorithm import Algorithm
@@ -16,7 +17,7 @@ from investing_algorithm_framework.domain import DATABASE_NAME, TimeUnit, \
16
17
  SQLALCHEMY_DATABASE_URI, OperationalException, StateHandler, \
17
18
  BACKTESTING_START_DATE, BACKTESTING_END_DATE, APP_MODE, MarketCredential, \
18
19
  AppMode, BacktestDateRange, DATABASE_DIRECTORY_NAME, DataSource, \
19
- BACKTESTING_INITIAL_AMOUNT, SNAPSHOT_INTERVAL, Backtest, \
20
+ BACKTESTING_INITIAL_AMOUNT, SNAPSHOT_INTERVAL, Backtest, DataError, \
20
21
  PortfolioConfiguration, SnapshotInterval, DataType, combine_backtests, \
21
22
  PortfolioProvider, OrderExecutor, ImproperlyConfigured, \
22
23
  DataProvider, INDEX_DATETIME, tqdm, BacktestPermutationTest, \
@@ -32,6 +33,7 @@ from .app_hook import AppHook
32
33
  from .eventloop import EventLoopService
33
34
  from .analysis import create_ohlcv_permutation
34
35
 
36
+
35
37
  logger = logging.getLogger("investing_algorithm_framework")
36
38
  COLOR_RESET = '\033[0m'
37
39
  COLOR_GREEN = '\033[92m'
@@ -789,6 +791,70 @@ class App:
789
791
  .market_credential_service()
790
792
  return market_credential_service.get_all()
791
793
 
794
+ def check_data_completeness(
795
+ self,
796
+ strategies: List[TradingStrategy],
797
+ backtest_date_range: BacktestDateRange
798
+ ) -> None:
799
+ """
800
+ Function to check the data completeness for a set of strategies
801
+ over a given backtest date range. This method checks if all data
802
+ sources required by the strategies have complete data for the
803
+ specified date range.
804
+
805
+ Args:
806
+ strategies (List[TradingStrategy]): List of strategy objects
807
+ to check data completeness for.
808
+ backtest_date_range (BacktestDateRange): The date range to
809
+ check data completeness for.
810
+ Returns:
811
+ None
812
+ """
813
+ data_sources = []
814
+
815
+ for strategy in strategies:
816
+ data_sources.extend(strategy.data_sources)
817
+
818
+ self.initialize_data_sources_backtest(
819
+ data_sources,
820
+ backtest_date_range,
821
+ show_progress=True
822
+ )
823
+ data_provider_service = self.container.data_provider_service()
824
+
825
+ for strategy in strategies:
826
+
827
+ for data_source in strategy.data_sources:
828
+
829
+ if DataType.OHLCV.equals(data_source.data_type):
830
+ df = data_provider_service.get_ohlcv_data(
831
+ symbol=data_source.symbol,
832
+ start_date=backtest_date_range.start_date,
833
+ end_date=backtest_date_range.end_date,
834
+ pandas=True
835
+ )
836
+ df = df.copy()
837
+ df['Datetime'] = pd.to_datetime(df['Datetime'])
838
+ df = df.sort_values('Datetime')\
839
+ .tail(data_source.window_size)
840
+ start = df['Datetime'].iloc[0]
841
+ end = df['Datetime'].iloc[-1]
842
+ freq = pd.to_timedelta(data_source.time_frame.value)
843
+ expected = pd.date_range(start, end, freq=freq)
844
+ actual = df['Datetime']
845
+ missing = expected.difference(actual)
846
+
847
+ # Calculate the percentage completeness
848
+ completeness = len(actual) / len(expected) * 100
849
+
850
+ if completeness < 100:
851
+ raise DataError(
852
+ f"Data completeness for data source "
853
+ f"{data_source.identifier} "
854
+ f"({data_source.symbol}) is {completeness:.2f}% "
855
+ f"complete. Missing data points: {len(missing)}"
856
+ )
857
+
792
858
  def run_vector_backtests(
793
859
  self,
794
860
  initial_amount,
@@ -1070,13 +1136,12 @@ class App:
1070
1136
  except Exception as e:
1071
1137
  logger.error(
1072
1138
  f"Error occurred during vector backtest for strategy "
1073
- f"{strategy.name}: {str(e)}"
1139
+ f"{strategy.strategy_id}: {str(e)}"
1074
1140
  )
1075
1141
  if continue_on_error:
1076
1142
  backtest = Backtest(
1077
1143
  backtest_runs=[],
1078
1144
  risk_free_rate=risk_free_rate,
1079
- backtest_summary={}
1080
1145
  )
1081
1146
  else:
1082
1147
  raise e
@@ -15,7 +15,7 @@ from .constants import ITEMIZE, ITEMIZED, PER_PAGE, PAGE, ENVIRONMENT, \
15
15
  from .data_provider import DataProvider
16
16
  from .data_structures import PeekableQueue
17
17
  from .decimal_parsing import parse_decimal_to_string, parse_string_to_decimal
18
- from .exceptions import OperationalException, ApiException, \
18
+ from .exceptions import OperationalException, ApiException, DataError, \
19
19
  PermissionDeniedApiException, ImproperlyConfigured, NetworkError
20
20
  from .models import OrderStatus, OrderSide, OrderType, TimeInterval, \
21
21
  TimeUnit, TimeFrame, PortfolioConfiguration, Portfolio, Position, \
@@ -143,5 +143,6 @@ __all__ = [
143
143
  "BacktestEvaluationFocus",
144
144
  'combine_backtests',
145
145
  'PositionSize',
146
- 'generate_backtest_summary_metrics'
146
+ 'generate_backtest_summary_metrics',
147
+ 'DataError'
147
148
  ]
@@ -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)
@@ -84,3 +84,20 @@ class NetworkError(Exception):
84
84
  "status": "error",
85
85
  "message": self.error_message
86
86
  }
87
+
88
+
89
+ class DataError(Exception):
90
+ """
91
+ Class DataError: Exception class indicating a problem occurred
92
+ during data retrieval or processing
93
+ """
94
+
95
+ def __init__(self, message) -> None:
96
+ super(DataError, self).__init__(message)
97
+ self.error_message = message
98
+
99
+ def to_response(self):
100
+ return {
101
+ "status": "error",
102
+ "message": self.error_message
103
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: investing-algorithm-framework
3
- Version: 7.17.0
3
+ Version: 7.19.1
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=e5i9FAiyvS1KwuvSkrFC0dPOgKCix9mGoGy-51Mqe1s,7017
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=-RG4r1cfghkx-yXoWe9xUMQeEUEvXCOUblhIvXYX5Lw,83256
11
+ investing_algorithm_framework/app/app.py,sha256=bubVZeIQx1JLl2CRDRpf0KLhgVWRIwaD_72HN3TG4qo,85834
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
@@ -82,14 +83,14 @@ investing_algorithm_framework/cli/templates/run_backtest.py.template,sha256=mHwK
82
83
  investing_algorithm_framework/cli/templates/strategy.py.template,sha256=Ox8IZ6XlPDCCgq9T8KO3NazR2UXJs3FgkjbgfPqoad8,4603
83
84
  investing_algorithm_framework/create_app.py,sha256=HN_45Bza5Ro3o-v364m2mjSVjjZnyESOGHn3dcPDfQ4,1372
84
85
  investing_algorithm_framework/dependency_container.py,sha256=LIqK4fH2OdIKpvixzI2eSCXp6EtvEcWAJxJo-7a9qp4,6699
85
- investing_algorithm_framework/domain/__init__.py,sha256=PASQlRGcU_MDIwnLppanXGo-BUXdkhajvpRvcwqxdL0,4903
86
+ investing_algorithm_framework/domain/__init__.py,sha256=o2a3lAaCs4Jir88lSa8ZlwZ8Ghjwy1--S_9KDwny5MU,4931
86
87
  investing_algorithm_framework/domain/backtesting/__init__.py,sha256=q-NejGHzE233w5jXPhSsuLpBZ_yl3m-qb2g6FnxZaps,699
87
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
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
@@ -97,7 +98,7 @@ investing_algorithm_framework/domain/constants.py,sha256=-ZU0z1DSgJaUQvkp_ELZslq
97
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
- investing_algorithm_framework/domain/exceptions.py,sha256=CjMPVqA5_hPoyeThH0vP-grGpmWXrKU_Tb19wWUJGqM,2359
101
+ investing_algorithm_framework/domain/exceptions.py,sha256=2BmMmpu3jFW7vJ4KVJw0AAPTS70yQ1NH9OMc-exEBuc,2768
101
102
  investing_algorithm_framework/domain/models/__init__.py,sha256=5SN_d8MTbQzgLIYpt0ySkG2UZ3MsUz_30w40-vxUmk0,1116
102
103
  investing_algorithm_framework/domain/models/app_mode.py,sha256=V1p9QMSNLlz6KjPib8d1J2EaSZh7jmXjZ1V2DFOrIaU,812
103
104
  investing_algorithm_framework/domain/models/base_model.py,sha256=1_DMaDR13gc6l5yWpMgKaAK7OJstc4lmvZV5DhwYM6o,659
@@ -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.17.0.dist-info/LICENSE,sha256=wbVEDvoZiMPHufRY3sLEffvAr7GH5hOIngHF8y4HFQg,11343
256
- investing_algorithm_framework-7.17.0.dist-info/METADATA,sha256=nxUTvzV-vZgjVC-lQBBld8S0QVX4J4GUMZPPxMg3acQ,19635
257
- investing_algorithm_framework-7.17.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
258
- investing_algorithm_framework-7.17.0.dist-info/entry_points.txt,sha256=jrPF0YksDs27vYzEvj3tXLe3OGWU24EJA05z5xHqmq8,91
259
- investing_algorithm_framework-7.17.0.dist-info/RECORD,,
256
+ investing_algorithm_framework-7.19.1.dist-info/LICENSE,sha256=wbVEDvoZiMPHufRY3sLEffvAr7GH5hOIngHF8y4HFQg,11343
257
+ investing_algorithm_framework-7.19.1.dist-info/METADATA,sha256=NfXQinQSPYQibLY_6KTabg9vi7NUStbMosX5TAtfbew,19635
258
+ investing_algorithm_framework-7.19.1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
259
+ investing_algorithm_framework-7.19.1.dist-info/entry_points.txt,sha256=jrPF0YksDs27vYzEvj3tXLe3OGWU24EJA05z5xHqmq8,91
260
+ investing_algorithm_framework-7.19.1.dist-info/RECORD,,