Add popularity score for EV-optimized number selection

Adds _calculate_popularity_score() which favors number combinations
unlikely to be picked by other players (numbers > 31, no consecutive
sequences, avoiding common "lucky numbers"). This doesn't improve hit
probability (lottery draws are i.i.d. random) but increases expected
value by reducing the chance of sharing a jackpot.

- New 20% weight in quality score
- New Popularity_Score column in tip CSV export

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 13:33:18 +02:00
co-authored by Claude Sonnet 4.6
parent 400f003aaf
commit ea354e3058
2 changed files with 41 additions and 6 deletions
@@ -461,6 +461,13 @@
"file": "weekly_tips_20260610_123037.csv", "file": "weekly_tips_20260610_123037.csv",
"avg_confidence": 0.48925897711447985, "avg_confidence": 0.48925897711447985,
"avg_quality": 0.6018162578186794 "avg_quality": 0.6018162578186794
},
{
"timestamp": "2026-06-10T13:32:25.391718",
"num_tips": 10,
"file": "weekly_tips_20260610_133225.csv",
"avg_confidence": 0.48925897711447985,
"avg_quality": 0.6080343315716299
} }
] ]
} }
@@ -623,27 +623,54 @@ class UltimateAIMLEurojackpotGenerator:
random.seed(42 + tip_number * 7) random.seed(42 + tip_number * 7)
return sorted(random.sample(top_euros, 2)) return sorted(random.sample(top_euros, 2))
@staticmethod
def _calculate_popularity_score(main_numbers):
"""
Schätzt den Erwartungswert-Vorteil durch Vermeidung populärer Zahlenkombinationen.
Höher = unpopulärer = höherer Gewinnanteil bei einem Treffer.
Basis: Spieler bevorzugen Geburtstagszahlen (1-31), Glückszahlen und Zahlenfolgen.
"""
n = len(main_numbers)
sorted_nums = sorted(main_numbers)
# Anteil Zahlen > 31 (Geburtstags-Range vermeiden)
above_31_ratio = sum(1 for x in main_numbers if x > 31) / n
# Aufeinanderfolgende Zahlen vermeiden (visuelle Muster)
consecutive_pairs = sum(1 for i in range(n - 1) if sorted_nums[i + 1] - sorted_nums[i] == 1)
consecutive_ratio = consecutive_pairs / (n - 1) if n > 1 else 0
# Häufig gespielte "Glückszahlen" vermeiden
lucky_numbers = {3, 7, 9, 11, 13, 17, 19, 21, 23}
lucky_ratio = sum(1 for x in main_numbers if x in lucky_numbers) / n
score = above_31_ratio * 0.5 + (1 - consecutive_ratio) * 0.3 + (1 - lucky_ratio) * 0.2
return min(max(score, 0.0), 1.0)
def _calculate_quality_score(self, main_numbers, euro_numbers, main_preds, euro_preds, pattern_weight): def _calculate_quality_score(self, main_numbers, euro_numbers, main_preds, euro_preds, pattern_weight):
"""Berechnet Qualität.""" """Berechnet Qualität."""
# Main quality # Main quality
main_scores = [main_preds.get(n, 0.1) for n in main_numbers] main_scores = [main_preds.get(n, 0.1) for n in main_numbers]
main_quality = np.mean(main_scores) * (1 + np.std(main_scores) * 0.5) main_quality = np.mean(main_scores) * (1 + np.std(main_scores) * 0.5)
# Euro quality # Euro quality
euro_scores = [euro_preds.get(n, 0.1) for n in euro_numbers] euro_scores = [euro_preds.get(n, 0.1) for n in euro_numbers]
euro_quality = np.mean(euro_scores) euro_quality = np.mean(euro_scores)
# Pattern quality # Pattern quality
pattern_quality = pattern_weight pattern_quality = pattern_weight
# Diversity # Diversity
distances = [] distances = []
for i, n1 in enumerate(main_numbers): for i, n1 in enumerate(main_numbers):
for n2 in main_numbers[i+1:]: for n2 in main_numbers[i+1:]:
distances.append(abs(n1 - n2)) distances.append(abs(n1 - n2))
diversity_quality = min(np.mean(distances) / 10.0, 1.0) if distances else 0.5 diversity_quality = min(np.mean(distances) / 10.0, 1.0) if distances else 0.5
quality = (main_quality * 0.4 + euro_quality * 0.25 + pattern_quality * 0.2 + diversity_quality * 0.15) # Popularity (EV-Vorteil bei Gewinn)
popularity_quality = self._calculate_popularity_score(main_numbers)
quality = (main_quality * 0.35 + euro_quality * 0.2 + pattern_quality * 0.15 + diversity_quality * 0.1 + popularity_quality * 0.2)
return min(quality, 1.0) return min(quality, 1.0)
def _print_tip_line(self, tip): def _print_tip_line(self, tip):
@@ -748,7 +775,8 @@ class UltimateAIMLEurojackpotGenerator:
'Euro_AI_Score': f"{tip['euro_ai_score']:.4f}", 'Euro_AI_Score': f"{tip['euro_ai_score']:.4f}",
'Pattern_Weight': f"{tip['pattern_weight']:.4f}", 'Pattern_Weight': f"{tip['pattern_weight']:.4f}",
'Confidence': f"{tip['confidence']:.4f}", 'Confidence': f"{tip['confidence']:.4f}",
'Quality': f"{tip['quality']:.4f}" 'Quality': f"{tip['quality']:.4f}",
'Popularity_Score': f"{self._calculate_popularity_score(tip['main_numbers']):.4f}"
}) })
df_export = pd.DataFrame(rows) df_export = pd.DataFrame(rows)