QuantumLearner commited on
Commit
e140149
·
verified ·
1 Parent(s): 118acd7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +90 -31
app.py CHANGED
@@ -382,52 +382,111 @@ if run:
382
 
383
  prog.progress(85, text="Computing interpretation...")
384
 
385
- # Dynamic Interpretation (same logic as raw)
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
- interp = ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
395
  if current_phase == 'Early':
396
- interp = (
397
- f"As of {current_date.date()}, the composite is {current_comp:.2f} "
398
- f"(+{current_slope:.3f} over {int(slope_window)}m) and S&P YoY is {current_spx_yoy:.1f}%. "
399
- "Early-phase conditions indicate nascent expansion. "
400
- "Leading activity indicators have bottomed and credit spreads are narrowing. "
401
- "Selective exposure to cyclical sectors may capture emerging growth while risks remain contained."
402
- )
 
 
403
  elif current_phase == 'Mid-Late':
404
- interp = (
405
- f"As of {current_date.date()}, the composite stands at {current_comp:.2f} "
406
- f"(slope {current_slope:.3f}) with S&P YoY {current_spx_yoy:.1f}%. "
407
- "Mid-to-late cycle signals peak growth. "
408
- "Inflationary pressures and monetary tightening typically intensify in this phase. "
409
- "Shift allocation toward high-quality equities and defensive sectors to protect gains."
410
- )
 
 
411
  elif current_phase == 'Decline':
412
- interp = (
413
- f"As of {current_date.date()}, the composite reads {current_comp:.2f} "
414
- f"(slope {current_slope:.3f}) and S&P YoY is {current_spx_yoy:.1f}%. "
415
- "Decline-phase patterns signal contraction. "
416
- "Risk sentiment deteriorates and liquidity tightens. "
417
- "Consider de-risking portfolios: increase fixed income, cash buffers, and low-volatility assets."
418
- )
 
 
419
  elif current_phase == 'Uncertain':
420
- interp = (
421
- f"As of {current_date.date()}, the composite is neutral at {current_comp:.2f} "
422
- f"(slope {current_slope:.3f}) with S&P YoY {current_spx_yoy:.1f}%. "
423
- "Signals conflict and volatility often rises. "
424
- "Maintain balanced allocations, await clearer directional cues, and manage risk with disciplined stops."
425
- )
 
 
 
 
426
  else:
427
- interp = "Phase classification unavailable."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
428
 
429
  with st.expander("Dynamic Interpretation", expanded=False):
430
- st.write(interp)
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