Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -382,52 +382,111 @@ if run:
|
|
| 382 |
|
| 383 |
prog.progress(85, text="Computing interpretation...")
|
| 384 |
|
| 385 |
-
# Dynamic Interpretation (
|
| 386 |
try:
|
| 387 |
phase_series = pd.Series(phase, index=comp.index)
|
| 388 |
current_date = phase_series.index[-1]
|
| 389 |
current_phase = phase_series.iloc[-1]
|
| 390 |
current_comp = comp_sm.loc[current_date]
|
|
|
|
| 391 |
current_slope = slope.loc[current_date]
|
| 392 |
current_spx_yoy = spx_yoy.loc[current_date]
|
| 393 |
|
| 394 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 395 |
if current_phase == 'Early':
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
"
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
|
|
|
|
|
|
| 403 |
elif current_phase == 'Mid-Late':
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
"
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
|
|
|
|
|
|
| 411 |
elif current_phase == 'Decline':
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
"
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
|
|
|
|
|
|
| 419 |
elif current_phase == 'Uncertain':
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
"
|
| 424 |
-
|
| 425 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 426 |
else:
|
| 427 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 428 |
|
| 429 |
with st.expander("Dynamic Interpretation", expanded=False):
|
| 430 |
-
st.
|
| 431 |
except Exception:
|
| 432 |
st.error("Failed to produce the interpretation.")
|
| 433 |
|
|
|
|
| 382 |
|
| 383 |
prog.progress(85, text="Computing interpretation...")
|
| 384 |
|
| 385 |
+
# Dynamic Interpretation (richer, more explanatory)
|
| 386 |
try:
|
| 387 |
phase_series = pd.Series(phase, index=comp.index)
|
| 388 |
current_date = phase_series.index[-1]
|
| 389 |
current_phase = phase_series.iloc[-1]
|
| 390 |
current_comp = comp_sm.loc[current_date]
|
| 391 |
+
current_comp_raw = comp.loc[current_date]
|
| 392 |
current_slope = slope.loc[current_date]
|
| 393 |
current_spx_yoy = spx_yoy.loc[current_date]
|
| 394 |
|
| 395 |
+
# Percentiles (rank-based, full sample)
|
| 396 |
+
comp_pct = float(comp_sm.dropna().rank(pct=True).loc[current_date] * 100)
|
| 397 |
+
spx_pct = float(spx_yoy.dropna().rank(pct=True).loc[current_date] * 100)
|
| 398 |
+
|
| 399 |
+
# Phase run length (periods and months)
|
| 400 |
+
changes = phase_series.ne(phase_series.shift())
|
| 401 |
+
last_change_idx = changes[changes].index[-1]
|
| 402 |
+
periods_in_phase = phase_series.index.get_loc(current_date) - phase_series.index.get_loc(last_change_idx) + 1
|
| 403 |
+
if freq == "QE":
|
| 404 |
+
months_in_phase = periods_in_phase * 3
|
| 405 |
+
period_label = "quarters"
|
| 406 |
+
else:
|
| 407 |
+
months_in_phase = periods_in_phase
|
| 408 |
+
period_label = "months"
|
| 409 |
+
|
| 410 |
+
# Breadth and contributors
|
| 411 |
+
z_now = z.loc[current_date].dropna()
|
| 412 |
+
inputs_now = z_now.reindex(inputs).dropna()
|
| 413 |
+
breadth_pos = float((inputs_now > 0).mean() * 100)
|
| 414 |
+
top_pos = inputs_now.sort_values(ascending=False).head(3)
|
| 415 |
+
top_neg = inputs_now.sort_values(ascending=True).head(3)
|
| 416 |
+
|
| 417 |
+
def fmt_contrib(s):
|
| 418 |
+
return ", ".join([f"{k} ({v:+.1f}σ)" for k, v in s.items()]) if len(s) else "n/a"
|
| 419 |
+
|
| 420 |
+
# Phase-specific read-through and triggers
|
| 421 |
+
phase_notes = []
|
| 422 |
+
triggers = []
|
| 423 |
+
|
| 424 |
if current_phase == 'Early':
|
| 425 |
+
phase_notes += [
|
| 426 |
+
"Growth is turning up from a weak base.",
|
| 427 |
+
"Leading activity improves. Credit spreads narrow.",
|
| 428 |
+
"Rate momentum eases on a 12-month view."
|
| 429 |
+
]
|
| 430 |
+
triggers += [
|
| 431 |
+
f"Upside: composite raw > {comp_thr:.2f}.",
|
| 432 |
+
f"Risk: slope < -{slope_thr:.3f} or composite raw > {-comp_thr:.2f}."
|
| 433 |
+
]
|
| 434 |
elif current_phase == 'Mid-Late':
|
| 435 |
+
phase_notes += [
|
| 436 |
+
"Growth remains above average but is slowing.",
|
| 437 |
+
"Pricing pressure and policy tightness rise.",
|
| 438 |
+
"Quality bias tends to help risk control."
|
| 439 |
+
]
|
| 440 |
+
triggers += [
|
| 441 |
+
f"Loss of momentum: composite raw < {comp_thr:.2f}.",
|
| 442 |
+
f"Downside break: composite raw < {-comp_thr:.2f}."
|
| 443 |
+
]
|
| 444 |
elif current_phase == 'Decline':
|
| 445 |
+
phase_notes += [
|
| 446 |
+
"Activity contracts. Risk appetite weakens.",
|
| 447 |
+
"Credit and liquidity conditions worsen.",
|
| 448 |
+
"Drawdown risk is elevated relative to trend."
|
| 449 |
+
]
|
| 450 |
+
triggers += [
|
| 451 |
+
f"Repair: slope > +{slope_thr:.3f}.",
|
| 452 |
+
f"Exit contraction: composite raw > {-comp_thr:.2f}."
|
| 453 |
+
]
|
| 454 |
elif current_phase == 'Uncertain':
|
| 455 |
+
phase_notes += [
|
| 456 |
+
"Signals conflict. Noise is high.",
|
| 457 |
+
"Avoid strong tilts until direction clears.",
|
| 458 |
+
"Use position sizing and stops."
|
| 459 |
+
]
|
| 460 |
+
triggers += [
|
| 461 |
+
f"Upside regime: composite raw > {comp_thr:.2f}.",
|
| 462 |
+
f"Early setup: composite raw < {-comp_thr:.2f} and slope > +{slope_thr:.3f}.",
|
| 463 |
+
f"Decline setup: composite raw < {-comp_thr:.2f} and slope < -{slope_thr:.3f}."
|
| 464 |
+
]
|
| 465 |
else:
|
| 466 |
+
phase_notes.append("Phase classification unavailable.")
|
| 467 |
+
|
| 468 |
+
# Build markdown
|
| 469 |
+
interp_md = f"""
|
| 470 |
+
**As of {current_date.date()}**
|
| 471 |
+
|
| 472 |
+
- Phase: **{current_phase}** for {periods_in_phase} {period_label} (~{months_in_phase} months).
|
| 473 |
+
- Composite (smoothed): {current_comp:.2f} (p{comp_pct:.0f}). Raw: {current_comp_raw:.2f}.
|
| 474 |
+
- Slope over {int(slope_window)}m: {current_slope:+.3f}.
|
| 475 |
+
- S&P 500 YoY: {current_spx_yoy:.1f}% (p{spx_pct:.0f}).
|
| 476 |
+
- Breadth: {breadth_pos:.0f}% of inputs > 0.
|
| 477 |
+
|
| 478 |
+
**What drives the score now**
|
| 479 |
+
- Positive: {fmt_contrib(top_pos)}
|
| 480 |
+
- Negative: {fmt_contrib(top_neg)}
|
| 481 |
+
|
| 482 |
+
**Phase read-through**
|
| 483 |
+
""" + "\n".join([f"- {line}" for line in phase_notes]) + """
|
| 484 |
+
|
| 485 |
+
**Triggers to watch**
|
| 486 |
+
""" + "\n".join([f"- {t}" for t in triggers])
|
| 487 |
|
| 488 |
with st.expander("Dynamic Interpretation", expanded=False):
|
| 489 |
+
st.markdown(interp_md)
|
| 490 |
except Exception:
|
| 491 |
st.error("Failed to produce the interpretation.")
|
| 492 |
|