Compare commits
4
Commits
427e704f3c
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec7290ece5 | ||
|
|
73a6b43ec9 | ||
|
|
d2c9369e3f | ||
|
|
612334dc1a |
No files matched your search
@@ -59,7 +59,6 @@ data/backups/
|
|||||||
data/*.backup*
|
data/*.backup*
|
||||||
data/generated_tips/weekly_lotto_tips_*.csv
|
data/generated_tips/weekly_lotto_tips_*.csv
|
||||||
data/performance_reports/
|
data/performance_reports/
|
||||||
data/data/
|
|
||||||
|
|
||||||
# graphify Code-Graph (regenerierbar via /graphify --update)
|
# graphify Code-Graph (regenerierbar via /graphify --update)
|
||||||
graphify-out/
|
graphify-out/
|
||||||
@@ -1,41 +1,57 @@
|
|||||||
# Lotto 6aus49 Analyse & Generator
|
# Lotto 6aus49 Analyse & Generator
|
||||||
|
|
||||||
Automatisiertes System zur Analyse von Lotto 6aus49-Daten und Generierung von KI-gestützten Tipps mit Telegram-Benachrichtigungen.
|
Automatisiertes System zur Analyse von Lotto 6aus49-Daten und Generierung von KI-gestützten Tipps mit Telegram-Benachrichtigungen. Schwesterprojekt von [Eurojackpot](../Eurojackpot) — gleiche Architektur, angepasst an das Lotto-Format (6 Zahlen aus 49 + 1 Superzahl).
|
||||||
|
|
||||||
|
Eine detaillierte Architektur- und Datenfluss-Beschreibung steht in [documentation/ARCHITECTURE.md](documentation/ARCHITECTURE.md).
|
||||||
|
|
||||||
## 🎲 Features
|
## 🎲 Features
|
||||||
|
|
||||||
- **🧠 AI/ML-basierte Tipp-Generierung**: Random Forest, Gradient Boosting & Neural Networks
|
- **🧠 AI/ML-basierte Tipp-Generierung**: Random Forest + LSTM (PyTorch) Hybrid-Predictor
|
||||||
- **🎨 Pattern-Analyse**: Historische Verteilungsmuster (NNMMHH, etc.)
|
- **🎨 Pattern-Analyse**: Historische Verteilungsmuster (N/M/H-Zonen)
|
||||||
- **⚡ Hybrid-Optimization**: Multi-objektive Optimierung mit 4 Strategien
|
- **⚡ 4-Strategien-System**: HYBRID-OPT, BALANCED-SPREAD, HIGH-EV, SOFT-CONTRARIAN mit adaptiver Gewichtung
|
||||||
- **📚 Real-Time Learning**: Kontinuierliche Anpassung der Modelle
|
- **💰 EV-Optimierung**: Quality-Score gewichtet gezielt auf unpopuläre (aber gleich wahrscheinliche) Zahlenkombinationen, um den Gewinnanteil im Trefferfall zu erhöhen
|
||||||
- **🤖 Automatisierung**: Wöchentliche Tipp-Generierung via Cron
|
- **📚 Real-Time Learning**: Kontinuierliches Retraining bei neuen Ziehungen
|
||||||
- **📲 Telegram-Benachrichtigungen**: Automatische Tipps direkt aufs Smartphone
|
- **🤖 Automatisierung**: Tipp-Generierung & Datenupdate via launchd, mit täglichem Watchdog gegen verpasste Läufe
|
||||||
- **🔄 Data-Updates**: 3 verschiedene Methoden (API, Web-Scraping, Manuelle Eingabe)
|
- **📲 Telegram-Benachrichtigungen**: Automatische Tipps direkt aufs Smartphone, plus Watchdog-Alarm bei Problemen
|
||||||
|
- **🔄 Data-Updates**: API-Kette (Lottoland → GitHub-Archiv → lottoAPI) mit Web-Scraping- und manuellem Fallback
|
||||||
|
|
||||||
## 📁 Verzeichnisstruktur
|
## 📁 Verzeichnisstruktur
|
||||||
|
|
||||||
```
|
```text
|
||||||
Lotto/
|
Lotto/
|
||||||
├── README.md # Diese Datei
|
├── README.md
|
||||||
├── config/ # Konfigurationsdateien
|
├── config/
|
||||||
│ ├── notifications.json # Telegram/Email Config
|
│ └── notifications.json # Telegram/Email Config
|
||||||
│ └── notifications.json.example # Beispiel-Konfiguration
|
├── data/
|
||||||
├── data/ # Generierte Daten
|
│ ├── AlleLottozahlen.csv # Ziehungshistorie
|
||||||
│ └── generated_tips/ # Hier landen die generierten Tipps
|
│ ├── backups/ # CSV-Backups (automatisch rotiert, letzte 30)
|
||||||
|
│ ├── ultimate_ml_models/ # Trainierte Modelle (RF + LSTM)
|
||||||
|
│ └── generated_tips/ # Generierte Tipps
|
||||||
├── scripts/
|
├── scripts/
|
||||||
│ ├── generators/ # Tipp-Generatoren
|
│ ├── generators/
|
||||||
│ │ └── ultimate_ai_ml_hybrid_generator.py
|
│ │ └── ultimate_ai_ml_hybrid_generator.py # aktiver Generator
|
||||||
│ ├── automation/ # Automatisierungs-Scripts
|
│ ├── automation/
|
||||||
│ │ └── weekly_tip_generator.py
|
│ │ ├── auto_update_and_learn.py # Update + Retraining
|
||||||
│ ├── analysis/ # Analyse-Scripts
|
│ │ ├── weekly_tip_generator.py # Tipp-Generierung
|
||||||
│ └── utils/ # Hilfsfunktionen
|
│ │ ├── com.lotto.update.plist
|
||||||
│ ├── notifier.py # Benachrichtigungs-System
|
│ │ ├── com.lotto.weekly.plist
|
||||||
│ ├── update_from_api.py # API-basiertes Update
|
│ │ └── com.lotto.watchdog.plist
|
||||||
│ ├── update_from_web.py # Web-Scraping Update
|
│ └── utils/
|
||||||
│ └── simple_update.py # Manuelle Eingabe
|
│ ├── notifier.py # Telegram/Email
|
||||||
├── results/ # Auswertungen & Grafiken
|
│ ├── update_from_api.py # API-Updater (aktiv)
|
||||||
├── logs/ # Log-Dateien
|
│ ├── update_from_web.py # Web-Scraping-Fallback (automatisch, wenn API nichts liefert)
|
||||||
└── setup_cron.sh # Cron-Job Einrichtung
|
│ ├── simple_update.py # Manuelle Eingabe
|
||||||
|
│ ├── watchdog.py # Prüft Daten-/Modell-Aktualität, alarmiert per Telegram
|
||||||
|
│ ├── validate_csv.py # manuelles Utility
|
||||||
|
│ ├── verify_draws.py # manuelles Utility
|
||||||
|
│ └── health_check.py # manuelles Utility
|
||||||
|
├── documentation/
|
||||||
|
│ └── ARCHITECTURE.md
|
||||||
|
├── results/ # Auswertungen (z.B. Superzahl-Statistik)
|
||||||
|
├── run_tip_generator.sh
|
||||||
|
├── run_update_and_learn.sh
|
||||||
|
├── run_watchdog.sh
|
||||||
|
└── logs/
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🚀 Quick Start
|
## 🚀 Quick Start
|
||||||
@@ -43,19 +59,16 @@ Lotto/
|
|||||||
### 1. Installation
|
### 1. Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Python-Abhängigkeiten installieren
|
python3 -m venv venv
|
||||||
pip install pandas numpy scikit-learn requests
|
source venv/bin/activate
|
||||||
|
pip install pandas numpy scikit-learn requests torch beautifulsoup4
|
||||||
# Optional: Deep Learning
|
|
||||||
pip install tensorflow
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Telegram-Bot einrichten
|
### 2. Telegram-Bot einrichten
|
||||||
|
|
||||||
1. Erstelle einen Bot mit [@BotFather](https://t.me/botfather)
|
1. Erstelle einen Bot mit [@BotFather](https://t.me/botfather)
|
||||||
2. Kopiere den Bot-Token
|
2. Kopiere den Bot-Token, hole deine Chat-ID (z.B. mit [@userinfobot](https://t.me/userinfobot))
|
||||||
3. Hole deine Chat-ID (z.B. mit [@userinfobot](https://t.me/userinfobot))
|
3. Konfiguriere `config/notifications.json` (siehe [TELEGRAM_BOT_SETUP.md](TELEGRAM_BOT_SETUP.md)):
|
||||||
4. Konfiguriere `config/notifications.json`:
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -67,360 +80,123 @@ pip install tensorflow
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Manuell Tipps generieren
|
Test-Benachrichtigung senden:
|
||||||
|
|
||||||
```bash
|
|
||||||
cd scripts/automation
|
|
||||||
python weekly_tip_generator.py --force --num-tips 10
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Telegram-Bot einrichten (optional)
|
|
||||||
|
|
||||||
Richte einen eigenen Lotto-Bot für Benachrichtigungen ein:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Anleitung lesen
|
|
||||||
cat TELEGRAM_BOT_SETUP.md
|
|
||||||
|
|
||||||
# Bot-Token in config/notifications.json eintragen
|
|
||||||
# Siehe detaillierte Anleitung in TELEGRAM_BOT_SETUP.md
|
|
||||||
```
|
|
||||||
|
|
||||||
**Kurzanleitung**:
|
|
||||||
1. Erstelle Bot mit @BotFather auf Telegram
|
|
||||||
2. Trage Bot-Token in `config/notifications.json` ein
|
|
||||||
3. Setze `"enabled": true`
|
|
||||||
|
|
||||||
### 5. Automatisierung einrichten
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Cron-Job erstellen (Dienstag & Freitag um 9 Uhr)
|
|
||||||
./setup_cron.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📊 Verwendung
|
|
||||||
|
|
||||||
### Tipps manuell generieren
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Standard: 10 Tipps
|
|
||||||
python scripts/automation/weekly_tip_generator.py --force
|
|
||||||
|
|
||||||
# Anzahl Tipps anpassen
|
|
||||||
python scripts/automation/weekly_tip_generator.py --force --num-tips 20
|
|
||||||
|
|
||||||
# Nur Historie anzeigen
|
|
||||||
python scripts/automation/weekly_tip_generator.py --history
|
|
||||||
```
|
|
||||||
|
|
||||||
### Generator direkt aufrufen
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd scripts/generators
|
|
||||||
python ultimate_ai_ml_hybrid_generator.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test-Benachrichtigung senden
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python scripts/utils/notifier.py --test
|
python scripts/utils/notifier.py --test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 3. Tipps manuell generieren
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python scripts/automation/weekly_tip_generator.py --force --num-tips 10
|
||||||
|
|
||||||
|
# Nur Historie anzeigen
|
||||||
|
python scripts/automation/weekly_tip_generator.py --history
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Daten manuell aktualisieren + Modelle neu trainieren
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./run_update_and_learn.sh
|
||||||
|
```
|
||||||
|
|
||||||
## 🧠 Generator-Strategien
|
## 🧠 Generator-Strategien
|
||||||
|
|
||||||
Der Ultimate AI/ML Hybrid Generator verwendet **4 verschiedene Strategien**:
|
Der Ultimate AI/ML Hybrid Generator (`scripts/generators/ultimate_ai_ml_hybrid_generator.py`) verteilt pro Lauf 10 Tipps über 4 Strategien, deren Gewichtung sich anhand der bisherigen Trefferleistung selbst anpasst (Startwerte in Klammern):
|
||||||
|
|
||||||
1. **PURE-AI** (30%): Basierend ausschließlich auf Machine Learning Predictions
|
1. **HYBRID-OPT** (40%): AI-Score + historisches Pattern-Gewicht kombiniert
|
||||||
2. **PURE-PATTERN** (25%): Historische Pattern-Analyse (NNMMHH-Kombinationen)
|
2. **BALANCED-SPREAD** (30%): erzwingt Verteilung über N/M/H-Zonen, kontrollierter Summenbereich
|
||||||
3. **HYBRID-OPT** (30%): Multi-objektive Optimierung (AI + Pattern + Diversity)
|
3. **HIGH-EV** (20%): 2–3 Zahlen >31, meidet empirisch belegte populäre Einzelzahlen — auf EV statt Trefferwahrscheinlichkeit optimiert
|
||||||
4. **ENSEMBLE** (15%): Best-of-all kombiniert alle Ansätze
|
4. **SOFT-CONTRARIAN** (10%): bevorzugt in den letzten 30 Ziehungen unterrepräsentierte Zahlen
|
||||||
|
|
||||||
### Ausgabe-Beispiel:
|
### Warum EV statt Trefferwahrscheinlichkeit?
|
||||||
|
|
||||||
```
|
Lotto-Ziehungen sind unabhängige Zufallsereignisse — kein Modell kann die
|
||||||
Nr 6 Numbers SZ Strategy AI-Score Pattern-W Confidence Quality
|
Trefferwahrscheinlichkeit über den statistischen Erwartungswert heben. Der
|
||||||
----------------------------------------------------------------------------------------
|
Quality-Score optimiert deshalb bewusst auf **Expected Value**: unpopuläre
|
||||||
1 7-14-21-28-35-42 3 PURE-AI 0.7234 0.3456 0.7123 ⭐ 0.756
|
Zahlenkombinationen haben bei einem Treffer weniger Mitgewinner und damit eine
|
||||||
2 2-11-19-27-36-45 7 HYBRID-OPT 0.6891 0.4123 0.6987 🌟 0.689
|
höhere Auszahlung. Details und Quellen dazu in
|
||||||
3 5-12-23-31-38-47 6 PURE-PATTERN 0.5234 0.5678 0.6234 🌟 0.621
|
[documentation/ARCHITECTURE.md](documentation/ARCHITECTURE.md#3-design-entscheidung-quality-score--ev-optimierung).
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📈 Performance Tracking
|
## 🔄 Daten aktualisieren
|
||||||
|
|
||||||
Alle Generierungen werden in `data/generated_tips/generation_history.json` getrackt:
|
Der automatisierte Workflow (`auto_update_and_learn.py`) versucht in dieser
|
||||||
|
Reihenfolge: **1.** `LottoAPIUpdater.fetch_from_all_apis()` (Lottoland zuerst,
|
||||||
|
liefert die neueste Ziehung meist noch am Ziehungstag, dann GitHub-Archiv für
|
||||||
|
die Historie), **2.** bei leerem API-Ergebnis automatisch der Web-Scraper
|
||||||
|
(`update_from_web.py`, lotto.de) als Fallback.
|
||||||
|
|
||||||
```json
|
Für manuelle Läufe steht zusätzlich bereit:
|
||||||
{
|
|
||||||
"generations": [
|
|
||||||
{
|
|
||||||
"timestamp": "2024-11-27T09:00:00",
|
|
||||||
"num_tips": 10,
|
|
||||||
"file": "weekly_lotto_tips_20241127_090000.csv",
|
|
||||||
"avg_confidence": 0.6834,
|
|
||||||
"avg_quality": 0.6521
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🤖 Automatisierung
|
|
||||||
|
|
||||||
### Cron-Job Zeitplan
|
|
||||||
|
|
||||||
Der `setup_cron.sh` Script richtet folgende Zeitpunkte ein:
|
|
||||||
|
|
||||||
- **Dienstag 09:00**: Tipps für Mittwoch-Ziehung
|
|
||||||
- **Freitag 09:00**: Tipps für Samstag-Ziehung
|
|
||||||
|
|
||||||
### Cron-Job manuell einrichten
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
crontab -e
|
# Letzter Fallback: manuelle Eingabe / CSV-Import
|
||||||
```
|
|
||||||
|
|
||||||
Füge hinzu:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Lotto 6aus49 - Dienstag & Freitag 09:00
|
|
||||||
0 9 * * 2,5 cd /path/to/Lotto && python3 scripts/automation/weekly_tip_generator.py >> logs/weekly_tips.log 2>&1
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📲 Telegram-Benachrichtigungen
|
|
||||||
|
|
||||||
Bei erfolgreicher Generierung erhältst du automatisch:
|
|
||||||
|
|
||||||
- ⭐ **Bester Tipp** (höchste Confidence)
|
|
||||||
- 📊 **Statistiken** (Ø Confidence & Quality)
|
|
||||||
- 🏆 **Top 3 Tipps** nach Confidence sortiert
|
|
||||||
|
|
||||||
Beispiel-Nachricht:
|
|
||||||
|
|
||||||
```
|
|
||||||
🎲 LOTTO 6AUS49 - NEUE TIPPS GENERIERT
|
|
||||||
========================================
|
|
||||||
|
|
||||||
📅 Generiert: 2024-11-27 09:00
|
|
||||||
📊 Anzahl Tipps: 10
|
|
||||||
|
|
||||||
⭐ BESTER TIPP:
|
|
||||||
🎯 Zahlen: 07 - 14 - 21 - 28 - 35 - 42
|
|
||||||
🌟 Superzahl: 3
|
|
||||||
📈 Confidence: 0.7234
|
|
||||||
🎨 Strategie: PURE-AI
|
|
||||||
|
|
||||||
🍀 Viel Glück!
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 Konfiguration
|
|
||||||
|
|
||||||
### notifications.json
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"telegram": {
|
|
||||||
"enabled": true,
|
|
||||||
"bot_token": "YOUR_BOT_TOKEN",
|
|
||||||
"chat_id": "YOUR_CHAT_ID"
|
|
||||||
},
|
|
||||||
"email": {
|
|
||||||
"enabled": false,
|
|
||||||
"smtp_server": "smtp.gmail.com",
|
|
||||||
"smtp_port": 587,
|
|
||||||
"smtp_user": "",
|
|
||||||
"smtp_password": "",
|
|
||||||
"from_email": "",
|
|
||||||
"to_email": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎰 Lotto 6aus49 Regeln
|
|
||||||
|
|
||||||
- **6 Zahlen** aus 50 (1-49)
|
|
||||||
- **1 Superzahl** aus 10 (0-9)
|
|
||||||
- Ziehungen: **Mittwoch** und **Samstag**
|
|
||||||
- Quoten: [www.lotto.de](https://www.lotto.de)
|
|
||||||
|
|
||||||
## 📝 Datenformat
|
|
||||||
|
|
||||||
CSV-Datei `AlleLottozahlen.csv`:
|
|
||||||
|
|
||||||
```csv
|
|
||||||
datum;Z1;Z2;Z3;Z4;Z5;Z6;SZ
|
|
||||||
2024-11-23;7;14;21;28;35;42;3
|
|
||||||
2024-11-20;2;11;19;27;36;45;7
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔄 Daten Aktualisieren
|
|
||||||
|
|
||||||
Das System bietet **3 verschiedene Methoden** zum Aktualisieren der Lottozahlen:
|
|
||||||
|
|
||||||
### Methode 1: API-Update (Empfohlen)
|
|
||||||
|
|
||||||
Automatisches Laden von öffentlichen Lotto-APIs:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd scripts/utils
|
|
||||||
python update_from_api.py
|
|
||||||
```
|
|
||||||
|
|
||||||
**Vorteile:**
|
|
||||||
- ✅ Automatisch
|
|
||||||
- ✅ Schnell
|
|
||||||
- ✅ Keine manuelle Eingabe
|
|
||||||
|
|
||||||
**Hinweis:** Funktioniert nur wenn APIs verfügbar sind.
|
|
||||||
|
|
||||||
### Methode 2: Web-Scraping
|
|
||||||
|
|
||||||
Scrapt aktuelle Ziehungen direkt von lotto.de:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd scripts/utils
|
|
||||||
python update_from_web.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Optionen:
|
|
||||||
```bash
|
|
||||||
# Mehr Seiten laden (mehr historische Daten)
|
|
||||||
python update_from_web.py --pages 10
|
|
||||||
|
|
||||||
# Spezifische Datei
|
|
||||||
python update_from_web.py /pfad/zur/datei.csv 5
|
|
||||||
```
|
|
||||||
|
|
||||||
**Vorteile:**
|
|
||||||
- ✅ Offizielle Quelle (lotto.de)
|
|
||||||
- ✅ Zuverlässig
|
|
||||||
- ✅ Viele historische Daten
|
|
||||||
|
|
||||||
**Nachteile:**
|
|
||||||
- ⚠️ Kann bei Struktur-Änderungen der Website fehlschlagen
|
|
||||||
|
|
||||||
### Methode 3: Manuelle Eingabe (Fallback)
|
|
||||||
|
|
||||||
Für den Fall dass API und Web-Scraping nicht funktionieren:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd scripts/utils
|
|
||||||
python simple_update.py
|
|
||||||
```
|
|
||||||
|
|
||||||
**Zwei Modi:**
|
|
||||||
|
|
||||||
1. **Einzelne Ziehung eingeben:**
|
|
||||||
```
|
|
||||||
Format: YYYY-MM-DD Z1 Z2 Z3 Z4 Z5 Z6 SZ
|
|
||||||
Beispiel: 2024-11-27 7 14 21 28 35 42 3
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **CSV-Import:**
|
|
||||||
```csv
|
|
||||||
datum;Z1;Z2;Z3;Z4;Z5;Z6;SZ
|
|
||||||
2024-11-27;7;14;21;28;35;42;3
|
|
||||||
2024-11-23;2;11;19;27;36;45;7
|
|
||||||
```
|
|
||||||
|
|
||||||
**Vorteile:**
|
|
||||||
- ✅ Funktioniert immer
|
|
||||||
- ✅ Volle Kontrolle
|
|
||||||
- ✅ CSV-Batch-Import möglich
|
|
||||||
|
|
||||||
### Automatisches Backup
|
|
||||||
|
|
||||||
Alle Update-Methoden erstellen **automatisch ein Backup** vor der Änderung:
|
|
||||||
- Gespeichert in: `Lotto/data/backups/`
|
|
||||||
- Format: `AlleLottozahlen.csv.backup_YYYYMMDD_HHMMSS`
|
|
||||||
|
|
||||||
### Update-Workflow Empfehlung
|
|
||||||
|
|
||||||
1. **Versuche zuerst API:**
|
|
||||||
```bash
|
|
||||||
python scripts/utils/update_from_api.py
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Fallback auf Web-Scraping:**
|
|
||||||
```bash
|
|
||||||
python scripts/utils/update_from_web.py
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Letzter Fallback - Manuelle Eingabe:**
|
|
||||||
```bash
|
|
||||||
python scripts/utils/simple_update.py
|
python scripts/utils/simple_update.py
|
||||||
```
|
```
|
||||||
|
|
||||||
### Nach dem Update
|
Jedes Update erstellt automatisch ein Backup unter `data/backups/` und
|
||||||
|
rotiert alte Backups (behält die letzten 30).
|
||||||
|
|
||||||
Nach dem erfolgreichen Update der Daten:
|
## 🐶 Watchdog
|
||||||
|
|
||||||
|
`scripts/utils/watchdog.py` läuft täglich (`com.lotto.watchdog.plist`) und
|
||||||
|
prüft, ob die letzte Ziehung in den Daten älter als 5 Tage ist bzw. ob die
|
||||||
|
Modelle noch auf einem älteren Stand trainiert sind als die neueste Ziehung.
|
||||||
|
Das fängt genau den Fall ab, dass ein `StartCalendarInterval`-Job verpasst
|
||||||
|
wurde (z.B. weil der Rechner im Schlaf war) — launchd holt das selbst nicht
|
||||||
|
nach. Bei einem Problem kommt ein Telegram-Alarm, sonst bleibt es still.
|
||||||
|
|
||||||
|
## 🤖 Automatisierung
|
||||||
|
|
||||||
|
Läuft über **launchd** (`~/Library/LaunchAgents/`), nicht crontab:
|
||||||
|
|
||||||
|
| Job | Plist | Zeitplan |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Update & Learning | `com.lotto.update.plist` | Mi + Sa, 20:00 Uhr |
|
||||||
|
| Tipp-Generierung | `com.lotto.weekly.plist` | Di + Fr, 21:00 Uhr |
|
||||||
|
| Watchdog | `com.lotto.watchdog.plist` | täglich, 21:30 Uhr |
|
||||||
|
|
||||||
|
## 📲 Telegram-Benachrichtigungen
|
||||||
|
|
||||||
|
Bei erfolgreicher Tipp-Generierung bzw. nach jeder ausgewerteten Ziehung
|
||||||
|
verschickt `LottoNotifier` automatisch eine Zusammenfassung (bester Tipp, Ø
|
||||||
|
Confidence/Quality, Trefferauswertung). Der Watchdog nutzt denselben Kanal
|
||||||
|
für Alarme.
|
||||||
|
|
||||||
|
## 📈 Performance Tracking
|
||||||
|
|
||||||
|
Alle Generierungen werden in `data/generated_tips/generation_history.json`
|
||||||
|
protokolliert, alle Ziehungsauswertungen in `data/learning_log.json`.
|
||||||
|
|
||||||
|
## 🧪 Tests
|
||||||
|
|
||||||
|
Regressionstests für den Popularity/EV-Score (`tests/test_popularity_score.py`):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Neue Tipps generieren (mit automatischem Retraining)
|
pip install pytest
|
||||||
python scripts/automation/weekly_tip_generator.py --force
|
pytest tests/
|
||||||
|
|
||||||
# Oder direkt den Generator aufrufen
|
|
||||||
python scripts/generators/ultimate_ai_ml_hybrid_generator.py
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🤖 Automatisches ML-Retraining
|
## 🗂️ Datenformat
|
||||||
|
|
||||||
Das System verwendet **intelligentes Retraining**:
|
`data/AlleLottozahlen.csv` (Semikolon-getrennt):
|
||||||
|
|
||||||
- ✅ **Automatische Erkennung**: Prüft ob CSV neuer als ML-Cache
|
```csv
|
||||||
- ✅ **Smart Caching**: Nutzt Cache wenn Daten unverändert
|
datum;Z1;Z2;Z3;Z4;Z5;Z6;SZ
|
||||||
- ✅ **Auto-Retrain**: Trainiert neu wenn neue Daten verfügbar
|
2026-08-08;2;8;22;30;38;43;4
|
||||||
|
|
||||||
**So funktioniert es:**
|
|
||||||
1. Nach `update_from_api.py` wird CSV aktualisiert
|
|
||||||
2. Beim nächsten Generator-Start: CSV-Timestamp > Cache-Timestamp
|
|
||||||
3. ✅ Automatisches Retraining mit neuen Daten
|
|
||||||
4. Neue ML-Modelle berücksichtigen aktuelle Ziehungen
|
|
||||||
|
|
||||||
**Manuelles Retraining erzwingen:**
|
|
||||||
```bash
|
|
||||||
# Cache löschen für komplettes Retraining
|
|
||||||
rm -rf ultimate_ml_models/
|
|
||||||
python scripts/generators/ultimate_ai_ml_hybrid_generator.py
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🛠️ Entwicklung
|
## Hinweise
|
||||||
|
|
||||||
### Neue Strategie hinzufügen
|
- Lotto 6aus49: 6 Zahlen aus 49 + 1 Superzahl (0–9), Ziehungen Mi + Sa
|
||||||
|
- Keine Gewinngarantie — statistisches Analyse-Tool, keine Vorhersagekraft
|
||||||
1. Öffne `scripts/generators/ultimate_ai_ml_hybrid_generator.py`
|
über den Zufalls-Erwartungswert hinaus (siehe Abschnitt "Warum EV statt
|
||||||
2. Füge neue Strategie in `strategy_weights` hinzu
|
Trefferwahrscheinlichkeit?")
|
||||||
3. Implementiere `_generate_<strategy>_tip()` Methode
|
- Bot-Token niemals öffentlich teilen
|
||||||
4. Update `_generate_tips_by_strategy()`
|
|
||||||
|
|
||||||
### Logging aktivieren
|
|
||||||
|
|
||||||
```python
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logging.basicConfig(
|
|
||||||
filename='logs/generator.log',
|
|
||||||
level=logging.INFO,
|
|
||||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
## ⚠️ Hinweise
|
|
||||||
|
|
||||||
- **Keine Gewinngarantie**: Dies ist ein statistisches Analyse-Tool
|
|
||||||
- **Historische Daten**: Vergangene Ziehungen garantieren keine zukünftigen Ergebnisse
|
|
||||||
- **Verantwortungsvoll spielen**: Lotto ist Glücksspiel
|
|
||||||
- **Bot-Token**: Niemals öffentlich teilen!
|
|
||||||
|
|
||||||
## 📜 Lizenz
|
|
||||||
|
|
||||||
Dieses Projekt ist für private Zwecke. Keine kommerzielle Nutzung.
|
|
||||||
|
|
||||||
## 🙋 Support
|
## 🙋 Support
|
||||||
|
|
||||||
Bei Fragen oder Problemen:
|
1. Logs prüfen: `logs/update_stdout.log`, `logs/stdout.log`, `logs/watchdog_stdout.log`
|
||||||
|
|
||||||
1. Check die Logs: `logs/weekly_tips.log`
|
|
||||||
2. Test-Benachrichtigung: `python scripts/utils/notifier.py --test`
|
2. Test-Benachrichtigung: `python scripts/utils/notifier.py --test`
|
||||||
3. Historie prüfen: `python scripts/automation/weekly_tip_generator.py --history`
|
3. Historie prüfen: `python scripts/automation/weekly_tip_generator.py --history`
|
||||||
|
|
||||||
|
|||||||
@@ -3154,3 +3154,7 @@ datum;Z1;Z2;Z3;Z4;Z5;Z6;SZ
|
|||||||
2026-08-19;3;23;31;33;35;47;6
|
2026-08-19;3;23;31;33;35;47;6
|
||||||
2026-08-22;7;16;22;28;30;31;4
|
2026-08-22;7;16;22;28;30;31;4
|
||||||
2026-08-26;6;15;30;33;42;49;1
|
2026-08-26;6;15;30;33;42;49;1
|
||||||
|
2026-08-29;9;18;28;39;41;48;6
|
||||||
|
2026-09-02;8;19;23;32;44;45;8
|
||||||
|
2026-09-05;6;11;28;31;37;47;5
|
||||||
|
2026-09-09;7;24;28;30;39;41;5
|
||||||
|
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -629,6 +629,34 @@
|
|||||||
"file": "weekly_lotto_tips_20260828_210030.csv",
|
"file": "weekly_lotto_tips_20260828_210030.csv",
|
||||||
"avg_confidence": 0.5506374712982418,
|
"avg_confidence": 0.5506374712982418,
|
||||||
"avg_quality": 0.5772785715969829
|
"avg_quality": 0.5772785715969829
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2026-09-01T21:00:51.165743",
|
||||||
|
"num_tips": 10,
|
||||||
|
"file": "weekly_lotto_tips_20260901_210051.csv",
|
||||||
|
"avg_confidence": 0.5179789527048461,
|
||||||
|
"avg_quality": 0.5936163803435738
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2026-09-04T21:08:30.163353",
|
||||||
|
"num_tips": 10,
|
||||||
|
"file": "weekly_lotto_tips_20260904_210830.csv",
|
||||||
|
"avg_confidence": 0.4558949537384773,
|
||||||
|
"avg_quality": 0.5771370784104253
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2026-09-08T21:11:41.032574",
|
||||||
|
"num_tips": 10,
|
||||||
|
"file": "weekly_lotto_tips_20260908_211141.csv",
|
||||||
|
"avg_confidence": 0.43113063183079586,
|
||||||
|
"avg_quality": 0.5745755401219569
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2026-09-11T21:00:21.363480",
|
||||||
|
"num_tips": 10,
|
||||||
|
"file": "weekly_lotto_tips_20260911_210021.csv",
|
||||||
|
"avg_confidence": 0.44672937432051574,
|
||||||
|
"avg_quality": 0.5866481687048961
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -948,6 +948,58 @@
|
|||||||
"main": 0.7,
|
"main": 0.7,
|
||||||
"sz_rate": 0.1
|
"sz_rate": 0.1
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2026-08-29T20:00:52.886607",
|
||||||
|
"draw_date": "2026-08-29",
|
||||||
|
"evaluation": {
|
||||||
|
"main": 2,
|
||||||
|
"sz": true,
|
||||||
|
"tip": 5
|
||||||
|
},
|
||||||
|
"avg_matches": {
|
||||||
|
"main": 0.8,
|
||||||
|
"sz_rate": 0.1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2026-09-02T20:00:54.961352",
|
||||||
|
"draw_date": "2026-09-02",
|
||||||
|
"evaluation": {
|
||||||
|
"main": 2,
|
||||||
|
"sz": false,
|
||||||
|
"tip": 1
|
||||||
|
},
|
||||||
|
"avg_matches": {
|
||||||
|
"main": 1.1,
|
||||||
|
"sz_rate": 0.1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2026-09-05T20:00:53.963202",
|
||||||
|
"draw_date": "2026-09-05",
|
||||||
|
"evaluation": {
|
||||||
|
"main": 2,
|
||||||
|
"sz": false,
|
||||||
|
"tip": 6
|
||||||
|
},
|
||||||
|
"avg_matches": {
|
||||||
|
"main": 0.6,
|
||||||
|
"sz_rate": 0.1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2026-09-09T20:00:55.140109",
|
||||||
|
"draw_date": "2026-09-09",
|
||||||
|
"evaluation": {
|
||||||
|
"main": 1,
|
||||||
|
"sz": true,
|
||||||
|
"tip": 7
|
||||||
|
},
|
||||||
|
"avg_matches": {
|
||||||
|
"main": 0.4,
|
||||||
|
"sz_rate": 0.1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"trained_at": "2026-08-26T20:00:56.283583",
|
"trained_at": "2026-09-09T20:00:55.132856",
|
||||||
"num_samples": 3135,
|
"num_samples": 3139,
|
||||||
"num_features": 20,
|
"num_features": 20,
|
||||||
"final_loss": 0.3730317278753353,
|
"final_loss": 0.3722805441180362,
|
||||||
"final_val_loss": 0.3733683332800865,
|
"final_val_loss": 0.37351479232311247,
|
||||||
"best_val_loss": 0.37311964482069016,
|
"best_val_loss": 0.3732968047261238,
|
||||||
"epochs_trained": 28
|
"epochs_trained": 30
|
||||||
}
|
}
|
||||||
Binary file not shown.
@@ -1,61 +1,61 @@
|
|||||||
{
|
{
|
||||||
"adjustments": {
|
"adjustments": {
|
||||||
"1": -0.19478554069866538,
|
"1": -0.2106701973228925,
|
||||||
"2": -0.18901075949778243,
|
"2": -0.20501004841262846,
|
||||||
"3": -0.17621167205923433,
|
"3": -0.1924650292512798,
|
||||||
"4": -0.18956571514574574,
|
"4": -0.20555398791384874,
|
||||||
"5": -0.17727980514372696,
|
"5": -0.1935119593606463,
|
||||||
"6": -0.20113063443288826,
|
"6": -0.20203896277790986,
|
||||||
"7": -0.11518430063362944,
|
"7": -0.11772408162401678,
|
||||||
"8": -0.18146617313838923,
|
"8": -0.182839102735047,
|
||||||
"9": -0.18759303973870778,
|
"9": -0.1889182285893702,
|
||||||
"10": -0.1677611820598764,
|
"10": -0.18418228569837258,
|
||||||
"11": -0.18219933065355118,
|
"11": -0.1834834548324124,
|
||||||
"12": -0.20960960726910235,
|
"12": -0.22519999876913804,
|
||||||
"13": -0.16373141392375076,
|
"13": -0.18023251047211447,
|
||||||
"14": -0.22788450336984212,
|
"14": -0.2431121290562519,
|
||||||
"15": -0.11295326493095584,
|
"15": -0.13046233309416472,
|
||||||
"16": -0.16515777429003534,
|
"16": -0.1816305568728396,
|
||||||
"17": -0.20387467951219515,
|
"17": -0.219578912192085,
|
||||||
"18": -0.20533691607503754,
|
"18": -0.20630988011957557,
|
||||||
"19": -0.19401957406253612,
|
"19": -0.19514331238199498,
|
||||||
"20": -0.06410859904192305,
|
"20": -0.08258725821483424,
|
||||||
"21": -0.2189476922004348,
|
"21": -0.2343527180513774,
|
||||||
"22": -0.18703774486077154,
|
"22": -0.2030761991014364,
|
||||||
"23": -0.17986662678637222,
|
"23": -0.18127130817689097,
|
||||||
"24": -0.19143450263033077,
|
"24": -0.19246067903363895,
|
||||||
"25": -0.15615639580581434,
|
"25": -0.17280786024659375,
|
||||||
"26": -0.1629012047154243,
|
"26": -0.17941878133115902,
|
||||||
"27": -0.16573545711626494,
|
"27": -0.1821967724064882,
|
||||||
"28": -0.14097169337511936,
|
"28": -0.11344696423263387,
|
||||||
"29": -0.19412106797494957,
|
"29": -0.21001891471456352,
|
||||||
"30": -0.15821678362881567,
|
"30": -0.15990234834240233,
|
||||||
"31": -0.1806700907184887,
|
"31": -0.18198457107372512,
|
||||||
"32": -0.14317751187959615,
|
"32": -0.1453104905226412,
|
||||||
"33": -0.18911248952822005,
|
"33": -0.2051097590511604,
|
||||||
"34": -0.24300222383281544,
|
"34": -0.25792975521862355,
|
||||||
"35": -0.14889768383078938,
|
"35": -0.16569323732909239,
|
||||||
"36": -0.19962377282817678,
|
"36": -0.2154123881285409,
|
||||||
"37": -0.12131189581893032,
|
"37": -0.1238046659849216,
|
||||||
"38": -0.23006634008932061,
|
"38": -0.24525065522729406,
|
||||||
"39": -0.14892185306375724,
|
"39": -0.1360896842813413,
|
||||||
"40": -0.19554822180621415,
|
"40": -0.21141773882959256,
|
||||||
"41": -0.2004735679542188,
|
"41": -0.18661807188758955,
|
||||||
"42": -0.1605865902425092,
|
"42": -0.1771501131113919,
|
||||||
"43": -0.16395664875494143,
|
"43": -0.1804532742794294,
|
||||||
"44": -0.16636537531663315,
|
"44": -0.16803806329101365,
|
||||||
"45": -0.22780245811738084,
|
"45": -0.2282555893180233,
|
||||||
"46": -0.2110493342300891,
|
"46": -0.22661114643098554,
|
||||||
"47": -0.17439901746633996,
|
"47": -0.17583798175724874,
|
||||||
"48": -0.16614824262473232,
|
"48": -0.16789912140710272,
|
||||||
"49": -0.15277862794792954
|
"49": -0.1694971427674608
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"incorrect": 2838,
|
"incorrect": 3010,
|
||||||
"correct": 396,
|
"correct": 420,
|
||||||
"cycles": 66,
|
"cycles": 70,
|
||||||
"last_update": "2026-08-26T20:00:56.286878"
|
"last_update": "2026-09-09T20:00:55.138048"
|
||||||
},
|
},
|
||||||
"learning_rate": 0.1,
|
"learning_rate": 0.1,
|
||||||
"last_saved": "2026-08-26T20:00:56.286936"
|
"last_saved": "2026-09-09T20:00:55.138109"
|
||||||
}
|
}
|
||||||
Binary file not shown.
@@ -63,7 +63,7 @@ Alle Pfade relativ zum Projekt-Root.
|
|||||||
|
|
||||||
| Komponente | Datei | Rolle |
|
| Komponente | Datei | Rolle |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `LottoAPIUpdater` | `scripts/utils/update_from_api.py` | Holt neue Ziehungen über `fetch_from_all_apis()`: Lottoland zuerst (liefert die neueste Ziehung meist noch am Ziehungstag), GitHub-Archiv für die Historie/als Fallback (kann tagelang hinterherhinken). `auto_update_and_learn.py` nutzt seit August 2026 `api_name='all'`, davor fest `api_name='github'` — daher wiederholt verspätete Ziehungen in der Vergangenheit. |
|
| `LottoAPIUpdater` | `scripts/utils/update_from_api.py` | Holt neue Ziehungen über `fetch_from_all_apis()`: Lottoland zuerst (liefert die neueste Ziehung meist noch am Ziehungstag), GitHub-Archiv für die Historie/als Fallback (kann tagelang hinterherhinken). `auto_update_and_learn.py` nutzt seit August 2026 `api_name='all'`, davor fest `api_name='github'` — daher wiederholt verspätete Ziehungen in der Vergangenheit. Liefert die API-Kette nichts Neues, greift automatisch `LottoWebUpdater` (lotto.de-Scraping) als dritter Fallback. |
|
||||||
| `AutoUpdateAndLearn` | `scripts/automation/auto_update_and_learn.py` | Orchestriert den 4-Schritte-Workflow: Daten aktualisieren → neue Ziehung prüfen → letzte Tipps evaluieren → Learning-Update (Retraining). |
|
| `AutoUpdateAndLearn` | `scripts/automation/auto_update_and_learn.py` | Orchestriert den 4-Schritte-Workflow: Daten aktualisieren → neue Ziehung prüfen → letzte Tipps evaluieren → Learning-Update (Retraining). |
|
||||||
| `FeatureEngineer` | `scripts/generators/ultimate_ai_ml_hybrid_generator.py` | Baut 20 Features (Frequenzen, Gaps, Momentum, Trends, Beziehungen, Zyklen) aus den Rohdaten. |
|
| `FeatureEngineer` | `scripts/generators/ultimate_ai_ml_hybrid_generator.py` | Baut 20 Features (Frequenzen, Gaps, Momentum, Trends, Beziehungen, Zyklen) aus den Rohdaten. |
|
||||||
| `AIMLEngine` | `scripts/generators/ultimate_ai_ml_hybrid_generator.py` | RandomForest-Modelle, ein Modell pro Zahl (1–49). Cached unter `data/ultimate_ml_models/`. |
|
| `AIMLEngine` | `scripts/generators/ultimate_ai_ml_hybrid_generator.py` | RandomForest-Modelle, ein Modell pro Zahl (1–49). Cached unter `data/ultimate_ml_models/`. |
|
||||||
@@ -126,17 +126,28 @@ spiegeln nicht den tatsächlichen Zeitplan wider:
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `com.lotto.update` | `scripts/automation/com.lotto.update.plist` | Mi + Sa, 20:00 Uhr |
|
| `com.lotto.update` | `scripts/automation/com.lotto.update.plist` | Mi + Sa, 20:00 Uhr |
|
||||||
| `com.lotto.weekly` | `scripts/automation/com.lotto.weekly.plist` | Di + Fr, 21:00 Uhr |
|
| `com.lotto.weekly` | `scripts/automation/com.lotto.weekly.plist` | Di + Fr, 21:00 Uhr |
|
||||||
|
| `com.lotto.watchdog` | `scripts/automation/com.lotto.watchdog.plist` | täglich, 21:30 Uhr |
|
||||||
|
|
||||||
Logs: `logs/update_stdout.log` (Update+Learning), `logs/stdout.log`
|
Logs: `logs/update_stdout.log` (Update+Learning), `logs/stdout.log`
|
||||||
(Tipp-Generierung).
|
(Tipp-Generierung), `logs/watchdog_stdout.log` (Watchdog).
|
||||||
|
|
||||||
|
`scripts/utils/watchdog.py` prüft, ob die letzte Ziehung in den Daten älter
|
||||||
|
als 5 Tage ist bzw. die Modelle hinter der neuesten Ziehung zurückliegen, und
|
||||||
|
schickt bei einem Treffer einen Telegram-Alarm über `LottoNotifier.send_alert()`.
|
||||||
|
Grund: `StartCalendarInterval`-Jobs, die launchd wegen Schlafmodus verpasst,
|
||||||
|
werden nicht automatisch nachgeholt — ohne diesen Check fiel das erst bei der
|
||||||
|
nächsten manuellen Nachfrage auf (so geschehen am 15.08.2026).
|
||||||
|
|
||||||
|
Backups (`data/backups/`) werden bei jedem Update automatisch erstellt und
|
||||||
|
auf die letzten 30 rotiert (`LottoAPIUpdater._prune_old_backups`).
|
||||||
|
|
||||||
## 5. Bekannte Schwachstellen / offene Punkte
|
## 5. Bekannte Schwachstellen / offene Punkte
|
||||||
|
|
||||||
- **`update_from_web.py`** (lotto.de-Scraper) ist als dritter Fallback
|
|
||||||
implementiert, aber nirgends in der automatisierten Pipeline eingebunden.
|
|
||||||
- **Utility-Skripte** in `scripts/utils/` (`health_check.py`, `validate_csv.py`,
|
- **Utility-Skripte** in `scripts/utils/` (`health_check.py`, `validate_csv.py`,
|
||||||
`verify_draws.py`, `model_evaluator.py`) existieren, sind aber nicht in die
|
`verify_draws.py`, `model_evaluator.py`) existieren, sind aber nicht in die
|
||||||
Cron-Automatisierung eingebunden; manuelle Ausführung bei Bedarf.
|
Cron-Automatisierung eingebunden; manuelle Ausführung bei Bedarf.
|
||||||
|
- Kein automatisiertes Retry/Catch-up für komplett verpasste launchd-Läufe
|
||||||
|
(nur Alarmierung via Watchdog) — Nachholen bleibt manuell.
|
||||||
|
|
||||||
## 6. Code-Graph
|
## 6. Code-Graph
|
||||||
|
|
||||||
@@ -149,3 +160,10 @@ liegt unter `graphify-out/`:
|
|||||||
|
|
||||||
Bei größeren strukturellen Änderungen: `/graphify --update` zum
|
Bei größeren strukturellen Änderungen: `/graphify --update` zum
|
||||||
inkrementellen Neuaufbau.
|
inkrementellen Neuaufbau.
|
||||||
|
|
||||||
|
## 7. Tests
|
||||||
|
|
||||||
|
`tests/test_popularity_score.py` (pytest) prüft den Popularity/EV-Score
|
||||||
|
gegen bekannte Vielspieler-Kombinationen und deckt Regressionen wie den
|
||||||
|
Sum-Extremity-Bug (Abschnitt 3) sowie die volle Superzahl-Abdeckung über
|
||||||
|
einen 10er-Batch ab. Ausführen mit `pytest tests/`.
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
# Häufigste Superzahlen – Lotto 6aus49
|
# Häufigste Superzahlen – Lotto 6aus49
|
||||||
|
|
||||||
Stand: 3.152 Ziehungen (inkl. Ziehung vom 15.08.2026)
|
Stand: 3.155 Ziehungen (inkl. Ziehung vom 26.08.2026)
|
||||||
|
|
||||||
| Rang | Superzahl | Häufigkeit | Anteil |
|
| Rang | Superzahl | Häufigkeit | Anteil |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| 1 | **7** | 342× | 10,85% |
|
| 1 | **7** | 342× | 10,84% |
|
||||||
| 2 | **5** | 333× | 10,56% |
|
| 2 | **5** | 333× | 10,55% |
|
||||||
| 3 | **4** | 327× | 10,37% |
|
| 3 | **4** | 328× | 10,40% |
|
||||||
| 4 | **6** | 318× | 10,09% |
|
| 4 | **6** | 319× | 10,11% |
|
||||||
| 5 | **3** | 317× | 10,06% |
|
| 5 | **3** | 317× | 10,05% |
|
||||||
| 6 | **0** | 311× | 9,87% |
|
| 6 | **0** | 311× | 9,86% |
|
||||||
| 7 | **1** | 306× | 9,71% |
|
| 7 | **1** | 307× | 9,73% |
|
||||||
| 8 | **2** | 306× | 9,71% |
|
| 8 | **2** | 306× | 9,70% |
|
||||||
| 9 | **8** | 299× | 9,49% |
|
| 9 | **8** | 299× | 9,48% |
|
||||||
| 10 | **9** | 293× | 9,30% |
|
| 10 | **9** | 293× | 9,29% |
|
||||||
|
|
||||||
## Einordnung
|
## Einordnung
|
||||||
|
|
||||||
Bei theoretisch exakt gleicher Wahrscheinlichkeit (10,0% pro Ziffer) liegt die
|
Bei theoretisch exakt gleicher Wahrscheinlichkeit (10,0% pro Ziffer) liegt die
|
||||||
erwartete Zufallsschwankung bei ±0,5 Prozentpunkten über 3.152 Ziehungen — die
|
erwartete Zufallsschwankung bei ±0,5 Prozentpunkten über 3.155 Ziehungen — die
|
||||||
gemessene Spanne (9,30%–10,85%) liegt genau in diesem Rauschband. Das ist
|
gemessene Spanne (9,29%–10,84%) liegt genau in diesem Rauschband. Das ist
|
||||||
**kein Beleg dafür, dass die 7 "häufiger kommt"**, sondern reine
|
**kein Beleg dafür, dass die 7 "häufiger kommt"**, sondern reine
|
||||||
Stichprobenstreuung, da jede Ziehung unabhängig und gleichverteilt ist.
|
Stichprobenstreuung, da jede Ziehung unabhängig und gleichverteilt ist.
|
||||||
|
|
||||||
|
|||||||
Executable
+4
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
cd "/Users/sebastianfrohlich/Projekte/Lotto"
|
||||||
|
source venv/bin/activate
|
||||||
|
python scripts/utils/watchdog.py
|
||||||
@@ -27,6 +27,7 @@ project_dir = os.path.dirname(os.path.dirname(script_dir))
|
|||||||
sys.path.insert(0, project_dir)
|
sys.path.insert(0, project_dir)
|
||||||
|
|
||||||
from scripts.utils.update_from_api import LottoAPIUpdater
|
from scripts.utils.update_from_api import LottoAPIUpdater
|
||||||
|
from scripts.utils.update_from_web import LottoWebUpdater
|
||||||
from scripts.generators.ultimate_ai_ml_hybrid_generator import UltimateAIMLHybridGenerator
|
from scripts.generators.ultimate_ai_ml_hybrid_generator import UltimateAIMLHybridGenerator
|
||||||
from scripts.utils.notifier import LottoNotifier
|
from scripts.utils.notifier import LottoNotifier
|
||||||
|
|
||||||
@@ -133,7 +134,16 @@ class AutoUpdateAndLearn:
|
|||||||
if success:
|
if success:
|
||||||
print(" ✅ Daten erfolgreich aktualisiert")
|
print(" ✅ Daten erfolgreich aktualisiert")
|
||||||
else:
|
else:
|
||||||
print(" ⚠️ Update ohne neue Daten")
|
print(" ⚠️ API-Update ohne neue Daten, versuche Web-Scraper...")
|
||||||
|
try:
|
||||||
|
scraper = LottoWebUpdater(self.data_file)
|
||||||
|
success = scraper.update(create_backup=False)
|
||||||
|
if success:
|
||||||
|
print(" ✅ Web-Scraper erfolgreich")
|
||||||
|
else:
|
||||||
|
print(" ⚠️ Web-Scraper ohne neue Daten")
|
||||||
|
except Exception as scrape_err:
|
||||||
|
print(f" ❌ Web-Scraper Fehler: {scrape_err}")
|
||||||
|
|
||||||
return success
|
return success
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key>
|
||||||
|
<string>com.lotto.watchdog</string>
|
||||||
|
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string>/Users/sebastianfrohlich/Projekte/Lotto/run_watchdog.sh</string>
|
||||||
|
</array>
|
||||||
|
|
||||||
|
<key>StartCalendarInterval</key>
|
||||||
|
<dict>
|
||||||
|
<key>Hour</key>
|
||||||
|
<integer>21</integer>
|
||||||
|
<key>Minute</key>
|
||||||
|
<integer>30</integer>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
<key>WorkingDirectory</key>
|
||||||
|
<string>/Users/sebastianfrohlich/Projekte/Lotto</string>
|
||||||
|
|
||||||
|
<key>StandardOutPath</key>
|
||||||
|
<string>/Users/sebastianfrohlich/Projekte/Lotto/logs/watchdog_stdout.log</string>
|
||||||
|
<key>StandardErrorPath</key>
|
||||||
|
<string>/Users/sebastianfrohlich/Projekte/Lotto/logs/watchdog_stderr.log</string>
|
||||||
|
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<false/>
|
||||||
|
|
||||||
|
<key>EnvironmentVariables</key>
|
||||||
|
<dict>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
||||||
|
<key>LANG</key>
|
||||||
|
<string>de_DE.UTF-8</string>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
<key>LaunchOnlyOnce</key>
|
||||||
|
<false/>
|
||||||
|
|
||||||
|
<key>Nice</key>
|
||||||
|
<integer>10</integer>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -265,6 +265,36 @@ class LottoNotifier:
|
|||||||
# TODO: Email-Versand implementieren wenn gewünscht
|
# TODO: Email-Versand implementieren wenn gewünscht
|
||||||
print("⚠️ Email-Benachrichtigung noch nicht implementiert")
|
print("⚠️ Email-Benachrichtigung noch nicht implementiert")
|
||||||
|
|
||||||
|
def send_alert(self, message: str) -> bool:
|
||||||
|
"""Sendet eine formlose Warn-/Alarm-Nachricht (z.B. vom Watchdog)."""
|
||||||
|
if not self.config['telegram']['enabled']:
|
||||||
|
print("⚠️ Telegram nicht aktiviert in config/notifications.json")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
bot_token = self.config['telegram']['bot_token']
|
||||||
|
chat_id = self.config['telegram']['chat_id']
|
||||||
|
|
||||||
|
url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
|
||||||
|
data = {
|
||||||
|
"chat_id": chat_id,
|
||||||
|
"text": message,
|
||||||
|
"parse_mode": "Markdown"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(url, data=data, timeout=10)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("✅ Alarm-Benachrichtigung gesendet")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"⚠️ Telegram-Fehler: {response.status_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Alarm-Benachrichtigung fehlgeschlagen: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def send_test_notification(self):
|
def send_test_notification(self):
|
||||||
"""Sendet Test-Benachrichtigung."""
|
"""Sendet Test-Benachrichtigung."""
|
||||||
if self.config['telegram']['enabled']:
|
if self.config['telegram']['enabled']:
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ class LottoAPIUpdater:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def create_backup(self) -> bool:
|
def create_backup(self) -> bool:
|
||||||
"""Erstellt Backup."""
|
"""Erstellt Backup und behält nur die letzten KEEP_BACKUPS Stück."""
|
||||||
if not os.path.exists(self.data_file):
|
if not os.path.exists(self.data_file):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -99,7 +99,10 @@ class LottoAPIUpdater:
|
|||||||
print("\n💾 Erstelle Backup...")
|
print("\n💾 Erstelle Backup...")
|
||||||
|
|
||||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
backup_dir = os.path.join(os.path.dirname(self.data_file), "data", "backups")
|
# self.data_file liegt bereits unter .../data/ - vorher wurde hier
|
||||||
|
# zusätzlich "data" angehängt, was seit Monaten ungenutzt nach
|
||||||
|
# .../data/data/backups/ statt .../data/backups/ geschrieben hat.
|
||||||
|
backup_dir = os.path.join(os.path.dirname(self.data_file), "backups")
|
||||||
os.makedirs(backup_dir, exist_ok=True)
|
os.makedirs(backup_dir, exist_ok=True)
|
||||||
|
|
||||||
filename = os.path.basename(self.data_file)
|
filename = os.path.basename(self.data_file)
|
||||||
@@ -108,12 +111,28 @@ class LottoAPIUpdater:
|
|||||||
shutil.copy2(self.data_file, self.backup_file)
|
shutil.copy2(self.data_file, self.backup_file)
|
||||||
print(f" ✅ Backup: {os.path.basename(self.backup_file)}")
|
print(f" ✅ Backup: {os.path.basename(self.backup_file)}")
|
||||||
|
|
||||||
|
self._prune_old_backups(backup_dir, filename)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" ⚠️ Backup-Fehler: {e}")
|
print(f" ⚠️ Backup-Fehler: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _prune_old_backups(backup_dir: str, filename: str, keep: int = 30) -> None:
|
||||||
|
"""Behält nur die KEEP neuesten Backups, löscht den Rest."""
|
||||||
|
pattern = f"{filename}.backup_"
|
||||||
|
backups = sorted(
|
||||||
|
(f for f in os.listdir(backup_dir) if f.startswith(pattern)),
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
|
for old in backups[keep:]:
|
||||||
|
try:
|
||||||
|
os.remove(os.path.join(backup_dir, old))
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
def fetch_from_api(self, api_name: str) -> List[Dict]:
|
def fetch_from_api(self, api_name: str) -> List[Dict]:
|
||||||
"""
|
"""
|
||||||
Holt Daten von spezifischer API.
|
Holt Daten von spezifischer API.
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Watchdog für die Lotto-Automatisierung.
|
||||||
|
|
||||||
|
Prüft die Aktualität von Ziehungsdaten und trainierten Modellen anhand des
|
||||||
|
Ziehungskalenders (Mi + Sa) und meldet sich per Telegram, wenn etwas länger
|
||||||
|
stillsteht als der launchd-Zeitplan erlaubt. Das fängt genau den Fall ab, in
|
||||||
|
dem ein StartCalendarInterval-Job verpasst wurde (z.B. weil der Mac im Schlaf
|
||||||
|
war) - launchd holt das selbst nicht nach, und ohne diesen Check fällt es
|
||||||
|
sonst erst bei der nächsten manuellen Nachfrage auf.
|
||||||
|
|
||||||
|
Aufruf: python3 scripts/utils/watchdog.py
|
||||||
|
Gedacht für einen täglichen launchd-Job (siehe com.lotto.watchdog.plist).
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
from notifier import LottoNotifier
|
||||||
|
|
||||||
|
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
DATA_FILE = os.path.join(PROJECT_ROOT, "data", "AlleLottozahlen.csv")
|
||||||
|
MODEL_FILE = os.path.join(PROJECT_ROOT, "data", "ultimate_ml_models", "trained_models.pkl")
|
||||||
|
|
||||||
|
# Lotto zieht Mi + Sa -> maximal 4 Tage zwischen zwei Ziehungen. 5 Tage
|
||||||
|
# Toleranz lassen dem Update-Job (selber Abend, 20 Uhr) Luft, ohne echte
|
||||||
|
# Ausfälle zu verpassen.
|
||||||
|
MAX_DAYS_SINCE_DRAW = 5
|
||||||
|
|
||||||
|
|
||||||
|
def check() -> list[str]:
|
||||||
|
"""Prüft Daten- und Modell-Aktualität, gibt gefundene Probleme zurück."""
|
||||||
|
problems = []
|
||||||
|
|
||||||
|
df = pd.read_csv(DATA_FILE, sep=';')
|
||||||
|
last_draw = pd.to_datetime(df['datum'], format='%Y-%m-%d').max().date()
|
||||||
|
days_since_draw = (datetime.now().date() - last_draw).days
|
||||||
|
|
||||||
|
if days_since_draw > MAX_DAYS_SINCE_DRAW:
|
||||||
|
problems.append(
|
||||||
|
f"Letzte Ziehung in den Daten ist {last_draw.isoformat()} "
|
||||||
|
f"({days_since_draw} Tage her) - der automatische Update-Lauf "
|
||||||
|
f"scheint ausgefallen zu sein."
|
||||||
|
)
|
||||||
|
|
||||||
|
if os.path.exists(MODEL_FILE):
|
||||||
|
model_date = datetime.fromtimestamp(os.path.getmtime(MODEL_FILE)).date()
|
||||||
|
if model_date < last_draw and (datetime.now().date() - model_date).days > 1:
|
||||||
|
problems.append(
|
||||||
|
f"Modelle sind vom {model_date.isoformat()}, aber die neueste "
|
||||||
|
f"Ziehung ({last_draw.isoformat()}) wurde noch nicht eingelernt."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
problems.append("Modell-Datei trained_models.pkl fehlt komplett.")
|
||||||
|
|
||||||
|
return problems
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
problems = check()
|
||||||
|
if not problems:
|
||||||
|
print("✅ Watchdog: alles aktuell.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("⚠️ Watchdog hat Probleme gefunden:")
|
||||||
|
for p in problems:
|
||||||
|
print(f" - {p}")
|
||||||
|
|
||||||
|
message = "🐶 *LOTTO WATCHDOG - PROBLEM ERKANNT*\n\n" + "\n".join(f"⚠️ {p}" for p in problems)
|
||||||
|
LottoNotifier().send_alert(message)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
"""
|
||||||
|
Regressionstests für den Popularity/EV-Score.
|
||||||
|
|
||||||
|
Diese Tests bestehen aus bekannten, real dokumentierten Vielspieler-
|
||||||
|
Kombinationen (siehe documentation/ARCHITECTURE.md Abschnitt 3) und stellen
|
||||||
|
sicher, dass der Score sie konsistent NIEDRIGER bewertet als gestreute
|
||||||
|
Kombinationen. Genau das hatte der Sum-Extremity-Bug (Commit e05f1f1)
|
||||||
|
verletzt: eng geclusterte Zahlenfolgen wie 1-2-3-4-5-6 wurden durch ihre
|
||||||
|
extreme (niedrige) Summe fälschlich AUFGEWERTET statt abgewertet, weil die
|
||||||
|
Summen-Komponente unabhängig von der Muster-Erkennung lief. Ohne diesen Test
|
||||||
|
wäre der Bug nur durch zufälliges manuelles Nachrechnen aufgefallen.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts", "generators"))
|
||||||
|
|
||||||
|
from ultimate_ai_ml_hybrid_generator import UltimateAIMLHybridGenerator as Generator
|
||||||
|
|
||||||
|
# Bekannte, historisch meistgespielte Kombinationen (siehe ARCHITECTURE.md).
|
||||||
|
OVERPLAYED_COMBOS = [
|
||||||
|
[1, 2, 3, 4, 5, 6],
|
||||||
|
[5, 10, 15, 20, 25, 30],
|
||||||
|
]
|
||||||
|
|
||||||
|
# Gestreute Kombinationen ohne erkennbares menschliches Auswahlmuster.
|
||||||
|
SPREAD_COMBOS = [
|
||||||
|
[3, 16, 22, 43, 48, 49], # echte Ziehung vom 29.07.2026
|
||||||
|
[1, 13, 25, 37, 41, 49],
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_overplayed_combos_score_low():
|
||||||
|
for combo in OVERPLAYED_COMBOS:
|
||||||
|
score = Generator._calculate_popularity_score(combo)
|
||||||
|
assert score < 0.3, f"{combo} sollte als populär (niedriger Score) gelten, war {score:.3f}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_spread_combos_score_high():
|
||||||
|
for combo in SPREAD_COMBOS:
|
||||||
|
score = Generator._calculate_popularity_score(combo)
|
||||||
|
assert score > 0.5, f"{combo} sollte als unpopulär (hoher Score) gelten, war {score:.3f}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_overplayed_never_outscores_spread():
|
||||||
|
"""Regressionsschutz für den Sum-Extremity-Bug: eine bekannte
|
||||||
|
Vielspieler-Kombination darf NIE höher bewerten als eine gestreute."""
|
||||||
|
worst_overplayed = max(Generator._calculate_popularity_score(c) for c in OVERPLAYED_COMBOS)
|
||||||
|
best_spread = min(Generator._calculate_popularity_score(c) for c in SPREAD_COMBOS)
|
||||||
|
assert worst_overplayed < best_spread
|
||||||
|
|
||||||
|
|
||||||
|
def test_arithmetic_progression_penalized_regardless_of_step():
|
||||||
|
"""Nicht nur direkte Nachbarn (+1), auch andere Schrittweiten (hier +7)
|
||||||
|
müssen als Muster erkannt werden."""
|
||||||
|
step_one = Generator._calculate_popularity_score([10, 11, 12, 13, 14, 15])
|
||||||
|
step_seven = Generator._calculate_popularity_score([2, 9, 16, 23, 30, 37])
|
||||||
|
spread = Generator._calculate_popularity_score([2, 14, 23, 31, 40, 47])
|
||||||
|
assert step_one < spread
|
||||||
|
assert step_seven < spread
|
||||||
|
|
||||||
|
|
||||||
|
def test_balanced_odd_even_split_scores_lower_than_extreme_split():
|
||||||
|
"""Menschen bevorzugen 3-3-Splits, obwohl jede Kombination gleich
|
||||||
|
wahrscheinlich ist - ein extremer Split (hier 6 gerade) sollte daher
|
||||||
|
einen höheren Score bekommen als ein 3-3-Split mit sonst identischen
|
||||||
|
Eigenschaften."""
|
||||||
|
balanced = Generator._calculate_popularity_score([2, 3, 34, 35, 46, 47]) # 3 gerade, 3 ungerade
|
||||||
|
extreme = Generator._calculate_popularity_score([2, 4, 34, 36, 46, 48]) # 6 gerade
|
||||||
|
assert extreme > balanced
|
||||||
|
|
||||||
|
|
||||||
|
def test_score_bounded_between_zero_and_one():
|
||||||
|
for combo in OVERPLAYED_COMBOS + SPREAD_COMBOS:
|
||||||
|
score = Generator._calculate_popularity_score(combo)
|
||||||
|
assert 0.0 <= score <= 1.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_superzahl_covers_all_ten_digits_across_one_batch():
|
||||||
|
"""Die EV-Rangfolge muss über einen 10er-Batch alle Ziffern 0-9 genau
|
||||||
|
einmal abdecken (Regressionsschutz gegen die alte, historienbasierte
|
||||||
|
Logik, die einzelne Ziffern - z.B. historisch häufige - bevorzugt hat)."""
|
||||||
|
digits = {Generator._get_smart_superzahl(None, tip) for tip in range(1, 11)}
|
||||||
|
assert digits == set(range(10))
|
||||||
Reference in new issue
Block a user