sovai 0.2.57__tar.gz → 0.2.59__tar.gz

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.
Files changed (104) hide show
  1. {sovai-0.2.57 → sovai-0.2.59}/PKG-INFO +1 -1
  2. {sovai-0.2.57 → sovai-0.2.59}/pyproject.toml +1 -1
  3. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/pandas_extensions.py +5 -2
  4. sovai-0.2.59/sovai/extensions/shapley_global_importance.py +214 -0
  5. {sovai-0.2.57 → sovai-0.2.59}/sovai/get_data.py +10 -1
  6. sovai-0.2.57/sovai/extensions/shapley_global_importance.py +0 -127
  7. {sovai-0.2.57 → sovai-0.2.59}/LICENSE +0 -0
  8. {sovai-0.2.57 → sovai-0.2.59}/README.md +0 -0
  9. {sovai-0.2.57 → sovai-0.2.59}/data/codes.parq +0 -0
  10. {sovai-0.2.57 → sovai-0.2.59}/data/tickers.parq +0 -0
  11. {sovai-0.2.57 → sovai-0.2.59}/sovai/.DS_Store +0 -0
  12. {sovai-0.2.57 → sovai-0.2.59}/sovai/__init__.py +0 -0
  13. {sovai-0.2.57 → sovai-0.2.59}/sovai/api_config.py +0 -0
  14. {sovai-0.2.57 → sovai-0.2.59}/sovai/assets/dropdown.css +0 -0
  15. {sovai-0.2.57 → sovai-0.2.59}/sovai/assets/features_mapping.parq +0 -0
  16. {sovai-0.2.57 → sovai-0.2.59}/sovai/basic_auth.py +0 -0
  17. {sovai-0.2.57 → sovai-0.2.59}/sovai/computations/__init__.py +0 -0
  18. {sovai-0.2.57 → sovai-0.2.59}/sovai/computations/functions.py +0 -0
  19. {sovai-0.2.57 → sovai-0.2.59}/sovai/errors/__init__.py +0 -0
  20. {sovai-0.2.57 → sovai-0.2.59}/sovai/errors/sovai_errors.py +0 -0
  21. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/__init__.py +0 -0
  22. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/anomalies.py +0 -0
  23. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/ask_df_llm.py +0 -0
  24. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/change_point_generator.py +0 -0
  25. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/clustering.py +0 -0
  26. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/core_kshape.py +0 -0
  27. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/cum_sum_torch.py +0 -0
  28. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/cusum.py +0 -0
  29. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/dimensionality_reduction.py +0 -0
  30. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/feature_extraction.py +0 -0
  31. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/feature_importance.py +0 -0
  32. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/feature_neutralizer.py +0 -0
  33. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/filter_df.py +0 -0
  34. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/fractional_differencing.py +0 -0
  35. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/nowcasting.py +0 -0
  36. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/pairwise.py +0 -0
  37. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/parallel_functions.py +0 -0
  38. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/pfa_feature_selector.py +0 -0
  39. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/regime_change.py +0 -0
  40. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/regime_change_pca.py +0 -0
  41. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/shapley_importance.py +0 -0
  42. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/signal_evaluation.py +0 -0
  43. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/technical_indicators.py +0 -0
  44. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/time_decomposition.py +0 -0
  45. {sovai-0.2.57 → sovai-0.2.59}/sovai/extensions/weight_optimization.py +0 -0
  46. {sovai-0.2.57 → sovai-0.2.59}/sovai/get_compute.py +0 -0
  47. {sovai-0.2.57 → sovai-0.2.59}/sovai/get_data copy.py +0 -0
  48. {sovai-0.2.57 → sovai-0.2.59}/sovai/get_plots.py +0 -0
  49. {sovai-0.2.57 → sovai-0.2.59}/sovai/get_reports.py +0 -0
  50. {sovai-0.2.57 → sovai-0.2.59}/sovai/get_tools.py +0 -0
  51. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/__init__.py +0 -0
  52. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/accounting/__init__.py +0 -0
  53. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/accounting/accounting_plots.py +0 -0
  54. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/allocation/__init__.py +0 -0
  55. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/allocation/allocation_plots.py +0 -0
  56. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/bankruptcy/__init__.py +0 -0
  57. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/bankruptcy/bankruptcy_plots.py +0 -0
  58. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/breakout/__init__.py +0 -0
  59. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/breakout/breakout_plots.py +0 -0
  60. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/corp_risk/__init__.py +0 -0
  61. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/corp_risk/corp_risk_plots.py +0 -0
  62. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/earnings_surprise/__init__.py +0 -0
  63. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/earnings_surprise/earnings_surprise_plots.py +0 -0
  64. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/insider/__init__.py +0 -0
  65. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/insider/insider_plots.py +0 -0
  66. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/institutional/__init__.py +0 -0
  67. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/institutional/institutional_plots.py +0 -0
  68. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/news/__init__.py +0 -0
  69. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/news/news_plots.py +0 -0
  70. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/ratios/__init__.py +0 -0
  71. {sovai-0.2.57 → sovai-0.2.59}/sovai/plots/ratios/ratios_plots.py +0 -0
  72. {sovai-0.2.57 → sovai-0.2.59}/sovai/reports/accounting/__init__.py +0 -0
  73. {sovai-0.2.57 → sovai-0.2.59}/sovai/reports/accounting/accounting_balance_sheet.py +0 -0
  74. {sovai-0.2.57 → sovai-0.2.59}/sovai/reports/bankruptcy/__init__.py +0 -0
  75. {sovai-0.2.57 → sovai-0.2.59}/sovai/reports/bankruptcy/bankruptcy_monthly_top.py +0 -0
  76. {sovai-0.2.57 → sovai-0.2.59}/sovai/reports/general/__init__.py +0 -0
  77. {sovai-0.2.57 → sovai-0.2.59}/sovai/reports/general/general_plots.py +0 -0
  78. {sovai-0.2.57 → sovai-0.2.59}/sovai/reports/news/__inti__.py +0 -0
  79. {sovai-0.2.57 → sovai-0.2.59}/sovai/reports/news/news_econometric_analysis.py +0 -0
  80. {sovai-0.2.57 → sovai-0.2.59}/sovai/studies/nowcasting.py +0 -0
  81. {sovai-0.2.57 → sovai-0.2.59}/sovai/token_auth.py +0 -0
  82. {sovai-0.2.57 → sovai-0.2.59}/sovai/tools/authentication/__init__.py +0 -0
  83. {sovai-0.2.57 → sovai-0.2.59}/sovai/tools/authentication/authentication.py +0 -0
  84. {sovai-0.2.57 → sovai-0.2.59}/sovai/tools/sec/__init__.py +0 -0
  85. {sovai-0.2.57 → sovai-0.2.59}/sovai/tools/sec/graphs.py +0 -0
  86. {sovai-0.2.57 → sovai-0.2.59}/sovai/tools/sec/llm_code_generator.py +0 -0
  87. {sovai-0.2.57 → sovai-0.2.59}/sovai/tools/sec/sec_10_k_8_k_filings.py +0 -0
  88. {sovai-0.2.57 → sovai-0.2.59}/sovai/tools/sec/sec_edgar_search.py +0 -0
  89. {sovai-0.2.57 → sovai-0.2.59}/sovai/utils/__init__.py +0 -0
  90. {sovai-0.2.57 → sovai-0.2.59}/sovai/utils/client_side.py +0 -0
  91. {sovai-0.2.57 → sovai-0.2.59}/sovai/utils/client_side_s3.py +0 -0
  92. {sovai-0.2.57 → sovai-0.2.59}/sovai/utils/client_side_s3_part_high copy 2.py +0 -0
  93. {sovai-0.2.57 → sovai-0.2.59}/sovai/utils/client_side_s3_part_high copy.py +0 -0
  94. {sovai-0.2.57 → sovai-0.2.59}/sovai/utils/client_side_s3_part_high.py +0 -0
  95. {sovai-0.2.57 → sovai-0.2.59}/sovai/utils/converter.py +0 -0
  96. {sovai-0.2.57 → sovai-0.2.59}/sovai/utils/datetime_formats.py +0 -0
  97. {sovai-0.2.57 → sovai-0.2.59}/sovai/utils/file_management.py +0 -0
  98. {sovai-0.2.57 → sovai-0.2.59}/sovai/utils/get_tickers.py +0 -0
  99. {sovai-0.2.57 → sovai-0.2.59}/sovai/utils/helpers.py +0 -0
  100. {sovai-0.2.57 → sovai-0.2.59}/sovai/utils/pandas_extensions copy.py +0 -0
  101. {sovai-0.2.57 → sovai-0.2.59}/sovai/utils/plot.py +0 -0
  102. {sovai-0.2.57 → sovai-0.2.59}/sovai/utils/port_manager.py +0 -0
  103. {sovai-0.2.57 → sovai-0.2.59}/sovai/utils/stream.py +0 -0
  104. {sovai-0.2.57 → sovai-0.2.59}/sovai/utils/verbose_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sovai
3
- Version: 0.2.57
3
+ Version: 0.2.59
4
4
  Summary: python for asset management
5
5
  License: Proprietary License
6
6
  Author: Derek Snow
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [project]
6
6
  name = "sovai"
7
- version = "0.2.57"
7
+ version = "0.2.59"
8
8
  description = "python for asset management"
9
9
  authors = [
10
10
  {name = "Derek Snow", email = "d.snow@outlook.com"}
@@ -8,9 +8,8 @@ from numpy.linalg import norm
8
8
  # from sovai.extensions.pfa_feature_selector import run_pfa_simulations
9
9
  pd.options.display.float_format = "{:.3f}".format
10
10
 
11
- from sovai.extensions.shapley_global_importance import run_simulations_global_importance, run_simulations_frame_global
12
11
 
13
- from sovai.extensions.shapley_importance import run_simulations_frame
12
+ # from sovai.extensions.shapley_importance import run_simulations_frame
14
13
 
15
14
  from sovai.extensions.feature_neutralizer import (
16
15
  neutralize_features_function,
@@ -758,6 +757,8 @@ class CustomDataFrame(pd.DataFrame):
758
757
 
759
758
  def importance(self, method="random_projection"):
760
759
 
760
+ from sovai.extensions.shapley_global_importance import run_simulations_global_importance
761
+
761
762
  if method == "random_projection":
762
763
  return random_projection_importance(self)
763
764
  elif method == "fourier":
@@ -1013,6 +1014,8 @@ class CustomDataFrame(pd.DataFrame):
1013
1014
  :param clustering_method: The clustering method to use ('OPTICS' or 'KMeans').
1014
1015
  :return: A DataFrame with average SHAP values per feature.
1015
1016
  """
1017
+ from sovai.extensions.shapley_global_importance import run_simulations_frame_global
1018
+
1016
1019
  # Running the simulations and getting average SHAP values
1017
1020
  avg_shap_values = run_simulations_frame_global(
1018
1021
  self, num_simulations=num_simulations, clustering_method=clustering_method
@@ -0,0 +1,214 @@
1
+ # Lightweight built-in imports can stay global
2
+ import random
3
+ import hashlib
4
+ import warnings
5
+ # import os # Removed as it was unused
6
+
7
+ # Note: Imports below are moved into functions/methods to defer loading cost,
8
+ # potentially speeding up the initial import of this script.
9
+
10
+ def hash_of_df(df, sample_size=100):
11
+ """Calculates a SHA256 hash of a sampled portion of a DataFrame."""
12
+ # Import pandas here as it's needed for DataFrame operations
13
+ import pandas as pd
14
+
15
+ df_sample = (
16
+ df.sample(n=min(sample_size, len(df)), random_state=42).to_string().encode()
17
+ )
18
+ return hashlib.sha256(df_sample).hexdigest()
19
+
20
+ class ClusteringExplainer:
21
+ """
22
+ Trains a classifier to predict cluster labels and provides SHAP explanations.
23
+ """
24
+ def __init__(self, random_state=42):
25
+ """Initializes the explainer and scaler."""
26
+ # Import StandardScaler here as it's used for self.scaler
27
+ from sklearn.preprocessing import StandardScaler
28
+
29
+ self.random_state = random_state
30
+ self.model = None
31
+ self.explainer = None
32
+ # Initialize scaler here after import
33
+ self.scaler = StandardScaler()
34
+
35
+ def fit(self, X, y):
36
+ """Fits the LGBM classifier and creates the SHAP explainer."""
37
+ # Imports needed specifically for fitting the model and explainer
38
+ from lightgbm import LGBMClassifier
39
+ from sklearn.utils.class_weight import compute_sample_weight
40
+ import shap
41
+
42
+ classes_weights = compute_sample_weight(class_weight="balanced", y=y)
43
+ self.model = LGBMClassifier(
44
+ objective="multiclass",
45
+ random_state=self.random_state,
46
+ verbose=-1,
47
+ force_col_wise=True,
48
+ min_gain_to_split=0.01
49
+ )
50
+ # Catch warnings during model fitting (e.g., from LightGBM)
51
+ with warnings.catch_warnings():
52
+ warnings.simplefilter("ignore")
53
+ self.model.fit(X, y, sample_weight=classes_weights)
54
+
55
+ # Create SHAP explainer after model is trained
56
+ self.explainer = shap.Explainer(self.model)
57
+
58
+ def get_shap_values(self, X):
59
+ """Gets SHAP values using the trained explainer."""
60
+ # No new imports needed here, uses self.explainer created in fit()
61
+ if self.explainer is None:
62
+ raise RuntimeError("Explainer not available. Call fit() method first.")
63
+ return self.explainer(X)
64
+
65
+ def get_shap_values_for_dataset(df, clustering_method="KMEANS", n_clusters=10, random_state=42, sample_size=5000):
66
+ """
67
+ Performs clustering, trains a model, and calculates mean absolute SHAP values.
68
+ """
69
+ # Imports needed for this function's operations
70
+ import pandas as pd
71
+ import numpy as np
72
+ from sklearn.cluster import KMeans, MeanShift, HDBSCAN
73
+ from sklearn.preprocessing import StandardScaler # Needed again for local scaler instance
74
+
75
+ # Sample for clustering and model training
76
+ X_sample = df.sample(n=min(sample_size, len(df)), random_state=random_state)
77
+
78
+ # Create and fit a local scaler for the sample
79
+ scaler = StandardScaler()
80
+ X_sample_scaled = scaler.fit_transform(X_sample)
81
+
82
+ # Select and fit the clustering algorithm
83
+ if clustering_method == "KMEANS":
84
+ # Use random module (imported globally)
85
+ num_clusters = random.randint(5, 10)
86
+ clustering = KMeans(n_clusters=num_clusters, random_state=random_state, n_init=10) # Added n_init
87
+ elif clustering_method == "MEANSHIFT":
88
+ clustering = MeanShift()
89
+ elif clustering_method == "HDBSCAN":
90
+ clustering = HDBSCAN(min_cluster_size=5)
91
+ else:
92
+ raise ValueError(f"Unsupported clustering method: {clustering_method}")
93
+
94
+ # Catch warnings during clustering (e.g., convergence warnings)
95
+ with warnings.catch_warnings():
96
+ warnings.simplefilter("ignore")
97
+ clustering.fit(X_sample_scaled)
98
+
99
+ y_sample = clustering.labels_
100
+
101
+ # Handle noise points from HDBSCAN if necessary
102
+ if clustering_method == "HDBSCAN":
103
+ # Check if noise points exist (-1 label)
104
+ if -1 in y_sample:
105
+ noise_label = np.max(y_sample) + 1
106
+ y_sample[y_sample == -1] = noise_label # Assign noise points to a new cluster index
107
+
108
+ # Create and fit the explainer using the clustered sample
109
+ clust_explnr = ClusteringExplainer(random_state=random_state)
110
+ clust_explnr.fit(X_sample_scaled, y_sample) # This triggers imports within ClusteringExplainer.fit
111
+
112
+ # Transform the entire dataset using the scaler fitted on the sample
113
+ X_full_scaled = scaler.transform(df)
114
+
115
+ # Get SHAP values for the entire scaled dataset
116
+ shap_values = clust_explnr.get_shap_values(X_full_scaled)
117
+
118
+ # Calculate mean absolute SHAP values across classes for each feature
119
+ # shap_values.values is typically (n_samples, n_features, n_classes) or (n_samples, n_features)
120
+ if len(shap_values.values.shape) == 3:
121
+ mean_abs_shap = np.mean(np.abs(shap_values.values), axis=2)
122
+ else: # If it's already 2D (e.g., binary classification or regression SHAP)
123
+ mean_abs_shap = np.abs(shap_values.values)
124
+
125
+ # Create DataFrame for the results
126
+ mean_abs_shap_df = pd.DataFrame(mean_abs_shap, columns=df.columns, index=df.index)
127
+
128
+ return mean_abs_shap_df
129
+
130
+ def run_simulations_frame_global(df, num_simulations=4, clustering_method="KMEANS"):
131
+ """
132
+ Runs multiple simulations of SHAP value calculation in parallel and averages.
133
+ """
134
+ # Imports needed for parallel execution and DataFrame manipulation
135
+ from joblib import Parallel, delayed
136
+ import pandas as pd
137
+
138
+ data_hash = hash_of_df(df) # Uses function defined above (triggers its pandas import if first call)
139
+ tasks = []
140
+ for i in range(num_simulations):
141
+ # Use random module (imported globally)
142
+ # Seed random state for reproducibility within the loop based on data hash
143
+ current_seed = int(data_hash, 16) + i
144
+ random.seed(current_seed)
145
+ # Generate distinct random states for clustering and model training per simulation
146
+ sim_random_state = random.randint(0, 2**32 - 1) # Use a large range for random state
147
+
148
+ tasks.append(
149
+ delayed(get_shap_values_for_dataset)(
150
+ df, clustering_method, 10, sim_random_state # Pass simulation-specific state
151
+ )
152
+ ) # This implicitly calls get_shap_values_for_dataset (triggering its imports)
153
+
154
+ # Catch warnings during parallel execution
155
+ with warnings.catch_warnings():
156
+ warnings.simplefilter("ignore")
157
+ # Run tasks in parallel, n_jobs=-1 uses all available CPU cores
158
+ all_shap_values = Parallel(n_jobs=-1, verbose=0)(tasks)
159
+
160
+ # Concatenate results and calculate the mean SHAP values grouped by index
161
+ # Assumes df.index is a MultiIndex with 'ticker' and 'date' levels
162
+ if isinstance(df.index, pd.MultiIndex) and all(level in df.index.names for level in ['ticker', 'date']):
163
+ avg_shap_values = pd.concat(all_shap_values).abs().groupby(level=["ticker", "date"]).mean()
164
+ else:
165
+ # Fallback if index is not the expected MultiIndex (might need adjustment)
166
+ # This assumes the index from the original df was preserved in mean_abs_shap_df
167
+ # The previous reset_index().groupby() might be safer if index structure varies
168
+ print("Warning: DataFrame index is not a MultiIndex with 'ticker' and 'date'. Averaging over the entire index.")
169
+ avg_shap_values = pd.concat(all_shap_values).abs().mean()
170
+
171
+
172
+ return avg_shap_values
173
+
174
+ def run_simulations_global_importance(df, num_simulations=4, clustering_method="KMEANS"):
175
+ """
176
+ Calculates overall feature importance based on averaged SHAP values.
177
+ """
178
+ # Imports needed for DataFrame creation and statistical calculation
179
+ import pandas as pd
180
+ from scipy import stats
181
+
182
+ # Get the averaged SHAP values per instance/group
183
+ avg_shap_values = run_simulations_frame_global(
184
+ df, num_simulations=num_simulations, clustering_method=clustering_method
185
+ ) # Triggers imports in run_simulations_frame_global if not already loaded
186
+
187
+ # Calculate the mean importance across all instances/groups for each feature
188
+ feature_importance_values = avg_shap_values.mean(axis=0)
189
+
190
+ # Create the feature importance DataFrame
191
+ feature_importance = pd.DataFrame(
192
+ {
193
+ "feature": feature_importance_values.index,
194
+ "importance": feature_importance_values.values,
195
+ }
196
+ )
197
+
198
+ # Calculate percentile rank for each feature's importance
199
+ feature_importance["importance_percentile"] = feature_importance["importance"].apply(
200
+ lambda x: stats.percentileofscore(feature_importance["importance"], x, kind='rank')
201
+ )
202
+ # Alternative using numpy might be faster for large numbers of features:
203
+ # feature_importance["importance_percentile"] = feature_importance["importance"].rank(pct=True) * 100
204
+
205
+ # Sort by importance percentile descending
206
+ return feature_importance.sort_values(
207
+ "importance_percentile", ascending=False
208
+ ).reset_index(drop=True)
209
+
210
+
211
+ # Usage example:
212
+ # Assuming 'df' is your pandas DataFrame with features and a MultiIndex ('ticker', 'date')
213
+ # importance_df = run_simulations_global_importance(df, num_simulations=10, clustering_method='KMEANS')
214
+ # print(importance_df)
@@ -808,6 +808,15 @@ def find_tickers(
808
808
  logger.debug(f"Mapped {len(sample_identifiers)} identifiers to {len(result)} unique tickers")
809
809
  return result
810
810
 
811
+ @lru_cache(maxsize=1)
812
+ def _get_ticker_codes_df():
813
+ logger.debug("Loading ticker mapping codes...")
814
+ try:
815
+ return pd.read_parquet("data/codes.parq")
816
+ except Exception as e:
817
+ logger.error(f"Failed to load ticker mapping data: {e}")
818
+ raise ValueError(f"Cannot perform ticker mapping: {e}")
819
+
811
820
 
812
821
  def ticker_mapper(params: Dict[str, Any], verbose: bool = False) -> Dict[str, Any]:
813
822
  """
@@ -828,7 +837,7 @@ def ticker_mapper(params: Dict[str, Any], verbose: bool = False) -> Dict[str, An
828
837
 
829
838
  # Load ticker mapping data
830
839
  try:
831
- df_codes = pd.read_parquet("data/codes.parq")
840
+ df_codes = _get_ticker_codes_df()
832
841
  except Exception as e:
833
842
  logger.error(f"Failed to load ticker mapping data: {e}")
834
843
  raise ValueError(f"Cannot perform ticker mapping: {e}")
@@ -1,127 +0,0 @@
1
- import pandas as pd
2
- import numpy as np
3
- import random
4
- from lightgbm import LGBMClassifier
5
- from sklearn.cluster import KMeans, MeanShift, HDBSCAN
6
- from sklearn.utils.class_weight import compute_sample_weight
7
- from sklearn.preprocessing import StandardScaler
8
- import shap
9
- from joblib import Parallel, delayed
10
- import hashlib
11
- from scipy import stats
12
- import os
13
- import warnings
14
-
15
-
16
- def hash_of_df(df, sample_size=100):
17
- df_sample = (
18
- df.sample(n=min(sample_size, len(df)), random_state=42).to_string().encode()
19
- )
20
- return hashlib.sha256(df_sample).hexdigest()
21
-
22
- class ClusteringExplainer:
23
- def __init__(self, random_state=42):
24
- self.random_state = random_state
25
- self.model = None
26
- self.explainer = None
27
- self.scaler = StandardScaler()
28
-
29
- def fit(self, X, y):
30
- classes_weights = compute_sample_weight(class_weight="balanced", y=y)
31
- self.model = LGBMClassifier(
32
- objective="multiclass",
33
- random_state=self.random_state,
34
- verbose=-1,
35
- force_col_wise=True,
36
- min_gain_to_split=0.01
37
- )
38
- with warnings.catch_warnings():
39
- warnings.simplefilter("ignore")
40
- self.model.fit(X, y, sample_weight=classes_weights)
41
- self.explainer = shap.Explainer(self.model)
42
-
43
- def get_shap_values(self, X):
44
- return self.explainer(X)
45
-
46
- def get_shap_values_for_dataset(df, clustering_method="KMEANS", n_clusters=10, random_state=42, sample_size=5000):
47
- # Sample for clustering and model training
48
- X_sample = df.sample(n=min(sample_size, len(df)), random_state=random_state)
49
-
50
- scaler = StandardScaler()
51
- X_sample_scaled = scaler.fit_transform(X_sample)
52
-
53
- if clustering_method == "KMEANS":
54
- num_clusters = random.randint(5, 10)
55
- clustering = KMeans(n_clusters=num_clusters, random_state=random_state)
56
- elif clustering_method == "MEANSHIFT":
57
- clustering = MeanShift()
58
- elif clustering_method == "HDBSCAN":
59
- clustering = HDBSCAN(min_cluster_size=5)
60
- else:
61
- raise ValueError(f"Unsupported clustering method: {clustering_method}")
62
-
63
- with warnings.catch_warnings():
64
- warnings.simplefilter("ignore")
65
- clustering.fit(X_sample_scaled)
66
-
67
- y_sample = clustering.labels_
68
-
69
- if clustering_method == "HDBSCAN":
70
- noise_label = max(y_sample) + 1
71
- y_sample[y_sample == -1] = noise_label
72
-
73
- clust_explnr = ClusteringExplainer(random_state=random_state)
74
- clust_explnr.fit(X_sample_scaled, y_sample)
75
-
76
- # Transform the entire dataset
77
- X_full_scaled = scaler.transform(df)
78
-
79
- # Get SHAP values for the entire dataset
80
- shap_values = clust_explnr.get_shap_values(X_full_scaled)
81
- mean_abs_shap = np.mean(np.abs(shap_values.values), axis=2)
82
- mean_abs_shap_df = pd.DataFrame(mean_abs_shap, columns=df.columns, index=df.index)
83
-
84
- return mean_abs_shap_df
85
-
86
- def run_simulations_frame_global(df, num_simulations=4, clustering_method="KMEANS"):
87
- data_hash = hash_of_df(df)
88
- tasks = []
89
- for i in range(num_simulations):
90
- random.seed(int(data_hash, 16) + i)
91
- kmeans_random_state = random.randint(0, 1000)
92
- lgbm_random_state = random.randint(0, 1000)
93
- tasks.append(
94
- delayed(get_shap_values_for_dataset)(
95
- df, clustering_method, 10, kmeans_random_state
96
- )
97
- )
98
- with warnings.catch_warnings():
99
- warnings.simplefilter("ignore")
100
- all_shap_values = Parallel(n_jobs=-1, verbose=0)(tasks)
101
- # print(all_shap_values)
102
- # print(all_shap_values.shape)
103
-
104
- # print(all_shap_values[0].index)
105
- # print(all_shap_values[2].index)
106
- avg_shap_values = pd.concat(all_shap_values).abs().reset_index().groupby(by=["ticker","date"]).mean()
107
- # avg_shap_values.index = df.index
108
- return avg_shap_values
109
-
110
- def run_simulations_global_importance(df, num_simulations=4, clustering_method="KMEANS"):
111
- avg_shap_values = run_simulations_frame_global(df, num_simulations=num_simulations, clustering_method=clustering_method)
112
- feature_importance = pd.DataFrame(
113
- {
114
- "feature": avg_shap_values.columns,
115
- "importance": avg_shap_values.mean().values,
116
- }
117
- )
118
- feature_importance["importance_percentile"] = stats.percentileofscore(
119
- feature_importance["importance"], feature_importance["importance"]
120
- )
121
- return feature_importance.sort_values(
122
- "importance_percentile", ascending=False
123
- ).reset_index(drop=True)
124
-
125
-
126
- # Usage example:
127
- # importance_df = run_simulations(df, num_simulations=4, clustering_method='KMEANS')
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes