Skip to content

Commit fb54d85

Browse files
committed
Update app.py
1 parent 41cbbcc commit fb54d85

1 file changed

Lines changed: 157 additions & 26 deletions

File tree

src/app.py

Lines changed: 157 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,25 @@
2626
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700&display=swap');
2727
html, body, [class*="css"] { font-family: 'Space Grotesk', sans-serif; }
2828
:root {
29-
--ink: #10212b;
30-
--muted: #5a6b75;
31-
--cream: #f6efe6;
32-
--sand: #efe3d5;
33-
--paper: #fffaf2;
34-
--accent: #2f6f6b;
29+
--ink: #0b0f14;
30+
--muted: #4a5563;
31+
--bg: #f7f9fc;
32+
--paper: #ffffff;
33+
--panel: #f0f4fa;
34+
--accent: #2563eb;
35+
--accent-2: #3b82f6;
3536
}
36-
body, .stApp {
37-
background: radial-gradient(1200px 500px at 20% -10%, var(--sand), transparent),
38-
linear-gradient(180deg, var(--cream) 0%, #f8f3ee 100%);
37+
body, .stApp, [data-testid="stAppViewContainer"] {
38+
background: var(--bg) !important;
3939
color: var(--ink);
4040
}
41+
.stSidebar, [data-testid="stSidebar"] {
42+
background: #eef2f7 !important;
43+
border-right: 1px solid #d5dde8;
44+
}
45+
.stSidebar *, [data-testid="stSidebar"] * {
46+
color: var(--ink) !important;
47+
}
4148
h1, h2, h3, h4, h5, h6, p, li, label, span, div {
4249
color: var(--ink) !important;
4350
}
@@ -47,26 +54,104 @@
4754
padding: 18px 22px;
4855
border-radius: 16px;
4956
background: var(--paper);
50-
border: 1px solid #eadfd2;
51-
box-shadow: 0 6px 20px rgba(16, 33, 43, 0.06);
57+
border: 1px solid #dbe3ef;
58+
box-shadow: 0 8px 22px rgba(11, 15, 20, 0.08);
5259
}
5360
.card {
5461
padding: 14px 16px;
5562
border-radius: 12px;
56-
background: var(--paper);
57-
border: 1px solid #eadfd2;
58-
box-shadow: 0 6px 16px rgba(16, 33, 43, 0.06);
63+
background: #eaf2ff;
64+
border: 1px solid #c9dcff;
65+
box-shadow: 0 6px 16px rgba(37, 99, 235, 0.15);
5966
}
6067
.pill {
6168
display: inline-block;
6269
padding: 2px 10px;
6370
border-radius: 999px;
6471
font-size: 12px;
65-
background: var(--accent);
72+
background: #1d4ed8;
6673
color: #ffffff;
6774
margin-right: 6px;
75+
box-shadow: 0 6px 14px rgba(29, 78, 216, 0.3);
6876
}
6977
.muted { color: var(--muted); }
78+
a { color: var(--accent); }
79+
.stTextInput input,
80+
.stNumberInput input,
81+
.stSelectbox select,
82+
.stMultiSelect div,
83+
.stDateInput input {
84+
background: #ffffff !important;
85+
color: #0e1b24 !important;
86+
border: 1px solid #cdd9ee !important;
87+
}
88+
.stDateInput input {
89+
border-color: #2563eb !important;
90+
box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.15);
91+
}
92+
.stDateInput div[role="button"] svg {
93+
color: #2563eb !important;
94+
fill: #2563eb !important;
95+
}
96+
[data-testid="stDateInputPicker"] {
97+
background: #ffffff !important;
98+
color: var(--ink) !important;
99+
border: 1px solid #dbe3ef !important;
100+
border-radius: 12px !important;
101+
box-shadow: 0 8px 20px rgba(11, 15, 20, 0.12);
102+
}
103+
[data-testid="stDateInputPicker"] * {
104+
color: var(--ink) !important;
105+
}
106+
[data-testid="stDateInputPicker"] button {
107+
color: var(--ink) !important;
108+
}
109+
[data-testid="stDateInputPicker"] [aria-selected="true"] {
110+
background: #2563eb !important;
111+
color: #ffffff !important;
112+
}
113+
.stSelectbox svg, .stMultiSelect svg {
114+
color: #0e1b24 !important;
115+
fill: #0e1b24 !important;
116+
}
117+
.stSlider label, .stSlider span {
118+
color: #0e1b24 !important;
119+
}
120+
.stSlider [data-testid="stThumbValue"] {
121+
color: #0e1b24 !important;
122+
background: #ffffff !important;
123+
border: 1px solid #cdd9ee !important;
124+
}
125+
.stSlider [data-testid="stTickBar"] {
126+
background: #cdd9ee !important;
127+
}
128+
.stSlider [data-testid="stTrack"] {
129+
background: #cfe0ff !important;
130+
}
131+
.stSlider [data-testid="stSliderThumb"] {
132+
background: var(--accent) !important;
133+
border: 2px solid #1d4ed8 !important;
134+
}
135+
.stSlider [data-testid="stSliderTrack"] {
136+
background: #cfe0ff !important;
137+
}
138+
.stDownloadButton button,
139+
.stButton button,
140+
.stForm button {
141+
background: var(--accent) !important;
142+
color: #ffffff !important;
143+
border: 1px solid #1d4ed8 !important;
144+
border-radius: 10px !important;
145+
}
146+
.stDownloadButton button:hover,
147+
.stButton button:hover,
148+
.stForm button:hover {
149+
background: var(--accent-2) !important;
150+
border-color: #1e40af !important;
151+
}
152+
.stSlider > div[data-baseweb="slider"] {
153+
color: var(--ink);
154+
}
70155
</style>
71156
""",
72157
unsafe_allow_html=True,
@@ -261,6 +346,18 @@ def attach_elo_from_history(df: pd.DataFrame, history: pd.DataFrame) -> pd.DataF
261346
pred["odds_home_f"] = to_float_series(pred, "odds_home")
262347
pred["p_home_f"] = to_float_series(pred, "p_home") # market implied probability
263348
pred["model_p_home_f"] = to_float_series(pred, "model_p_home") # model probability
349+
pred["model_p_draw_f"] = to_float_series(pred, "model_p_draw")
350+
pred["model_p_away_f"] = to_float_series(pred, "model_p_away")
351+
pred["model_favor"] = np.where(
352+
pred["model_p_home_f"] >= pred["model_p_away_f"],
353+
pred.get("home", ""),
354+
pred.get("away", ""),
355+
)
356+
pred["model_favor"] = np.where(
357+
pred["model_p_draw_f"] > pred[["model_p_home_f", "model_p_away_f"]].max(axis=1),
358+
"Draw",
359+
pred["model_favor"],
360+
)
264361

265362
# compute edge
266363
pred["edge_home"] = pred["model_p_home_f"] - pred["p_home_f"]
@@ -313,6 +410,16 @@ def attach_elo_from_history(df: pd.DataFrame, history: pd.DataFrame) -> pd.DataF
313410
"Candidate",
314411
"Skip",
315412
)
413+
df["model_favor"] = np.where(
414+
df["model_p_home_f"] >= df["model_p_away_f"],
415+
df.get("home", ""),
416+
df.get("away", ""),
417+
)
418+
df["model_favor"] = np.where(
419+
df["model_p_draw_f"] > df[["model_p_home_f", "model_p_away_f"]].max(axis=1),
420+
"Draw",
421+
df["model_favor"],
422+
)
316423

317424
# -------------------------
318425
# Simple guide
@@ -454,6 +561,8 @@ def attach_elo_from_history(df: pd.DataFrame, history: pd.DataFrame) -> pd.DataF
454561
probs = model.predict_proba(X.values)
455562
future = future.loc[X.index].copy()
456563
future["model_p_home"] = probs[:, 0]
564+
future["model_p_draw"] = probs[:, 1]
565+
future["model_p_away"] = probs[:, 2]
457566
future["edge_home"] = future["model_p_home"] - future["p_home"]
458567
future["ev_home"] = future.apply(
459568
lambda r: compute_ev(r.get("odds_home"), r.get("model_p_home")), axis=1
@@ -463,11 +572,21 @@ def attach_elo_from_history(df: pd.DataFrame, history: pd.DataFrame) -> pd.DataF
463572
"Candidate",
464573
"Skip",
465574
)
575+
future["model_favor"] = np.where(
576+
future["model_p_home"] >= future["model_p_away"],
577+
future.get("home", ""),
578+
future.get("away", ""),
579+
)
580+
future["model_favor"] = np.where(
581+
future["model_p_draw"] > future[["model_p_home", "model_p_away"]].max(axis=1),
582+
"Draw",
583+
future["model_favor"],
584+
)
466585
if "date" in future.columns:
467586
today = pd.Timestamp.today().normalize()
468587
future = future.loc[future["date"] >= today].copy()
469588
show_cols = [
470-
c for c in ["date", "home", "away", "p_home", "model_p_home", "edge_home", "ev_home", "simple_label"]
589+
c for c in ["date", "home", "away", "model_favor", "p_home", "model_p_home", "edge_home", "ev_home", "simple_label"]
471590
if c in future.columns
472591
]
473592
future_sorted = future[show_cols].sort_values("edge_home", ascending=False)
@@ -507,6 +626,10 @@ def attach_elo_from_history(df: pd.DataFrame, history: pd.DataFrame) -> pd.DataF
507626
# Match explorer
508627
# -------------------------
509628
st.header("Match explorer (filtered matches)")
629+
st.markdown(
630+
'<div style="height:2px;background:#2563eb;border-radius:999px;margin:6px 0 14px 0;"></div>',
631+
unsafe_allow_html=True,
632+
)
510633
if df.empty:
511634
st.info("No matches available with current filters.")
512635
else:
@@ -523,16 +646,24 @@ def fmt(i):
523646
row = st.selectbox("Pick match (index)", indices, format_func=fmt)
524647
r = df.iloc[row]
525648
st.subheader(f"{r.get('home','?')} vs {r.get('away','?')} ({r.get('date','')})")
526-
st.write({
527-
"Market p_home": r.get("p_home_f"),
528-
"Model p_home": r.get("model_p_home_f"),
529-
"Edge (model - market)": r.get("edge_home"),
530-
"Label": r.get("simple_label"),
531-
"Odds_home": r.get("odds_home_f"),
532-
"EV (per unit stake)": r.get("ev_home"),
533-
"Kelly fraction (capped)": r.get("kelly_frac_capped"),
534-
"Suggested stake ($)": r.get("stake_usd")
535-
})
649+
st.markdown(
650+
f"""
651+
<div class="card">
652+
<strong>Model favors:</strong> {r.get("model_favor", "")}<br/>
653+
<strong>Market win chance:</strong> {r.get("p_home_f", 0):.3f}<br/>
654+
<strong>Model win chance:</strong> {r.get("model_p_home_f", 0):.3f}<br/>
655+
<strong>Model draw chance:</strong> {r.get("model_p_draw_f", 0):.3f}<br/>
656+
<strong>Model away win chance:</strong> {r.get("model_p_away_f", 0):.3f}<br/>
657+
<strong>Edge (model - market):</strong> {r.get("edge_home", 0):.3f}<br/>
658+
<strong>Label:</strong> {r.get("simple_label")}<br/>
659+
<strong>Odds (home):</strong> {r.get("odds_home_f", 0):.3f}<br/>
660+
<strong>EV (per $1):</strong> {r.get("ev_home", 0):.3f}<br/>
661+
<strong>Kelly (capped):</strong> {r.get("kelly_frac_capped", 0):.3f}<br/>
662+
<strong>Suggested stake:</strong> ${r.get("stake_usd", 0):.2f}
663+
</div>
664+
""",
665+
unsafe_allow_html=True,
666+
)
536667

537668
# -------------------------
538669
# Bankroll / Backtest plot

0 commit comments

Comments
 (0)