fundedness 0.2.2__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.2 → fundedness-0.2.3}/PKG-INFO +2 -1
- {fundedness-0.2.2 → fundedness-0.2.3}/README.md +1 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/docs/guide/utility-optimization.md +14 -14
- {fundedness-0.2.2 → fundedness-0.2.3}/docs/javascripts/mathjax.js +0 -7
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/models/market.py +26 -15
- {fundedness-0.2.2 → fundedness-0.2.3}/pyproject.toml +1 -1
- {fundedness-0.2.2 → fundedness-0.2.3}/streamlit_app/app.py +6 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/streamlit_app/pages/0_Inputs.py +4 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/streamlit_app/pages/1_CEFR_Dashboard.py +4 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/streamlit_app/pages/2_Time_Runway.py +4 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/streamlit_app/pages/3_Withdrawal_Lab.py +4 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/streamlit_app/pages/4_Sensitivity.py +4 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/streamlit_app/pages/5_Utility_Optimization.py +4 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/.github/workflows/docs.yml +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/.github/workflows/publish.yml +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/.gitignore +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/CLAUDE.md +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/api/__init__.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/api/main.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/api/routes/__init__.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/api/routes/cefr.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/api/routes/compare.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/api/routes/simulate.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/background_information.md +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/docs/api/core.md +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/docs/api/merton.md +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/docs/api/models.md +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/docs/api/viz.md +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/docs/api/withdrawals.md +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/docs/examples/tutorials.md +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/docs/getting-started/installation.md +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/docs/getting-started/quickstart.md +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/docs/guide/cefr.md +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/docs/guide/simulations.md +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/docs/guide/visualizations.md +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/docs/guide/withdrawals.md +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/docs/index.md +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/examples/01_cefr_basics.ipynb +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/examples/02_time_distribution.ipynb +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/examples/03_withdrawal_comparison.ipynb +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/__init__.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/allocation/__init__.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/allocation/base.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/allocation/constant.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/allocation/glidepath.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/allocation/merton_optimal.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/cefr.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/liabilities.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/liquidity.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/merton.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/models/__init__.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/models/assets.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/models/household.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/models/liabilities.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/models/simulation.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/models/tax.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/models/utility.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/optimize.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/policies.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/risk.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/simulate.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/viz/__init__.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/viz/colors.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/viz/comparison.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/viz/fan_chart.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/viz/histogram.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/viz/optimal.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/viz/survival.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/viz/tornado.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/viz/waterfall.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/withdrawals/__init__.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/withdrawals/base.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/withdrawals/comparison.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/withdrawals/fixed_swr.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/withdrawals/guardrails.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/withdrawals/merton_optimal.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/withdrawals/rmd_style.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/fundedness/withdrawals/vpw.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/mkdocs.yml +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/requirements.txt +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/streamlit_app/__init__.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/streamlit_app/components/__init__.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/streamlit_app/components/asset_editor.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/streamlit_app/components/liability_editor.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/streamlit_app/components/metrics_display.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/streamlit_app/utils/__init__.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/streamlit_app/utils/session_state.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/tests/__init__.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/tests/conftest.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/tests/test_api.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/tests/test_cefr.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/tests/test_liabilities.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/tests/test_merton.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/tests/test_optimize.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/tests/test_simulate.py +0 -0
- {fundedness-0.2.2 → fundedness-0.2.3}/tests/test_withdrawals.py +0 -0
|
@@ -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/
|
|
@@ -57,6 +57,7 @@ Description-Content-Type: text/markdown
|
|
|
57
57
|
[](https://pypi.org/project/fundedness/)
|
|
58
58
|
[](https://pypi.org/project/fundedness/)
|
|
59
59
|
[](https://engineerinvestor.github.io/financial-health-calculator/)
|
|
60
|
+
[](https://financial-health-calculator.streamlit.app/)
|
|
60
61
|
[](https://opensource.org/licenses/MIT)
|
|
61
62
|
[](https://colab.research.google.com/github/engineerinvestor/financial-health-calculator/blob/main/examples/01_cefr_basics.ipynb)
|
|
62
63
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[](https://pypi.org/project/fundedness/)
|
|
4
4
|
[](https://pypi.org/project/fundedness/)
|
|
5
5
|
[](https://engineerinvestor.github.io/financial-health-calculator/)
|
|
6
|
+
[](https://financial-health-calculator.streamlit.app/)
|
|
6
7
|
[](https://opensource.org/licenses/MIT)
|
|
7
8
|
[](https://colab.research.google.com/github/engineerinvestor/financial-health-calculator/blob/main/examples/01_cefr_basics.ipynb)
|
|
8
9
|
|
|
@@ -18,34 +18,34 @@ 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
22
|
k^* = \frac{\mu - r}{\gamma \cdot \sigma^2}
|
|
23
|
-
|
|
23
|
+
$$
|
|
24
24
|
|
|
25
25
|
Where:
|
|
26
26
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
27
|
+
- $\mu$ = expected stock return
|
|
28
|
+
- $r$ = bond/risk-free return
|
|
29
|
+
- $\gamma$ = risk aversion coefficient
|
|
30
|
+
- $\sigma$ = stock volatility
|
|
31
31
|
|
|
32
32
|
### Certainty Equivalent Return
|
|
33
33
|
|
|
34
34
|
The guaranteed return that provides the same utility as the risky portfolio:
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
$$
|
|
37
37
|
r_{CE} = r + k^*(\mu - r) - \frac{\gamma \cdot (k^*)^2 \cdot \sigma^2}{2}
|
|
38
|
-
|
|
38
|
+
$$
|
|
39
39
|
|
|
40
40
|
### Optimal Spending Rate
|
|
41
41
|
|
|
42
42
|
For an infinite horizon:
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
$$
|
|
45
45
|
c^* = r_{CE} - \frac{r_{CE} - \rho}{\gamma}
|
|
46
|
-
|
|
46
|
+
$$
|
|
47
47
|
|
|
48
|
-
Where
|
|
48
|
+
Where $\rho$ is your time preference (discount rate).
|
|
49
49
|
|
|
50
50
|
## Quick Start
|
|
51
51
|
|
|
@@ -88,11 +88,11 @@ print(f"Year 1 spending: ${1_000_000 * result.optimal_spending_rate:,.0f}")
|
|
|
88
88
|
|
|
89
89
|
Near the subsistence floor, you can't afford to take risk. The wealth-adjusted allocation accounts for this:
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
$$
|
|
92
92
|
k_{adj} = k^* \cdot \frac{W - F}{W}
|
|
93
|
-
|
|
93
|
+
$$
|
|
94
94
|
|
|
95
|
-
Where
|
|
95
|
+
Where $W$ is wealth and $F$ is the subsistence floor.
|
|
96
96
|
|
|
97
97
|
As wealth approaches the floor, allocation approaches zero. As wealth rises far above the floor, allocation approaches the unconstrained optimal.
|
|
98
98
|
|
|
@@ -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)
|
|
@@ -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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|