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.
- fundedness-0.2.3/.streamlit/config.toml +11 -0
- fundedness-0.2.3/CLAUDE.md +169 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/PKG-INFO +38 -3
- {fundedness-0.2.1 → fundedness-0.2.3}/README.md +37 -2
- {fundedness-0.2.1 → fundedness-0.2.3}/docs/guide/utility-optimization.md +12 -4
- fundedness-0.2.3/docs/javascripts/mathjax.js +12 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/__init__.py +1 -1
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/models/market.py +26 -15
- {fundedness-0.2.1 → fundedness-0.2.3}/mkdocs.yml +6 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/pyproject.toml +1 -1
- {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/app.py +6 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/pages/0_Inputs.py +4 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/pages/1_CEFR_Dashboard.py +4 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/pages/2_Time_Runway.py +4 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/pages/3_Withdrawal_Lab.py +4 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/pages/4_Sensitivity.py +4 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/pages/5_Utility_Optimization.py +4 -0
- fundedness-0.2.1/CLAUDE.md +0 -98
- {fundedness-0.2.1 → fundedness-0.2.3}/.github/workflows/docs.yml +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/.github/workflows/publish.yml +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/.gitignore +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/api/__init__.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/api/main.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/api/routes/__init__.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/api/routes/cefr.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/api/routes/compare.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/api/routes/simulate.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/background_information.md +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/docs/api/core.md +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/docs/api/merton.md +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/docs/api/models.md +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/docs/api/viz.md +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/docs/api/withdrawals.md +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/docs/examples/tutorials.md +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/docs/getting-started/installation.md +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/docs/getting-started/quickstart.md +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/docs/guide/cefr.md +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/docs/guide/simulations.md +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/docs/guide/visualizations.md +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/docs/guide/withdrawals.md +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/docs/index.md +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/examples/01_cefr_basics.ipynb +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/examples/02_time_distribution.ipynb +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/examples/03_withdrawal_comparison.ipynb +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/allocation/__init__.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/allocation/base.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/allocation/constant.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/allocation/glidepath.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/allocation/merton_optimal.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/cefr.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/liabilities.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/liquidity.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/merton.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/models/__init__.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/models/assets.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/models/household.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/models/liabilities.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/models/simulation.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/models/tax.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/models/utility.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/optimize.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/policies.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/risk.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/simulate.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/viz/__init__.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/viz/colors.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/viz/comparison.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/viz/fan_chart.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/viz/histogram.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/viz/optimal.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/viz/survival.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/viz/tornado.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/viz/waterfall.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/withdrawals/__init__.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/withdrawals/base.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/withdrawals/comparison.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/withdrawals/fixed_swr.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/withdrawals/guardrails.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/withdrawals/merton_optimal.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/withdrawals/rmd_style.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/fundedness/withdrawals/vpw.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/requirements.txt +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/__init__.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/components/__init__.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/components/asset_editor.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/components/liability_editor.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/components/metrics_display.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/utils/__init__.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/streamlit_app/utils/session_state.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/tests/__init__.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/tests/conftest.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/tests/test_api.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/tests/test_cefr.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/tests/test_liabilities.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/tests/test_merton.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/tests/test_optimize.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/tests/test_simulate.py +0 -0
- {fundedness-0.2.1 → fundedness-0.2.3}/tests/test_withdrawals.py +0 -0
|
@@ -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.
|
|
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
|
[](https://pypi.org/project/fundedness/)
|
|
58
58
|
[](https://pypi.org/project/fundedness/)
|
|
59
|
-
[](https://github.com/engineerinvestor/financial-health-calculator/actions/workflows/ci.yml)
|
|
60
|
-
[](https://codecov.io/gh/engineerinvestor/financial-health-calculator)
|
|
61
59
|
[](https://engineerinvestor.github.io/financial-health-calculator/)
|
|
60
|
+
[](https://financial-health-calculator.streamlit.app/)
|
|
62
61
|
[](https://opensource.org/licenses/MIT)
|
|
63
62
|
[](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
|
[](https://pypi.org/project/fundedness/)
|
|
4
4
|
[](https://pypi.org/project/fundedness/)
|
|
5
|
-
[](https://github.com/engineerinvestor/financial-health-calculator/actions/workflows/ci.yml)
|
|
6
|
-
[](https://codecov.io/gh/engineerinvestor/financial-health-calculator)
|
|
7
5
|
[](https://engineerinvestor.github.io/financial-health-calculator/)
|
|
6
|
+
[](https://financial-health-calculator.streamlit.app/)
|
|
8
7
|
[](https://opensource.org/licenses/MIT)
|
|
9
8
|
[](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
|
-
$$
|
|
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
|
-
$$
|
|
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
|
-
$$
|
|
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
|
-
$$
|
|
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
|
|
|
@@ -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:
|
|
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 =
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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
|
fundedness-0.2.1/CLAUDE.md
DELETED
|
@@ -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
|
-
[](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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|