diff --git a/app/db.py b/app/db.py index 47b2fa4fd..49f73eb03 100644 --- a/app/db.py +++ b/app/db.py @@ -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: diff --git a/static/predictions.html b/static/predictions.html index 916591aed..125dd3d1e 100644 --- a/static/predictions.html +++ b/static/predictions.html @@ -253,19 +253,8 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
-
-
Signal Breakdown
- - - -
SignalWeightABC
-
- - - -
-
Live Market Sentiment
- @@ -480,7 +477,7 @@ function renderPrediction(data) { // Signal table const tbody = $('signal-table').querySelector('tbody'); - const sigNames = {'base_rate':'Base Rate','markov_1':'Markov-1','markov_2':'Markov-2','recent_20':'Recent 20','streak':'Streak','balance':'Balance'}; + const sigNames = {'base_rate':'Base Rate','markov_1':'Markov-1','markov_2':'Markov-2','recent_50':'Recent 50','streak':'Streak','balance':'Balance'}; tbody.innerHTML = Object.entries(data.signals).map(([key, sig]) => `${sigNames[key]||key}${(sig.weight*100).toFixed(0)}%` + CHAIRS.map(c => `${pct(sig.probs[c])}`).join('') + '' @@ -1051,7 +1048,7 @@ function renderRunsTest(runs) { function renderBacktest(bt) { if (bt.error) { $('backtest-cards').innerHTML = `
${bt.error}
`; return; } - const names = {base_rate:'Base Rate',markov_1:'Markov-1',markov_2:'Markov-2',recent_20:'Recent 20',streak:'Streak',balance:'Balance',combined:'Combined'}; + const names = {base_rate:'Base Rate',markov_1:'Markov-1',markov_2:'Markov-2',recent_50:'Recent 50',streak:'Streak',balance:'Balance',combined:'Combined'}; $('backtest-cards').innerHTML = Object.entries(bt.accuracy).map(([key, acc]) => { const fh = bt.full_hits?.[key] ?? '?'; const sh = bt.semi_hits?.[key] ?? '?'; @@ -1062,7 +1059,7 @@ function renderBacktest(bt) { }).join(''); if (bt.rolling_accuracy) { const ctx = $('backtest-chart').getContext('2d'); - const colors = {base_rate:'#8b8fa3',markov_1:'#3b82f6',markov_2:'#ec4899',recent_20:'#f59e0b',streak:'#10b981',balance:'#f472b6',combined:'#6c5ce7'}; + const colors = {base_rate:'#8b8fa3',markov_1:'#3b82f6',markov_2:'#ec4899',recent_50:'#f59e0b',streak:'#10b981',balance:'#f472b6',combined:'#6c5ce7'}; const datasets = Object.entries(bt.rolling_accuracy).map(([key, data]) => ({ label: names[key]||key, data, borderColor: colors[key]||'#fff', backgroundColor: 'transparent', borderWidth: key === 'combined' ? 3 : 1.5, pointRadius: 0, tension: 0.3,