Add Deep Learning (LSTM) + Quick Wins automation features
Major improvements: - Deep Learning integration with PyTorch LSTM (Bidirectional, 128→64 units) - Hybrid predictor: 40% RandomForest + 60% Deep Learning - LaunchAgent for automatic weekly tip generation (Tue/Fri 21:00) - Health-Check system with auto-recovery and Telegram alerts - Model caching and intelligent retraining logic - Updated CSV data and generated tips - Performance reports for recent draws Technical details: - PyTorch used instead of TensorFlow (Python 3.14 compatibility) - Apple Silicon MPS acceleration support - Sequence learning with 20-draw history - Early stopping and learning rate scheduling 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4967,3 +4967,9 @@ datum;Z1;Z2;Z3;Z4;Z5;Z6;SZ
|
||||
2025-12-06;15;26;27;33;35;37;2.0
|
||||
2025-12-10;3;11;12;13;17;24;8.0
|
||||
2025-12-13;6;11;21;32;37;45;3.0
|
||||
2025-12-17;6;13;25;26;32;37;2.0
|
||||
2025-12-20;12;23;32;33;40;42;5.0
|
||||
2025-12-24;7;8;25;32;37;39;8.0
|
||||
2025-12-27;7;10;27;28;38;46;6.0
|
||||
2025-12-31;3;5;7;28;36;41;5.0
|
||||
2026-01-03;3;8;11;12;16;46;8.0
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -62,6 +62,62 @@
|
||||
"file": "weekly_lotto_tips_20251216_090036.csv",
|
||||
"avg_confidence": 0.2526153083488077,
|
||||
"avg_quality": 0.4270460107501856
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-12-19T09:00:47.995332",
|
||||
"num_tips": 10,
|
||||
"file": "weekly_lotto_tips_20251219_090047.csv",
|
||||
"avg_confidence": 0.24851173163378942,
|
||||
"avg_quality": 0.42254649408361306
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-12-23T09:00:39.273434",
|
||||
"num_tips": 10,
|
||||
"file": "weekly_lotto_tips_20251223_090039.csv",
|
||||
"avg_confidence": 0.24564556363017442,
|
||||
"avg_quality": 0.42297042510202276
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-12-26T09:00:29.472712",
|
||||
"num_tips": 10,
|
||||
"file": "weekly_lotto_tips_20251226_090029.csv",
|
||||
"avg_confidence": 0.24881269580518275,
|
||||
"avg_quality": 0.4232548718131768
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-12-30T09:00:29.644794",
|
||||
"num_tips": 10,
|
||||
"file": "weekly_lotto_tips_20251230_090029.csv",
|
||||
"avg_confidence": 0.2493553812828854,
|
||||
"avg_quality": 0.422702688633027
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-05T11:57:12.284547",
|
||||
"num_tips": 10,
|
||||
"file": "weekly_lotto_tips_20260105_115712.csv",
|
||||
"avg_confidence": 0.2534916290186907,
|
||||
"avg_quality": 0.4275052872775139
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-05T14:18:43.847636",
|
||||
"num_tips": 5,
|
||||
"file": "weekly_lotto_tips_20260105_141843.csv",
|
||||
"avg_confidence": 0.22396252964735192,
|
||||
"avg_quality": 0.4176696066987704
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-05T14:26:54.619984",
|
||||
"num_tips": 5,
|
||||
"file": "weekly_lotto_tips_20260105_142654.csv",
|
||||
"avg_confidence": 0.38086368558448874,
|
||||
"avg_quality": 0.5874078043214264
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-05T19:31:53.727772",
|
||||
"num_tips": 10,
|
||||
"file": "weekly_lotto_tips_20260105_193153.csv",
|
||||
"avg_confidence": 0.4169894391741245,
|
||||
"avg_quality": 0.6052850635944613
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
Tip_Number,Numbers,Superzahl,Strategy,AI_Score,Pattern_Weight,Confidence,Quality
|
||||
1,12-14-17-45-46-48,9,HYBRID-OPT,0.2117,0.0976,0.3294,0.4224
|
||||
2,1-12-23-32-45-46,9,HYBRID-OPT,0.2040,0.1473,0.3434,0.4340
|
||||
3,3-8-14-17-21-46,9,HYBRID-OPT,0.2185,0.0870,0.3287,0.4242
|
||||
4,7-8-28-45-46-48,8,HYBRID-OPT,0.2617,0.0976,0.3519,0.4478
|
||||
5,6-8-14-21-28-46,7,HYBRID-OPT,0.2717,0.0870,0.3527,0.4473
|
||||
6,5-8-9-12-16-46,5,PURE-AI,0.1900,0.0046,0.1529,0.3874
|
||||
7,2-3-7-25-32-46,7,PURE-AI,0.1141,0.0870,0.1087,0.3761
|
||||
8,8-13-21-28-44-46,6,ENSEMBLE,0.2620,0.1473,0.2047,0.4621
|
||||
9,8-12-13-21-28-46,3,ENSEMBLE,0.2900,0.0870,0.1885,0.4519
|
||||
10,2-9-24-30-35-49,9,PURE-PATTERN,0.0700,0.1473,0.1241,0.3722
|
||||
|
@@ -0,0 +1,11 @@
|
||||
Tip_Number,Numbers,Superzahl,Strategy,AI_Score,Pattern_Weight,Confidence,Quality
|
||||
1,8-21-23-29-33-46,0,HYBRID-OPT,0.1950,0.0909,0.3196,0.4164
|
||||
2,2-8-14-28-32-45,0,HYBRID-OPT,0.2299,0.0869,0.3339,0.4325
|
||||
3,12-15-21-28-42-46,0,HYBRID-OPT,0.1900,0.1473,0.3370,0.4310
|
||||
4,4-8-28-45-46-48,8,HYBRID-OPT,0.2402,0.0976,0.3423,0.4409
|
||||
5,6-8-14-21-28-46,5,HYBRID-OPT,0.2620,0.0869,0.3483,0.4448
|
||||
6,1-6-8-23-28-40,7,PURE-AI,0.1775,0.0869,0.1594,0.4091
|
||||
7,3-12-19-21-46-48,7,PURE-AI,0.1280,0.1473,0.1319,0.4016
|
||||
8,8-12-13-21-28-46,6,ENSEMBLE,0.2898,0.0869,0.1883,0.4517
|
||||
9,8-12-13-21-28-46,3,ENSEMBLE,0.2898,0.0869,0.1883,0.4517
|
||||
10,2-9-24-30-35-49,0,PURE-PATTERN,0.0145,0.1473,0.1074,0.3500
|
||||
|
@@ -0,0 +1,11 @@
|
||||
Tip_Number,Numbers,Superzahl,Strategy,AI_Score,Pattern_Weight,Confidence,Quality
|
||||
1,1-12-21-28-39-46,0,HYBRID-OPT,0.2145,0.1475,0.3481,0.4397
|
||||
2,2-8-14-28-32-45,0,HYBRID-OPT,0.2556,0.0869,0.3454,0.4409
|
||||
3,3-12-23-28-33-45,0,HYBRID-OPT,0.2199,0.1475,0.3506,0.4416
|
||||
4,1-2-21-28-42-46,5,HYBRID-OPT,0.1943,0.1475,0.3390,0.4314
|
||||
5,6-8-14-21-28-46,1,HYBRID-OPT,0.2743,0.0869,0.3538,0.4479
|
||||
6,1-5-8-12-33-46,7,PURE-AI,0.1998,0.0179,0.1634,0.3950
|
||||
7,3-17-25-37-39-46,7,PURE-AI,0.1299,0.0861,0.1212,0.3824
|
||||
8,8-12-13-21-28-37,6,ENSEMBLE,0.2498,0.0869,0.1683,0.4369
|
||||
9,8-12-13-21-28-37,3,ENSEMBLE,0.2498,0.0869,0.1683,0.4369
|
||||
10,2-9-24-30-35-49,0,PURE-PATTERN,0.0890,0.1475,0.1299,0.3798
|
||||
|
@@ -0,0 +1,11 @@
|
||||
Tip_Number,Numbers,Superzahl,Strategy,AI_Score,Pattern_Weight,Confidence,Quality
|
||||
1,1-12-21-28-38-46,6,HYBRID-OPT,0.2045,0.1476,0.3437,0.4363
|
||||
2,2-8-21-23-40-45,6,HYBRID-OPT,0.2043,0.1476,0.3436,0.4358
|
||||
3,8-12-23-25-33-45,6,HYBRID-OPT,0.2097,0.1476,0.3461,0.4382
|
||||
4,8-28-39-45-46-48,5,HYBRID-OPT,0.2574,0.0392,0.3296,0.4289
|
||||
5,4-8-21-32-38-45,1,HYBRID-OPT,0.1751,0.1476,0.3305,0.4234
|
||||
6,1-8-13-28-33-38,7,PURE-AI,0.1995,0.0760,0.1748,0.4145
|
||||
7,7-10-11-37-46-48,7,PURE-AI,0.1077,0.0312,0.0924,0.3567
|
||||
8,8-13-21-28-37-46,6,ENSEMBLE,0.2645,0.1476,0.2061,0.4627
|
||||
9,8-13-21-28-37-46,3,ENSEMBLE,0.2645,0.1476,0.2061,0.4627
|
||||
10,2-9-24-30-35-49,6,PURE-PATTERN,0.0585,0.1476,0.1209,0.3677
|
||||
|
@@ -0,0 +1,11 @@
|
||||
Tip_Number,Numbers,Superzahl,Strategy,AI_Score,Pattern_Weight,Confidence,Quality
|
||||
1,8-13-27-28-33-37,8,HYBRID-OPT,0.2045,0.1476,0.3437,0.4379
|
||||
2,2-8-14-28-32-45,8,HYBRID-OPT,0.2483,0.0869,0.3421,0.4385
|
||||
3,3-8-14-17-21-46,8,HYBRID-OPT,0.2177,0.0869,0.3284,0.4239
|
||||
4,1-11-25-28-45-46,0,HYBRID-OPT,0.2043,0.1476,0.3436,0.4372
|
||||
5,6-8-14-21-28-46,1,HYBRID-OPT,0.2709,0.0869,0.3523,0.4471
|
||||
6,8-10-13-25-28-40,2,PURE-AI,0.2045,0.0869,0.1810,0.4197
|
||||
7,7-9-11-32-38-46,7,PURE-AI,0.1140,0.0762,0.1064,0.3729
|
||||
8,8-13-21-28-37-46,6,ENSEMBLE,0.2662,0.1476,0.2069,0.4632
|
||||
9,8-13-21-28-37-46,3,ENSEMBLE,0.2662,0.1476,0.2069,0.4632
|
||||
10,2-9-24-30-35-49,8,PURE-PATTERN,0.0680,0.1476,0.1237,0.3715
|
||||
|
@@ -0,0 +1,6 @@
|
||||
Tip_Number,Numbers,Superzahl,Strategy,AI_Score,Pattern_Weight,Confidence,Quality
|
||||
1,8-13-27-28-33-37,8,HYBRID-OPT,0.2045,0.1476,0.3437,0.4379
|
||||
2,2-8-14-28-32-45,8,HYBRID-OPT,0.2483,0.0869,0.3421,0.4385
|
||||
3,3-12-14-37-39-45,8,PURE-AI,0.1644,0.0312,0.1377,0.3816
|
||||
4,8-13-21-28-37-46,0,ENSEMBLE,0.2662,0.1476,0.2069,0.4632
|
||||
5,3-22-23-36-39-43,1,PURE-PATTERN,0.0972,0.0860,0.0894,0.3672
|
||||
|
@@ -0,0 +1,6 @@
|
||||
Tip_Number,Numbers,Superzahl,Strategy,AI_Score,Pattern_Weight,Confidence,Quality
|
||||
1,8-12-23-28-39-43,8,HYBRID-OPT,0.5407,0.1476,0.4950,0.6337
|
||||
2,2-8-14-28-32-45,8,HYBRID-OPT,0.5998,0.0869,0.5003,0.6480
|
||||
3,7-14-21-23-46-47,8,PURE-AI,0.4403,0.1476,0.3817,0.5620
|
||||
4,8-13-23-28-37-46,0,ENSEMBLE,0.6773,0.1476,0.4124,0.6830
|
||||
5,3-22-23-36-39-43,1,PURE-PATTERN,0.1821,0.0860,0.1149,0.4103
|
||||
|
@@ -0,0 +1,11 @@
|
||||
Tip_Number,Numbers,Superzahl,Strategy,AI_Score,Pattern_Weight,Confidence,Quality
|
||||
1,8-12-23-28-39-43,8,HYBRID-OPT,0.5407,0.1476,0.4950,0.6337
|
||||
2,2-8-14-28-32-45,8,HYBRID-OPT,0.5998,0.0869,0.5003,0.6480
|
||||
3,2-8-13-21-28-38,8,HYBRID-OPT,0.5219,0.0869,0.4653,0.6054
|
||||
4,7-8-28-37-45-46,0,HYBRID-OPT,0.6448,0.0975,0.5243,0.6669
|
||||
5,6-8-14-21-28-46,1,HYBRID-OPT,0.6636,0.0869,0.5290,0.6663
|
||||
6,8-23-27-28-42-47,2,PURE-AI,0.5046,0.0909,0.4219,0.5999
|
||||
7,6-19-21-37-46-49,7,PURE-AI,0.3274,0.0860,0.2791,0.4843
|
||||
8,8-13-23-28-37-46,6,ENSEMBLE,0.6773,0.1476,0.4124,0.6830
|
||||
9,8-13-23-28-37-46,3,ENSEMBLE,0.6773,0.1476,0.4124,0.6830
|
||||
10,2-9-24-30-35-49,8,PURE-PATTERN,0.0898,0.1476,0.1302,0.3824
|
||||
|
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"checks": [
|
||||
{
|
||||
"timestamp": "2026-01-06T10:11:32.908565",
|
||||
"status": "OK",
|
||||
"issues": [],
|
||||
"warnings": [
|
||||
"Learning State: No learning state found",
|
||||
"Disk Space: 121.4 GB free (13.1%)"
|
||||
],
|
||||
"recovered": []
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-07T09:13:37.832795",
|
||||
"status": "OK",
|
||||
"issues": [],
|
||||
"warnings": [
|
||||
"Learning State: No learning state found",
|
||||
"Disk Space: 115.3 GB free (12.4%)"
|
||||
],
|
||||
"recovered": []
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-07T09:17:51.274727",
|
||||
"status": "OK",
|
||||
"issues": [],
|
||||
"warnings": [
|
||||
"Learning State: No learning state found",
|
||||
"Disk Space: 115.3 GB free (12.4%)"
|
||||
],
|
||||
"recovered": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -43,6 +43,58 @@
|
||||
"main": 1.2,
|
||||
"sz_rate": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-12-22T21:00:50.378355",
|
||||
"draw_date": "2025-12-20",
|
||||
"evaluation": {
|
||||
"main": 3,
|
||||
"sz": false,
|
||||
"tip": 2
|
||||
},
|
||||
"avg_matches": {
|
||||
"main": 0.7,
|
||||
"sz_rate": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-12-25T21:00:35.769591",
|
||||
"draw_date": "2025-12-24",
|
||||
"evaluation": {
|
||||
"main": 2,
|
||||
"sz": false,
|
||||
"tip": 2
|
||||
},
|
||||
"avg_matches": {
|
||||
"main": 0.8,
|
||||
"sz_rate": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-12-29T21:00:36.294861",
|
||||
"draw_date": "2025-12-27",
|
||||
"evaluation": {
|
||||
"main": 2,
|
||||
"sz": false,
|
||||
"tip": 1
|
||||
},
|
||||
"avg_matches": {
|
||||
"main": 1.2,
|
||||
"sz_rate": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-05T11:56:39.905126",
|
||||
"draw_date": "2026-01-03",
|
||||
"evaluation": {
|
||||
"main": 2,
|
||||
"sz": false,
|
||||
"tip": 1
|
||||
},
|
||||
"avg_matches": {
|
||||
"main": 1.5,
|
||||
"sz_rate": 0.0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
======================================================================
|
||||
LOTTO 6AUS49 PERFORMANCE REPORT
|
||||
======================================================================
|
||||
Erstellt: 2025-12-22 21:00:50
|
||||
|
||||
ZIEHUNG VOM 2025-12-20
|
||||
----------------------------------------------------------------------
|
||||
Hauptzahlen: [12, 23, 32, 33, 40, 42]
|
||||
Superzahl: 5
|
||||
|
||||
EVALUATION DER TIPPS
|
||||
----------------------------------------------------------------------
|
||||
Datei: weekly_lotto_tips_20251219_090047.csv
|
||||
Tipps evaluiert: 10
|
||||
Avg Main Matches: 0.70
|
||||
SZ Match Rate: 10.0%
|
||||
Bester Tipp: #2
|
||||
- Main Treffer: 3
|
||||
- SZ Match: Nein
|
||||
|
||||
======================================================================
|
||||
@@ -0,0 +1,21 @@
|
||||
======================================================================
|
||||
LOTTO 6AUS49 PERFORMANCE REPORT
|
||||
======================================================================
|
||||
Erstellt: 2025-12-25 21:00:35
|
||||
|
||||
ZIEHUNG VOM 2025-12-24
|
||||
----------------------------------------------------------------------
|
||||
Hauptzahlen: [7, 8, 25, 32, 37, 39]
|
||||
Superzahl: 8
|
||||
|
||||
EVALUATION DER TIPPS
|
||||
----------------------------------------------------------------------
|
||||
Datei: weekly_lotto_tips_20251223_090039.csv
|
||||
Tipps evaluiert: 10
|
||||
Avg Main Matches: 0.80
|
||||
SZ Match Rate: 10.0%
|
||||
Bester Tipp: #2
|
||||
- Main Treffer: 2
|
||||
- SZ Match: Nein
|
||||
|
||||
======================================================================
|
||||
@@ -0,0 +1,21 @@
|
||||
======================================================================
|
||||
LOTTO 6AUS49 PERFORMANCE REPORT
|
||||
======================================================================
|
||||
Erstellt: 2025-12-29 21:00:36
|
||||
|
||||
ZIEHUNG VOM 2025-12-27
|
||||
----------------------------------------------------------------------
|
||||
Hauptzahlen: [7, 10, 27, 28, 38, 46]
|
||||
Superzahl: 6
|
||||
|
||||
EVALUATION DER TIPPS
|
||||
----------------------------------------------------------------------
|
||||
Datei: weekly_lotto_tips_20251226_090029.csv
|
||||
Tipps evaluiert: 10
|
||||
Avg Main Matches: 1.20
|
||||
SZ Match Rate: 10.0%
|
||||
Bester Tipp: #1
|
||||
- Main Treffer: 2
|
||||
- SZ Match: Nein
|
||||
|
||||
======================================================================
|
||||
@@ -0,0 +1,21 @@
|
||||
======================================================================
|
||||
LOTTO 6AUS49 PERFORMANCE REPORT
|
||||
======================================================================
|
||||
Erstellt: 2026-01-05 11:56:39
|
||||
|
||||
ZIEHUNG VOM 2026-01-03
|
||||
----------------------------------------------------------------------
|
||||
Hauptzahlen: [3, 8, 11, 12, 16, 46]
|
||||
Superzahl: 8
|
||||
|
||||
EVALUATION DER TIPPS
|
||||
----------------------------------------------------------------------
|
||||
Datei: weekly_lotto_tips_20251230_090029.csv
|
||||
Tipps evaluiert: 10
|
||||
Avg Main Matches: 1.50
|
||||
SZ Match Rate: 0.0%
|
||||
Bester Tipp: #1
|
||||
- Main Treffer: 2
|
||||
- SZ Match: Nein
|
||||
|
||||
======================================================================
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"trained_at": "2026-01-05T14:26:53.036260",
|
||||
"num_samples": 4954,
|
||||
"num_features": 20,
|
||||
"final_loss": 0.3721399052489188,
|
||||
"final_val_loss": 0.3729977588499746,
|
||||
"best_val_loss": 0.37259673303173435,
|
||||
"epochs_trained": 30
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"adjustments": {
|
||||
"1": -0.019751246878125,
|
||||
"2": -0.019751246878125,
|
||||
"3": -0.004826246878125001,
|
||||
"4": -0.019751246878125,
|
||||
"5": -0.019751246878125,
|
||||
"6": -0.019751246878125,
|
||||
"7": 0.009875251246875,
|
||||
"8": 0.009949876246875,
|
||||
"9": -0.019751246878125,
|
||||
"10": -0.004900871878125002,
|
||||
"11": -0.004826246878125001,
|
||||
"12": 0.00987599563125,
|
||||
"13": -0.019751246878125,
|
||||
"14": -0.019751246878125,
|
||||
"15": -0.019751246878125,
|
||||
"16": -0.004826246878125001,
|
||||
"17": -0.019751246878125,
|
||||
"18": -0.019751246878125,
|
||||
"19": -0.019751246878125,
|
||||
"20": -0.019751246878125,
|
||||
"21": -0.019751246878125,
|
||||
"22": -0.019751246878125,
|
||||
"23": -0.0050490043687499995,
|
||||
"24": -0.019751246878125,
|
||||
"25": -0.004975123753125,
|
||||
"26": -0.019751246878125,
|
||||
"27": -0.004900871878125002,
|
||||
"28": -0.004900871878125002,
|
||||
"29": -0.019751246878125,
|
||||
"30": -0.019751246878125,
|
||||
"31": -0.019751246878125,
|
||||
"32": 0.00972711875625,
|
||||
"33": -0.0050490043687499995,
|
||||
"34": -0.019751246878125,
|
||||
"35": -0.019751246878125,
|
||||
"36": -0.019751246878125,
|
||||
"37": -0.004975123753125,
|
||||
"38": -0.004900871878125002,
|
||||
"39": -0.004975123753125,
|
||||
"40": -0.0050490043687499995,
|
||||
"41": -0.019751246878125,
|
||||
"42": -0.0050490043687499995,
|
||||
"43": -0.019751246878125,
|
||||
"44": -0.019751246878125,
|
||||
"45": -0.019751246878125,
|
||||
"46": 0.010024128121874998,
|
||||
"47": -0.019751246878125,
|
||||
"48": -0.019751246878125,
|
||||
"49": -0.019751246878125
|
||||
},
|
||||
"stats": {
|
||||
"incorrect": 172,
|
||||
"correct": 24,
|
||||
"cycles": 4,
|
||||
"last_update": "2026-01-05T11:56:39.904258"
|
||||
},
|
||||
"learning_rate": 0.1,
|
||||
"last_saved": "2026-01-05T11:56:39.904307"
|
||||
}
|
||||
Binary file not shown.
@@ -151,7 +151,7 @@ class AutoUpdateAndLearn:
|
||||
print("\n🎯 EVALUIERE LETZTE TIPPS")
|
||||
print("=" * 70)
|
||||
|
||||
# Finde neueste Tipps-Datei
|
||||
# Finde Tipps-Datei die VOR der Ziehung generiert wurde
|
||||
if not os.path.exists(self.tips_dir):
|
||||
print(" ⚠️ Keine Tipps zum Evaluieren")
|
||||
return {}
|
||||
@@ -165,8 +165,30 @@ class AutoUpdateAndLearn:
|
||||
print(" ⚠️ Keine Tipps-Dateien gefunden")
|
||||
return {}
|
||||
|
||||
latest_tips_file = os.path.join(self.tips_dir, tip_files[0])
|
||||
print(f" 📁 Evaluiere: {tip_files[0]}")
|
||||
# Finde Tip-Datei die vor der Ziehung erstellt wurde
|
||||
draw_date = new_draw['date']
|
||||
selected_tip_file = None
|
||||
|
||||
for tip_file in tip_files:
|
||||
# Parse Timestamp aus Dateiname: weekly_lotto_tips_YYYYMMDD_HHMMSS.csv
|
||||
try:
|
||||
parts = tip_file.replace('.csv', '').split('_')
|
||||
tip_date_str = parts[-2] # YYYYMMDD
|
||||
tip_date = pd.to_datetime(tip_date_str, format='%Y%m%d')
|
||||
|
||||
# Nehme erste Datei die vor der Ziehung war
|
||||
if tip_date < draw_date:
|
||||
selected_tip_file = tip_file
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
if not selected_tip_file:
|
||||
# Fallback: nehme älteste Datei
|
||||
selected_tip_file = tip_files[-1]
|
||||
|
||||
latest_tips_file = os.path.join(self.tips_dir, selected_tip_file)
|
||||
print(f" 📁 Evaluiere: {selected_tip_file}")
|
||||
|
||||
try:
|
||||
tips_df = pd.read_csv(latest_tips_file)
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?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>
|
||||
<!-- Label - Eindeutige Identifier -->
|
||||
<key>Label</key>
|
||||
<string>com.lotto.weekly</string>
|
||||
|
||||
<!-- Programm-Pfad -->
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/bin/bash</string>
|
||||
<string>-c</string>
|
||||
<string>cd "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Lotto" && source venv/bin/activate && python scripts/automation/weekly_tip_generator.py 2>&1 | tee -a logs/launchagent.log</string>
|
||||
</array>
|
||||
|
||||
<!-- Zeitplan: Dienstag und Freitag um 21:00 -->
|
||||
<key>StartCalendarInterval</key>
|
||||
<array>
|
||||
<!-- Dienstag 21:00 (vor Mittwoch-Ziehung) -->
|
||||
<dict>
|
||||
<key>Weekday</key>
|
||||
<integer>2</integer>
|
||||
<key>Hour</key>
|
||||
<integer>21</integer>
|
||||
<key>Minute</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<!-- Freitag 21:00 (vor Samstag-Ziehung) -->
|
||||
<dict>
|
||||
<key>Weekday</key>
|
||||
<integer>5</integer>
|
||||
<key>Hour</key>
|
||||
<integer>21</integer>
|
||||
<key>Minute</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
|
||||
<!-- Arbeitsverzeichnis -->
|
||||
<key>WorkingDirectory</key>
|
||||
<string>/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Lotto</string>
|
||||
|
||||
<!-- Standard Output/Error Logging -->
|
||||
<key>StandardOutPath</key>
|
||||
<string>/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Lotto/logs/stdout.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Lotto/logs/stderr.log</string>
|
||||
|
||||
<!-- Wichtig: RunAtLoad für sofortigen Test -->
|
||||
<key>RunAtLoad</key>
|
||||
<false/>
|
||||
|
||||
<!-- Environment Variables -->
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
||||
<key>LANG</key>
|
||||
<string>de_DE.UTF-8</string>
|
||||
</dict>
|
||||
|
||||
<!-- Wichtig: Auch bei Sleep/Wake ausführen -->
|
||||
<key>LaunchOnlyOnce</key>
|
||||
<false/>
|
||||
|
||||
<!-- Process Nice Level (niedrigere Priorität) -->
|
||||
<key>Nice</key>
|
||||
<integer>10</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -40,15 +40,34 @@ try:
|
||||
except ImportError:
|
||||
ML_AVAILABLE = False
|
||||
|
||||
# Deep Learning (optional)
|
||||
# Deep Learning (optional) - PyTorch or TensorFlow
|
||||
DEEP_LEARNING_AVAILABLE = False
|
||||
try:
|
||||
import tensorflow as tf
|
||||
from tensorflow.keras.models import Sequential
|
||||
from tensorflow.keras.layers import LSTM, Dense, Dropout
|
||||
from tensorflow.keras.optimizers import Adam
|
||||
import torch
|
||||
DEEP_LEARNING_AVAILABLE = True
|
||||
except ImportError:
|
||||
DEEP_LEARNING_AVAILABLE = False
|
||||
try:
|
||||
import tensorflow as tf
|
||||
from tensorflow.keras.models import Sequential
|
||||
from tensorflow.keras.layers import LSTM, Dense, Dropout
|
||||
from tensorflow.keras.optimizers import Adam
|
||||
DEEP_LEARNING_AVAILABLE = True
|
||||
except ImportError:
|
||||
DEEP_LEARNING_AVAILABLE = False
|
||||
|
||||
# Import Deep Learning Engine (PyTorch-based for Python 3.14+ compatibility)
|
||||
try:
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'utils'))
|
||||
from deep_learning_engine_pytorch import DeepLearningEngine, HybridDeepLearningPredictor
|
||||
DL_ENGINE_AVAILABLE = True
|
||||
except ImportError:
|
||||
try:
|
||||
from deep_learning_engine import DeepLearningEngine, HybridDeepLearningPredictor
|
||||
DL_ENGINE_AVAILABLE = True
|
||||
except ImportError:
|
||||
DL_ENGINE_AVAILABLE = False
|
||||
DeepLearningEngine = None
|
||||
HybridDeepLearningPredictor = None
|
||||
|
||||
|
||||
class UltimateAIMLHybridGenerator:
|
||||
@@ -159,11 +178,13 @@ class UltimateAIMLHybridGenerator:
|
||||
"""Zeigt System-Status."""
|
||||
print("\n📊 SYSTEM STATUS:")
|
||||
print(f" 🧠 ML Available: {'✅' if ML_AVAILABLE else '❌ (pip install scikit-learn)'}")
|
||||
print(f" 🚀 Deep Learning: {'✅' if DEEP_LEARNING_AVAILABLE else '⚠️ Optional (pip install tensorflow)'}")
|
||||
print(f" 🚀 Deep Learning: {'✅ ACTIVE' if self.ai_ml_engine.use_deep_learning else '⚠️ Optional (pip install tensorflow)'}")
|
||||
print(f" 🎯 Models Trained: {'✅' if self.is_trained else '⚠️ Using frequency-based fallback'}")
|
||||
print(f" 📁 Data Size: {len(self.df):,} drawings")
|
||||
print(f" 🎨 Patterns: {len(self.pattern_engine.pattern_frequencies)}")
|
||||
print(f" ⚡ Fast Mode: {'ON' if self.fast_mode else 'OFF'}")
|
||||
if self.ai_ml_engine.use_deep_learning:
|
||||
print(f" 🔮 LSTM: Hybrid Mode (RF 40% + DL 60%)")
|
||||
|
||||
def generate_ultimate_tips(self, num_tips=10):
|
||||
"""
|
||||
@@ -586,19 +607,26 @@ class UltimateAIMLHybridGenerator:
|
||||
# ============================================================================
|
||||
|
||||
class AIMLEngine:
|
||||
"""AI/ML Engine - OPTIMIZED."""
|
||||
|
||||
"""AI/ML Engine - OPTIMIZED mit Deep Learning Support."""
|
||||
|
||||
def __init__(self, fast_mode=True):
|
||||
self.models = {}
|
||||
self.trained_models = {}
|
||||
self.scaler = StandardScaler()
|
||||
self.is_trained = False
|
||||
self.fast_mode = fast_mode
|
||||
|
||||
# Deep Learning
|
||||
self.dl_engine = None
|
||||
self.hybrid_predictor = None
|
||||
self.use_deep_learning = DL_ENGINE_AVAILABLE and DEEP_LEARNING_AVAILABLE
|
||||
|
||||
def initialize(self, df, features_df, cache_path):
|
||||
"""Initialisiert AI/ML System mit intelligentem Retraining."""
|
||||
self.cache_path = cache_path
|
||||
self.data_file = None # Wird vom Generator gesetzt
|
||||
self.df = df # Store for DL
|
||||
self.features_df = features_df # Store for DL
|
||||
|
||||
if not ML_AVAILABLE:
|
||||
print(" ⚠️ ML not available - using frequency-based fallback")
|
||||
@@ -630,6 +658,36 @@ class AIMLEngine:
|
||||
else:
|
||||
print(" ⚠️ Insufficient data - using fallback")
|
||||
|
||||
# Initialize Deep Learning
|
||||
if self.use_deep_learning:
|
||||
print(" 🧠 Initializing Deep Learning (LSTM)...", end=" ", flush=True)
|
||||
try:
|
||||
self.dl_engine = DeepLearningEngine(
|
||||
num_numbers=49,
|
||||
sequence_length=20,
|
||||
cache_dir=os.path.join(cache_path, 'deep_learning'),
|
||||
fast_mode=self.fast_mode
|
||||
)
|
||||
|
||||
# Train or load LSTM
|
||||
self.dl_engine.train(df, features_df, force_retrain=needs_retrain)
|
||||
|
||||
# Create hybrid predictor
|
||||
self.hybrid_predictor = HybridDeepLearningPredictor(
|
||||
dl_engine=self.dl_engine,
|
||||
rf_weight=0.4,
|
||||
dl_weight=0.6
|
||||
)
|
||||
print("✅")
|
||||
except Exception as e:
|
||||
print(f"⚠️ DL init failed: {e}")
|
||||
self.use_deep_learning = False
|
||||
else:
|
||||
if not DEEP_LEARNING_AVAILABLE:
|
||||
print(" ℹ️ Deep Learning disabled (TensorFlow not installed)")
|
||||
elif not DL_ENGINE_AVAILABLE:
|
||||
print(" ℹ️ Deep Learning Engine not available")
|
||||
|
||||
def _needs_retraining(self, cache_file):
|
||||
"""Prüft ob Retraining nötig ist."""
|
||||
if not os.path.exists(cache_file):
|
||||
@@ -735,46 +793,57 @@ class AIMLEngine:
|
||||
return np.array(X), np.array(y)
|
||||
|
||||
def predict_all_numbers(self, features_df):
|
||||
"""Vorhersage für alle Zahlen."""
|
||||
"""Vorhersage für alle Zahlen - mit Deep Learning Hybrid."""
|
||||
predictions = {}
|
||||
|
||||
|
||||
# Get RandomForest predictions
|
||||
if not self.is_trained or not self.trained_models:
|
||||
# Fallback
|
||||
for num in range(1, 50):
|
||||
predictions[num] = 0.2 + random.random() * 0.3
|
||||
return predictions
|
||||
|
||||
current_features = self._get_current_features(features_df)
|
||||
|
||||
for number in range(1, 50):
|
||||
if number not in self.trained_models:
|
||||
predictions[number] = 0.15 + (number % 10) * 0.03
|
||||
continue
|
||||
|
||||
number_models = self.trained_models[number]
|
||||
ensemble_pred = 0
|
||||
total_weight = 0
|
||||
|
||||
for model_name, model_data in number_models.items():
|
||||
try:
|
||||
model = model_data['model']
|
||||
scaler = model_data['scaler']
|
||||
score = model_data['score']
|
||||
|
||||
features_scaled = scaler.transform([current_features])
|
||||
pred = model.predict(features_scaled)[0]
|
||||
|
||||
weight = score
|
||||
ensemble_pred += pred * weight
|
||||
total_weight += weight
|
||||
except:
|
||||
pass
|
||||
|
||||
if total_weight > 0:
|
||||
predictions[number] = np.clip(ensemble_pred / total_weight, 0, 1)
|
||||
else:
|
||||
predictions[number] = 0.2 + random.random() * 0.2
|
||||
|
||||
else:
|
||||
current_features = self._get_current_features(features_df)
|
||||
|
||||
for number in range(1, 50):
|
||||
if number not in self.trained_models:
|
||||
predictions[number] = 0.15 + (number % 10) * 0.03
|
||||
continue
|
||||
|
||||
number_models = self.trained_models[number]
|
||||
ensemble_pred = 0
|
||||
total_weight = 0
|
||||
|
||||
for model_name, model_data in number_models.items():
|
||||
try:
|
||||
model = model_data['model']
|
||||
scaler = model_data['scaler']
|
||||
score = model_data['score']
|
||||
|
||||
features_scaled = scaler.transform([current_features])
|
||||
pred = model.predict(features_scaled)[0]
|
||||
|
||||
weight = score
|
||||
ensemble_pred += pred * weight
|
||||
total_weight += weight
|
||||
except:
|
||||
pass
|
||||
|
||||
if total_weight > 0:
|
||||
predictions[number] = np.clip(ensemble_pred / total_weight, 0, 1)
|
||||
else:
|
||||
predictions[number] = 0.2 + random.random() * 0.2
|
||||
|
||||
# Use Deep Learning Hybrid if available
|
||||
if self.use_deep_learning and self.hybrid_predictor:
|
||||
try:
|
||||
predictions = self.hybrid_predictor.predict(
|
||||
predictions,
|
||||
self.df,
|
||||
self.features_df
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"⚠️ DL prediction failed: {e}, using RF only")
|
||||
|
||||
return predictions
|
||||
|
||||
def _get_current_features(self, features_df):
|
||||
|
||||
@@ -0,0 +1,473 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Deep Learning Engine mit LSTM für Lotto-Vorhersagen
|
||||
====================================================
|
||||
|
||||
Implementiert LSTM-basierte Modelle zur Vorhersage von Lotto-Zahlen
|
||||
basierend auf historischen Sequenzen und Features.
|
||||
|
||||
Features:
|
||||
- LSTM-Netzwerk für zeitliche Sequenzen
|
||||
- Sequence-to-Probability Mapping
|
||||
- Feature Engineering Integration
|
||||
- Model Persistence & Caching
|
||||
- Hybrid mit RandomForest
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import os
|
||||
import json
|
||||
import pickle
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
import warnings
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
# TensorFlow/Keras Imports
|
||||
try:
|
||||
import tensorflow as tf
|
||||
from tensorflow import keras
|
||||
from tensorflow.keras.models import Sequential, load_model
|
||||
from tensorflow.keras.layers import LSTM, Dense, Dropout, Bidirectional, BatchNormalization
|
||||
from tensorflow.keras.optimizers import Adam
|
||||
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau
|
||||
from tensorflow.keras.regularizers import l2
|
||||
TENSORFLOW_AVAILABLE = True
|
||||
except ImportError:
|
||||
TENSORFLOW_AVAILABLE = False
|
||||
print("⚠️ TensorFlow not available. Install with: pip install tensorflow")
|
||||
|
||||
|
||||
class DeepLearningEngine:
|
||||
"""
|
||||
LSTM-basierter Deep Learning Engine für Lotto-Vorhersagen.
|
||||
|
||||
Architecture:
|
||||
- Input: Sequence of historical draws + features
|
||||
- LSTM layers: Learn temporal patterns
|
||||
- Dense layers: Map to probability distribution
|
||||
- Output: Probability for each number
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
num_numbers: int = 49,
|
||||
sequence_length: int = 20,
|
||||
cache_dir: str = None,
|
||||
fast_mode: bool = True
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
num_numbers: Maximum number (49 for Lotto, 50 for Eurojackpot)
|
||||
sequence_length: How many past draws to consider
|
||||
cache_dir: Directory for model persistence
|
||||
fast_mode: Use faster training (fewer epochs)
|
||||
"""
|
||||
self.num_numbers = num_numbers
|
||||
self.sequence_length = sequence_length
|
||||
self.cache_dir = cache_dir
|
||||
self.fast_mode = fast_mode
|
||||
|
||||
# Model components
|
||||
self.model = None
|
||||
self.is_trained = False
|
||||
self.training_history = {}
|
||||
|
||||
# Configuration
|
||||
self.config = {
|
||||
'lstm_units_1': 128,
|
||||
'lstm_units_2': 64,
|
||||
'dense_units': 128,
|
||||
'dropout_rate': 0.3,
|
||||
'learning_rate': 0.001,
|
||||
'batch_size': 32,
|
||||
'epochs': 30 if fast_mode else 100,
|
||||
'validation_split': 0.2
|
||||
}
|
||||
|
||||
if cache_dir:
|
||||
os.makedirs(cache_dir, exist_ok=True)
|
||||
self.model_path = os.path.join(cache_dir, f'lstm_model_{num_numbers}.h5')
|
||||
self.config_path = os.path.join(cache_dir, f'lstm_config_{num_numbers}.json')
|
||||
else:
|
||||
self.model_path = None
|
||||
self.config_path = None
|
||||
|
||||
print(f"🧠 Deep Learning Engine initialized")
|
||||
print(f" Numbers: 1-{num_numbers}")
|
||||
print(f" Sequence Length: {sequence_length}")
|
||||
print(f" Fast Mode: {fast_mode}")
|
||||
|
||||
def _build_model(self, num_features: int) -> Sequential:
|
||||
"""
|
||||
Builds LSTM architecture.
|
||||
|
||||
Architecture:
|
||||
Input (sequence_length, num_features)
|
||||
↓
|
||||
Bidirectional LSTM(128) + Dropout(0.3)
|
||||
↓
|
||||
Bidirectional LSTM(64) + Dropout(0.3)
|
||||
↓
|
||||
Dense(128, relu) + BatchNorm + Dropout(0.3)
|
||||
↓
|
||||
Dense(num_numbers, sigmoid)
|
||||
"""
|
||||
model = Sequential([
|
||||
# First Bidirectional LSTM layer
|
||||
Bidirectional(
|
||||
LSTM(
|
||||
self.config['lstm_units_1'],
|
||||
return_sequences=True,
|
||||
kernel_regularizer=l2(0.01)
|
||||
),
|
||||
input_shape=(self.sequence_length, num_features)
|
||||
),
|
||||
Dropout(self.config['dropout_rate']),
|
||||
BatchNormalization(),
|
||||
|
||||
# Second Bidirectional LSTM layer
|
||||
Bidirectional(
|
||||
LSTM(
|
||||
self.config['lstm_units_2'],
|
||||
return_sequences=False,
|
||||
kernel_regularizer=l2(0.01)
|
||||
)
|
||||
),
|
||||
Dropout(self.config['dropout_rate']),
|
||||
BatchNormalization(),
|
||||
|
||||
# Dense layers
|
||||
Dense(
|
||||
self.config['dense_units'],
|
||||
activation='relu',
|
||||
kernel_regularizer=l2(0.01)
|
||||
),
|
||||
BatchNormalization(),
|
||||
Dropout(self.config['dropout_rate']),
|
||||
|
||||
# Output layer - probability for each number
|
||||
Dense(self.num_numbers, activation='sigmoid')
|
||||
])
|
||||
|
||||
# Compile
|
||||
model.compile(
|
||||
optimizer=Adam(learning_rate=self.config['learning_rate']),
|
||||
loss='binary_crossentropy',
|
||||
metrics=['accuracy', 'AUC']
|
||||
)
|
||||
|
||||
return model
|
||||
|
||||
def _prepare_sequences(
|
||||
self,
|
||||
df: pd.DataFrame,
|
||||
features_df: pd.DataFrame
|
||||
) -> Tuple[np.ndarray, np.ndarray]:
|
||||
"""
|
||||
Prepares sequences for LSTM training.
|
||||
|
||||
Args:
|
||||
df: Historical draws (with columns Z1-Z6)
|
||||
features_df: Engineered features
|
||||
|
||||
Returns:
|
||||
X: (num_samples, sequence_length, num_features)
|
||||
y: (num_samples, num_numbers) - binary matrix
|
||||
"""
|
||||
print(f" Preparing sequences (length={self.sequence_length})...")
|
||||
|
||||
# Ensure data is sorted by date
|
||||
if 'datum' in df.columns:
|
||||
df = df.sort_values('datum').reset_index(drop=True)
|
||||
|
||||
# Extract number columns
|
||||
num_cols = [col for col in df.columns if col.startswith('Z')]
|
||||
|
||||
# Normalize features to [0, 1]
|
||||
features_normalized = features_df.copy()
|
||||
for col in features_normalized.columns:
|
||||
min_val = features_normalized[col].min()
|
||||
max_val = features_normalized[col].max()
|
||||
if max_val > min_val:
|
||||
features_normalized[col] = (features_normalized[col] - min_val) / (max_val - min_val)
|
||||
else:
|
||||
features_normalized[col] = 0.5
|
||||
|
||||
X_sequences = []
|
||||
y_targets = []
|
||||
|
||||
# Create sequences
|
||||
for i in range(self.sequence_length, len(df)):
|
||||
# Get sequence of features
|
||||
sequence = features_normalized.iloc[i - self.sequence_length:i].values
|
||||
X_sequences.append(sequence)
|
||||
|
||||
# Target: next draw as binary vector
|
||||
target = np.zeros(self.num_numbers)
|
||||
next_draw = df.iloc[i][num_cols].values
|
||||
for num in next_draw:
|
||||
if 1 <= num <= self.num_numbers:
|
||||
target[int(num) - 1] = 1
|
||||
y_targets.append(target)
|
||||
|
||||
X = np.array(X_sequences)
|
||||
y = np.array(y_targets)
|
||||
|
||||
print(f" ✅ Created {len(X)} sequences")
|
||||
print(f" Shape: X={X.shape}, y={y.shape}")
|
||||
|
||||
return X, y
|
||||
|
||||
def train(
|
||||
self,
|
||||
df: pd.DataFrame,
|
||||
features_df: pd.DataFrame,
|
||||
force_retrain: bool = False
|
||||
) -> bool:
|
||||
"""
|
||||
Trains LSTM model on historical data.
|
||||
|
||||
Args:
|
||||
df: Historical draws
|
||||
features_df: Engineered features
|
||||
force_retrain: Retrain even if cached model exists
|
||||
|
||||
Returns:
|
||||
Success status
|
||||
"""
|
||||
if not TENSORFLOW_AVAILABLE:
|
||||
print("❌ TensorFlow not available")
|
||||
return False
|
||||
|
||||
# Check for cached model
|
||||
if not force_retrain and self.model_path and os.path.exists(self.model_path):
|
||||
print(" 📦 Loading cached LSTM model...")
|
||||
try:
|
||||
self.model = load_model(self.model_path)
|
||||
self.is_trained = True
|
||||
|
||||
# Load config
|
||||
if os.path.exists(self.config_path):
|
||||
with open(self.config_path, 'r') as f:
|
||||
self.training_history = json.load(f)
|
||||
|
||||
print(f" ✅ Loaded cached model")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Failed to load cached model: {e}")
|
||||
print(" 🔄 Training new model...")
|
||||
|
||||
print(f"\n🧠 TRAINING DEEP LEARNING MODEL (LSTM)")
|
||||
print("=" * 70)
|
||||
|
||||
# Prepare data
|
||||
X, y = self._prepare_sequences(df, features_df)
|
||||
|
||||
if len(X) < 100:
|
||||
print(" ⚠️ Not enough data for training (need >100 sequences)")
|
||||
return False
|
||||
|
||||
# Build model
|
||||
print(f" Building LSTM architecture...")
|
||||
num_features = X.shape[2]
|
||||
self.model = self._build_model(num_features)
|
||||
|
||||
# Show summary
|
||||
print(f"\n 📊 Model Summary:")
|
||||
total_params = self.model.count_params()
|
||||
print(f" Total parameters: {total_params:,}")
|
||||
|
||||
# Callbacks
|
||||
callbacks = [
|
||||
EarlyStopping(
|
||||
monitor='val_loss',
|
||||
patience=10,
|
||||
restore_best_weights=True,
|
||||
verbose=0
|
||||
),
|
||||
ReduceLROnPlateau(
|
||||
monitor='val_loss',
|
||||
factor=0.5,
|
||||
patience=5,
|
||||
verbose=0
|
||||
)
|
||||
]
|
||||
|
||||
# Train
|
||||
print(f"\n 🚀 Training for {self.config['epochs']} epochs...")
|
||||
print(f" Batch size: {self.config['batch_size']}")
|
||||
print(f" Validation split: {self.config['validation_split']:.1%}")
|
||||
|
||||
try:
|
||||
history = self.model.fit(
|
||||
X, y,
|
||||
batch_size=self.config['batch_size'],
|
||||
epochs=self.config['epochs'],
|
||||
validation_split=self.config['validation_split'],
|
||||
callbacks=callbacks,
|
||||
verbose=1
|
||||
)
|
||||
|
||||
# Store training history
|
||||
self.training_history = {
|
||||
'trained_at': datetime.now().isoformat(),
|
||||
'num_samples': len(X),
|
||||
'num_features': num_features,
|
||||
'final_loss': float(history.history['loss'][-1]),
|
||||
'final_val_loss': float(history.history['val_loss'][-1]),
|
||||
'final_accuracy': float(history.history['accuracy'][-1]),
|
||||
'final_val_accuracy': float(history.history['val_accuracy'][-1]),
|
||||
'epochs_trained': len(history.history['loss'])
|
||||
}
|
||||
|
||||
self.is_trained = True
|
||||
|
||||
# Save model
|
||||
if self.model_path:
|
||||
print(f"\n 💾 Saving model to cache...")
|
||||
self.model.save(self.model_path)
|
||||
|
||||
with open(self.config_path, 'w') as f:
|
||||
json.dump(self.training_history, f, indent=2)
|
||||
|
||||
print(f" ✅ Model saved")
|
||||
|
||||
# Print results
|
||||
print(f"\n ✅ TRAINING COMPLETED")
|
||||
print(f" Final Loss: {self.training_history['final_loss']:.4f}")
|
||||
print(f" Final Val Loss: {self.training_history['final_val_loss']:.4f}")
|
||||
print(f" Final Accuracy: {self.training_history['final_accuracy']:.4f}")
|
||||
print(f" Epochs: {self.training_history['epochs_trained']}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n ❌ Training failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def predict(
|
||||
self,
|
||||
recent_df: pd.DataFrame,
|
||||
recent_features: pd.DataFrame
|
||||
) -> Dict[int, float]:
|
||||
"""
|
||||
Predicts probabilities for each number.
|
||||
|
||||
Args:
|
||||
recent_df: Recent draws (at least sequence_length)
|
||||
recent_features: Recent features
|
||||
|
||||
Returns:
|
||||
{number: probability} for numbers 1-num_numbers
|
||||
"""
|
||||
if not self.is_trained or self.model is None:
|
||||
print("⚠️ Model not trained, returning uniform distribution")
|
||||
return {i: 0.5 for i in range(1, self.num_numbers + 1)}
|
||||
|
||||
# Prepare last sequence
|
||||
if len(recent_df) < self.sequence_length:
|
||||
print(f"⚠️ Not enough recent data (need {self.sequence_length}, got {len(recent_df)})")
|
||||
return {i: 0.5 for i in range(1, self.num_numbers + 1)}
|
||||
|
||||
# Get last sequence
|
||||
recent_features_normalized = recent_features.copy()
|
||||
for col in recent_features_normalized.columns:
|
||||
min_val = recent_features_normalized[col].min()
|
||||
max_val = recent_features_normalized[col].max()
|
||||
if max_val > min_val:
|
||||
recent_features_normalized[col] = (recent_features_normalized[col] - min_val) / (max_val - min_val)
|
||||
else:
|
||||
recent_features_normalized[col] = 0.5
|
||||
|
||||
sequence = recent_features_normalized.iloc[-self.sequence_length:].values
|
||||
X = np.array([sequence]) # Shape: (1, sequence_length, num_features)
|
||||
|
||||
# Predict
|
||||
predictions = self.model.predict(X, verbose=0)[0] # Shape: (num_numbers,)
|
||||
|
||||
# Convert to dictionary
|
||||
result = {i + 1: float(predictions[i]) for i in range(self.num_numbers)}
|
||||
|
||||
return result
|
||||
|
||||
def get_model_info(self) -> Dict:
|
||||
"""Returns model information."""
|
||||
return {
|
||||
'is_trained': self.is_trained,
|
||||
'tensorflow_available': TENSORFLOW_AVAILABLE,
|
||||
'num_numbers': self.num_numbers,
|
||||
'sequence_length': self.sequence_length,
|
||||
'config': self.config,
|
||||
'training_history': self.training_history,
|
||||
'model_exists': self.model is not None
|
||||
}
|
||||
|
||||
|
||||
class HybridDeepLearningPredictor:
|
||||
"""
|
||||
Kombiniert RandomForest + LSTM für robustere Vorhersagen.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dl_engine: DeepLearningEngine,
|
||||
rf_weight: float = 0.4,
|
||||
dl_weight: float = 0.6
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
dl_engine: Deep Learning Engine
|
||||
rf_weight: Weight for RandomForest predictions
|
||||
dl_weight: Weight for Deep Learning predictions
|
||||
"""
|
||||
self.dl_engine = dl_engine
|
||||
self.rf_weight = rf_weight
|
||||
self.dl_weight = dl_weight
|
||||
|
||||
print(f"🔀 Hybrid Predictor: RF={rf_weight:.1%} + DL={dl_weight:.1%}")
|
||||
|
||||
def predict(
|
||||
self,
|
||||
rf_predictions: Dict[int, float],
|
||||
recent_df: pd.DataFrame,
|
||||
recent_features: pd.DataFrame
|
||||
) -> Dict[int, float]:
|
||||
"""
|
||||
Combines RandomForest and Deep Learning predictions.
|
||||
|
||||
Args:
|
||||
rf_predictions: Predictions from RandomForest
|
||||
recent_df: Recent draws for DL
|
||||
recent_features: Recent features for DL
|
||||
|
||||
Returns:
|
||||
Combined predictions
|
||||
"""
|
||||
# Get DL predictions
|
||||
dl_predictions = self.dl_engine.predict(recent_df, recent_features)
|
||||
|
||||
# Combine
|
||||
combined = {}
|
||||
for num in range(1, self.dl_engine.num_numbers + 1):
|
||||
rf_score = rf_predictions.get(num, 0.5)
|
||||
dl_score = dl_predictions.get(num, 0.5)
|
||||
|
||||
combined[num] = (
|
||||
self.rf_weight * rf_score +
|
||||
self.dl_weight * dl_score
|
||||
)
|
||||
|
||||
# Normalize to [0, 1]
|
||||
min_score = min(combined.values())
|
||||
max_score = max(combined.values())
|
||||
if max_score > min_score:
|
||||
combined = {
|
||||
num: (score - min_score) / (max_score - min_score)
|
||||
for num, score in combined.items()
|
||||
}
|
||||
|
||||
return combined
|
||||
@@ -0,0 +1,526 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Deep Learning Engine mit PyTorch LSTM für Lotto-Vorhersagen
|
||||
============================================================
|
||||
|
||||
PyTorch-basierte Implementierung für Python 3.14+ Kompatibilität.
|
||||
TensorFlow unterstützt Python 3.14 noch nicht, daher verwenden wir PyTorch.
|
||||
|
||||
Features:
|
||||
- LSTM-Netzwerk für zeitliche Sequenzen
|
||||
- Sequence-to-Probability Mapping
|
||||
- Feature Engineering Integration
|
||||
- Model Persistence & Caching
|
||||
- Hybrid mit RandomForest
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import os
|
||||
import json
|
||||
import pickle
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
import warnings
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
# PyTorch Imports
|
||||
try:
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.optim as optim
|
||||
from torch.utils.data import Dataset, DataLoader
|
||||
PYTORCH_AVAILABLE = True
|
||||
except ImportError:
|
||||
PYTORCH_AVAILABLE = False
|
||||
print("⚠️ PyTorch not available. Install with: pip install torch")
|
||||
|
||||
|
||||
class LSTMModel(nn.Module):
|
||||
"""PyTorch LSTM Model for Lotto prediction."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
num_features: int,
|
||||
num_numbers: int,
|
||||
sequence_length: int,
|
||||
lstm_units_1: int = 128,
|
||||
lstm_units_2: int = 64,
|
||||
dense_units: int = 128,
|
||||
dropout_rate: float = 0.3
|
||||
):
|
||||
super(LSTMModel, self).__init__()
|
||||
|
||||
self.lstm1 = nn.LSTM(
|
||||
input_size=num_features,
|
||||
hidden_size=lstm_units_1,
|
||||
batch_first=True,
|
||||
bidirectional=True
|
||||
)
|
||||
self.dropout1 = nn.Dropout(dropout_rate)
|
||||
self.batch_norm1 = nn.BatchNorm1d(lstm_units_1 * 2)
|
||||
|
||||
self.lstm2 = nn.LSTM(
|
||||
input_size=lstm_units_1 * 2,
|
||||
hidden_size=lstm_units_2,
|
||||
batch_first=True,
|
||||
bidirectional=True
|
||||
)
|
||||
self.dropout2 = nn.Dropout(dropout_rate)
|
||||
self.batch_norm2 = nn.BatchNorm1d(lstm_units_2 * 2)
|
||||
|
||||
self.fc1 = nn.Linear(lstm_units_2 * 2, dense_units)
|
||||
self.batch_norm3 = nn.BatchNorm1d(dense_units)
|
||||
self.dropout3 = nn.Dropout(dropout_rate)
|
||||
|
||||
self.fc2 = nn.Linear(dense_units, num_numbers)
|
||||
|
||||
def forward(self, x):
|
||||
# LSTM 1
|
||||
x, _ = self.lstm1(x)
|
||||
x = self.dropout1(x)
|
||||
# Take last output
|
||||
x = x[:, -1, :]
|
||||
x = self.batch_norm1(x)
|
||||
|
||||
# LSTM 2 needs 3D input
|
||||
x = x.unsqueeze(1)
|
||||
x, _ = self.lstm2(x)
|
||||
x = x[:, -1, :]
|
||||
x = self.dropout2(x)
|
||||
x = self.batch_norm2(x)
|
||||
|
||||
# Dense layers
|
||||
x = torch.relu(self.fc1(x))
|
||||
x = self.batch_norm3(x)
|
||||
x = self.dropout3(x)
|
||||
|
||||
# Output
|
||||
x = torch.sigmoid(self.fc2(x))
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class LottoDataset(Dataset):
|
||||
"""PyTorch Dataset for Lotto sequences."""
|
||||
|
||||
def __init__(self, X, y):
|
||||
self.X = torch.FloatTensor(X)
|
||||
self.y = torch.FloatTensor(y)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.X)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
return self.X[idx], self.y[idx]
|
||||
|
||||
|
||||
class DeepLearningEngine:
|
||||
"""
|
||||
PyTorch-based LSTM Deep Learning Engine for Lotto predictions.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
num_numbers: int = 49,
|
||||
sequence_length: int = 20,
|
||||
cache_dir: str = None,
|
||||
fast_mode: bool = True
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
num_numbers: Maximum number (49 for Lotto, 50 for Eurojackpot)
|
||||
sequence_length: How many past draws to consider
|
||||
cache_dir: Directory for model persistence
|
||||
fast_mode: Use faster training (fewer epochs)
|
||||
"""
|
||||
self.num_numbers = num_numbers
|
||||
self.sequence_length = sequence_length
|
||||
self.cache_dir = cache_dir
|
||||
self.fast_mode = fast_mode
|
||||
|
||||
# Device
|
||||
self.device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu')
|
||||
|
||||
# Model components
|
||||
self.model = None
|
||||
self.is_trained = False
|
||||
self.training_history = {}
|
||||
|
||||
# Configuration
|
||||
self.config = {
|
||||
'lstm_units_1': 128,
|
||||
'lstm_units_2': 64,
|
||||
'dense_units': 128,
|
||||
'dropout_rate': 0.3,
|
||||
'learning_rate': 0.001,
|
||||
'batch_size': 32,
|
||||
'epochs': 30 if fast_mode else 100,
|
||||
'validation_split': 0.2
|
||||
}
|
||||
|
||||
if cache_dir:
|
||||
os.makedirs(cache_dir, exist_ok=True)
|
||||
self.model_path = os.path.join(cache_dir, f'lstm_model_{num_numbers}.pth')
|
||||
self.config_path = os.path.join(cache_dir, f'lstm_config_{num_numbers}.json')
|
||||
else:
|
||||
self.model_path = None
|
||||
self.config_path = None
|
||||
|
||||
print(f"🧠 Deep Learning Engine initialized (PyTorch)")
|
||||
print(f" Numbers: 1-{num_numbers}")
|
||||
print(f" Sequence Length: {sequence_length}")
|
||||
print(f" Device: {self.device}")
|
||||
print(f" Fast Mode: {fast_mode}")
|
||||
|
||||
def _build_model(self, num_features: int) -> LSTMModel:
|
||||
"""Builds LSTM architecture."""
|
||||
model = LSTMModel(
|
||||
num_features=num_features,
|
||||
num_numbers=self.num_numbers,
|
||||
sequence_length=self.sequence_length,
|
||||
lstm_units_1=self.config['lstm_units_1'],
|
||||
lstm_units_2=self.config['lstm_units_2'],
|
||||
dense_units=self.config['dense_units'],
|
||||
dropout_rate=self.config['dropout_rate']
|
||||
)
|
||||
return model.to(self.device)
|
||||
|
||||
def _prepare_sequences(
|
||||
self,
|
||||
df: pd.DataFrame,
|
||||
features_df: pd.DataFrame
|
||||
) -> Tuple[np.ndarray, np.ndarray]:
|
||||
"""Prepares sequences for LSTM training."""
|
||||
print(f" Preparing sequences (length={self.sequence_length})...")
|
||||
|
||||
# Ensure data is sorted by date
|
||||
if 'datum' in df.columns:
|
||||
df = df.sort_values('datum').reset_index(drop=True)
|
||||
|
||||
# Extract number columns
|
||||
num_cols = [col for col in df.columns if col.startswith('Z')]
|
||||
|
||||
# Normalize features to [0, 1]
|
||||
features_normalized = features_df.copy()
|
||||
for col in features_normalized.columns:
|
||||
min_val = features_normalized[col].min()
|
||||
max_val = features_normalized[col].max()
|
||||
if max_val > min_val:
|
||||
features_normalized[col] = (features_normalized[col] - min_val) / (max_val - min_val)
|
||||
else:
|
||||
features_normalized[col] = 0.5
|
||||
|
||||
X_sequences = []
|
||||
y_targets = []
|
||||
|
||||
# Create sequences
|
||||
for i in range(self.sequence_length, len(df)):
|
||||
# Get sequence of features
|
||||
sequence = features_normalized.iloc[i - self.sequence_length:i].values
|
||||
X_sequences.append(sequence)
|
||||
|
||||
# Target: next draw as binary vector
|
||||
target = np.zeros(self.num_numbers)
|
||||
next_draw = df.iloc[i][num_cols].values
|
||||
for num in next_draw:
|
||||
if 1 <= num <= self.num_numbers:
|
||||
target[int(num) - 1] = 1
|
||||
y_targets.append(target)
|
||||
|
||||
X = np.array(X_sequences)
|
||||
y = np.array(y_targets)
|
||||
|
||||
print(f" ✅ Created {len(X)} sequences")
|
||||
print(f" Shape: X={X.shape}, y={y.shape}")
|
||||
|
||||
return X, y
|
||||
|
||||
def train(
|
||||
self,
|
||||
df: pd.DataFrame,
|
||||
features_df: pd.DataFrame,
|
||||
force_retrain: bool = False
|
||||
) -> bool:
|
||||
"""Trains LSTM model on historical data."""
|
||||
if not PYTORCH_AVAILABLE:
|
||||
print("❌ PyTorch not available")
|
||||
return False
|
||||
|
||||
# Check for cached model
|
||||
if not force_retrain and self.model_path and os.path.exists(self.model_path):
|
||||
print(" 📦 Loading cached LSTM model...")
|
||||
try:
|
||||
checkpoint = torch.load(self.model_path, weights_only=False)
|
||||
num_features = checkpoint['num_features']
|
||||
|
||||
self.model = self._build_model(num_features)
|
||||
self.model.load_state_dict(checkpoint['model_state_dict'])
|
||||
self.model.eval()
|
||||
self.is_trained = True
|
||||
|
||||
# Load config
|
||||
if os.path.exists(self.config_path):
|
||||
with open(self.config_path, 'r') as f:
|
||||
self.training_history = json.load(f)
|
||||
|
||||
print(f" ✅ Loaded cached model")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Failed to load cached model: {e}")
|
||||
print(" 🔄 Training new model...")
|
||||
|
||||
print(f"\n🧠 TRAINING DEEP LEARNING MODEL (PyTorch LSTM)")
|
||||
print("=" * 70)
|
||||
|
||||
# Prepare data
|
||||
X, y = self._prepare_sequences(df, features_df)
|
||||
|
||||
if len(X) < 100:
|
||||
print(" ⚠️ Not enough data for training (need >100 sequences)")
|
||||
return False
|
||||
|
||||
# Split data
|
||||
split_idx = int(len(X) * (1 - self.config['validation_split']))
|
||||
X_train, X_val = X[:split_idx], X[split_idx:]
|
||||
y_train, y_val = y[:split_idx], y[split_idx:]
|
||||
|
||||
# Create datasets
|
||||
train_dataset = LottoDataset(X_train, y_train)
|
||||
val_dataset = LottoDataset(X_val, y_val)
|
||||
|
||||
train_loader = DataLoader(
|
||||
train_dataset,
|
||||
batch_size=self.config['batch_size'],
|
||||
shuffle=True
|
||||
)
|
||||
val_loader = DataLoader(
|
||||
val_dataset,
|
||||
batch_size=self.config['batch_size'],
|
||||
shuffle=False
|
||||
)
|
||||
|
||||
# Build model
|
||||
print(f" Building LSTM architecture...")
|
||||
num_features = X.shape[2]
|
||||
self.model = self._build_model(num_features)
|
||||
|
||||
# Show summary
|
||||
total_params = sum(p.numel() for p in self.model.parameters())
|
||||
print(f"\n 📊 Model Summary:")
|
||||
print(f" Total parameters: {total_params:,}")
|
||||
|
||||
# Loss and optimizer
|
||||
criterion = nn.BCELoss()
|
||||
optimizer = optim.Adam(self.model.parameters(), lr=self.config['learning_rate'])
|
||||
scheduler = optim.lr_scheduler.ReduceLROnPlateau(
|
||||
optimizer, mode='min', factor=0.5, patience=5
|
||||
)
|
||||
|
||||
# Train
|
||||
print(f"\n 🚀 Training for {self.config['epochs']} epochs...")
|
||||
print(f" Batch size: {self.config['batch_size']}")
|
||||
print(f" Validation split: {self.config['validation_split']:.1%}")
|
||||
|
||||
best_val_loss = float('inf')
|
||||
patience_counter = 0
|
||||
patience = 10
|
||||
|
||||
try:
|
||||
for epoch in range(self.config['epochs']):
|
||||
# Training
|
||||
self.model.train()
|
||||
train_loss = 0.0
|
||||
for batch_X, batch_y in train_loader:
|
||||
batch_X = batch_X.to(self.device)
|
||||
batch_y = batch_y.to(self.device)
|
||||
|
||||
optimizer.zero_grad()
|
||||
outputs = self.model(batch_X)
|
||||
loss = criterion(outputs, batch_y)
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
train_loss += loss.item()
|
||||
|
||||
train_loss /= len(train_loader)
|
||||
|
||||
# Validation
|
||||
self.model.eval()
|
||||
val_loss = 0.0
|
||||
with torch.no_grad():
|
||||
for batch_X, batch_y in val_loader:
|
||||
batch_X = batch_X.to(self.device)
|
||||
batch_y = batch_y.to(self.device)
|
||||
|
||||
outputs = self.model(batch_X)
|
||||
loss = criterion(outputs, batch_y)
|
||||
val_loss += loss.item()
|
||||
|
||||
val_loss /= len(val_loader)
|
||||
|
||||
# Learning rate scheduling
|
||||
scheduler.step(val_loss)
|
||||
|
||||
# Print progress every 5 epochs
|
||||
if (epoch + 1) % 5 == 0:
|
||||
print(f" Epoch {epoch+1}/{self.config['epochs']}: "
|
||||
f"Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}")
|
||||
|
||||
# Early stopping
|
||||
if val_loss < best_val_loss:
|
||||
best_val_loss = val_loss
|
||||
patience_counter = 0
|
||||
|
||||
# Save best model
|
||||
if self.model_path:
|
||||
torch.save({
|
||||
'model_state_dict': self.model.state_dict(),
|
||||
'num_features': num_features,
|
||||
'config': self.config
|
||||
}, self.model_path)
|
||||
else:
|
||||
patience_counter += 1
|
||||
if patience_counter >= patience:
|
||||
print(f" Early stopping at epoch {epoch+1}")
|
||||
break
|
||||
|
||||
# Load best model
|
||||
if self.model_path and os.path.exists(self.model_path):
|
||||
checkpoint = torch.load(self.model_path, weights_only=False)
|
||||
self.model.load_state_dict(checkpoint['model_state_dict'])
|
||||
|
||||
# Store training history
|
||||
self.training_history = {
|
||||
'trained_at': datetime.now().isoformat(),
|
||||
'num_samples': len(X),
|
||||
'num_features': num_features,
|
||||
'final_loss': float(train_loss),
|
||||
'final_val_loss': float(val_loss),
|
||||
'best_val_loss': float(best_val_loss),
|
||||
'epochs_trained': epoch + 1
|
||||
}
|
||||
|
||||
self.is_trained = True
|
||||
|
||||
# Save config
|
||||
if self.config_path:
|
||||
with open(self.config_path, 'w') as f:
|
||||
json.dump(self.training_history, f, indent=2)
|
||||
|
||||
# Print results
|
||||
print(f"\n ✅ TRAINING COMPLETED")
|
||||
print(f" Best Val Loss: {best_val_loss:.4f}")
|
||||
print(f" Epochs: {self.training_history['epochs_trained']}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n ❌ Training failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def predict(
|
||||
self,
|
||||
recent_df: pd.DataFrame,
|
||||
recent_features: pd.DataFrame
|
||||
) -> Dict[int, float]:
|
||||
"""Predicts probabilities for each number."""
|
||||
if not self.is_trained or self.model is None:
|
||||
print("⚠️ Model not trained, returning uniform distribution")
|
||||
return {i: 0.5 for i in range(1, self.num_numbers + 1)}
|
||||
|
||||
if len(recent_df) < self.sequence_length:
|
||||
print(f"⚠️ Not enough recent data (need {self.sequence_length}, got {len(recent_df)})")
|
||||
return {i: 0.5 for i in range(1, self.num_numbers + 1)}
|
||||
|
||||
# Prepare sequence
|
||||
recent_features_normalized = recent_features.copy()
|
||||
for col in recent_features_normalized.columns:
|
||||
min_val = recent_features_normalized[col].min()
|
||||
max_val = recent_features_normalized[col].max()
|
||||
if max_val > min_val:
|
||||
recent_features_normalized[col] = (recent_features_normalized[col] - min_val) / (max_val - min_val)
|
||||
else:
|
||||
recent_features_normalized[col] = 0.5
|
||||
|
||||
sequence = recent_features_normalized.iloc[-self.sequence_length:].values
|
||||
X = torch.FloatTensor(sequence).unsqueeze(0).to(self.device)
|
||||
|
||||
# Predict
|
||||
self.model.eval()
|
||||
with torch.no_grad():
|
||||
predictions = self.model(X)[0].cpu().numpy()
|
||||
|
||||
# Convert to dictionary
|
||||
result = {i + 1: float(predictions[i]) for i in range(self.num_numbers)}
|
||||
|
||||
return result
|
||||
|
||||
def get_model_info(self) -> Dict:
|
||||
"""Returns model information."""
|
||||
return {
|
||||
'is_trained': self.is_trained,
|
||||
'pytorch_available': PYTORCH_AVAILABLE,
|
||||
'num_numbers': self.num_numbers,
|
||||
'sequence_length': self.sequence_length,
|
||||
'device': str(self.device),
|
||||
'config': self.config,
|
||||
'training_history': self.training_history,
|
||||
'model_exists': self.model is not None
|
||||
}
|
||||
|
||||
|
||||
class HybridDeepLearningPredictor:
|
||||
"""Kombiniert RandomForest + LSTM für robustere Vorhersagen."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dl_engine: DeepLearningEngine,
|
||||
rf_weight: float = 0.4,
|
||||
dl_weight: float = 0.6
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
dl_engine: Deep Learning Engine
|
||||
rf_weight: Weight for RandomForest predictions
|
||||
dl_weight: Weight for Deep Learning predictions
|
||||
"""
|
||||
self.dl_engine = dl_engine
|
||||
self.rf_weight = rf_weight
|
||||
self.dl_weight = dl_weight
|
||||
|
||||
print(f"🔀 Hybrid Predictor: RF={rf_weight:.1%} + DL={dl_weight:.1%}")
|
||||
|
||||
def predict(
|
||||
self,
|
||||
rf_predictions: Dict[int, float],
|
||||
recent_df: pd.DataFrame,
|
||||
recent_features: pd.DataFrame
|
||||
) -> Dict[int, float]:
|
||||
"""Combines RandomForest and Deep Learning predictions."""
|
||||
# Get DL predictions
|
||||
dl_predictions = self.dl_engine.predict(recent_df, recent_features)
|
||||
|
||||
# Combine
|
||||
combined = {}
|
||||
for num in range(1, self.dl_engine.num_numbers + 1):
|
||||
rf_score = rf_predictions.get(num, 0.5)
|
||||
dl_score = dl_predictions.get(num, 0.5)
|
||||
|
||||
combined[num] = (
|
||||
self.rf_weight * rf_score +
|
||||
self.dl_weight * dl_score
|
||||
)
|
||||
|
||||
# Normalize to [0, 1]
|
||||
min_score = min(combined.values())
|
||||
max_score = max(combined.values())
|
||||
if max_score > min_score:
|
||||
combined = {
|
||||
num: (score - min_score) / (max_score - min_score)
|
||||
for num, score in combined.items()
|
||||
}
|
||||
|
||||
return combined
|
||||
@@ -0,0 +1,345 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Health-Check System mit Auto-Recovery
|
||||
======================================
|
||||
|
||||
Prüft System-Gesundheit und behebt automatisch Probleme:
|
||||
- CSV-Datei vorhanden und aktuell?
|
||||
- Models trainiert und verfügbar?
|
||||
- Learning State konsistent?
|
||||
- Logs rotieren?
|
||||
- Telegram-Bot erreichbar?
|
||||
|
||||
Bei Problemen:
|
||||
- Auto-Retry
|
||||
- Telegram-Alerts
|
||||
- Logging
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple
|
||||
import pandas as pd
|
||||
|
||||
# Add parent dir to path
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_dir = os.path.dirname(os.path.dirname(script_dir))
|
||||
sys.path.insert(0, project_dir)
|
||||
|
||||
from scripts.utils.notifier import LottoNotifier
|
||||
|
||||
|
||||
class HealthCheck:
|
||||
"""System Health-Check mit Auto-Recovery."""
|
||||
|
||||
def __init__(self, data_dir: str, lottery_name: str = "Lotto"):
|
||||
self.data_dir = data_dir
|
||||
self.lottery_name = lottery_name
|
||||
self.notifier = LottoNotifier()
|
||||
|
||||
self.health_log = os.path.join(data_dir, "health_check.json")
|
||||
self.issues = []
|
||||
self.warnings = []
|
||||
self.recovered = []
|
||||
|
||||
print(f"🏥 HEALTH-CHECK SYSTEM - {lottery_name}")
|
||||
print("=" * 70)
|
||||
|
||||
def run_all_checks(self) -> bool:
|
||||
"""Führt alle Health-Checks aus."""
|
||||
all_ok = True
|
||||
|
||||
checks = [
|
||||
("CSV Data", self.check_csv_data),
|
||||
("ML Models", self.check_ml_models),
|
||||
("Learning State", self.check_learning_state),
|
||||
("Logs", self.check_logs),
|
||||
("Telegram", self.check_telegram),
|
||||
("Disk Space", self.check_disk_space)
|
||||
]
|
||||
|
||||
for name, check_func in checks:
|
||||
print(f"\n🔍 Checking: {name}...", end=" ", flush=True)
|
||||
try:
|
||||
status, message = check_func()
|
||||
if status == "OK":
|
||||
print(f"✅ {message}")
|
||||
elif status == "WARNING":
|
||||
print(f"⚠️ {message}")
|
||||
self.warnings.append(f"{name}: {message}")
|
||||
elif status == "ERROR":
|
||||
print(f"❌ {message}")
|
||||
self.issues.append(f"{name}: {message}")
|
||||
all_ok = False
|
||||
elif status == "RECOVERED":
|
||||
print(f"🔧 {message}")
|
||||
self.recovered.append(f"{name}: {message}")
|
||||
except Exception as e:
|
||||
print(f"❌ Exception: {e}")
|
||||
self.issues.append(f"{name}: Exception - {e}")
|
||||
all_ok = False
|
||||
|
||||
# Summary
|
||||
print("\n" + "=" * 70)
|
||||
self._print_summary()
|
||||
|
||||
# Save health log
|
||||
self._save_health_log(all_ok)
|
||||
|
||||
# Send alert if issues
|
||||
if self.issues:
|
||||
self._send_alert()
|
||||
|
||||
return all_ok
|
||||
|
||||
def check_csv_data(self) -> Tuple[str, str]:
|
||||
"""Prüft CSV-Datei."""
|
||||
csv_file = os.path.join(self.data_dir, "AlleLottozahlen.csv")
|
||||
|
||||
if not os.path.exists(csv_file):
|
||||
return ("ERROR", f"CSV file not found: {csv_file}")
|
||||
|
||||
# Check age
|
||||
mtime = os.path.getmtime(csv_file)
|
||||
age_days = (time.time() - mtime) / 86400
|
||||
|
||||
if age_days > 10:
|
||||
return ("WARNING", f"CSV file is {age_days:.1f} days old")
|
||||
|
||||
# Check content
|
||||
try:
|
||||
df = pd.read_csv(csv_file, sep=';')
|
||||
if len(df) < 100:
|
||||
return ("ERROR", f"CSV has only {len(df)} rows")
|
||||
|
||||
return ("OK", f"{len(df):,} draws, {age_days:.1f} days old")
|
||||
except Exception as e:
|
||||
return ("ERROR", f"CSV parse error: {e}")
|
||||
|
||||
def check_ml_models(self) -> Tuple[str, str]:
|
||||
"""Prüft ML Models."""
|
||||
models_dir = os.path.join(self.data_dir, "ultimate_ml_models")
|
||||
|
||||
if not os.path.exists(models_dir):
|
||||
return ("WARNING", "No models cache found (will train on next run)")
|
||||
|
||||
# Check RandomForest models
|
||||
rf_model = os.path.join(models_dir, "trained_models.pkl")
|
||||
if os.path.exists(rf_model):
|
||||
age_days = (time.time() - os.path.getmtime(rf_model)) / 86400
|
||||
status = "OK" if age_days < 10 else "WARNING"
|
||||
msg = f"RandomForest models {age_days:.1f} days old"
|
||||
else:
|
||||
status = "WARNING"
|
||||
msg = "RandomForest models not found"
|
||||
|
||||
# Check Deep Learning models
|
||||
dl_model = os.path.join(models_dir, "deep_learning", "lstm_model_49.pth")
|
||||
if os.path.exists(dl_model):
|
||||
age_days = (time.time() - os.path.getmtime(dl_model)) / 86400
|
||||
msg += f", LSTM {age_days:.1f} days old"
|
||||
else:
|
||||
msg += ", LSTM not found"
|
||||
|
||||
return (status, msg)
|
||||
|
||||
def check_learning_state(self) -> Tuple[str, str]:
|
||||
"""Prüft Learning State."""
|
||||
state_file = os.path.join(self.data_dir, "learning_state.json")
|
||||
|
||||
if not os.path.exists(state_file):
|
||||
return ("WARNING", "No learning state found")
|
||||
|
||||
try:
|
||||
with open(state_file, 'r') as f:
|
||||
state = json.load(f)
|
||||
|
||||
cycles = state.get('learning_cycle', 0)
|
||||
last_update = state.get('last_update', '')
|
||||
|
||||
if not last_update:
|
||||
return ("WARNING", f"{cycles} cycles, no last_update timestamp")
|
||||
|
||||
last_dt = datetime.fromisoformat(last_update)
|
||||
age_days = (datetime.now() - last_dt).days
|
||||
|
||||
if age_days > 10:
|
||||
return ("WARNING", f"{cycles} cycles, last update {age_days} days ago")
|
||||
|
||||
return ("OK", f"{cycles} cycles, last update {age_days} days ago")
|
||||
|
||||
except Exception as e:
|
||||
return ("ERROR", f"State parse error: {e}")
|
||||
|
||||
def check_logs(self) -> Tuple[str, str]:
|
||||
"""Prüft und rotiert Logs."""
|
||||
logs_dir = os.path.join(os.path.dirname(self.data_dir), "logs")
|
||||
|
||||
if not os.path.exists(logs_dir):
|
||||
os.makedirs(logs_dir, exist_ok=True)
|
||||
return ("RECOVERED", "Created logs directory")
|
||||
|
||||
# Check log sizes
|
||||
total_size = 0
|
||||
large_logs = []
|
||||
|
||||
for log_file in Path(logs_dir).glob("*.log"):
|
||||
size_mb = log_file.stat().st_size / 1024 / 1024
|
||||
total_size += size_mb
|
||||
|
||||
if size_mb > 50: # > 50 MB
|
||||
large_logs.append(log_file.name)
|
||||
|
||||
# Rotate large logs
|
||||
if large_logs:
|
||||
for log_name in large_logs:
|
||||
self._rotate_log(os.path.join(logs_dir, log_name))
|
||||
|
||||
return ("RECOVERED", f"Rotated {len(large_logs)} large logs, total {total_size:.1f} MB")
|
||||
|
||||
return ("OK", f"Total size {total_size:.1f} MB")
|
||||
|
||||
def check_telegram(self) -> Tuple[str, str]:
|
||||
"""Prüft Telegram-Bot."""
|
||||
config = self.notifier.config
|
||||
|
||||
if not config.get("telegram", {}).get("enabled"):
|
||||
return ("WARNING", "Telegram disabled in config")
|
||||
|
||||
bot_token = config.get("telegram", {}).get("bot_token")
|
||||
if not bot_token or bot_token == "YOUR_BOT_TOKEN":
|
||||
return ("WARNING", "Telegram bot_token not configured")
|
||||
|
||||
# Simple check: Token format
|
||||
if len(bot_token) < 20 or ':' not in bot_token:
|
||||
return ("ERROR", "Invalid bot_token format")
|
||||
|
||||
return ("OK", "Telegram configured")
|
||||
|
||||
def check_disk_space(self) -> Tuple[str, str]:
|
||||
"""Prüft Festplatten-Speicher."""
|
||||
import shutil
|
||||
|
||||
usage = shutil.disk_usage(self.data_dir)
|
||||
free_gb = usage.free / 1024 / 1024 / 1024
|
||||
percent_free = (usage.free / usage.total) * 100
|
||||
|
||||
if percent_free < 10:
|
||||
return ("ERROR", f"Only {free_gb:.1f} GB free ({percent_free:.1f}%)")
|
||||
elif percent_free < 20:
|
||||
return ("WARNING", f"{free_gb:.1f} GB free ({percent_free:.1f}%)")
|
||||
|
||||
return ("OK", f"{free_gb:.1f} GB free ({percent_free:.1f}%)")
|
||||
|
||||
def _rotate_log(self, log_path: str):
|
||||
"""Rotiert ein Log-File."""
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
backup_path = f"{log_path}.{timestamp}"
|
||||
|
||||
os.rename(log_path, backup_path)
|
||||
print(f" 📦 Rotated: {os.path.basename(log_path)} → {os.path.basename(backup_path)}")
|
||||
|
||||
def _print_summary(self):
|
||||
"""Druckt Zusammenfassung."""
|
||||
print("\n📊 SUMMARY:")
|
||||
|
||||
if not self.issues and not self.warnings and not self.recovered:
|
||||
print(" ✅ All checks passed - System healthy!")
|
||||
return
|
||||
|
||||
if self.recovered:
|
||||
print(f"\n 🔧 Auto-Recovered ({len(self.recovered)}):")
|
||||
for item in self.recovered:
|
||||
print(f" • {item}")
|
||||
|
||||
if self.warnings:
|
||||
print(f"\n ⚠️ Warnings ({len(self.warnings)}):")
|
||||
for item in self.warnings:
|
||||
print(f" • {item}")
|
||||
|
||||
if self.issues:
|
||||
print(f"\n ❌ Issues ({len(self.issues)}):")
|
||||
for item in self.issues:
|
||||
print(f" • {item}")
|
||||
|
||||
def _save_health_log(self, all_ok: bool):
|
||||
"""Speichert Health-Log."""
|
||||
log_entry = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"status": "OK" if all_ok else "ISSUES",
|
||||
"issues": self.issues,
|
||||
"warnings": self.warnings,
|
||||
"recovered": self.recovered
|
||||
}
|
||||
|
||||
# Load existing log
|
||||
if os.path.exists(self.health_log):
|
||||
with open(self.health_log, 'r') as f:
|
||||
log_data = json.load(f)
|
||||
else:
|
||||
log_data = {"checks": []}
|
||||
|
||||
# Append new entry
|
||||
log_data["checks"].append(log_entry)
|
||||
|
||||
# Keep only last 100 entries
|
||||
log_data["checks"] = log_data["checks"][-100:]
|
||||
|
||||
# Save
|
||||
with open(self.health_log, 'w') as f:
|
||||
json.dump(log_data, f, indent=2)
|
||||
|
||||
def _send_alert(self):
|
||||
"""Sendet Telegram-Alert bei Problemen."""
|
||||
message = f"🚨 *HEALTH-CHECK ALERT - {self.lottery_name}*\n\n"
|
||||
message += f"❌ *{len(self.issues)} Issues detected:*\n"
|
||||
|
||||
for issue in self.issues:
|
||||
message += f"• {issue}\n"
|
||||
|
||||
if self.warnings:
|
||||
message += f"\n⚠️ {len(self.warnings)} Warnings:\n"
|
||||
for warning in self.warnings:
|
||||
message += f"• {warning}\n"
|
||||
|
||||
message += f"\n🕐 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
|
||||
try:
|
||||
self.notifier._send_telegram(message)
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Could not send alert: {e}")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function."""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="System Health-Check")
|
||||
parser.add_argument(
|
||||
'--data-dir',
|
||||
type=str,
|
||||
default="/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Lotto/data",
|
||||
help='Data directory'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--lottery',
|
||||
type=str,
|
||||
default="Lotto",
|
||||
help='Lottery name (Lotto or Eurojackpot)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Run health check
|
||||
checker = HealthCheck(args.data_dir, args.lottery)
|
||||
success = checker.run_all_checks()
|
||||
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+14
-15
@@ -107,14 +107,6 @@ class LottoNotifier:
|
||||
message += f"📅 *Generiert:* {timestamp}\n"
|
||||
message += f"📊 *Anzahl Tipps:* {len(tips)}\n\n"
|
||||
|
||||
# Bester Tipp
|
||||
message += "⭐ *BESTER TIPP:*\n"
|
||||
numbers_str = ' - '.join([f"{n:02d}" for n in best_tip['numbers']])
|
||||
message += f"🎯 Zahlen: `{numbers_str}`\n"
|
||||
message += f"🌟 Superzahl: `{best_tip['superzahl']}`\n"
|
||||
message += f"📈 Confidence: `{best_tip['confidence']:.4f}`\n"
|
||||
message += f"🎨 Strategie: `{best_tip['strategy']}`\n\n"
|
||||
|
||||
# Statistiken
|
||||
avg_conf = sum(t['confidence'] for t in tips) / len(tips)
|
||||
avg_qual = sum(t['quality'] for t in tips) / len(tips)
|
||||
@@ -123,16 +115,23 @@ class LottoNotifier:
|
||||
message += f"🎯 Ø Confidence: `{avg_conf:.4f}`\n"
|
||||
message += f"💎 Ø Quality: `{avg_qual:.4f}`\n\n"
|
||||
|
||||
# Top 3 Tipps
|
||||
message += "🏆 *TOP 3 TIPPS:*\n"
|
||||
sorted_tips = sorted(tips, key=lambda t: t['confidence'], reverse=True)[:3]
|
||||
# Top 5 Tipps mit Details
|
||||
message += "🏆 *TOP 5 EMPFEHLUNGEN:*\n"
|
||||
message += "━━━━━━━━━━━━━━━━━━━━━━━\n"
|
||||
|
||||
sorted_tips = sorted(tips, key=lambda t: t['confidence'], reverse=True)[:5]
|
||||
|
||||
for i, tip in enumerate(sorted_tips, 1):
|
||||
nums = ' - '.join([f"{n:02d}" for n in tip['numbers']])
|
||||
message += f"{i}. `{nums}` + SZ `{tip['superzahl']}` "
|
||||
message += f"({tip['confidence']:.3f})\n"
|
||||
# Emoji basierend auf Rang
|
||||
emoji = "🏆" if i == 1 else "🥈" if i == 2 else "🥉" if i == 3 else "⭐"
|
||||
|
||||
message += "\n🍀 *Viel Glück!*"
|
||||
nums = '-'.join([f"{n:02d}" for n in tip['numbers']])
|
||||
message += f"\n{emoji} *#{i}* Conf: `{tip['confidence']:.2%}`\n"
|
||||
message += f"🔢 `{nums}` + SZ `{tip['superzahl']}`\n"
|
||||
message += f"📈 {tip['strategy']} | Q: `{tip['quality']:.3f}`\n"
|
||||
|
||||
message += "\n💡 Alle 10 Tipps in der CSV-Datei!\n"
|
||||
message += "🍀 *Viel Glück!*"
|
||||
|
||||
return message
|
||||
|
||||
|
||||
Reference in New Issue
Block a user