P2SAMAPA commited on
Commit
cedae60
·
unverified ·
1 Parent(s): accc7f8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +89 -32
app.py CHANGED
@@ -72,17 +72,40 @@ def load_training_meta():
72
  return None
73
 
74
 
75
- @st.cache_data(ttl=300)
76
- def load_sweep_signals(year: int):
77
- """Load cached sweep signals for a given year. Returns None if not yet trained."""
 
 
 
 
 
 
 
 
 
78
  try:
79
- from huggingface_hub import hf_hub_download
80
- path = hf_hub_download(repo_id=HF_OUTPUT_REPO, filename=f"signals_{year}.json",
81
  repo_type="dataset", force_download=True)
82
  with open(path) as f:
83
- return json.load(f)
84
  except Exception:
85
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
 
88
  # ── GitHub Actions helpers ────────────────────────────────────────────────────
@@ -561,26 +584,58 @@ with tab2:
561
  "**Score:** 40% Return · 20% Z · 20% Sharpe · 20% (–MaxDD)"
562
  )
563
 
564
- # ── Load cached sweep signals ─────────────────────────────────────────────
565
- sweep_cache = {}
566
- missing_years = []
 
 
 
 
567
  for yr in SWEEP_YEARS:
568
- cached = load_sweep_signals(yr)
569
- if cached:
570
- sweep_cache[yr] = cached
 
 
 
571
  else:
572
  missing_years.append(yr)
573
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
574
  # ── Status grid ──────────────────────────────────────────────────────────
575
  cols = st.columns(len(SWEEP_YEARS))
576
  for i, yr in enumerate(SWEEP_YEARS):
577
  with cols[i]:
578
  if yr in sweep_cache:
579
  sig = sweep_cache[yr]['next_signal']
580
- st.success(f"**{yr}**\n {sig}")
 
 
 
581
  else:
582
  st.error(f"**{yr}**\n⏳ Not run")
583
 
 
584
  st.divider()
585
 
586
  # ── Sweep button ──────────��───────────────────────────────────────────────
@@ -590,30 +645,32 @@ with tab2:
590
  "🚀 Run Consensus Sweep",
591
  type="primary",
592
  use_container_width=True,
593
- disabled=(is_training or len(missing_years) == 0),
594
- help="Triggers parallel GitHub Actions jobs for missing years only"
595
  )
596
  with col_info:
597
- if len(missing_years) == 0:
598
- st.success("✅ All years cachedconsensus ready!")
599
  elif is_training:
600
- st.warning(f"⏳ Training in progress... ({len(sweep_cache)}/{len(SWEEP_YEARS)} cached)")
601
  else:
602
- st.info(f"⚡ **{len(sweep_cache)}/{len(SWEEP_YEARS)}** years cached · "
603
- f"Will trigger **{len(missing_years)}** parallel jobs: "
604
- f"{', '.join(str(y) for y in missing_years)}")
 
 
605
 
606
- if sweep_btn and missing_years:
607
- sweep_mode_str = ",".join(str(y) for y in missing_years)
608
  with st.spinner(f"🚀 Triggering parallel training for: {sweep_mode_str}..."):
609
  ok = trigger_github_training(
610
- start_year=missing_years[0],
611
  sweep_mode=sweep_mode_str,
612
  force_refresh=False
613
  )
614
  if ok:
615
  st.success(
616
- f"✅ Triggered **{len(missing_years)}** parallel jobs for: {sweep_mode_str}. "
617
  f"Each takes ~90 mins. Refresh this tab when complete."
618
  )
619
  time.sleep(2)
@@ -622,11 +679,11 @@ with tab2:
622
  st.error("❌ Failed to trigger GitHub Actions sweep.")
623
 
624
  # ── Consensus results ─────────────────────────────────────────────────────
625
- if len(sweep_cache) == 0:
626
  st.info("👆 Click **🚀 Run Consensus Sweep** to train all years.")
627
  st.stop()
628
 
629
- consensus = compute_consensus(sweep_cache)
630
  if not consensus:
631
  st.warning("⚠️ Could not compute consensus.")
632
  st.stop()
@@ -635,7 +692,7 @@ with tab2:
635
  w_info = consensus['etf_summary'][winner]
636
  win_color = ETF_COLORS.get(winner, "#00d1b2")
637
  score_share = w_info['score_share'] * 100
638
- n_cached = len(sweep_cache)
639
 
640
  # ── Consensus winner banner ───────────────────────────────────────────────
641
  split_signal = w_info['score_share'] < 0.4
@@ -648,7 +705,7 @@ with tab2:
648
  padding:32px;text-align:center;margin:20px 0;
649
  box-shadow:0 8px 24px rgba(0,0,0,0.4);">
650
  <div style="font-size:11px;letter-spacing:3px;color:#aaa;margin-bottom:12px;">
651
- WEIGHTED CONSENSUS · TFT · {n_cached} START YEARS
652
  </div>
653
  <div style="font-size:72px;font-weight:900;color:{win_color};
654
  text-shadow:0 0 30px {win_color}88;letter-spacing:2px;">
@@ -762,7 +819,7 @@ with tab2:
762
  for row in sorted(consensus['per_year'], key=lambda r: r['year']):
763
  etf = row['signal']
764
  col = ETF_COLORS.get(etf, "#888")
765
- cached = row['year'] in sweep_cache
766
  table_rows.append({
767
  'Start Year': row['year'],
768
  'Signal': etf,
@@ -773,7 +830,7 @@ with tab2:
773
  'Sharpe': f"{row['sharpe']:.2f}",
774
  'Max Drawdown': f"{row['max_dd']*100:.2f}%",
775
  'Lookback': f"{row['lookback']}d",
776
- 'Cache': "" if cached else "🆕",
777
  })
778
 
779
  tbl_df = pd.DataFrame(table_rows)
 
72
  return None
73
 
74
 
75
+ def _today_est():
76
+ from datetime import timezone
77
+ return (datetime.now(timezone.utc) - timedelta(hours=5)).date()
78
+
79
+
80
+ @st.cache_data(ttl=60)
81
+ def load_sweep_signals(year: int, for_date: str):
82
+ """Load date-stamped sweep signals. Returns (data, is_today)."""
83
+ from huggingface_hub import hf_hub_download
84
+ date_tag = for_date.replace("-", "")
85
+
86
+ # Try today's file
87
  try:
88
+ path = hf_hub_download(repo_id=HF_OUTPUT_REPO,
89
+ filename=f"signals_{year}_{date_tag}.json",
90
  repo_type="dataset", force_download=True)
91
  with open(path) as f:
92
+ return json.load(f), True
93
  except Exception:
94
+ pass
95
+
96
+ # Fall back to yesterday's file
97
+ try:
98
+ from datetime import date as _date, timedelta as _td
99
+ yesterday = (_date.fromisoformat(for_date) - _td(days=1)).strftime("%Y%m%d")
100
+ path = hf_hub_download(repo_id=HF_OUTPUT_REPO,
101
+ filename=f"signals_{year}_{yesterday}.json",
102
+ repo_type="dataset", force_download=True)
103
+ with open(path) as f:
104
+ return json.load(f), False
105
+ except Exception:
106
+ pass
107
+
108
+ return None, False
109
 
110
 
111
  # ── GitHub Actions helpers ────────────────────────────────────────────────────
 
584
  "**Score:** 40% Return · 20% Z · 20% Sharpe · 20% (–MaxDD)"
585
  )
586
 
587
+ # ── Date-aware sweep cache loading ───────────────────────────────────────
588
+ today_str = str(_today_est())
589
+ sweep_cache = {} # today's results
590
+ prev_cache = {} # yesterday's results (fallback)
591
+ stale_years = [] # years where only yesterday's data exists
592
+ missing_years = [] # years with no data at all
593
+
594
  for yr in SWEEP_YEARS:
595
+ data, is_today = load_sweep_signals(yr, today_str)
596
+ if data and is_today:
597
+ sweep_cache[yr] = data
598
+ elif data and not is_today:
599
+ prev_cache[yr] = data
600
+ stale_years.append(yr)
601
  else:
602
  missing_years.append(yr)
603
 
604
+ # Display cache = today's where available, yesterday's as fallback
605
+ display_cache = {**prev_cache, **sweep_cache} # today overrides yesterday
606
+ years_needing_run = [yr for yr in SWEEP_YEARS if yr not in sweep_cache]
607
+ sweep_complete = len(sweep_cache) == len(SWEEP_YEARS)
608
+
609
+ # ── Stale data warning banner ─────────────────────────────────────────────
610
+ if stale_years and not sweep_complete:
611
+ from datetime import date as _d, timedelta as _td
612
+ yesterday = str(_d.fromisoformat(today_str) - _td(days=1))
613
+ st.warning(
614
+ f"⚠️ Showing **yesterday's results** ({yesterday}) for: "
615
+ f"{', '.join(str(y) for y in stale_years)}. "
616
+ f"Today's sweep has not run yet — auto-runs at 8pm EST or click below.",
617
+ icon="📅"
618
+ )
619
+ if is_training and not sweep_complete:
620
+ st.info(
621
+ f"⏳ **Training in progress** — {len(sweep_cache)}/{len(SWEEP_YEARS)} years "
622
+ f"complete today. Showing previous results where available.", icon="🔄"
623
+ )
624
+
625
  # ── Status grid ──────────────────────────────────────────────────────────
626
  cols = st.columns(len(SWEEP_YEARS))
627
  for i, yr in enumerate(SWEEP_YEARS):
628
  with cols[i]:
629
  if yr in sweep_cache:
630
  sig = sweep_cache[yr]['next_signal']
631
+ st.success(f"**{yr}**\n {sig}")
632
+ elif yr in prev_cache:
633
+ sig = prev_cache[yr]['next_signal']
634
+ st.warning(f"**{yr}**\n📅 {sig}")
635
  else:
636
  st.error(f"**{yr}**\n⏳ Not run")
637
 
638
+ st.caption("✅ = today's result · 📅 = yesterday's result (stale) · ⏳ = not yet run")
639
  st.divider()
640
 
641
  # ── Sweep button ──────────��───────────────────────────────────────────────
 
645
  "🚀 Run Consensus Sweep",
646
  type="primary",
647
  use_container_width=True,
648
+ disabled=(is_training or sweep_complete),
649
+ help="Only runs years missing today's fresh results"
650
  )
651
  with col_info:
652
+ if sweep_complete:
653
+ st.success(f"✅ Today's sweep complete ({today_str}) {len(SWEEP_YEARS)}/{len(SWEEP_YEARS)} years fresh")
654
  elif is_training:
655
+ st.warning(f"⏳ Training in progress... ({len(sweep_cache)}/{len(SWEEP_YEARS)} fresh today)")
656
  else:
657
+ st.info(
658
+ f"**{len(sweep_cache)}/{len(SWEEP_YEARS)}** years fresh for today ({today_str}). \n"
659
+ f"Will trigger **{len(years_needing_run)}** jobs: "
660
+ f"{', '.join(str(y) for y in years_needing_run)}"
661
+ )
662
 
663
+ if sweep_btn and years_needing_run:
664
+ sweep_mode_str = ",".join(str(y) for y in years_needing_run)
665
  with st.spinner(f"🚀 Triggering parallel training for: {sweep_mode_str}..."):
666
  ok = trigger_github_training(
667
+ start_year=years_needing_run[0],
668
  sweep_mode=sweep_mode_str,
669
  force_refresh=False
670
  )
671
  if ok:
672
  st.success(
673
+ f"✅ Triggered **{len(years_needing_run)}** parallel jobs for: {sweep_mode_str}. "
674
  f"Each takes ~90 mins. Refresh this tab when complete."
675
  )
676
  time.sleep(2)
 
679
  st.error("❌ Failed to trigger GitHub Actions sweep.")
680
 
681
  # ── Consensus results ─────────────────────────────────────────────────────
682
+ if len(display_cache) == 0:
683
  st.info("👆 Click **🚀 Run Consensus Sweep** to train all years.")
684
  st.stop()
685
 
686
+ consensus = compute_consensus(display_cache)
687
  if not consensus:
688
  st.warning("⚠️ Could not compute consensus.")
689
  st.stop()
 
692
  w_info = consensus['etf_summary'][winner]
693
  win_color = ETF_COLORS.get(winner, "#00d1b2")
694
  score_share = w_info['score_share'] * 100
695
+ n_cached = len(display_cache)
696
 
697
  # ── Consensus winner banner ───────────────────────────────────────────────
698
  split_signal = w_info['score_share'] < 0.4
 
705
  padding:32px;text-align:center;margin:20px 0;
706
  box-shadow:0 8px 24px rgba(0,0,0,0.4);">
707
  <div style="font-size:11px;letter-spacing:3px;color:#aaa;margin-bottom:12px;">
708
+ WEIGHTED CONSENSUS · TFT · {n_cached} START YEARS · {today_str}
709
  </div>
710
  <div style="font-size:72px;font-weight:900;color:{win_color};
711
  text-shadow:0 0 30px {win_color}88;letter-spacing:2px;">
 
819
  for row in sorted(consensus['per_year'], key=lambda r: r['year']):
820
  etf = row['signal']
821
  col = ETF_COLORS.get(etf, "#888")
822
+ _in_today = row['year'] in sweep_cache
823
  table_rows.append({
824
  'Start Year': row['year'],
825
  'Signal': etf,
 
830
  'Sharpe': f"{row['sharpe']:.2f}",
831
  'Max Drawdown': f"{row['max_dd']*100:.2f}%",
832
  'Lookback': f"{row['lookback']}d",
833
+ 'Cache': "✅ Today" if row['year'] in sweep_cache else "📅 Prev",
834
  })
835
 
836
  tbl_df = pd.DataFrame(table_rows)