fundedness 0.2.1__tar.gz → 0.2.3__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.

Potentially problematic release.


This version of fundedness might be problematic. Click here for more details.

Files changed (98) hide show
  1. fundedness-0.2.3/.streamlit/config.toml +11 -0
  2. fundedness-0.2.3/CLAUDE.md +169 -0
  3. {fundedness-0.2.1 → fundedness-0.2.3}/PKG-INFO +38 -3
  4. {fundedness-0.2.1 → fundedness-0.2.3}/README.md +37 -2
  5. {fundedness-0.2.1 → fundedness-0.2.3}/docs/guide/utility-optimization.md +12 -4
  6. fundedness-0.2.3/docs/javascripts/mathjax.js +12 -0
  7. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/__init__.py +1 -1
  8. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/models/market.py +26 -15
  9. {fundedness-0.2.1 → fundedness-0.2.3}/mkdocs.yml +6 -0
  10. {fundedness-0.2.1 → fundedness-0.2.3}/pyproject.toml +1 -1
  11. {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/app.py +6 -0
  12. {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/pages/0_Inputs.py +4 -0
  13. {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/pages/1_CEFR_Dashboard.py +4 -0
  14. {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/pages/2_Time_Runway.py +4 -0
  15. {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/pages/3_Withdrawal_Lab.py +4 -0
  16. {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/pages/4_Sensitivity.py +4 -0
  17. {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/pages/5_Utility_Optimization.py +4 -0
  18. fundedness-0.2.1/CLAUDE.md +0 -98
  19. {fundedness-0.2.1 → fundedness-0.2.3}/.github/workflows/docs.yml +0 -0
  20. {fundedness-0.2.1 → fundedness-0.2.3}/.github/workflows/publish.yml +0 -0
  21. {fundedness-0.2.1 → fundedness-0.2.3}/.gitignore +0 -0
  22. {fundedness-0.2.1 → fundedness-0.2.3}/api/__init__.py +0 -0
  23. {fundedness-0.2.1 → fundedness-0.2.3}/api/main.py +0 -0
  24. {fundedness-0.2.1 → fundedness-0.2.3}/api/routes/__init__.py +0 -0
  25. {fundedness-0.2.1 → fundedness-0.2.3}/api/routes/cefr.py +0 -0
  26. {fundedness-0.2.1 → fundedness-0.2.3}/api/routes/compare.py +0 -0
  27. {fundedness-0.2.1 → fundedness-0.2.3}/api/routes/simulate.py +0 -0
  28. {fundedness-0.2.1 → fundedness-0.2.3}/background_information.md +0 -0
  29. {fundedness-0.2.1 → fundedness-0.2.3}/docs/api/core.md +0 -0
  30. {fundedness-0.2.1 → fundedness-0.2.3}/docs/api/merton.md +0 -0
  31. {fundedness-0.2.1 → fundedness-0.2.3}/docs/api/models.md +0 -0
  32. {fundedness-0.2.1 → fundedness-0.2.3}/docs/api/viz.md +0 -0
  33. {fundedness-0.2.1 → fundedness-0.2.3}/docs/api/withdrawals.md +0 -0
  34. {fundedness-0.2.1 → fundedness-0.2.3}/docs/examples/tutorials.md +0 -0
  35. {fundedness-0.2.1 → fundedness-0.2.3}/docs/getting-started/installation.md +0 -0
  36. {fundedness-0.2.1 → fundedness-0.2.3}/docs/getting-started/quickstart.md +0 -0
  37. {fundedness-0.2.1 → fundedness-0.2.3}/docs/guide/cefr.md +0 -0
  38. {fundedness-0.2.1 → fundedness-0.2.3}/docs/guide/simulations.md +0 -0
  39. {fundedness-0.2.1 → fundedness-0.2.3}/docs/guide/visualizations.md +0 -0
  40. {fundedness-0.2.1 → fundedness-0.2.3}/docs/guide/withdrawals.md +0 -0
  41. {fundedness-0.2.1 → fundedness-0.2.3}/docs/index.md +0 -0
  42. {fundedness-0.2.1 → fundedness-0.2.3}/examples/01_cefr_basics.ipynb +0 -0
  43. {fundedness-0.2.1 → fundedness-0.2.3}/examples/02_time_distribution.ipynb +0 -0
  44. {fundedness-0.2.1 → fundedness-0.2.3}/examples/03_withdrawal_comparison.ipynb +0 -0
  45. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/allocation/__init__.py +0 -0
  46. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/allocation/base.py +0 -0
  47. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/allocation/constant.py +0 -0
  48. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/allocation/glidepath.py +0 -0
  49. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/allocation/merton_optimal.py +0 -0
  50. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/cefr.py +0 -0
  51. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/liabilities.py +0 -0
  52. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/liquidity.py +0 -0
  53. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/merton.py +0 -0
  54. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/models/__init__.py +0 -0
  55. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/models/assets.py +0 -0
  56. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/models/household.py +0 -0
  57. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/models/liabilities.py +0 -0
  58. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/models/simulation.py +0 -0
  59. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/models/tax.py +0 -0
  60. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/models/utility.py +0 -0
  61. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/optimize.py +0 -0
  62. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/policies.py +0 -0
  63. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/risk.py +0 -0
  64. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/simulate.py +0 -0
  65. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/viz/__init__.py +0 -0
  66. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/viz/colors.py +0 -0
  67. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/viz/comparison.py +0 -0
  68. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/viz/fan_chart.py +0 -0
  69. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/viz/histogram.py +0 -0
  70. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/viz/optimal.py +0 -0
  71. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/viz/survival.py +0 -0
  72. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/viz/tornado.py +0 -0
  73. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/viz/waterfall.py +0 -0
  74. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/withdrawals/__init__.py +0 -0
  75. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/withdrawals/base.py +0 -0
  76. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/withdrawals/comparison.py +0 -0
  77. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/withdrawals/fixed_swr.py +0 -0
  78. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/withdrawals/guardrails.py +0 -0
  79. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/withdrawals/merton_optimal.py +0 -0
  80. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/withdrawals/rmd_style.py +0 -0
  81. {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/withdrawals/vpw.py +0 -0
  82. {fundedness-0.2.1 → fundedness-0.2.3}/requirements.txt +0 -0
  83. {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/__init__.py +0 -0
  84. {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/components/__init__.py +0 -0
  85. {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/components/asset_editor.py +0 -0
  86. {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/components/liability_editor.py +0 -0
  87. {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/components/metrics_display.py +0 -0
  88. {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/utils/__init__.py +0 -0
  89. {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/utils/session_state.py +0 -0
  90. {fundedness-0.2.1 → fundedness-0.2.3}/tests/__init__.py +0 -0
  91. {fundedness-0.2.1 → fundedness-0.2.3}/tests/conftest.py +0 -0
  92. {fundedness-0.2.1 → fundedness-0.2.3}/tests/test_api.py +0 -0
  93. {fundedness-0.2.1 → fundedness-0.2.3}/tests/test_cefr.py +0 -0
  94. {fundedness-0.2.1 → fundedness-0.2.3}/tests/test_liabilities.py +0 -0
  95. {fundedness-0.2.1 → fundedness-0.2.3}/tests/test_merton.py +0 -0
  96. {fundedness-0.2.1 → fundedness-0.2.3}/tests/test_optimize.py +0 -0
  97. {fundedness-0.2.1 → fundedness-0.2.3}/tests/test_simulate.py +0 -0
  98. {fundedness-0.2.1 → fundedness-0.2.3}/tests/test_withdrawals.py +0 -0
@@ -0,0 +1,11 @@
1
+ [theme]
2
+ primaryColor = "#3498db"
3
+ backgroundColor = "#ffffff"
4
+ secondaryBackgroundColor = "#f8f9fa"
5
+ textColor = "#2c3e50"
6
+ font = "sans serif"
7
+
8
+ [server]
9
+ headless = true
10
+ enableCORS = false
11
+ enableXsrfProtection = true
@@ -0,0 +1,169 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ This is a Python financial planning toolkit that implements:
8
+ - **CEFR (Certainty-Equivalent Funded Ratio)**: A fundedness metric that applies after-tax, liquidity, and risk haircuts to assets
9
+ - **Merton Optimal Policies**: Spending and asset allocation policies that maximize expected lifetime utility
10
+ - **Withdrawal Strategy Lab**: Comparison framework for withdrawal strategies (fixed SWR, guardrails, VPW, RMD-style, Merton optimal)
11
+
12
+ The project includes both a Python package (`fundedness/`) and a Streamlit web application (`streamlit_app/`).
13
+
14
+ ## Development Status
15
+
16
+ This project is **actively developed** and published on PyPI as `fundedness`.
17
+
18
+ Current version: **0.2.x**
19
+
20
+ ## Commands
21
+
22
+ - **Install**: `pip install fundedness` or `pip install "fundedness[all]"` for all extras
23
+ - **Testing**: `pytest` (runs 99+ tests)
24
+ - **Docs**: `mkdocs serve` (local) or auto-deployed to GitHub Pages
25
+ - **Streamlit**: `streamlit run streamlit_app/app.py`
26
+ - **API**: `uvicorn api.main:app --reload`
27
+
28
+ ## Architecture
29
+
30
+ ### Core Package Structure (`fundedness/`)
31
+
32
+ ```
33
+ fundedness/
34
+ ├── __init__.py # Package exports
35
+ ├── cefr.py # CEFR ratio calculation with haircut breakdowns
36
+ ├── liabilities.py # Liability PV calculations with schedules
37
+ ├── liquidity.py # Liquidity factor adjustments
38
+ ├── risk.py # Reliability/concentration haircuts
39
+ ├── simulate.py # Monte Carlo engine with utility tracking
40
+ ├── merton.py # Merton optimal formulas (allocation, spending, CE return)
41
+ ├── optimize.py # Parametric policy optimization
42
+ ├── policies.py # Spending/allocation policy interface
43
+ ├── models/ # Pydantic data models
44
+ │ ├── assets.py
45
+ │ ├── household.py
46
+ │ ├── liabilities.py
47
+ │ ├── market.py
48
+ │ ├── simulation.py
49
+ │ ├── tax.py
50
+ │ └── utility.py
51
+ ├── withdrawals/ # Withdrawal strategy implementations
52
+ │ ├── base.py
53
+ │ ├── fixed_swr.py
54
+ │ ├── guardrails.py
55
+ │ ├── vpw.py
56
+ │ ├── rmd_style.py
57
+ │ ├── merton_optimal.py
58
+ │ └── comparison.py
59
+ ├── allocation/ # Asset allocation strategies
60
+ │ ├── base.py
61
+ │ ├── constant.py
62
+ │ ├── glidepath.py
63
+ │ └── merton_optimal.py
64
+ └── viz/ # Plotly visualizations
65
+ ├── colors.py
66
+ ├── fan_chart.py
67
+ ├── waterfall.py
68
+ ├── survival.py
69
+ ├── comparison.py
70
+ ├── optimal.py
71
+ └── ...
72
+ ```
73
+
74
+ ### Data Models (Pydantic)
75
+
76
+ Key models: `Household`, `BalanceSheet`, `Asset`, `Liability`, `MarketModel`, `TaxModel`, `UtilityModel`, `SimulationConfig`
77
+
78
+ ### Core Formulas
79
+
80
+ **CEFR Calculation:**
81
+ ```
82
+ CEFR = Σ(Asset × (1-tax_rate) × liquidity_factor × reliability_factor) / PV(Liabilities)
83
+ ```
84
+
85
+ **Merton Optimal Allocation:**
86
+ ```
87
+ k* = (μ - r) / (γ × σ²)
88
+ ```
89
+
90
+ **Merton Optimal Spending Rate:**
91
+ ```
92
+ c* = rce - (rce - ρ) / γ
93
+ ```
94
+
95
+ **CRRA Utility with Floor:**
96
+ ```
97
+ u(C) = (C - F)^(1-γ) / (1-γ) where C > F (subsistence floor)
98
+ ```
99
+
100
+ ## Key Concepts
101
+
102
+ - **Haircuts**: Three adjustments to assets—after-tax (τ), liquidity (λ), reliability (ρ)
103
+ - **Floor/Flex spending**: Essential spending floor vs adjustable discretionary
104
+ - **Time metrics**: Time-to-floor-breach, time-to-ruin, max spending drawdown
105
+ - **Confidence intervals**: Scenario percentiles (P10/P50/P90), not statistical CIs
106
+ - **Utility optimization**: Merton framework for optimal spending and allocation
107
+
108
+ ## Default Haircut Assumptions
109
+
110
+ Liquidity: cash=1.0, taxable_index=0.95, retirement=0.85, home_equity=0.5, private_business=0.3
111
+ Reliability: diversified_bonds=0.95, diversified_equity=0.85, single_stock=0.60, startup=0.30
112
+
113
+ ## Deployment
114
+
115
+ - **PyPI**: Published as `fundedness` - releases triggered by GitHub Release
116
+ - **Docs**: MkDocs Material deployed to GitHub Pages via `docs.yml` workflow
117
+ - **Web App**: Streamlit Cloud (free tier)
118
+ - **API**: FastAPI with endpoints in `api/`
119
+
120
+ ## Visualization Standards
121
+
122
+ Use **Plotly** as the primary visualization library for beautiful, interactive charts:
123
+ - Clean, professional appearance with `template="plotly_white"`
124
+ - Interactive features: hover tooltips, zoom, pan
125
+ - Consistent color palette: blues (#3498db, #2980b9) for wealth, greens (#27ae60, #2ecc71) for spending/survival
126
+ - Fan charts with gradient opacity for percentile bands (P10-P90)
127
+ - Export capability to HTML/PNG for reports
128
+
129
+ ## Documentation Notes
130
+
131
+ ### LaTeX Equations in MkDocs
132
+
133
+ **Issue**: LaTeX equations using `$$...$$` syntax don't render in MkDocs Material.
134
+
135
+ **Solution**: Use `pymdownx.arithmatex` extension with MathJax:
136
+
137
+ 1. In `mkdocs.yml`:
138
+ ```yaml
139
+ markdown_extensions:
140
+ - pymdownx.arithmatex:
141
+ generic: true
142
+
143
+ extra_javascript:
144
+ - javascripts/mathjax.js
145
+ - https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js
146
+ ```
147
+
148
+ 2. Create `docs/javascripts/mathjax.js`:
149
+ ```javascript
150
+ window.MathJax = {
151
+ tex: {
152
+ inlineMath: [["\\(", "\\)"]],
153
+ displayMath: [["\\[", "\\]"]],
154
+ processEscapes: true,
155
+ processEnvironments: true
156
+ },
157
+ options: {
158
+ ignoreHtmlClass: ".*|",
159
+ processHtmlClass: "arithmatex"
160
+ }
161
+ };
162
+ ```
163
+
164
+ 3. Use `\[...\]` for display math and `\(...\)` for inline math in markdown files.
165
+
166
+ ## References
167
+
168
+ - Merton, R.C. (1969). Lifetime Portfolio Selection under Uncertainty. *The Review of Economics and Statistics*, 51(3), 247-257.
169
+ - Haghani, V. & White, J. (2023). *The Missing Billionaires: A Guide to Better Financial Decisions*. Wiley.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fundedness
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: A Python financial planning toolkit with CEFR calculations, Monte Carlo simulations, and beautiful visualizations
5
5
  Project-URL: Homepage, https://github.com/engineerinvestor/financial-health-calculator
6
6
  Project-URL: Documentation, https://engineerinvestor.github.io/financial-health-calculator/
@@ -56,9 +56,8 @@ Description-Content-Type: text/markdown
56
56
 
57
57
  [![PyPI version](https://img.shields.io/pypi/v/fundedness.svg)](https://pypi.org/project/fundedness/)
58
58
  [![Python versions](https://img.shields.io/pypi/pyversions/fundedness.svg)](https://pypi.org/project/fundedness/)
59
- [![CI](https://github.com/engineerinvestor/financial-health-calculator/actions/workflows/ci.yml/badge.svg)](https://github.com/engineerinvestor/financial-health-calculator/actions/workflows/ci.yml)
60
- [![codecov](https://codecov.io/gh/engineerinvestor/financial-health-calculator/branch/main/graph/badge.svg)](https://codecov.io/gh/engineerinvestor/financial-health-calculator)
61
59
  [![Documentation](https://img.shields.io/badge/docs-mkdocs-blue.svg)](https://engineerinvestor.github.io/financial-health-calculator/)
60
+ [![Streamlit App](https://static.streamlit.io/badges/streamlit_badge_black_white.svg)](https://financial-health-calculator.streamlit.app/)
62
61
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
63
62
  [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/engineerinvestor/financial-health-calculator/blob/main/examples/01_cefr_basics.ipynb)
64
63
 
@@ -260,6 +259,42 @@ MIT License
260
259
 
261
260
  2. Merton, R. C. (1969). Lifetime Portfolio Selection under Uncertainty: The Continuous-Time Case. *The Review of Economics and Statistics*, 51(3), 247-257.
262
261
 
262
+ ## Citation
263
+
264
+ If you use this package in academic work, please cite:
265
+
266
+ ```bibtex
267
+ @software{fundedness,
268
+ title = {Fundedness: A Python Financial Planning Toolkit},
269
+ author = {Engineer Investor},
270
+ year = {2024},
271
+ url = {https://github.com/engineerinvestor/financial-health-calculator},
272
+ version = {0.2.1}
273
+ }
274
+ ```
275
+
276
+ For the underlying methodology, please also cite:
277
+
278
+ ```bibtex
279
+ @article{merton1969lifetime,
280
+ title = {Lifetime Portfolio Selection under Uncertainty: The Continuous-Time Case},
281
+ author = {Merton, Robert C.},
282
+ journal = {The Review of Economics and Statistics},
283
+ volume = {51},
284
+ number = {3},
285
+ pages = {247--257},
286
+ year = {1969},
287
+ publisher = {MIT Press}
288
+ }
289
+
290
+ @book{haghani2023missing,
291
+ title = {The Missing Billionaires: A Guide to Better Financial Decisions},
292
+ author = {Haghani, Victor and White, James},
293
+ year = {2023},
294
+ publisher = {Wiley}
295
+ }
296
+ ```
297
+
263
298
  ## Disclaimer
264
299
 
265
300
  This tool is for educational purposes only and does not constitute financial advice. Consult a qualified financial advisor for personalized recommendations.
@@ -2,9 +2,8 @@
2
2
 
3
3
  [![PyPI version](https://img.shields.io/pypi/v/fundedness.svg)](https://pypi.org/project/fundedness/)
4
4
  [![Python versions](https://img.shields.io/pypi/pyversions/fundedness.svg)](https://pypi.org/project/fundedness/)
5
- [![CI](https://github.com/engineerinvestor/financial-health-calculator/actions/workflows/ci.yml/badge.svg)](https://github.com/engineerinvestor/financial-health-calculator/actions/workflows/ci.yml)
6
- [![codecov](https://codecov.io/gh/engineerinvestor/financial-health-calculator/branch/main/graph/badge.svg)](https://codecov.io/gh/engineerinvestor/financial-health-calculator)
7
5
  [![Documentation](https://img.shields.io/badge/docs-mkdocs-blue.svg)](https://engineerinvestor.github.io/financial-health-calculator/)
6
+ [![Streamlit App](https://static.streamlit.io/badges/streamlit_badge_black_white.svg)](https://financial-health-calculator.streamlit.app/)
8
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
8
  [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/engineerinvestor/financial-health-calculator/blob/main/examples/01_cefr_basics.ipynb)
10
9
 
@@ -206,6 +205,42 @@ MIT License
206
205
 
207
206
  2. Merton, R. C. (1969). Lifetime Portfolio Selection under Uncertainty: The Continuous-Time Case. *The Review of Economics and Statistics*, 51(3), 247-257.
208
207
 
208
+ ## Citation
209
+
210
+ If you use this package in academic work, please cite:
211
+
212
+ ```bibtex
213
+ @software{fundedness,
214
+ title = {Fundedness: A Python Financial Planning Toolkit},
215
+ author = {Engineer Investor},
216
+ year = {2024},
217
+ url = {https://github.com/engineerinvestor/financial-health-calculator},
218
+ version = {0.2.1}
219
+ }
220
+ ```
221
+
222
+ For the underlying methodology, please also cite:
223
+
224
+ ```bibtex
225
+ @article{merton1969lifetime,
226
+ title = {Lifetime Portfolio Selection under Uncertainty: The Continuous-Time Case},
227
+ author = {Merton, Robert C.},
228
+ journal = {The Review of Economics and Statistics},
229
+ volume = {51},
230
+ number = {3},
231
+ pages = {247--257},
232
+ year = {1969},
233
+ publisher = {MIT Press}
234
+ }
235
+
236
+ @book{haghani2023missing,
237
+ title = {The Missing Billionaires: A Guide to Better Financial Decisions},
238
+ author = {Haghani, Victor and White, James},
239
+ year = {2023},
240
+ publisher = {Wiley}
241
+ }
242
+ ```
243
+
209
244
  ## Disclaimer
210
245
 
211
246
  This tool is for educational purposes only and does not constitute financial advice. Consult a qualified financial advisor for personalized recommendations.
@@ -18,7 +18,9 @@ Utility optimization provides a rigorous framework for finding the **optimal** s
18
18
 
19
19
  The Merton formula gives the optimal fraction to invest in risky assets:
20
20
 
21
- $$k^* = \frac{\mu - r}{\gamma \times \sigma^2}$$
21
+ $$
22
+ k^* = \frac{\mu - r}{\gamma \cdot \sigma^2}
23
+ $$
22
24
 
23
25
  Where:
24
26
 
@@ -31,13 +33,17 @@ Where:
31
33
 
32
34
  The guaranteed return that provides the same utility as the risky portfolio:
33
35
 
34
- $$r_{CE} = r + k^*(\mu - r) - \frac{\gamma \times k^{*2} \times \sigma^2}{2}$$
36
+ $$
37
+ r_{CE} = r + k^*(\mu - r) - \frac{\gamma \cdot (k^*)^2 \cdot \sigma^2}{2}
38
+ $$
35
39
 
36
40
  ### Optimal Spending Rate
37
41
 
38
42
  For an infinite horizon:
39
43
 
40
- $$c^* = r_{CE} - \frac{r_{CE} - \rho}{\gamma}$$
44
+ $$
45
+ c^* = r_{CE} - \frac{r_{CE} - \rho}{\gamma}
46
+ $$
41
47
 
42
48
  Where $\rho$ is your time preference (discount rate).
43
49
 
@@ -82,7 +88,9 @@ print(f"Year 1 spending: ${1_000_000 * result.optimal_spending_rate:,.0f}")
82
88
 
83
89
  Near the subsistence floor, you can't afford to take risk. The wealth-adjusted allocation accounts for this:
84
90
 
85
- $$k_{adj} = k^* \times \frac{W - F}{W}$$
91
+ $$
92
+ k_{adj} = k^* \cdot \frac{W - F}{W}
93
+ $$
86
94
 
87
95
  Where $W$ is wealth and $F$ is the subsistence floor.
88
96
 
@@ -0,0 +1,12 @@
1
+ window.MathJax = {
2
+ tex: {
3
+ inlineMath: [["\\(", "\\)"]],
4
+ displayMath: [["\\[", "\\]"]],
5
+ processEscapes: true,
6
+ processEnvironments: true
7
+ },
8
+ options: {
9
+ ignoreHtmlClass: ".*|",
10
+ processHtmlClass: "arithmatex"
11
+ }
12
+ };
@@ -8,7 +8,7 @@ This package provides tools for:
8
8
  - Beautiful Plotly visualizations
9
9
  """
10
10
 
11
- __version__ = "0.2.1"
11
+ __version__ = "0.2.2"
12
12
 
13
13
  from fundedness.cefr import CEFRResult, compute_cefr
14
14
  from fundedness.merton import (
@@ -139,22 +139,22 @@ class MarketModel(BaseModel):
139
139
 
140
140
  def expected_portfolio_return(
141
141
  self,
142
- stock_weight: float,
143
- bond_weight: Optional[float] = None,
144
- ) -> float:
142
+ stock_weight: float | np.ndarray,
143
+ bond_weight: float | np.ndarray | None = None,
144
+ ) -> float | np.ndarray:
145
145
  """Calculate expected return for a portfolio.
146
146
 
147
147
  Args:
148
- stock_weight: Weight in stocks (0-1)
148
+ stock_weight: Weight in stocks (0-1), scalar or array
149
149
  bond_weight: Weight in bonds (remainder is cash if not specified)
150
150
 
151
151
  Returns:
152
- Expected annual real return
152
+ Expected annual real return (scalar or array matching input)
153
153
  """
154
154
  if bond_weight is None:
155
155
  bond_weight = 1 - stock_weight
156
156
 
157
- cash_weight = max(0, 1 - stock_weight - bond_weight)
157
+ cash_weight = np.maximum(0, 1 - stock_weight - bond_weight)
158
158
 
159
159
  return (
160
160
  stock_weight * self.stock_return
@@ -164,25 +164,36 @@ class MarketModel(BaseModel):
164
164
 
165
165
  def portfolio_volatility(
166
166
  self,
167
- stock_weight: float,
168
- bond_weight: Optional[float] = None,
169
- ) -> float:
167
+ stock_weight: float | np.ndarray,
168
+ bond_weight: float | np.ndarray | None = None,
169
+ ) -> float | np.ndarray:
170
170
  """Calculate portfolio volatility.
171
171
 
172
172
  Args:
173
- stock_weight: Weight in stocks (0-1)
173
+ stock_weight: Weight in stocks (0-1), scalar or array
174
174
  bond_weight: Weight in bonds (remainder is cash if not specified)
175
175
 
176
176
  Returns:
177
- Annual portfolio volatility
177
+ Annual portfolio volatility (scalar or array matching input)
178
178
  """
179
179
  if bond_weight is None:
180
180
  bond_weight = 1 - stock_weight
181
181
 
182
- cash_weight = max(0, 1 - stock_weight - bond_weight)
183
- weights = np.array([stock_weight, bond_weight, cash_weight, 0])
182
+ cash_weight = np.maximum(0, 1 - stock_weight - bond_weight)
184
183
 
185
184
  cov = self.get_covariance_matrix()
186
- portfolio_variance = weights @ cov @ weights
187
185
 
188
- return np.sqrt(portfolio_variance)
186
+ # Handle both scalar and array inputs
187
+ if isinstance(stock_weight, np.ndarray):
188
+ # Vectorized computation for array inputs
189
+ # weights shape: (n_samples, 4)
190
+ weights = np.column_stack([stock_weight, bond_weight, cash_weight, np.zeros_like(stock_weight)])
191
+ # cov @ weights.T has shape (4, n_samples)
192
+ # We want sum of weights[i] * (cov @ weights[i]) for each i
193
+ # This is equivalent to diag(weights @ cov @ weights.T)
194
+ portfolio_variance = np.einsum('ij,jk,ik->i', weights, cov, weights)
195
+ return np.sqrt(portfolio_variance)
196
+ else:
197
+ weights = np.array([stock_weight, bond_weight, cash_weight, 0])
198
+ portfolio_variance = weights @ cov @ weights
199
+ return np.sqrt(portfolio_variance)
@@ -46,6 +46,12 @@ nav:
46
46
  markdown_extensions:
47
47
  - pymdownx.highlight
48
48
  - pymdownx.superfences
49
+ - pymdownx.arithmatex:
50
+ generic: true
49
51
  - admonition
50
52
  - toc:
51
53
  permalink: true
54
+
55
+ extra_javascript:
56
+ - javascripts/mathjax.js
57
+ - https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "fundedness"
7
- version = "0.2.1"
7
+ version = "0.2.3"
8
8
  description = "A Python financial planning toolkit with CEFR calculations, Monte Carlo simulations, and beautiful visualizations"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -10,6 +10,12 @@ st.set_page_config(
10
10
  initial_sidebar_state="expanded",
11
11
  )
12
12
 
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ # Add parent directory to path for imports when running on Streamlit Cloud
17
+ sys.path.insert(0, str(Path(__file__).parent.parent))
18
+
13
19
  from streamlit_app.utils.session_state import initialize_session_state
14
20
 
15
21
  # Initialize session state
@@ -1,5 +1,9 @@
1
1
  """Inputs page for asset, liability, and assumption entry."""
2
2
 
3
+ import sys
4
+ from pathlib import Path
5
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
6
+
3
7
  import streamlit as st
4
8
 
5
9
  from fundedness.models.household import Household, Person
@@ -1,5 +1,9 @@
1
1
  """CEFR Dashboard page."""
2
2
 
3
+ import sys
4
+ from pathlib import Path
5
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
6
+
3
7
  import streamlit as st
4
8
 
5
9
  from fundedness.cefr import compute_cefr
@@ -1,5 +1,9 @@
1
1
  """Time Runway page with Monte Carlo projections."""
2
2
 
3
+ import sys
4
+ from pathlib import Path
5
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
6
+
3
7
  import numpy as np
4
8
  import streamlit as st
5
9
 
@@ -1,5 +1,9 @@
1
1
  """Withdrawal Strategy Lab page."""
2
2
 
3
+ import sys
4
+ from pathlib import Path
5
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
6
+
3
7
  import numpy as np
4
8
  import streamlit as st
5
9
 
@@ -1,5 +1,9 @@
1
1
  """Sensitivity Analysis page."""
2
2
 
3
+ import sys
4
+ from pathlib import Path
5
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
6
+
3
7
  import streamlit as st
4
8
 
5
9
  from fundedness.cefr import compute_cefr
@@ -1,5 +1,9 @@
1
1
  """Utility Optimization page - Merton optimal spending and allocation."""
2
2
 
3
+ import sys
4
+ from pathlib import Path
5
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
6
+
3
7
  import numpy as np
4
8
  import streamlit as st
5
9
 
@@ -1,98 +0,0 @@
1
- # CLAUDE.md
2
-
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
-
5
- ## Project Overview
6
-
7
- This is a Python financial planning toolkit that implements:
8
- - **CEFR (Certainty-Equivalent Funded Ratio)**: A fundedness metric that applies after-tax, liquidity, and risk haircuts to assets
9
- - **Victor-style lifetime utility optimization**: Spending and asset allocation policies that maximize expected lifetime utility
10
- - **Withdrawal Strategy Lab**: Comparison framework for withdrawal strategies (fixed SWR, guardrails, VPW, RMD-style)
11
-
12
- The project includes both a Python package (`fundedness/`) and a Streamlit web application (`streamlit_app/`).
13
-
14
- ## Development Status
15
-
16
- This project is in the **specification/planning phase**. The `background_information.md` file contains the complete design spec. No implementation code exists yet.
17
-
18
- ## Planned Commands
19
-
20
- Once implemented, the project will use:
21
- - **Package manager**: pip
22
- - **Testing**: pytest (with property tests for monotonicity)
23
- - **Docs**: MkDocs
24
- - **CLI**: `fundedness cefr config.yaml`, `fundedness simulate config.yaml`, `fundedness policy-search config.yaml`
25
- - **Streamlit**: `streamlit run streamlit_app/app.py`
26
-
27
- ## Architecture
28
-
29
- ### Core Package Structure (`fundedness/`)
30
-
31
- ```
32
- fundedness/
33
- ├── cefr.py # CEFR ratio calculation with haircut breakdowns
34
- ├── liabilities.py # Liability PV calculations with schedules
35
- ├── taxes.py # Tax modeling by account type
36
- ├── liquidity.py # Liquidity factor adjustments
37
- ├── risk.py # Reliability/concentration haircuts
38
- ├── markets.py # Return/covariance/inflation assumptions
39
- ├── simulate.py # Monte Carlo engine (time-to-floor, time-to-ruin)
40
- ├── utility.py # CRRA utility with subsistence floor
41
- ├── policies.py # Spending/allocation policy interface
42
- ├── optimize.py # Parametric policy search (v0.4+)
43
- ├── withdrawals/ # Withdrawal strategy implementations
44
- ├── allocation/ # Glidepath strategies (constant, rising equity, bucket)
45
- └── tax/strategy.py # Tax-aware withdrawal sequencing
46
- ```
47
-
48
- ### Data Models (Pydantic)
49
-
50
- Key models: `Household`, `BalanceSheet`, `Asset`, `Liability`, `MarketModel`, `TaxModel`, `UtilityModel`, `SimulationConfig`
51
-
52
- ### Core Formulas
53
-
54
- **CEFR Calculation:**
55
- ```
56
- CEFR = Σ(Asset × (1-tax_rate) × liquidity_factor × reliability_factor) / PV(Liabilities)
57
- ```
58
-
59
- **CRRA Utility with Floor:**
60
- ```
61
- u(C) = (C - F)^(1-γ) / (1-γ) where C > F (subsistence floor)
62
- ```
63
-
64
- ## Development Tiers
65
-
66
- 1. **MVP (v0.1-0.3)**: CEFR + Monte Carlo runway with P10/P50/P90 bands
67
- 2. **v0.4-0.7**: Victor-style parametric policy search
68
- 3. **v1.0+**: Tax-aware account flows, Roth conversions
69
-
70
- ## Key Concepts
71
-
72
- - **Haircuts**: Three adjustments to assets—after-tax (τ), liquidity (λ), reliability (ρ)
73
- - **Floor/Flex spending**: Essential spending floor vs adjustable discretionary
74
- - **Time metrics**: Time-to-floor-breach, time-to-ruin, max spending drawdown
75
- - **Confidence intervals**: Scenario percentiles (P10/P50/P90), not statistical CIs
76
-
77
- ## Default Haircut Assumptions
78
-
79
- Liquidity: cash=1.0, taxable_index=0.95, retirement=0.85, home_equity=0.5, private_business=0.3
80
- Reliability: diversified_bonds=0.95, diversified_equity=0.85, single_stock=0.60, startup=0.30
81
-
82
- ## Deployment Preferences
83
-
84
- - **Web App**: Deploy on Streamlit Cloud (free tier)
85
- - **API**: Expose core functionality via a REST API (FastAPI recommended) for programmatic access
86
- - **Tutorials**: Create Jupyter notebooks in `examples/` that run in Google Colab. Include working "Open in Colab" badge links in the README using the format:
87
- ```
88
- [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/engineerinvestor/financial-health-calculator/blob/main/examples/NOTEBOOK.ipynb)
89
- ```
90
-
91
- ## Visualization Standards
92
-
93
- Use **Plotly** as the primary visualization library for beautiful, interactive charts:
94
- - Clean, professional appearance with `template="plotly_white"`
95
- - Interactive features: hover tooltips, zoom, pan
96
- - Consistent color palette: blues (#3498db, #2980b9) for wealth, greens (#27ae60, #2ecc71) for spending/survival
97
- - Fan charts with gradient opacity for percentile bands (P10-P90)
98
- - Export capability to HTML/PNG for reports
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