Update app.py
Browse files
app.py
CHANGED
|
@@ -360,24 +360,17 @@ def suggest_one_per_band(synth: pd.DataFrame, sigma_mkt: float, universe_user: L
|
|
| 360 |
for band in ["Low", "Medium", "High"]:
|
| 361 |
lo, hi = _band_bounds(sigma_mkt, band)
|
| 362 |
pool = synth[(synth["sigma_hist"] >= lo) & (synth["sigma_hist"] <= hi)].copy()
|
| 363 |
-
note = ""
|
| 364 |
if pool.empty:
|
| 365 |
-
#
|
| 366 |
if band.lower() == "low":
|
| 367 |
pool = synth.nsmallest(50, "sigma_hist").copy()
|
| 368 |
-
note = " (closest available: min σ)"
|
| 369 |
elif band.lower() == "high":
|
| 370 |
pool = synth.nlargest(50, "sigma_hist").copy()
|
| 371 |
-
note = " (closest available: max σ)"
|
| 372 |
else: # medium
|
| 373 |
tmp = synth.copy()
|
| 374 |
tmp["dist_med"] = (tmp["sigma_hist"] - sigma_mkt).abs()
|
| 375 |
pool = tmp.nsmallest(100, "dist_med").drop(columns=["dist_med"])
|
| 376 |
-
note = " (closest to market σ)"
|
| 377 |
chosen = rerank_and_pick_one(pool, universe_user, band)
|
| 378 |
-
if not chosen.empty:
|
| 379 |
-
chosen = chosen.copy()
|
| 380 |
-
chosen["__note__"] = note
|
| 381 |
out[band.lower()] = chosen
|
| 382 |
return out
|
| 383 |
|
|
@@ -517,8 +510,7 @@ def compute(
|
|
| 517 |
def _fmt(row: pd.Series) -> str:
|
| 518 |
if row is None or row.empty:
|
| 519 |
return "No pick available."
|
| 520 |
-
|
| 521 |
-
return f"CAPM E[r] {row['mu_capm']*100:.2f}%, σ(h) {row['sigma_hist']*100:.2f}%{note}"
|
| 522 |
|
| 523 |
txt_low = _fmt(picks.get("low", pd.Series(dtype=object)))
|
| 524 |
txt_med = _fmt(picks.get("medium", pd.Series(dtype=object)))
|
|
@@ -553,7 +545,7 @@ def compute(
|
|
| 553 |
sugg_sigma_hist=chosen_sigma, sugg_mu_capm=chosen_mu
|
| 554 |
)
|
| 555 |
|
| 556 |
-
# ----------
|
| 557 |
info = "\n".join([
|
| 558 |
"### Inputs",
|
| 559 |
f"- Lookback years {years_lookback}",
|
|
@@ -562,7 +554,7 @@ def compute(
|
|
| 562 |
f"- Market ERP {erp_ann:.2%}",
|
| 563 |
f"- Market σ (hist) {sigma_mkt:.2%}",
|
| 564 |
"",
|
| 565 |
-
"### Your portfolio
|
| 566 |
f"- CAPM E[r] {mu_capm:.2%}",
|
| 567 |
f"- σ (historical) {sigma_hist:.2%}",
|
| 568 |
"",
|
|
@@ -603,7 +595,7 @@ with gr.Blocks(title="Efficient Portfolio Advisor") as demo:
|
|
| 603 |
search_btn = gr.Button("Search")
|
| 604 |
add_btn = gr.Button("Add selected to portfolio")
|
| 605 |
|
| 606 |
-
gr.Markdown("### Portfolio positions
|
| 607 |
table = gr.Dataframe(
|
| 608 |
headers=["ticker", "amount_usd"],
|
| 609 |
datatype=["str", "number"],
|
|
@@ -614,7 +606,7 @@ with gr.Blocks(title="Efficient Portfolio Advisor") as demo:
|
|
| 614 |
horizon = gr.Number(label="Horizon in years (1–100)", value=HORIZON_YEARS, precision=0)
|
| 615 |
lookback = gr.Slider(1, 15, value=DEFAULT_LOOKBACK_YEARS, step=1, label="Lookback years for betas & covariances")
|
| 616 |
|
| 617 |
-
gr.Markdown("### Suggestions
|
| 618 |
with gr.Row():
|
| 619 |
btn_low = gr.Button("Show Low")
|
| 620 |
btn_med = gr.Button("Show Medium")
|
|
|
|
| 360 |
for band in ["Low", "Medium", "High"]:
|
| 361 |
lo, hi = _band_bounds(sigma_mkt, band)
|
| 362 |
pool = synth[(synth["sigma_hist"] >= lo) & (synth["sigma_hist"] <= hi)].copy()
|
|
|
|
| 363 |
if pool.empty:
|
| 364 |
+
# choose reasonable substitutes quietly (no extra text)
|
| 365 |
if band.lower() == "low":
|
| 366 |
pool = synth.nsmallest(50, "sigma_hist").copy()
|
|
|
|
| 367 |
elif band.lower() == "high":
|
| 368 |
pool = synth.nlargest(50, "sigma_hist").copy()
|
|
|
|
| 369 |
else: # medium
|
| 370 |
tmp = synth.copy()
|
| 371 |
tmp["dist_med"] = (tmp["sigma_hist"] - sigma_mkt).abs()
|
| 372 |
pool = tmp.nsmallest(100, "dist_med").drop(columns=["dist_med"])
|
|
|
|
| 373 |
chosen = rerank_and_pick_one(pool, universe_user, band)
|
|
|
|
|
|
|
|
|
|
| 374 |
out[band.lower()] = chosen
|
| 375 |
return out
|
| 376 |
|
|
|
|
| 510 |
def _fmt(row: pd.Series) -> str:
|
| 511 |
if row is None or row.empty:
|
| 512 |
return "No pick available."
|
| 513 |
+
return f"CAPM E[r] {row['mu_capm']*100:.2f}%, σ(h) {row['sigma_hist']*100:.2f}%"
|
|
|
|
| 514 |
|
| 515 |
txt_low = _fmt(picks.get("low", pd.Series(dtype=object)))
|
| 516 |
txt_med = _fmt(picks.get("medium", pd.Series(dtype=object)))
|
|
|
|
| 545 |
sugg_sigma_hist=chosen_sigma, sugg_mu_capm=chosen_mu
|
| 546 |
)
|
| 547 |
|
| 548 |
+
# ---------- summary text (cosmetic-only changes made) ----------
|
| 549 |
info = "\n".join([
|
| 550 |
"### Inputs",
|
| 551 |
f"- Lookback years {years_lookback}",
|
|
|
|
| 554 |
f"- Market ERP {erp_ann:.2%}",
|
| 555 |
f"- Market σ (hist) {sigma_mkt:.2%}",
|
| 556 |
"",
|
| 557 |
+
"### Your portfolio",
|
| 558 |
f"- CAPM E[r] {mu_capm:.2%}",
|
| 559 |
f"- σ (historical) {sigma_hist:.2%}",
|
| 560 |
"",
|
|
|
|
| 595 |
search_btn = gr.Button("Search")
|
| 596 |
add_btn = gr.Button("Add selected to portfolio")
|
| 597 |
|
| 598 |
+
gr.Markdown("### Portfolio positions")
|
| 599 |
table = gr.Dataframe(
|
| 600 |
headers=["ticker", "amount_usd"],
|
| 601 |
datatype=["str", "number"],
|
|
|
|
| 606 |
horizon = gr.Number(label="Horizon in years (1–100)", value=HORIZON_YEARS, precision=0)
|
| 607 |
lookback = gr.Slider(1, 15, value=DEFAULT_LOOKBACK_YEARS, step=1, label="Lookback years for betas & covariances")
|
| 608 |
|
| 609 |
+
gr.Markdown("### Suggestions")
|
| 610 |
with gr.Row():
|
| 611 |
btn_low = gr.Button("Show Low")
|
| 612 |
btn_med = gr.Button("Show Medium")
|