RiskLabAI 1.0.2__tar.gz → 1.0.4__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 (110) hide show
  1. {risklabai-1.0.2 → risklabai-1.0.4}/PKG-INFO +1 -1
  2. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/backtest/__init__.py +1 -1
  3. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/backtest/backtest_overfitting_simulation.py +16 -0
  4. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/backtest/backtest_statistics.py +6 -0
  5. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/backtest/bet_sizing.py +8 -0
  6. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/backtest/probabilistic_sharpe_ratio.py +6 -0
  7. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/backtest/probability_of_backtest_overfitting.py +9 -0
  8. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/backtest/validation/__init__.py +4 -4
  9. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/feature_importance/clustered_feature_importance_mda.py +14 -3
  10. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/feature_importance/feature_importance_mda.py +10 -4
  11. risklabai-1.0.4/RiskLabAI/utils/publication_plots.py +180 -0
  12. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI.egg-info/PKG-INFO +1 -1
  13. {risklabai-1.0.2 → risklabai-1.0.4}/pyproject.toml +1 -1
  14. risklabai-1.0.2/RiskLabAI/utils/publication_plots.py +0 -75
  15. {risklabai-1.0.2 → risklabai-1.0.4}/LICENSE +0 -0
  16. {risklabai-1.0.2 → risklabai-1.0.4}/README.md +0 -0
  17. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/__init__.py +0 -0
  18. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/backtest/backtest_synthetic_data.py +0 -0
  19. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/backtest/strategy_risk.py +0 -0
  20. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/backtest/test_set_overfitting.py +0 -0
  21. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/backtest/validation/adaptive_combinatorial_purged.py +0 -0
  22. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/backtest/validation/bagged_combinatorial_purged.py +0 -0
  23. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/backtest/validation/combinatorial_purged.py +0 -0
  24. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/backtest/validation/cross_validator_controller.py +0 -0
  25. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/backtest/validation/cross_validator_factory.py +0 -0
  26. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/backtest/validation/cross_validator_interface.py +0 -0
  27. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/backtest/validation/kfold.py +0 -0
  28. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/backtest/validation/purged_kfold.py +0 -0
  29. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/backtest/validation/walk_forward.py +0 -0
  30. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/cluster/__init__.py +0 -0
  31. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/cluster/clustering.py +0 -0
  32. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/controller/__init__.py +0 -0
  33. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/controller/bars_initializer.py +0 -0
  34. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/controller/data_structure_controller.py +0 -0
  35. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/__init__.py +0 -0
  36. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/denoise/__init__.py +0 -0
  37. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/denoise/denoising.py +0 -0
  38. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/differentiation/__init__.py +0 -0
  39. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/differentiation/differentiation.py +0 -0
  40. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/distance/__init__.py +0 -0
  41. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/distance/distance_metric.py +0 -0
  42. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/labeling/__init__.py +0 -0
  43. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/labeling/financial_labels.py +0 -0
  44. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/labeling/labeling.py +0 -0
  45. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/structures/__init__.py +0 -0
  46. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/structures/abstract_bars.py +0 -0
  47. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/structures/abstract_imbalance_bars.py +0 -0
  48. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/structures/abstract_information_driven_bars.py +0 -0
  49. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/structures/abstract_run_bars.py +0 -0
  50. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/structures/imbalance_bars.py +0 -0
  51. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/structures/run_bars.py +0 -0
  52. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/structures/standard_bars.py +0 -0
  53. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/structures/time_bars.py +0 -0
  54. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/synthetic_data/__init__.py +0 -0
  55. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/synthetic_data/drift_burst_hypothesis.py +0 -0
  56. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/synthetic_data/simulation.py +0 -0
  57. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/synthetic_data/synthetic_controlled_environment.py +0 -0
  58. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/weights/__init__.py +0 -0
  59. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/data/weights/sample_weights.py +0 -0
  60. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/ensemble/__init__.py +0 -0
  61. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/ensemble/bagging_classifier_accuracy.py +0 -0
  62. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/ensemble/empirical_bagging_accuracy.py +0 -0
  63. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/__init__.py +0 -0
  64. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/entropy_features/__init__.py +0 -0
  65. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/entropy_features/entropy.py +0 -0
  66. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/entropy_features/kontoyiannis.py +0 -0
  67. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/entropy_features/lempel_ziv.py +0 -0
  68. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/entropy_features/plug_in.py +0 -0
  69. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/entropy_features/pmf.py +0 -0
  70. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/entropy_features/shannon.py +0 -0
  71. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/feature_importance/__init__.py +0 -0
  72. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/feature_importance/clustered_feature_importance_mdi.py +0 -0
  73. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/feature_importance/feature_importance_controller.py +0 -0
  74. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/feature_importance/feature_importance_factory.py +0 -0
  75. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/feature_importance/feature_importance_mdi.py +0 -0
  76. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/feature_importance/feature_importance_sfi.py +0 -0
  77. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/feature_importance/feature_importance_strategy.py +0 -0
  78. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/feature_importance/generate_synthetic_data.py +0 -0
  79. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/feature_importance/orthogonal_features.py +0 -0
  80. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/feature_importance/weighted_tau.py +0 -0
  81. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/microstructural_features/__init__.py +0 -0
  82. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/microstructural_features/bekker_parkinson_volatility_estimator.py +0 -0
  83. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/microstructural_features/corwin_schultz.py +0 -0
  84. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/structural_breaks/__init__.py +0 -0
  85. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/features/structural_breaks/structural_breaks.py +0 -0
  86. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/hpc/__init__.py +0 -0
  87. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/hpc/hpc.py +0 -0
  88. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/optimization/__init__.py +0 -0
  89. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/optimization/hedging.py +0 -0
  90. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/optimization/hrp.py +0 -0
  91. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/optimization/hyper_parameter_tuning.py +0 -0
  92. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/optimization/nco.py +0 -0
  93. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/pde/__init__.py +0 -0
  94. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/pde/equation.py +0 -0
  95. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/pde/model.py +0 -0
  96. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/pde/solver.py +0 -0
  97. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/utils/__init__.py +0 -0
  98. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/utils/constants.py +0 -0
  99. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/utils/ewma.py +0 -0
  100. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/utils/momentum_mean_reverting_strategy_sides.py +0 -0
  101. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/utils/progress.py +0 -0
  102. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/utils/smoothing_average.py +0 -0
  103. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/utils/update_figure_layout.py +0 -0
  104. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI/utils/utilities_lopez.py +0 -0
  105. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI.egg-info/SOURCES.txt +0 -0
  106. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI.egg-info/dependency_links.txt +0 -0
  107. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI.egg-info/entry_points.txt +0 -0
  108. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI.egg-info/requires.txt +0 -0
  109. {risklabai-1.0.2 → risklabai-1.0.4}/RiskLabAI.egg-info/top_level.txt +0 -0
  110. {risklabai-1.0.2 → risklabai-1.0.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: RiskLabAI
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: Financial AI using Python, based on 'Advances in Financial Machine Learning' and 'Machine Learning for Asset Managers'.
5
5
  Author-email: RiskLab <arian@risklab.ai>
6
6
  License: BSD 3-Clause License
@@ -16,6 +16,7 @@ from .backtest_statistics import (
16
16
  calculate_hhi_concentration,
17
17
  calculate_hhi,
18
18
  compute_drawdowns_time_under_water,
19
+ sharpe_ratio as pbo_sharpe_ratio,
19
20
  )
20
21
  from .backtest_synthetic_data import synthetic_back_testing
21
22
  from .bet_sizing import (
@@ -52,7 +53,6 @@ from .test_set_overfitting import (
52
53
  strategy_type2_error_probability,
53
54
  )
54
55
  from .probability_of_backtest_overfitting import (
55
- sharpe_ratio as pbo_sharpe_ratio, # aliased
56
56
  performance_evaluation,
57
57
  probability_of_backtest_overfitting,
58
58
  )
@@ -1,3 +1,19 @@
1
+ """
2
+ Orchestrates complex backtest overfitting and simulation scenarios,
3
+ including PBO, DSR, and hardware performance profiling.
4
+
5
+ ## TODO:
6
+ - [ ] **Refactor Feature Generation:** Move hardcoded parameters in
7
+ `financial_features_backtest_overfitting_simulation`
8
+ (e.g., volatility=100, rolling=20, TA-lib windows)
9
+ to a configuration dictionary or function arguments.
10
+ - [ ] **Refactor Profiling:** Move hardware/performance functions
11
+ (`get_cpu_info`, `format_cpu_info`,
12
+ `measure_computational_requirements`, etc.)
13
+ to a separate `RiskLabAI.utils.profiling` module
14
+ to reduce this module's responsibilities.
15
+ """
16
+
1
17
  import platform
2
18
  import time
3
19
  import numpy as np
@@ -1,6 +1,12 @@
1
1
  """
2
2
  Calculates various backtest statistics like holding period,
3
3
  concentration, and drawdowns.
4
+
5
+ ## TODO:
6
+ - [ ] Optimize `calculate_holding_period` with Numba (@jit)
7
+ to improve performance, similar to the `sharpe_ratio` function.
8
+ - [ ] Add an `annualized_sharpe_ratio` helper function that
9
+ wraps the Numba `sharpe_ratio` and scales it by sqrt(freq).
4
10
  """
5
11
 
6
12
  from typing import Tuple, Optional
@@ -3,6 +3,14 @@ Functions for calculating bet size based on model probabilities and
3
3
  other strategy parameters.
4
4
 
5
5
  Includes implementations from de Prado (2018).
6
+
7
+ ## TODO:
8
+ - [ ] **HPC Dependency:** The `mpPandasObj` placeholder in
9
+ `strategy_bet_sizing` and `avgActiveSignals` should be
10
+ hardened. If `RiskLabAI.hpc` is a core dependency,
11
+ consider raising an `ImportError` instead of using a
12
+ placeholder that returns an empty DataFrame, which could
13
+ fail silently.
6
14
  """
7
15
 
8
16
  from typing import Optional, Any
@@ -1,6 +1,12 @@
1
1
  """
2
2
  Implements the Probabilistic Sharpe Ratio (PSR) and related metrics
3
3
  as described by Marcos Lopez de Prado.
4
+
5
+ ## TODO:
6
+ - [ ] Add a `compute_psr_curve` helper function (as seen in the
7
+ original notebook) that iterates `probabilistic_sharpe_ratio`
8
+ over a range of `observed_sharpe_ratio` values to
9
+ easily plot the PSR curve.
4
10
  """
5
11
 
6
12
  from typing import List
@@ -1,5 +1,14 @@
1
1
  """
2
2
  Implements the Probability of Backtest Overfitting (PBO) calculation.
3
+
4
+ ## TODO:
5
+ - [ ] Add a `get_pbo` wrapper function (as seen in the
6
+ original notebook) that simplifies the call to
7
+ `probability_of_backtest_overfitting` and returns
8
+ only the PBO value.
9
+ - [ ] Add a `pbo_overfitting_plot` helper function to
10
+ visualize the logit distribution (as seen in the
11
+ original notebook).
3
12
  """
4
13
 
5
14
  from typing import Tuple, Callable, List, Optional
@@ -28,10 +28,10 @@ __all__ = [
28
28
  # Validators
29
29
  "KFold",
30
30
  "PurgedKFold",
31
- "WalkForwardCrossValidator",
32
- "CombinatorialPurgedKFold",
33
- "BaggedCombinatorialPurgedKFold",
34
- "AdaptiveCombinatorialPurgedKFold",
31
+ "WalkForward", # <-- Fix
32
+ "CombinatorialPurged", # <-- Fix
33
+ "BaggedCombinatorialPurged", # <-- Fix
34
+ "AdaptiveCombinatorialPurged", # <-- Fix
35
35
 
36
36
  # Utilities
37
37
  "CrossValidatorFactory",
@@ -18,11 +18,13 @@ class ClusteredFeatureImportanceMDA(FeatureImportanceStrategy):
18
18
  and measures the decrease in model performance.
19
19
  """
20
20
 
21
+
21
22
  def __init__(
22
23
  self,
23
24
  classifier: object,
24
25
  clusters: Dict[str, List[str]],
25
26
  n_splits: int = 10,
27
+ random_state: int = 42, # <-- ADD THIS
26
28
  ):
27
29
  """
28
30
  Initialize the strategy.
@@ -39,6 +41,8 @@ class ClusteredFeatureImportanceMDA(FeatureImportanceStrategy):
39
41
  self.classifier = classifier
40
42
  self.clusters = clusters
41
43
  self.n_splits = n_splits
44
+ self.random_state = random_state
45
+
42
46
 
43
47
  def compute(self, x: pd.DataFrame, y: pd.Series, **kwargs: Any) -> pd.DataFrame:
44
48
  """
@@ -68,8 +72,11 @@ class ClusteredFeatureImportanceMDA(FeatureImportanceStrategy):
68
72
  if score_weights is None:
69
73
  score_weights = np.ones(x.shape[0])
70
74
 
71
- cv_generator = KFold(n_splits=self.n_splits)
75
+
76
+
77
+ cv_generator = KFold(n_splits=self.n_splits, shuffle=True, random_state=self.random_state)
72
78
  baseline_scores = pd.Series(dtype=float)
79
+
73
80
  shuffled_scores = pd.DataFrame(columns=self.clusters.keys(), dtype=float)
74
81
 
75
82
  for i, (train_idx, test_idx) in enumerate(cv_generator.split(X=x)):
@@ -98,17 +105,21 @@ class ClusteredFeatureImportanceMDA(FeatureImportanceStrategy):
98
105
  sample_weight=w_test,
99
106
  )
100
107
 
108
+
101
109
  # Get scores for each shuffled *cluster*
110
+ rng = np.random.default_rng(self.random_state + i)
102
111
  for cluster_name in shuffled_scores.columns:
103
112
  x_test_shuffled = x_test.copy(deep=True)
104
113
  for feature in self.clusters[cluster_name]:
105
- np.random.shuffle(x_test_shuffled[feature].values)
114
+ rng.shuffle(x_test_shuffled[feature].values) # <-- This is correct
106
115
 
107
116
  prob = classifier_fit.predict_proba(x_test_shuffled)
108
117
  shuffled_scores.loc[i, cluster_name] = -log_loss(
109
- y_test, prob, labels=self.classifier.classes_
118
+ y_test, prob, labels=self.classifier.classes_,
119
+ sample_weight=w_test
110
120
  )
111
121
 
122
+
112
123
  # Calculate importance as the simple drop in score
113
124
  importances = shuffled_scores.rsub(baseline_scores, axis=0)
114
125
 
@@ -17,7 +17,8 @@ class FeatureImportanceMDA(FeatureImportanceStrategy):
17
17
  much the model's performance (e.g., log loss) decreases.
18
18
  """
19
19
 
20
- def __init__(self, classifier: object, n_splits: int = 10):
20
+
21
+ def __init__(self, classifier: object, n_splits: int = 10, random_state: int = 42):
21
22
  """
22
23
  Initialize the strategy.
23
24
 
@@ -30,6 +31,8 @@ class FeatureImportanceMDA(FeatureImportanceStrategy):
30
31
  """
31
32
  self.classifier = classifier
32
33
  self.n_splits = n_splits
34
+ self.random_state = random_state
35
+
33
36
 
34
37
  def compute(self, x: pd.DataFrame, y: pd.Series, **kwargs: Any) -> pd.DataFrame:
35
38
  """
@@ -58,7 +61,8 @@ class FeatureImportanceMDA(FeatureImportanceStrategy):
58
61
  if score_weights is None:
59
62
  score_weights = np.ones(x.shape[0])
60
63
 
61
- cv_generator = KFold(n_splits=self.n_splits)
64
+
65
+ cv_generator = KFold(n_splits=self.n_splits, shuffle=True, random_state=self.random_state)
62
66
  baseline_scores = pd.Series(dtype=float)
63
67
  shuffled_scores = pd.DataFrame(columns=x.columns, dtype=float)
64
68
 
@@ -90,11 +94,13 @@ class FeatureImportanceMDA(FeatureImportanceStrategy):
90
94
  )
91
95
 
92
96
  # Get scores for each shuffled feature
97
+ rng = np.random.default_rng(self.random_state + i)
93
98
  for feature in x.columns:
94
99
  x_test_shuffled = x_test.copy(deep=True)
95
- np.random.shuffle(x_test_shuffled[feature].values)
96
-
100
+ rng.shuffle(x_test_shuffled[feature].values) # <-- USE SEEDED SHUFFLE
101
+
97
102
  shuffled_proba = fitted_classifier.predict_proba(x_test_shuffled)
103
+
98
104
  shuffled_scores.loc[i, feature] = -log_loss(
99
105
  y_test,
100
106
  shuffled_proba,
@@ -0,0 +1,180 @@
1
+ """
2
+ Utilities for creating publication-quality plots with Matplotlib
3
+ and Seaborn, using Times New Roman font and high DPI.
4
+
5
+ Provides 6 themes and a configuration-based saving function.
6
+ """
7
+
8
+ import matplotlib.pyplot as plt
9
+ import matplotlib.figure as fig # For type hinting
10
+ import seaborn as sns
11
+ import os
12
+ from typing import Optional, Dict, Any
13
+
14
+ # [THEMES dictionary remains the same]
15
+ THEMES: Dict[str, Dict[str, Any]] = {
16
+ 'light': {
17
+ 'figure.facecolor': '#FFFFFF',
18
+ 'axes.facecolor': '#FFFFFF',
19
+ 'text.color': '#000000',
20
+ 'axes.labelcolor': '#000000',
21
+ 'axes.edgecolor': '#000000',
22
+ 'xtick.color': '#000000',
23
+ 'ytick.color': '#000000',
24
+ 'grid.color': '#CCCCCC',
25
+ 'legend.facecolor': '#FFFFFF',
26
+ 'legend.edgecolor': '#B0B0B0',
27
+ },
28
+ 'medium': {
29
+ 'figure.facecolor': '#B0B0B0', # A more solid, medium grey
30
+ 'axes.facecolor': '#B0B0B0',
31
+ 'text.color': '#FFFFFF', # White text (like the dark theme)
32
+ 'axes.labelcolor': '#FFFFFF',
33
+ 'axes.edgecolor': '#FFFFFF',
34
+ 'xtick.color': '#FFFFFF',
35
+ 'ytick.color': '#FFFFFF',
36
+ 'grid.color': '#E0E0E0', # Lighter grid lines on medium bg
37
+ 'legend.facecolor': '#B0B0B0',
38
+ 'legend.edgecolor': '#FFFFFF',
39
+ },
40
+ 'dark': {
41
+ 'figure.facecolor': '#2E2E2E',
42
+ 'axes.facecolor': '#2E2E2E',
43
+ 'text.color': '#F0F0F0',
44
+ 'axes.labelcolor': '#F0F0F0',
45
+ 'axes.edgecolor': '#F0F0F0',
46
+ 'xtick.color': '#F0F0F0',
47
+ 'ytick.color': '#F0F0F0',
48
+ 'grid.color': '#6A6A6A',
49
+ 'legend.facecolor': '#2E2E2E',
50
+ 'legend.edgecolor': '#F0F0F0',
51
+ }
52
+ }
53
+
54
+ # --- MODULE-LEVEL CONFIGURATION ---
55
+ # This dictionary will store the settings from setup_publication_style
56
+ _CONFIG = {
57
+ 'save_plots': False,
58
+ 'save_dir': 'figs'
59
+ }
60
+
61
+ # --- UPDATED FUNCTION ---
62
+ def setup_publication_style(
63
+ theme: str = 'light',
64
+ quality: int = 300,
65
+ save_plots: bool = False, # <-- New parameter
66
+ save_dir: str = 'figs' # <-- New parameter
67
+ ) -> None:
68
+ """
69
+ Sets the global Matplotlib rcParams and saving configuration.
70
+
71
+ Call this function once at the beginning of your notebook.
72
+
73
+ Parameters
74
+ ----------
75
+ theme : str, optional
76
+ The theme to apply. Defaults to 'light'.
77
+ quality : int, optional
78
+ The DPI for the figures. Defaults to 300.
79
+ save_plots : bool, optional
80
+ Global switch to enable/disable saving plots. Defaults to False.
81
+ save_dir : str, optional
82
+ The directory to save figures in. Defaults to 'figs'.
83
+ """
84
+
85
+ # [All the theme parsing and styling code remains the same]
86
+ # ... (omitted for brevity) ...
87
+ is_transparent = False
88
+ base_theme_name = theme
89
+ if theme.endswith('-transparent'):
90
+ is_transparent = True
91
+ base_theme_name = theme.replace('-transparent', '')
92
+ if base_theme_name not in THEMES:
93
+ base_theme_name = 'light'
94
+ params = THEMES[base_theme_name].copy()
95
+ common_params = {
96
+ 'font.size': 12, 'axes.labelsize': 12, 'axes.titlesize': 14,
97
+ 'axes.titleweight': 'bold', 'xtick.labelsize': 12, 'ytick.labelsize': 12,
98
+ 'legend.fontsize': 12, 'legend.title_fontsize': 13,
99
+ 'figure.dpi': quality, 'savefig.dpi': quality, 'axes.grid': True,
100
+ 'grid.linestyle': '--', 'grid.alpha': 0.7, 'axes.linewidth': 1.2,
101
+ }
102
+ params.update(common_params)
103
+ if is_transparent:
104
+ params['figure.facecolor'] = (0, 0, 0, 0)
105
+ params['axes.facecolor'] = (0, 0, 0, 0)
106
+ params['savefig.transparent'] = True
107
+ params['legend.facecolor'] = (0, 0, 0, 0)
108
+ else:
109
+ params['savefig.transparent'] = False
110
+ try:
111
+ plt.rc('font', family='Times New Roman')
112
+ except:
113
+ print("Warning: Times New Roman not found. Defaulting to serif.")
114
+ plt.rc('font', family='serif')
115
+ plt.rcParams.update(params)
116
+ sns_style = "darkgrid" if base_theme_name == 'dark' else "whitegrid"
117
+ sns.set_style(sns_style, rc=params)
118
+
119
+ # --- Store saving configuration ---
120
+ _CONFIG['save_plots'] = save_plots
121
+ _CONFIG['save_dir'] = save_dir
122
+
123
+ print(f"Matplotlib style updated. Theme: '{theme}', Quality: {quality} DPI.")
124
+ if save_plots:
125
+ print(f"Plot saving enabled. Saving to: '{save_dir}'")
126
+ else:
127
+ print("Plot saving disabled.")
128
+
129
+ # [apply_plot_style function remains exactly the same]
130
+ def apply_plot_style(
131
+ ax: plt.Axes,
132
+ title: str,
133
+ xlabel: str,
134
+ ylabel: str,
135
+ legend_title: Optional[str] = None
136
+ ) -> None:
137
+ ax.set_title(title)
138
+ ax.set_xlabel(xlabel)
139
+ ax.set_ylabel(ylabel)
140
+ if ax.get_legend() and legend_title is not None:
141
+ ax.legend(title=legend_title)
142
+
143
+ # --- UPDATED FUNCTION ---
144
+ def finalize_plot(
145
+ fig: fig.Figure,
146
+ filename: str
147
+ ) -> None:
148
+ """
149
+ Shows the plot and saves it *if* saving was enabled in
150
+ setup_publication_style.
151
+
152
+ Parameters
153
+ ----------
154
+ fig : plt.Figure
155
+ The figure object to save.
156
+ filename : str
157
+ The name of the file (e.g., 'model_performance.png').
158
+ This is required, but only used if saving is enabled.
159
+ """
160
+
161
+ # --- 1. Save the figure if global switch is on ---
162
+ if _CONFIG['save_plots']:
163
+ save_dir = _CONFIG['save_dir']
164
+
165
+ # Create the directory if it doesn't exist
166
+ os.makedirs(save_dir, exist_ok=True)
167
+
168
+ # Construct the full path
169
+ full_path = os.path.join(save_dir, filename)
170
+
171
+ # Save the figure
172
+ fig.savefig(full_path, bbox_inches='tight')
173
+
174
+ print(f"Figure saved to: {full_path}")
175
+
176
+ # --- 2. Always show the plot ---
177
+ plt.show()
178
+
179
+ # --- 3. Close the figure object ---
180
+ plt.close(fig)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: RiskLabAI
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: Financial AI using Python, based on 'Advances in Financial Machine Learning' and 'Machine Learning for Asset Managers'.
5
5
  Author-email: RiskLab <arian@risklab.ai>
6
6
  License: BSD 3-Clause License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "RiskLabAI"
7
- version = "1.0.2"
7
+ version = "1.0.4"
8
8
  authors = [
9
9
  { name = "RiskLab", email = "arian@risklab.ai" },
10
10
  ]
@@ -1,75 +0,0 @@
1
- """
2
- Utilities for creating publication-quality plots with Matplotlib
3
- and Seaborn, using Times New Roman font and high DPI.
4
- """
5
-
6
- import matplotlib.pyplot as plt
7
- import seaborn as sns
8
- from typing import Optional, List
9
-
10
- def setup_publication_style() -> None:
11
- """
12
- Sets the global Matplotlib rcParams for a consistent,
13
- publication-quality (Times New Roman, 300 DPI) style.
14
-
15
- Call this function once at the beginning of your notebook.
16
- """
17
-
18
- # Check if Times New Roman is available
19
- try:
20
- plt.rc('font', family='Times New Roman')
21
- except:
22
- print("Warning: Times New Roman not found. Defaulting to serif.")
23
- plt.rc('font', family='serif')
24
-
25
- params = {
26
- 'font.size': 12,
27
- 'axes.labelsize': 12,
28
- 'axes.titlesize': 14,
29
- 'xtick.labelsize': 12,
30
- 'ytick.labelsize': 12,
31
- 'legend.fontsize': 12,
32
- 'figure.dpi': 300,
33
- 'savefig.dpi': 300,
34
- 'savefig.transparent': True,
35
- 'axes.grid': True,
36
- 'grid.linestyle': '--',
37
- 'grid.alpha': 0.5,
38
- 'axes.edgecolor': 'black',
39
- 'axes.linewidth': 1.2,
40
- }
41
-
42
- plt.rcParams.update(params)
43
- sns.set_style("whitegrid", params)
44
- print("Matplotlib style updated for publication.")
45
-
46
-
47
- def apply_plot_style(
48
- ax: plt.Axes,
49
- title: str,
50
- xlabel: str,
51
- ylabel: str,
52
- legend_title: Optional[str] = None
53
- ) -> None:
54
- """
55
- Applies the standardized style to a specific Matplotlib Axes object.
56
-
57
- Parameters
58
- ----------
59
- ax : plt.Axes
60
- The Matplotlib axes to style.
61
- title : str
62
- The title for the plot.
63
- xlabel : str
64
- The label for the x-axis.
65
- ylabel : str
66
- The label for the y-axis.
67
- legend_title : str, optional
68
- Title for the legend, if any.
69
- """
70
- ax.set_title(title, fontsize=14, fontweight='bold')
71
- ax.set_xlabel(xlabel, fontsize=12)
72
- ax.set_ylabel(ylabel, fontsize=12)
73
-
74
- if ax.get_legend():
75
- ax.legend(title=legend_title, fontsize=12)
File without changes
File without changes
File without changes