Ground popularity/EV score in empirical player-choice biases

Ports the fix already applied to the sibling Lotto 6aus49 project.
Since draws are i.i.d., only expected value (jackpot-sharing
avoidance) is improvable, not hit probability. Replaces the ad-hoc
"lucky numbers" list with empirically-documented picks, adds general
arithmetic-progression detection (not just +1 consecutive pairs), and
scores extreme odd/even splits higher (adjusted for 5 main numbers:
distance from the 2.5 midpoint, not 3 as in the 6-number Lotto case).
Deliberately omits a sum-near-mean component - testing against known
overplayed combinations (1-2-3-4-5, 5-10-15-20-25) during the Lotto
port showed it inverted the ranking for tightly clustered sequences.

Raises popularity's weight in the quality score from 20% to 45%
(main/euro/pattern/recency reduced accordingly), since those signals
carry no real predictive value for hit probability.

Also fixes _get_smart_euro_numbers(), which previously returned the
literal same Euro-number pair for tips #1-3 every single run (top-2
AI predictions, no randomization) and had zero EV consideration. Now
weighted-random across all 12 candidates with lucky numbers (3, 7)
downweighted - deliberately more conservative than the main-number
heuristic since 1-12 overlaps with calendar months and has no clear
birthday-range split to exploit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 18:57:55 +02:00
co-authored by Claude Sonnet 5
parent f23922e05e
commit 0285a1c5fa
@@ -93,6 +93,11 @@ class UltimateAIMLEurojackpotGenerator:
Ultimate Eurojackpot Generator - ALLE Strategien kombiniert Ultimate Eurojackpot Generator - ALLE Strategien kombiniert
""" """
# Empirisch belegte "griffige" Einzelzahlen, die Spieler überproportional oft frei
# wählen (Quelle: Analysen realer Tippscheine, z.B. frz. 6aus49 über 25 Jahre:
# populärste Zahlen 5,7,9,11,12,13). Unabhängig vom Geburtstags-Bias (<=31).
POPULAR_PLAYER_PICKS = {5, 7, 9, 11, 12, 13}
def __init__(self, data_path, fast_mode=True): def __init__(self, data_path, fast_mode=True):
self.data_path = data_path self.data_path = data_path
self.df = None self.df = None
@@ -441,7 +446,7 @@ class UltimateAIMLEurojackpotGenerator:
"""High-EV: 2 Zahlen >31, max. 1 Lucky Number, soft Consecutive-Vermeidung.""" """High-EV: 2 Zahlen >31, max. 1 Lucky Number, soft Consecutive-Vermeidung."""
random.seed(42 + tip_number * 41) random.seed(42 + tip_number * 41)
lucky_numbers = {3, 7, 9, 11, 13, 17, 19, 21, 23} lucky_numbers = UltimateAIMLEurojackpotGenerator.POPULAR_PLAYER_PICKS
above_31_target = 2 # für 5 Zahlen: 2 above-31 = solide EV-Basis above_31_target = 2 # für 5 Zahlen: 2 above-31 = solide EV-Basis
selected_main = [] selected_main = []
@@ -671,41 +676,91 @@ class UltimateAIMLEurojackpotGenerator:
return (distance_score * 0.6 + range_score * 0.4) return (distance_score * 0.6 + range_score * 0.4)
def _get_smart_euro_numbers(self, tip_number, euro_preds): def _get_smart_euro_numbers(self, tip_number, euro_preds):
"""Intelligente Euro-Zahlen Auswahl.""" """
sorted_euro = sorted(euro_preds.items(), key=lambda x: x[1], reverse=True) EV-bewusste Euro-Zahlen-Auswahl: gewichtete Zufallswahl aus den
AI-Predictions statt starrer Top-2 (vorher: Tipps 1-3 bekamen IMMER
exakt dasselbe Euro-Paar), mit Abwertung klassischer Glückszahlen
(3, 7) - analog zur Hauptzahlen-Logik in _generate_high_ev_tip().
# Strategy based on tip number Schwächer empirisch belegt als die Hauptzahlen-Heuristik: 1-12
if tip_number <= 3: überschneidet sich mit Kalendermonaten, es gibt keine klare
# Top predictions Geburtstags-Grenze wie bei den Hauptzahlen (>31). Deshalb bewusst
return sorted([num for num, _ in sorted_euro[:2]]) konservativ: nur die zwei bekanntesten Glückszahlen abgewertet,
else: keine erfundene Rangfolge aller 12 Ziffern.
# Mix of top and diversity """
top_euros = [num for num, _ in sorted_euro[:6]] random.seed(100 + tip_number * 13)
random.seed(42 + tip_number * 7) lucky_euro = {3, 7}
return sorted(random.sample(top_euros, 2))
pool = list(euro_preds.keys())
weights = []
for n in pool:
w = euro_preds.get(n, 0.1)
if n in lucky_euro:
w *= 0.5
weights.append(max(0.001, w))
chosen = []
for _ in range(2):
pick = random.choices(pool, weights=weights)[0]
idx = pool.index(pick)
pool.pop(idx)
weights.pop(idx)
chosen.append(pick)
return sorted(chosen)
@staticmethod @staticmethod
def _calculate_popularity_score(main_numbers): def _calculate_popularity_score(main_numbers):
""" """
Schätzt den Erwartungswert-Vorteil durch Vermeidung populärer Zahlenkombinationen. Schätzt den Erwartungswert-Vorteil durch Vermeidung populärer Zahlenkombinationen.
Höher = unpopulärer = höherer Gewinnanteil bei einem Treffer. Höher = unpopulärer bei Mitspielern = höherer Gewinnanteil bei einem Treffer
Basis: Spieler bevorzugen Geburtstagszahlen (1-31), Glückszahlen und Zahlenfolgen. (Hit-Wahrscheinlichkeit selbst ist bei i.i.d. Ziehungen nicht beeinflussbar).
Basis: dokumentierte Spielerverhalten-Biases (Henze/Riedwyl-artige Analysen
realer Tippscheine): Geburtstagszahlen (<=31), einzelne "griffige" Zahlen,
Zahlenfolgen/arithmetische Muster (z.B. 5-10-15-20-25) und "zufällig
aussehende" ausgeglichene Odd/Even-Splits werden von Menschen
überproportional oft gewählt - jede einzelne Kombination ist aber exakt
gleich wahrscheinlich, unabhängig von diesen Eigenschaften.
Hinweis: eine "Summe nahe am Mittel"-Komponente wurde bewusst NICHT
aufgenommen - sie widerspricht sich mit der Muster-Erkennung, da gerade
die meistgespielten sequenziellen Kombinationen (z.B. 1-2-3-4-5) durch
ihre enge Zahlen-Clusterung zugleich eine extreme Summe erzeugen.
""" """
n = len(main_numbers) n = len(main_numbers)
sorted_nums = sorted(main_numbers) sorted_nums = sorted(main_numbers)
# Anteil Zahlen > 31 (Geburtstags-Range vermeiden) # 1) Geburtstags-Range (1-31) meiden
above_31_ratio = sum(1 for x in main_numbers if x > 31) / n above_31_ratio = sum(1 for x in main_numbers if x > 31) / n
# Aufeinanderfolgende Zahlen vermeiden (visuelle Muster) # 2) Empirisch belegte populäre Einzelzahlen meiden
consecutive_pairs = sum(1 for i in range(n - 1) if sorted_nums[i + 1] - sorted_nums[i] == 1) popular_ratio = sum(
consecutive_ratio = consecutive_pairs / (n - 1) if n > 1 else 0 1 for x in main_numbers
if x in UltimateAIMLEurojackpotGenerator.POPULAR_PLAYER_PICKS
) / n
# Häufig gespielte "Glückszahlen" vermeiden # 3) Muster/Zahlenfolgen meiden: direkte Nachbarn UND allgemeine
lucky_numbers = {3, 7, 9, 11, 13, 17, 19, 21, 23} # arithmetische Folgen (konstante Schrittweite, z.B. 5-10-15-20-25
lucky_ratio = sum(1 for x in main_numbers if x in lucky_numbers) / n # zählt zu den meistgespielten Kombinationen überhaupt)
diffs = [sorted_nums[i + 1] - sorted_nums[i] for i in range(n - 1)]
small_step_ratio = sum(1 for d in diffs if d <= 5) / (n - 1) if diffs else 0
is_perfect_progression = len(diffs) > 1 and len(set(diffs)) == 1
pattern_penalty = small_step_ratio * (0.6 if not is_perfect_progression else 1.0)
score = above_31_ratio * 0.5 + (1 - consecutive_ratio) * 0.3 + (1 - lucky_ratio) * 0.2 # 4) Odd/Even-Split: Menschen bevorzugen "ausgeglichen aussehende" Splits,
# obwohl jede einzelne Kombination gleich wahrscheinlich ist. Bei 5
# Zahlen ist der ausgeglichenste Split 2-3/3-2 (kein exaktes Halbieren
# möglich), daher Distanz zum Mittelpunkt 2.5 statt n/2.
even_count = sum(1 for x in main_numbers if x % 2 == 0)
split_extremity = abs(even_count - (n / 2)) / (n / 2)
score = (
above_31_ratio * 0.30
+ (1 - popular_ratio) * 0.20
+ (1 - pattern_penalty) * 0.30
+ split_extremity * 0.20
)
return min(max(score, 0.0), 1.0) 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):
@@ -741,13 +796,17 @@ class UltimateAIMLEurojackpotGenerator:
else: else:
recency_quality = 0.5 recency_quality = 0.5
# Popularity/EV dominiert bewusst: AI-Score, Pattern und Recency sagen nichts
# über die (unbeeinflussbare) Trefferwahrscheinlichkeit aus - Recency/Pattern
# folgen zudem Heuristiken, die auch andere Systemspieler nutzen und damit die
# Popularity eher untergraben statt sie zu unterstützen.
quality = ( quality = (
main_quality * 0.25 main_quality * 0.15
+ euro_quality * 0.15 + euro_quality * 0.10
+ pattern_quality * 0.15 + pattern_quality * 0.10
+ diversity_quality * 0.10 + diversity_quality * 0.10
+ popularity_quality * 0.20 + popularity_quality * 0.45
+ recency_quality * 0.15 + recency_quality * 0.10
) )
return min(quality, 1.0) return min(quality, 1.0)