Skip to content

Commit 78e711f

Browse files
Show Merton ratio, clamping explanation, and sensitivity heatmap
Market assumption sliders appeared non-functional because clamping hid their effect. Add a Merton ratio metric that responds directly to slider changes, an info box explaining when clamping is active, and a sensitivity heatmap showing allocation across equity premiums and volatility levels. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 48923a1 commit 78e711f

1 file changed

Lines changed: 66 additions & 3 deletions

File tree

streamlit_app/app.py

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,10 +309,22 @@ def _compute_strategies(
309309
"wealth, job stability, and risk tolerance."
310310
)
311311

312-
col1, col2, col3 = st.columns(3)
312+
col1, col2, col3, col4 = st.columns(4)
313313
col1.metric("Recommended Equity", f"{result.alpha_recommended:.0%}")
314-
col2.metric("Human Capital", f"${h:,.0f}")
315-
col3.metric("H/W Ratio", f"{hw_ratio:.1f}x")
314+
col2.metric("Merton Ratio (\u03b1*)", f"{result.alpha_star:.0%}")
315+
col3.metric("Human Capital", f"${h:,.0f}")
316+
col4.metric("H/W Ratio", f"{hw_ratio:.1f}x")
317+
318+
if result.alpha_unconstrained > 1.0:
319+
st.info(
320+
f"Your unconstrained allocation is {result.alpha_unconstrained:.0%}, "
321+
f"capped at 100%. Your human capital is so large relative to your "
322+
f"financial wealth (H/W = {hw_ratio:.1f}x) that even a modest Merton "
323+
f"ratio ({result.alpha_star:.0%}) produces a recommendation above 100%. "
324+
f"Market assumption changes affect the Merton ratio but may not change "
325+
f"the final recommendation until your H/W ratio decreases (as you age "
326+
f"and save more)."
327+
)
316328

317329

318330
# ---------------------------------------------------------------------------
@@ -395,6 +407,57 @@ def _compute_strategies(
395407
with st.expander("Detailed Explanation"):
396408
st.text(result.explain)
397409

410+
# Market assumptions sensitivity heatmap
411+
st.subheader("Market Assumptions Sensitivity")
412+
st.markdown(
413+
"This heatmap shows how your recommended equity allocation changes "
414+
"across different equity premiums and volatility levels. Each cell "
415+
"uses your current profile inputs."
416+
)
417+
418+
eq_premiums = np.arange(0.01, 0.085, 0.01)
419+
sigmas_range = np.arange(0.10, 0.32, 0.02)
420+
heat_z: list[list[float]] = []
421+
for s_val in sigmas_range:
422+
row: list[float] = []
423+
for ep in eq_premiums:
424+
mu_val = r + ep
425+
r_cell = _compute_allocation(
426+
age,
427+
retirement_age,
428+
income,
429+
wealth,
430+
risk_tolerance,
431+
beta,
432+
float(mu_val),
433+
r,
434+
float(s_val),
435+
income_growth,
436+
)
437+
row.append(r_cell.alpha_recommended)
438+
heat_z.append(row)
439+
440+
heat_fig = go.Figure(
441+
go.Heatmap(
442+
z=heat_z,
443+
x=[f"{ep:.0%}" for ep in eq_premiums],
444+
y=[f"{s:.0%}" for s in sigmas_range],
445+
colorscale="RdYlGn",
446+
zmin=0,
447+
zmax=1,
448+
text=[[f"{v:.0%}" for v in row] for row in heat_z],
449+
texttemplate="%{text}",
450+
colorbar=dict(title="Equity %", tickformat=".0%"),
451+
)
452+
)
453+
heat_fig.update_layout(
454+
xaxis_title="Equity Premium (mu \u2212 r)",
455+
yaxis_title="Volatility (\u03c3)",
456+
height=420,
457+
margin=dict(t=20, b=40),
458+
)
459+
st.plotly_chart(heat_fig, use_container_width=True)
460+
398461

399462
# ---- Tab 2: How Your Job Matters --------------------------------------------
400463
with tab2:

0 commit comments

Comments
 (0)