fix streak signal, reweight predictions, and reorder UI

- Fix streak signal: was giving 100% to streak chair after normalization
  (non-streak chairs were 0), now properly distributes probability with
  streak chair getting less as streak grows (actual mean reversion)
- Change recent window from 20 to 50 games
- Reweight signals based on backtest: base_rate 0.15→0.20 (best performer),
  recent 0.10→0.15, streak 0.10→0.05, balance 0.15→0.10
- Move Live Market Sentiment above Signal Breakdown
This commit is contained in:
2026-02-26 10:36:53 +05:00
parent 9762c0f9bf
commit 2c7cd67ab7
2 changed files with 32 additions and 52 deletions

View File

@@ -1031,12 +1031,12 @@ def _bayesian_prediction(winners, markov1, markov2):
key2 = f"{winners[-2]}{winners[-1]}"
m2 = markov2.get(key2, {c: 1 / 3 for c in CHAIR_LABELS})
# Signal 4: Recent 20-game frequency — 15%
recent = winners[-20:] if len(winners) >= 20 else winners
# Signal 4: Recent 50-game frequency — 15%
recent = winners[-50:] if len(winners) >= 50 else winners
recent_total = len(recent)
rec = {c: recent.count(c) / recent_total for c in CHAIR_LABELS}
# Signal 5: Streak momentum/regression — 10%
# Signal 5: Streak regression — 5%
streak_chair = winners[-1]
streak_len = 0
for w in reversed(winners):
@@ -1045,20 +1045,11 @@ def _bayesian_prediction(winners, markov1, markov2):
else:
break
# Regression to mean: longer streaks → lower probability of continuation
streak = {}
for c in CHAIR_LABELS:
if c == streak_chair:
streak[c] = max(0.1, 1 / 3 - streak_len * 0.05)
else:
streak[c] = 0
# Normalize streak signal
s_total = sum(streak.values())
if s_total > 0:
streak = {c: streak[c] / s_total for c in CHAIR_LABELS}
else:
streak = {c: 1 / 3 for c in CHAIR_LABELS}
cont_prob = max(0.1, 1 / 3 - streak_len * 0.05)
other_prob = (1 - cont_prob) / 2
streak = {c: (cont_prob if c == streak_chair else other_prob) for c in CHAIR_LABELS}
# Signal 6: Balance / Mean Reversion — 15%
# Signal 6: Balance / Mean Reversion — 10%
# Look at last 50 games, invert frequencies to favor under-represented chairs
window = min(50, len(winners))
recent_50 = winners[-window:]
@@ -1067,8 +1058,8 @@ def _bayesian_prediction(winners, markov1, markov2):
bal_total = sum(balance.values())
balance = {c: balance[c] / bal_total for c in CHAIR_LABELS}
weights = {"base_rate": 0.15, "markov_1": 0.25, "markov_2": 0.25, "recent_20": 0.10, "streak": 0.10, "balance": 0.15}
signals = {"base_rate": base, "markov_1": m1, "markov_2": m2, "recent_20": rec, "streak": streak, "balance": balance}
weights = {"base_rate": 0.20, "markov_1": 0.25, "markov_2": 0.25, "recent_50": 0.15, "streak": 0.05, "balance": 0.10}
signals = {"base_rate": base, "markov_1": m1, "markov_2": m2, "recent_50": rec, "streak": streak, "balance": balance}
combined = {c: 0 for c in CHAIR_LABELS}
for sig_name, weight in weights.items():
@@ -1189,7 +1180,7 @@ def _backtest_theories(winners):
if len(winners) <= warmup:
return {"error": "Not enough data for backtesting"}
theories = ["base_rate", "markov_1", "markov_2", "recent_20", "streak", "balance", "combined"]
theories = ["base_rate", "markov_1", "markov_2", "recent_50", "streak", "balance", "combined"]
full_hits = {t: 0 for t in theories}
semi_hits = {t: 0 for t in theories}
total_tested = 0
@@ -1217,8 +1208,8 @@ def _backtest_theories(winners):
m2_probs = m2.get(key2, {c: 1 / 3 for c in CHAIR_LABELS})
m2_ranked = sorted(CHAIR_LABELS, key=lambda c: m2_probs.get(c, 0), reverse=True)
# Recent-20
recent = history[-20:] if len(history) >= 20 else history
# Recent-50
recent = history[-50:] if len(history) >= 50 else history
rec = {c: recent.count(c) / len(recent) for c in CHAIR_LABELS}
rec_ranked = sorted(CHAIR_LABELS, key=lambda c: rec[c], reverse=True)
@@ -1230,17 +1221,9 @@ def _backtest_theories(winners):
streak_len += 1
else:
break
streak_probs = {}
for c in CHAIR_LABELS:
if c == streak_chair:
streak_probs[c] = max(0.1, 1 / 3 - streak_len * 0.05)
else:
streak_probs[c] = 0
s_total = sum(streak_probs.values())
if s_total > 0:
streak_probs = {c: streak_probs[c] / s_total for c in CHAIR_LABELS}
else:
streak_probs = {c: 1 / 3 for c in CHAIR_LABELS}
cont_prob = max(0.1, 1 / 3 - streak_len * 0.05)
other_prob = (1 - cont_prob) / 2
streak_probs = {c: (cont_prob if c == streak_chair else other_prob) for c in CHAIR_LABELS}
streak_ranked = sorted(CHAIR_LABELS, key=lambda c: streak_probs[c], reverse=True)
# Balance / Mean Reversion
@@ -1254,8 +1237,8 @@ def _backtest_theories(winners):
# Combined Bayesian
combined = {c: 0 for c in CHAIR_LABELS}
weights = {"base_rate": 0.15, "markov_1": 0.25, "markov_2": 0.25, "recent_20": 0.10, "streak": 0.10, "balance": 0.15}
signals = {"base_rate": base, "markov_1": m1_probs, "markov_2": m2_probs, "recent_20": rec, "streak": streak_probs, "balance": bal_probs}
weights = {"base_rate": 0.20, "markov_1": 0.25, "markov_2": 0.25, "recent_50": 0.15, "streak": 0.05, "balance": 0.10}
signals = {"base_rate": base, "markov_1": m1_probs, "markov_2": m2_probs, "recent_50": rec, "streak": streak_probs, "balance": bal_probs}
for sig_name, weight in weights.items():
for c in CHAIR_LABELS:
combined[c] += weight * signals[sig_name].get(c, 1 / 3)
@@ -1263,7 +1246,7 @@ def _backtest_theories(winners):
ranked = {
"base_rate": base_ranked, "markov_1": m1_ranked, "markov_2": m2_ranked,
"recent_20": rec_ranked, "streak": streak_ranked, "balance": bal_ranked,
"recent_50": rec_ranked, "streak": streak_ranked, "balance": bal_ranked,
"combined": combined_ranked,
}
for t in theories: