quantlite 0.8.0__tar.gz → 1.0.0__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 (140) hide show
  1. {quantlite-0.8.0/src/quantlite.egg-info → quantlite-1.0.0}/PKG-INFO +155 -1
  2. {quantlite-0.8.0 → quantlite-1.0.0}/README.md +154 -0
  3. {quantlite-0.8.0 → quantlite-1.0.0}/pyproject.toml +1 -1
  4. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/__init__.py +20 -1
  5. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/antifragile/__init__.py +42 -22
  6. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/ergodicity/__init__.py +30 -16
  7. quantlite-1.0.0/src/quantlite/pipeline.py +338 -0
  8. quantlite-1.0.0/src/quantlite/regime_integration/__init__.py +31 -0
  9. quantlite-1.0.0/src/quantlite/regime_integration/portfolio.py +267 -0
  10. quantlite-1.0.0/src/quantlite/regime_integration/reporting.py +204 -0
  11. quantlite-1.0.0/src/quantlite/regime_integration/risk.py +212 -0
  12. quantlite-1.0.0/src/quantlite/simulation/__init__.py +45 -0
  13. quantlite-1.0.0/src/quantlite/simulation/copula_mc.py +238 -0
  14. quantlite-1.0.0/src/quantlite/simulation/evt_simulation.py +304 -0
  15. quantlite-1.0.0/src/quantlite/simulation/regime_mc.py +328 -0
  16. {quantlite-0.8.0 → quantlite-1.0.0/src/quantlite.egg-info}/PKG-INFO +155 -1
  17. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite.egg-info/SOURCES.txt +14 -0
  18. quantlite-1.0.0/tests/test_pipeline.py +165 -0
  19. quantlite-1.0.0/tests/test_regime_integration.py +287 -0
  20. quantlite-1.0.0/tests/test_sim_copula.py +159 -0
  21. quantlite-1.0.0/tests/test_sim_evt.py +164 -0
  22. quantlite-1.0.0/tests/test_sim_regime.py +211 -0
  23. {quantlite-0.8.0 → quantlite-1.0.0}/LICENSE +0 -0
  24. {quantlite-0.8.0 → quantlite-1.0.0}/setup.cfg +0 -0
  25. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/backtesting/__init__.py +0 -0
  26. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/backtesting/analysis.py +0 -0
  27. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/backtesting/engine.py +0 -0
  28. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/backtesting/legacy.py +0 -0
  29. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/backtesting/signals.py +0 -0
  30. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/contagion/__init__.py +0 -0
  31. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/core/__init__.py +0 -0
  32. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/core/types.py +0 -0
  33. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/crypto/__init__.py +0 -0
  34. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/crypto/exchange.py +0 -0
  35. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/crypto/onchain.py +0 -0
  36. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/crypto/stablecoin.py +0 -0
  37. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/data/__init__.py +0 -0
  38. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/data/base.py +0 -0
  39. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/data/cache.py +0 -0
  40. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/data/crypto.py +0 -0
  41. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/data/fred.py +0 -0
  42. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/data/local.py +0 -0
  43. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/data/registry.py +0 -0
  44. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/data/yahoo.py +0 -0
  45. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/data_generation.py +0 -0
  46. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/dependency/__init__.py +0 -0
  47. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/dependency/clustering.py +0 -0
  48. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/dependency/copulas.py +0 -0
  49. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/dependency/correlation.py +0 -0
  50. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/distributions/__init__.py +0 -0
  51. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/distributions/fat_tails.py +0 -0
  52. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/diversification/__init__.py +0 -0
  53. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/factors/__init__.py +0 -0
  54. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/factors/classical.py +0 -0
  55. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/factors/custom.py +0 -0
  56. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/factors/tail_risk.py +0 -0
  57. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/forensics/__init__.py +0 -0
  58. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/instruments/__init__.py +0 -0
  59. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/instruments/bond_pricing.py +0 -0
  60. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/instruments/exotic_options.py +0 -0
  61. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/instruments/option_pricing.py +0 -0
  62. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/metrics.py +0 -0
  63. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/monte_carlo.py +0 -0
  64. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/network/__init__.py +0 -0
  65. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/overfit/__init__.py +0 -0
  66. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/portfolio/__init__.py +0 -0
  67. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/portfolio/optimisation.py +0 -0
  68. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/portfolio/rebalancing.py +0 -0
  69. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/regimes/__init__.py +0 -0
  70. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/regimes/changepoint.py +0 -0
  71. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/regimes/conditional.py +0 -0
  72. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/regimes/hmm.py +0 -0
  73. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/report/__init__.py +0 -0
  74. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/report/html_renderer.py +0 -0
  75. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/report/pdf_renderer.py +0 -0
  76. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/report/sections.py +0 -0
  77. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/report/tearsheet.py +0 -0
  78. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/resample/__init__.py +0 -0
  79. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/risk/__init__.py +0 -0
  80. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/risk/evt.py +0 -0
  81. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/risk/metrics.py +0 -0
  82. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/scenarios/__init__.py +0 -0
  83. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/visualisation.py +0 -0
  84. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/viz/__init__.py +0 -0
  85. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/viz/dependency.py +0 -0
  86. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/viz/plotly_backend/__init__.py +0 -0
  87. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/viz/plotly_backend/dependency.py +0 -0
  88. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/viz/plotly_backend/portfolio.py +0 -0
  89. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/viz/plotly_backend/regimes.py +0 -0
  90. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/viz/plotly_backend/risk.py +0 -0
  91. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/viz/plotly_backend/theme.py +0 -0
  92. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/viz/portfolio.py +0 -0
  93. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/viz/regimes.py +0 -0
  94. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/viz/risk.py +0 -0
  95. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite/viz/theme.py +0 -0
  96. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite.egg-info/dependency_links.txt +0 -0
  97. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite.egg-info/requires.txt +0 -0
  98. {quantlite-0.8.0 → quantlite-1.0.0}/src/quantlite.egg-info/top_level.txt +0 -0
  99. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_analysis.py +0 -0
  100. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_antifragile.py +0 -0
  101. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_backtesting.py +0 -0
  102. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_changepoint.py +0 -0
  103. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_clustering.py +0 -0
  104. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_conditional.py +0 -0
  105. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_contagion.py +0 -0
  106. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_copulas.py +0 -0
  107. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_correlation.py +0 -0
  108. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_crypto_exchange.py +0 -0
  109. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_crypto_onchain.py +0 -0
  110. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_crypto_stablecoin.py +0 -0
  111. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_data_connectors.py +0 -0
  112. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_data_generation.py +0 -0
  113. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_diversification.py +0 -0
  114. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_engine.py +0 -0
  115. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_ergodicity.py +0 -0
  116. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_evt.py +0 -0
  117. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_factors_classical.py +0 -0
  118. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_factors_custom.py +0 -0
  119. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_factors_tail_risk.py +0 -0
  120. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_fat_tails.py +0 -0
  121. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_forensics.py +0 -0
  122. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_hmm.py +0 -0
  123. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_instruments.py +0 -0
  124. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_metrics.py +0 -0
  125. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_monte_carlo.py +0 -0
  126. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_network.py +0 -0
  127. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_optimisation.py +0 -0
  128. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_overfit.py +0 -0
  129. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_plotly_viz.py +0 -0
  130. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_rebalancing.py +0 -0
  131. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_report.py +0 -0
  132. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_resample.py +0 -0
  133. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_risk_metrics.py +0 -0
  134. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_scenarios.py +0 -0
  135. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_signals.py +0 -0
  136. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_visualisation.py +0 -0
  137. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_viz.py +0 -0
  138. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_viz_dependency.py +0 -0
  139. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_viz_portfolio.py +0 -0
  140. {quantlite-0.8.0 → quantlite-1.0.0}/tests/test_viz_regimes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantlite
3
- Version: 0.8.0
3
+ Version: 1.0.0
4
4
  Summary: A fat-tail-native quantitative finance toolkit: EVT, risk metrics, and honest modelling for markets that bite.
5
5
  Author-email: Prasant Sudhakaran <code@prasant.net>
6
6
  License: MIT License
@@ -716,6 +716,104 @@ Same Stephen Few theme, same muted palette, but with hover info, zoom, and nativ
716
716
  | `quantlite.factors.classical` | Fama-French three/five-factor, Carhart four-factor, factor attribution, factor summary |
717
717
  | `quantlite.factors.custom` | CustomFactor, significance testing, correlation matrix, factor portfolios, decay analysis |
718
718
  | `quantlite.factors.tail_risk` | CVaR decomposition, regime factor exposure, crowding score, tail factor beta |
719
+ | `quantlite.simulation.evt_simulation` | EVT tail simulation, parametric tail simulation, historical bootstrap EVT, scenario fan |
720
+ | `quantlite.simulation.copula_mc` | Gaussian copula MC, t-copula MC, stress correlation MC, joint tail probability |
721
+ | `quantlite.simulation.regime_mc` | Regime-switching simulation, stress test scenarios, reverse stress test, simulation summary |
722
+
723
+ ## v0.9: Fat-Tail Monte Carlo
724
+
725
+ Three simulation families that go beyond naive Monte Carlo. Standard MC assumes returns are Gaussian and independent. QuantLite's fat-tail MC uses EVT for realistic tails, copulas for joint dependence, and regime switching for structural breaks.
726
+
727
+ ### EVT Tail Simulation
728
+
729
+ Generate scenarios with GPD-fitted tails that respect the true shape of financial returns.
730
+
731
+ ```python
732
+ import numpy as np
733
+ from quantlite.simulation import evt_tail_simulation, scenario_fan
734
+
735
+ rng = np.random.default_rng(42)
736
+ returns = np.concatenate([
737
+ rng.normal(0.0003, 0.01, 900),
738
+ rng.standard_t(3, 100) * 0.03,
739
+ ])
740
+
741
+ # EVT-based scenario generation
742
+ simulated = evt_tail_simulation(returns, n_scenarios=20000, seed=42)
743
+ print(f"Historical 1st pctl: {np.percentile(returns, 1):.4f}")
744
+ print(f"Simulated 1st pctl: {np.percentile(simulated, 1):.4f}")
745
+
746
+ # Fan chart across multiple horizons
747
+ fan = scenario_fan(returns, horizons=[1, 5, 21, 63, 252])
748
+ for h in fan["horizons"]:
749
+ p5, p95 = fan["fans"][h]["5"], fan["fans"][h]["95"]
750
+ print(f" {h:>3}d: [{p5:+.2%}, {p95:+.2%}]")
751
+ ```
752
+
753
+ ![EVT Tail Simulation](docs/images/evt_tail_simulation.png)
754
+
755
+ ![Scenario Fan](docs/images/scenario_fan.png)
756
+
757
+ ### Copula Monte Carlo
758
+
759
+ Multivariate simulation that preserves fat-tailed marginals and captures tail dependence.
760
+
761
+ ```python
762
+ import numpy as np
763
+ from quantlite.simulation import t_copula_mc, joint_tail_probability
764
+
765
+ rng = np.random.default_rng(42)
766
+ fund_a = np.concatenate([rng.normal(0.0005, 0.012, 900), rng.standard_t(3, 100) * 0.03])
767
+ fund_b = np.concatenate([rng.normal(0.0003, 0.015, 900), rng.standard_t(4, 100) * 0.025])
768
+ corr = np.array([[1.0, 0.6], [0.6, 1.0]])
769
+
770
+ # t-copula captures tail dependence that Gaussian copula misses
771
+ simulated = t_copula_mc([fund_a, fund_b], corr, df=4, n_scenarios=50000)
772
+
773
+ result = joint_tail_probability(simulated, thresholds=[-0.03, -0.03])
774
+ print(f"Joint crash probability: {result['joint_probability']:.4f}")
775
+ print(f"Marginal probabilities: {result['marginal_probabilities']}")
776
+ ```
777
+
778
+ ![Copula Comparison](docs/images/copula_comparison.png)
779
+
780
+ ![Stressed Correlations](docs/images/stressed_correlations.png)
781
+
782
+ ### Regime-Switching Simulation
783
+
784
+ Paths that switch between calm, volatile, and crisis regimes via a Markov chain.
785
+
786
+ ```python
787
+ import numpy as np
788
+ from quantlite.simulation import (
789
+ regime_switching_simulation,
790
+ reverse_stress_test,
791
+ simulation_summary,
792
+ )
793
+
794
+ regimes = [
795
+ {"mu": 0.0004, "sigma": 0.008}, # Calm
796
+ {"mu": 0.0001, "sigma": 0.020}, # Volatile
797
+ {"mu": -0.002, "sigma": 0.035}, # Crisis
798
+ ]
799
+ transition = np.array([
800
+ [0.95, 0.04, 0.01],
801
+ [0.10, 0.80, 0.10],
802
+ [0.05, 0.15, 0.80],
803
+ ])
804
+
805
+ sim = regime_switching_simulation(regimes, transition, n_steps=252, n_scenarios=5000)
806
+ stats = simulation_summary(sim["returns"])
807
+ print(f"VaR 95%: {stats['var']['95%']:.2%}")
808
+ print(f"CVaR 95%: {stats['cvar']['95%']:.2%}")
809
+ print(f"P(loss > 20%): {stats['probability_of_ruin']['20%']:.2%}")
810
+ ```
811
+
812
+ ![Regime Simulation](docs/images/regime_simulation.png)
813
+
814
+ ![Reverse Stress Test](docs/images/reverse_stress_test.png)
815
+
816
+ See [docs/simulation_evt.md](docs/simulation_evt.md), [docs/simulation_copula.md](docs/simulation_copula.md), and [docs/simulation_regime.md](docs/simulation_regime.md) for the full API reference.
719
817
 
720
818
  ## v0.8: Factor Models
721
819
 
@@ -998,6 +1096,62 @@ print(f"Tail diversification: {td['tail_diversification']:.3f}")
998
1096
  | `quantlite.network` | Correlation networks, eigenvector centrality, cascade simulation, community detection |
999
1097
  | `quantlite.diversification` | Effective Number of Bets, entropy diversification, tail diversification, diversification ratio, Herfindahl index |
1000
1098
 
1099
+ ## v1.0: The Dream API
1100
+
1101
+ QuantLite v1.0 introduces the **Dream API**, a five-function pipeline that chains the entire quant workflow:
1102
+
1103
+ ```python
1104
+ import quantlite as ql
1105
+
1106
+ data = ql.fetch(["AAPL", "BTC-USD", "GLD", "TLT"], period="5y")
1107
+ regimes = ql.detect_regimes(data, n_regimes=3)
1108
+ weights = ql.construct_portfolio(data, regime_aware=True, regimes=regimes)
1109
+ result = ql.backtest(data, weights)
1110
+ ql.tearsheet(result, regimes=regimes, save="portfolio.txt")
1111
+ ```
1112
+
1113
+ ### Regime-Aware Portfolio Construction
1114
+
1115
+ Weights automatically tilt defensive during crisis regimes, increasing allocations to bonds and gold while reducing equity exposure:
1116
+
1117
+ ![Pipeline Equity Curve](https://raw.githubusercontent.com/prasants/QuantLite/main/docs/images/pipeline_equity_curve.png)
1118
+
1119
+ ### Regime Risk Analysis
1120
+
1121
+ VaR, CVaR, volatility, skewness, and kurtosis computed separately for each market regime:
1122
+
1123
+ ![Regime Risk Summary](https://raw.githubusercontent.com/prasants/QuantLite/main/docs/images/regime_risk_summary.png)
1124
+
1125
+ ### Regime-Filtered Backtesting
1126
+
1127
+ Different weight sets applied per regime, with full performance attribution:
1128
+
1129
+ ![Regime Filtered Backtest](https://raw.githubusercontent.com/prasants/QuantLite/main/docs/images/regime_filtered_backtest.png)
1130
+
1131
+ ### v1.0 Module Reference
1132
+
1133
+ | Module | Description |
1134
+ |--------|-------------|
1135
+ | `quantlite.pipeline` | Dream API: `fetch`, `detect_regimes`, `construct_portfolio`, `backtest`, `tearsheet` |
1136
+ | `quantlite.regime_integration` | Regime-conditional risk, defensive portfolio tilting, filtered backtesting, tearsheets |
1137
+ | `quantlite.regimes` | HMM regime detection, Bayesian changepoint detection, conditional metrics |
1138
+ | `quantlite.portfolio` | Markowitz, CVaR, risk parity, HRP, Black-Litterman, Kelly optimisation |
1139
+ | `quantlite.backtesting` | Multi-asset engine with circuit breakers, slippage, regime-aware signals |
1140
+ | `quantlite.risk` | VaR, CVaR, Sortino, Calmar, omega ratio, tail ratio, drawdown analysis |
1141
+ | `quantlite.data` | Unified fetching: Yahoo Finance, CCXT, FRED, local files |
1142
+ | `quantlite.distributions` | Student-t, stable, GPD, GEV fitting and simulation |
1143
+ | `quantlite.simulation` | Fat-tail Monte Carlo: EVT-based, copula-based engines |
1144
+ | `quantlite.viz` | Stephen Few-inspired charts: regimes, portfolios, risk dashboards |
1145
+ | `quantlite.factors` | Classical factors, custom factors, tail risk factors |
1146
+ | `quantlite.ergodicity` | Time-average vs ensemble-average growth, Kelly sizing |
1147
+ | `quantlite.antifragile` | Barbell metrics, convexity scores, stress testing |
1148
+ | `quantlite.scenarios` | Historical, hypothetical, and Monte Carlo scenario analysis |
1149
+ | `quantlite.forensics` | Overfitting detection, data snooping tests, walk-forward analysis |
1150
+ | `quantlite.contagion` | CoVaR, Delta CoVaR, MES, Granger causality |
1151
+ | `quantlite.network` | Correlation networks, centrality, cascade simulation |
1152
+ | `quantlite.diversification` | Effective Number of Bets, entropy, tail diversification |
1153
+ | `quantlite.crypto` | On-chain risk, stablecoin depeg, exchange risk scoring |
1154
+
1001
1155
  ## Design Philosophy
1002
1156
 
1003
1157
  1. **Fat tails are the default.** Gaussian assumptions are explicitly opt-in, never implicit.
@@ -635,6 +635,104 @@ Same Stephen Few theme, same muted palette, but with hover info, zoom, and nativ
635
635
  | `quantlite.factors.classical` | Fama-French three/five-factor, Carhart four-factor, factor attribution, factor summary |
636
636
  | `quantlite.factors.custom` | CustomFactor, significance testing, correlation matrix, factor portfolios, decay analysis |
637
637
  | `quantlite.factors.tail_risk` | CVaR decomposition, regime factor exposure, crowding score, tail factor beta |
638
+ | `quantlite.simulation.evt_simulation` | EVT tail simulation, parametric tail simulation, historical bootstrap EVT, scenario fan |
639
+ | `quantlite.simulation.copula_mc` | Gaussian copula MC, t-copula MC, stress correlation MC, joint tail probability |
640
+ | `quantlite.simulation.regime_mc` | Regime-switching simulation, stress test scenarios, reverse stress test, simulation summary |
641
+
642
+ ## v0.9: Fat-Tail Monte Carlo
643
+
644
+ Three simulation families that go beyond naive Monte Carlo. Standard MC assumes returns are Gaussian and independent. QuantLite's fat-tail MC uses EVT for realistic tails, copulas for joint dependence, and regime switching for structural breaks.
645
+
646
+ ### EVT Tail Simulation
647
+
648
+ Generate scenarios with GPD-fitted tails that respect the true shape of financial returns.
649
+
650
+ ```python
651
+ import numpy as np
652
+ from quantlite.simulation import evt_tail_simulation, scenario_fan
653
+
654
+ rng = np.random.default_rng(42)
655
+ returns = np.concatenate([
656
+ rng.normal(0.0003, 0.01, 900),
657
+ rng.standard_t(3, 100) * 0.03,
658
+ ])
659
+
660
+ # EVT-based scenario generation
661
+ simulated = evt_tail_simulation(returns, n_scenarios=20000, seed=42)
662
+ print(f"Historical 1st pctl: {np.percentile(returns, 1):.4f}")
663
+ print(f"Simulated 1st pctl: {np.percentile(simulated, 1):.4f}")
664
+
665
+ # Fan chart across multiple horizons
666
+ fan = scenario_fan(returns, horizons=[1, 5, 21, 63, 252])
667
+ for h in fan["horizons"]:
668
+ p5, p95 = fan["fans"][h]["5"], fan["fans"][h]["95"]
669
+ print(f" {h:>3}d: [{p5:+.2%}, {p95:+.2%}]")
670
+ ```
671
+
672
+ ![EVT Tail Simulation](docs/images/evt_tail_simulation.png)
673
+
674
+ ![Scenario Fan](docs/images/scenario_fan.png)
675
+
676
+ ### Copula Monte Carlo
677
+
678
+ Multivariate simulation that preserves fat-tailed marginals and captures tail dependence.
679
+
680
+ ```python
681
+ import numpy as np
682
+ from quantlite.simulation import t_copula_mc, joint_tail_probability
683
+
684
+ rng = np.random.default_rng(42)
685
+ fund_a = np.concatenate([rng.normal(0.0005, 0.012, 900), rng.standard_t(3, 100) * 0.03])
686
+ fund_b = np.concatenate([rng.normal(0.0003, 0.015, 900), rng.standard_t(4, 100) * 0.025])
687
+ corr = np.array([[1.0, 0.6], [0.6, 1.0]])
688
+
689
+ # t-copula captures tail dependence that Gaussian copula misses
690
+ simulated = t_copula_mc([fund_a, fund_b], corr, df=4, n_scenarios=50000)
691
+
692
+ result = joint_tail_probability(simulated, thresholds=[-0.03, -0.03])
693
+ print(f"Joint crash probability: {result['joint_probability']:.4f}")
694
+ print(f"Marginal probabilities: {result['marginal_probabilities']}")
695
+ ```
696
+
697
+ ![Copula Comparison](docs/images/copula_comparison.png)
698
+
699
+ ![Stressed Correlations](docs/images/stressed_correlations.png)
700
+
701
+ ### Regime-Switching Simulation
702
+
703
+ Paths that switch between calm, volatile, and crisis regimes via a Markov chain.
704
+
705
+ ```python
706
+ import numpy as np
707
+ from quantlite.simulation import (
708
+ regime_switching_simulation,
709
+ reverse_stress_test,
710
+ simulation_summary,
711
+ )
712
+
713
+ regimes = [
714
+ {"mu": 0.0004, "sigma": 0.008}, # Calm
715
+ {"mu": 0.0001, "sigma": 0.020}, # Volatile
716
+ {"mu": -0.002, "sigma": 0.035}, # Crisis
717
+ ]
718
+ transition = np.array([
719
+ [0.95, 0.04, 0.01],
720
+ [0.10, 0.80, 0.10],
721
+ [0.05, 0.15, 0.80],
722
+ ])
723
+
724
+ sim = regime_switching_simulation(regimes, transition, n_steps=252, n_scenarios=5000)
725
+ stats = simulation_summary(sim["returns"])
726
+ print(f"VaR 95%: {stats['var']['95%']:.2%}")
727
+ print(f"CVaR 95%: {stats['cvar']['95%']:.2%}")
728
+ print(f"P(loss > 20%): {stats['probability_of_ruin']['20%']:.2%}")
729
+ ```
730
+
731
+ ![Regime Simulation](docs/images/regime_simulation.png)
732
+
733
+ ![Reverse Stress Test](docs/images/reverse_stress_test.png)
734
+
735
+ See [docs/simulation_evt.md](docs/simulation_evt.md), [docs/simulation_copula.md](docs/simulation_copula.md), and [docs/simulation_regime.md](docs/simulation_regime.md) for the full API reference.
638
736
 
639
737
  ## v0.8: Factor Models
640
738
 
@@ -917,6 +1015,62 @@ print(f"Tail diversification: {td['tail_diversification']:.3f}")
917
1015
  | `quantlite.network` | Correlation networks, eigenvector centrality, cascade simulation, community detection |
918
1016
  | `quantlite.diversification` | Effective Number of Bets, entropy diversification, tail diversification, diversification ratio, Herfindahl index |
919
1017
 
1018
+ ## v1.0: The Dream API
1019
+
1020
+ QuantLite v1.0 introduces the **Dream API**, a five-function pipeline that chains the entire quant workflow:
1021
+
1022
+ ```python
1023
+ import quantlite as ql
1024
+
1025
+ data = ql.fetch(["AAPL", "BTC-USD", "GLD", "TLT"], period="5y")
1026
+ regimes = ql.detect_regimes(data, n_regimes=3)
1027
+ weights = ql.construct_portfolio(data, regime_aware=True, regimes=regimes)
1028
+ result = ql.backtest(data, weights)
1029
+ ql.tearsheet(result, regimes=regimes, save="portfolio.txt")
1030
+ ```
1031
+
1032
+ ### Regime-Aware Portfolio Construction
1033
+
1034
+ Weights automatically tilt defensive during crisis regimes, increasing allocations to bonds and gold while reducing equity exposure:
1035
+
1036
+ ![Pipeline Equity Curve](https://raw.githubusercontent.com/prasants/QuantLite/main/docs/images/pipeline_equity_curve.png)
1037
+
1038
+ ### Regime Risk Analysis
1039
+
1040
+ VaR, CVaR, volatility, skewness, and kurtosis computed separately for each market regime:
1041
+
1042
+ ![Regime Risk Summary](https://raw.githubusercontent.com/prasants/QuantLite/main/docs/images/regime_risk_summary.png)
1043
+
1044
+ ### Regime-Filtered Backtesting
1045
+
1046
+ Different weight sets applied per regime, with full performance attribution:
1047
+
1048
+ ![Regime Filtered Backtest](https://raw.githubusercontent.com/prasants/QuantLite/main/docs/images/regime_filtered_backtest.png)
1049
+
1050
+ ### v1.0 Module Reference
1051
+
1052
+ | Module | Description |
1053
+ |--------|-------------|
1054
+ | `quantlite.pipeline` | Dream API: `fetch`, `detect_regimes`, `construct_portfolio`, `backtest`, `tearsheet` |
1055
+ | `quantlite.regime_integration` | Regime-conditional risk, defensive portfolio tilting, filtered backtesting, tearsheets |
1056
+ | `quantlite.regimes` | HMM regime detection, Bayesian changepoint detection, conditional metrics |
1057
+ | `quantlite.portfolio` | Markowitz, CVaR, risk parity, HRP, Black-Litterman, Kelly optimisation |
1058
+ | `quantlite.backtesting` | Multi-asset engine with circuit breakers, slippage, regime-aware signals |
1059
+ | `quantlite.risk` | VaR, CVaR, Sortino, Calmar, omega ratio, tail ratio, drawdown analysis |
1060
+ | `quantlite.data` | Unified fetching: Yahoo Finance, CCXT, FRED, local files |
1061
+ | `quantlite.distributions` | Student-t, stable, GPD, GEV fitting and simulation |
1062
+ | `quantlite.simulation` | Fat-tail Monte Carlo: EVT-based, copula-based engines |
1063
+ | `quantlite.viz` | Stephen Few-inspired charts: regimes, portfolios, risk dashboards |
1064
+ | `quantlite.factors` | Classical factors, custom factors, tail risk factors |
1065
+ | `quantlite.ergodicity` | Time-average vs ensemble-average growth, Kelly sizing |
1066
+ | `quantlite.antifragile` | Barbell metrics, convexity scores, stress testing |
1067
+ | `quantlite.scenarios` | Historical, hypothetical, and Monte Carlo scenario analysis |
1068
+ | `quantlite.forensics` | Overfitting detection, data snooping tests, walk-forward analysis |
1069
+ | `quantlite.contagion` | CoVaR, Delta CoVaR, MES, Granger causality |
1070
+ | `quantlite.network` | Correlation networks, centrality, cascade simulation |
1071
+ | `quantlite.diversification` | Effective Number of Bets, entropy, tail diversification |
1072
+ | `quantlite.crypto` | On-chain risk, stablecoin depeg, exchange risk scoring |
1073
+
920
1074
  ## Design Philosophy
921
1075
 
922
1076
  1. **Fat tails are the default.** Gaussian assumptions are explicitly opt-in, never implicit.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "quantlite"
7
- version = "0.8.0"
7
+ version = "1.0.0"
8
8
  description = "A fat-tail-native quantitative finance toolkit: EVT, risk metrics, and honest modelling for markets that bite."
9
9
  requires-python = ">=3.9"
10
10
  license = { file = "LICENSE" }
@@ -6,7 +6,7 @@ portfolio optimisation, multi-asset backtesting, and
6
6
  Stephen Few-inspired visualisation.
7
7
  """
8
8
 
9
- __version__ = "0.2.0"
9
+ __version__ = "1.0.0"
10
10
 
11
11
  from .backtesting import (
12
12
  BacktestConfig,
@@ -77,6 +77,16 @@ __all__ = [
77
77
  "diversification",
78
78
  # Crypto-native risk
79
79
  "crypto",
80
+ # Fat-tail Monte Carlo simulation
81
+ "simulation",
82
+ # Regime-aware integration
83
+ "regime_integration",
84
+ # Dream API (pipeline)
85
+ "fetch",
86
+ "detect_regimes",
87
+ "construct_portfolio",
88
+ "backtest",
89
+ "tearsheet",
80
90
  ]
81
91
 
82
92
  from . import ( # noqa: E402
@@ -88,6 +98,15 @@ from . import ( # noqa: E402
88
98
  forensics,
89
99
  network,
90
100
  overfit,
101
+ regime_integration,
91
102
  resample,
92
103
  scenarios,
104
+ simulation,
93
105
  )
106
+ from .pipeline import ( # noqa: E402
107
+ backtest,
108
+ construct_portfolio,
109
+ detect_regimes,
110
+ tearsheet,
111
+ )
112
+ from .pipeline import fetch as fetch # noqa: E402
@@ -218,44 +218,40 @@ def barbell_allocation(
218
218
  def lindy_estimate(age: float, confidence: float = 0.95) -> dict[str, float]:
219
219
  """Estimate remaining life expectancy using the Lindy effect.
220
220
 
221
- For non-perishable entities (ideas, technologies, institutions),
222
- expected remaining lifespan is proportional to current age.
221
+ Models non-perishable entities (ideas, technologies, institutions)
222
+ with a Pareto survival distribution (alpha = 1). Under this model:
223
223
 
224
- Uses a Pareto-like survival model where the expected remaining
225
- life equals the current age (for the median estimate).
224
+ P(T > age + t | T > age) = age / (age + t)
225
+
226
+ Key properties:
227
+
228
+ * **Expected remaining life** = age (the longer it has survived,
229
+ the longer we expect it to last).
230
+ * **Lower bound at confidence level c**: the additional time *t*
231
+ such that we are *c*-confident the entity survives at least *t*
232
+ more units. Solve ``age / (age + t) = 1 - c`` to get
233
+ ``t = age * c / (1 - c)``.
226
234
 
227
235
  Parameters
228
236
  ----------
229
237
  age : float
230
238
  Current age of the entity (in any consistent unit).
231
239
  confidence : float
232
- Confidence level for the survival bound (default 0.95).
240
+ Confidence level for the survival lower bound (default 0.95).
233
241
 
234
242
  Returns
235
243
  -------
236
244
  dict
237
- Keys: 'age', 'expected_remaining' (median estimate),
238
- 'lower_bound' (at given confidence), 'total_expected'.
245
+ Keys: 'age', 'expected_remaining', 'lower_bound' (at the
246
+ given confidence), 'total_expected'.
239
247
  """
240
248
  if age <= 0:
241
249
  raise ValueError("age must be positive")
242
250
  if not 0 < confidence < 1:
243
251
  raise ValueError("confidence must be between 0 and 1")
244
252
 
245
- # Under Lindy (Pareto with alpha=1), expected remaining life = age
246
253
  expected_remaining = age
247
-
248
- # Lower bound: at confidence level, survival beyond this point
249
- # P(survive t more) = age / (age + t), so t = age * (1/p - 1)
250
- lower_bound = age * (1.0 / (1.0 - confidence) - 1.0) * (1.0 - confidence)
251
- # Simplifies to: age * confidence / (1 - confidence) * (1 - confidence) = age * confidence
252
- # Actually: P(T > age + t | T > age) = age / (age + t) for Pareto
253
- # Set this = 1 - confidence: age/(age+t) = 1 - confidence
254
- # t = age * confidence / (1 - confidence)
255
- lower_bound = age * (1.0 - confidence) / confidence
256
- # That's the point we're confident we'll reach (small value)
257
- # More useful: expected remaining at median
258
- # P(T > age + t | T > age) = 0.5 => t = age (median remaining = age)
254
+ lower_bound = age * confidence / (1.0 - confidence)
259
255
 
260
256
  return {
261
257
  "age": age,
@@ -268,11 +264,25 @@ def lindy_estimate(age: float, confidence: float = 0.95) -> dict[str, float]:
268
264
  def skin_in_game_score(
269
265
  manager_returns: ArrayLike,
270
266
  fund_returns: ArrayLike,
267
+ alignment_weight: float = 0.4,
268
+ downside_weight: float = 0.4,
269
+ asymmetry_weight: float = 0.2,
271
270
  ) -> dict[str, float]:
272
271
  """Measure principal-agent alignment via payoff asymmetry.
273
272
 
274
273
  Compares the manager's exposure to downside vs upside relative
275
- to the fund. A good score means the manager shares the pain.
274
+ to the fund. A good score means the manager shares the pain.
275
+
276
+ The composite score weights three components:
277
+
278
+ * **Alignment** (default 0.4): correlation between manager and fund
279
+ returns. Are incentives actually correlated?
280
+ * **Downside sharing** (default 0.4): when the fund loses, does the
281
+ manager bleed proportionally? This matters as much as alignment —
282
+ asymmetric downside is the hallmark of agency problems.
283
+ * **Upside asymmetry** (default 0.2): does the manager capture
284
+ disproportionate upside? A secondary check — some asymmetry is
285
+ expected (performance fees), but extreme values signal misalignment.
276
286
 
277
287
  Parameters
278
288
  ----------
@@ -280,6 +290,12 @@ def skin_in_game_score(
280
290
  Returns experienced by the manager (compensation-adjusted).
281
291
  fund_returns : array-like
282
292
  Returns experienced by the fund investors.
293
+ alignment_weight : float
294
+ Weight for the alignment (correlation) component (default 0.4).
295
+ downside_weight : float
296
+ Weight for the downside-sharing component (default 0.4).
297
+ asymmetry_weight : float
298
+ Weight for the upside-asymmetry component (default 0.2).
283
299
 
284
300
  Returns
285
301
  -------
@@ -317,7 +333,11 @@ def skin_in_game_score(
317
333
 
318
334
  # Composite score: high alignment + high downside sharing + low upside asymmetry = good
319
335
  # Normalise to [0, 1] approximately
320
- score = (alignment * 0.4) + (min(downside_sharing, 1.0) * 0.4) + (max(0, 1.0 - abs(upside_asymmetry - 1.0)) * 0.2)
336
+ score = (
337
+ alignment * alignment_weight
338
+ + min(downside_sharing, 1.0) * downside_weight
339
+ + max(0, 1.0 - abs(upside_asymmetry - 1.0)) * asymmetry_weight
340
+ )
321
341
 
322
342
  return {
323
343
  "alignment": alignment,
@@ -91,11 +91,13 @@ def ergodicity_gap(returns: ArrayLike) -> float:
91
91
  def kelly_fraction(returns: ArrayLike, risk_free: float = 0.0) -> float:
92
92
  """Compute the optimal Kelly fraction for geometric growth.
93
93
 
94
- The Kelly criterion maximises the expected logarithmic growth rate.
95
- For a simple binary-style approximation from empirical returns,
96
- we optimise f to maximise E[log(1 + f * (r - risk_free))].
94
+ The Kelly criterion maximises the expected logarithmic growth rate:
97
95
 
98
- Uses a numerical grid search over [0, 2] for robustness.
96
+ f* = argmax_f E[log(1 + f * (r - r_f))]
97
+
98
+ Uses ``scipy.optimize.minimize_scalar`` with bounded search on
99
+ [-0.5, 3.0]. Falls back to Brent-bounded optimisation if the
100
+ primary solve fails.
99
101
 
100
102
  Parameters
101
103
  ----------
@@ -110,24 +112,36 @@ def kelly_fraction(returns: ArrayLike, risk_free: float = 0.0) -> float:
110
112
  Optimal fraction of capital to deploy. Can be < 0 (short)
111
113
  or > 1 (leveraged).
112
114
  """
115
+ from scipy.optimize import minimize_scalar
116
+
113
117
  r = _to_array(returns)
114
118
  excess = r - risk_free
115
119
 
116
- # Grid search over fractions from -0.5 to 3.0
117
- fractions = np.linspace(-0.5, 3.0, 3500)
118
- best_f = 0.0
119
- best_g = -np.inf
120
-
121
- for f in fractions:
120
+ def neg_expected_log_growth(f: float) -> float:
122
121
  portfolio = 1.0 + f * excess
123
122
  if np.any(portfolio <= 0):
124
- continue
125
- g = np.mean(np.log(portfolio))
126
- if g > best_g:
127
- best_g = g
128
- best_f = f
123
+ return 1e10 # infeasible
124
+ return -float(np.mean(np.log(portfolio)))
125
+
126
+ bounds = (-0.5, 3.0)
127
+
128
+ result = minimize_scalar(
129
+ neg_expected_log_growth, bounds=bounds, method="bounded",
130
+ options={"xatol": 1e-8, "maxiter": 500},
131
+ )
132
+
133
+ if result.success:
134
+ return float(round(result.x, 4))
135
+
136
+ # Fallback: try again with Brent in the same interval
137
+ result2 = minimize_scalar(
138
+ neg_expected_log_growth, bracket=(-0.5, 0.5, 3.0), method="brent",
139
+ )
140
+ if result2.success and bounds[0] <= result2.x <= bounds[1]:
141
+ return float(round(result2.x, 4))
129
142
 
130
- return float(round(best_f, 4))
143
+ # Last resort: return 0 (no bet)
144
+ return 0.0
131
145
 
132
146
 
133
147
  def leverage_effect(