@@ -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 )
313313col1 .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 --------------------------------------------
400463with tab2 :
0 commit comments