348 lines
10 KiB
Python
Executable File
348 lines
10 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
Agent Turf Autonome - Prédictions quotidiennes
|
||
Usage: python3 agent_turf.py [--dry-run]
|
||
"""
|
||
|
||
import sqlite3
|
||
import subprocess
|
||
import sys
|
||
import json
|
||
import os
|
||
from datetime import datetime, timedelta
|
||
from pathlib import Path
|
||
|
||
DB_PATH = "/home/h3r7/turf_scraper/turf.db"
|
||
LOG_FILE = "/home/h3r7/turf_scraper/logs/agent_turf.log"
|
||
VPS_HOST = "10.0.1.1"
|
||
|
||
|
||
def log(msg: str):
|
||
"""Log avec timestamp"""
|
||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
line = f"[{timestamp}] {msg}"
|
||
print(line)
|
||
|
||
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
|
||
with open(LOG_FILE, "a") as f:
|
||
f.write(line + "\n")
|
||
|
||
|
||
def sync_database():
|
||
"""Sync base de données depuis VPS"""
|
||
log("=== SYNC DATABASE ===")
|
||
|
||
# Check remote size
|
||
cmd = f"sshpass -p 'Cronstadt35*' ssh -o StrictHostKeyChecking=no h3r7@{VPS_HOST} stat -c %s {DB_PATH}"
|
||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
||
|
||
if result.returncode != 0:
|
||
log(f"ERROR: Cannot connect to VPS")
|
||
return False
|
||
|
||
remote_size = int(result.stdout.strip())
|
||
local_size = os.path.getsize(DB_PATH) if os.path.exists(DB_PATH) else 0
|
||
|
||
if remote_size == local_size:
|
||
log(f"Database up to date ({remote_size} bytes)")
|
||
return True
|
||
|
||
log(f"Syncing database (remote: {remote_size}, local: {local_size})...")
|
||
|
||
cmd = f"sshpass -p 'Cronstadt35*' scp -o StrictHostKeyChecking=no h3r7@{VPS_HOST}:{DB_PATH} {DB_PATH}.tmp"
|
||
result = subprocess.run(cmd, shell=True, capture_output=True)
|
||
|
||
if result.returncode == 0:
|
||
os.rename(DB_PATH + ".tmp", DB_PATH)
|
||
log(f"Database synced successfully")
|
||
return True
|
||
else:
|
||
log(f"ERROR: Sync failed")
|
||
return False
|
||
|
||
|
||
def generate_predictions():
|
||
"""Génère les prédictions du jour"""
|
||
log("=== GENERATE PREDICTIONS ===")
|
||
|
||
conn = sqlite3.connect(DB_PATH)
|
||
conn.row_factory = sqlite3.Row
|
||
cursor = conn.cursor()
|
||
|
||
today = datetime.now().strftime("%Y-%m-%d")
|
||
|
||
# Get today's races
|
||
cursor.execute(
|
||
"""
|
||
SELECT DISTINCT c.date_programme, c.num_reunion, c.num_course,
|
||
c.libelle, c.heure_depart_str, r.hippodrome_court
|
||
FROM pmu_courses c
|
||
JOIN pmu_reunions r ON r.date_programme = c.date_programme AND r.num_reunion = c.num_reunion
|
||
WHERE c.date_programme = ? AND r.pays_code = 'FRA'
|
||
ORDER BY c.heure_depart_str
|
||
""",
|
||
(today,),
|
||
)
|
||
|
||
races = cursor.fetchall()
|
||
log(f"Found {len(races)} races today")
|
||
|
||
predictions = []
|
||
|
||
for race in races:
|
||
date, num_reunion, num_course, libelle, heure, hippodrome = race
|
||
|
||
# Get scoring
|
||
cursor.execute(
|
||
"""
|
||
SELECT horse_name, horse_number, score, cote, rang_scoring
|
||
FROM scoring
|
||
WHERE date = ? AND race_name LIKE ?
|
||
ORDER BY rang_scoring
|
||
LIMIT 4
|
||
""",
|
||
(date, f"%{libelle}%"),
|
||
)
|
||
|
||
scores = cursor.fetchall()
|
||
|
||
if scores:
|
||
pred = {
|
||
"race": f"{heure} - {hippodrome} - {libelle}",
|
||
"top4": [dict(s) for s in scores],
|
||
}
|
||
predictions.append(pred)
|
||
log(
|
||
f" {pred['race'][:50]} -> {', '.join([s['horse_name'] for s in scores[:3]])}"
|
||
)
|
||
|
||
conn.close()
|
||
return predictions
|
||
|
||
|
||
def save_recommendations(predictions):
|
||
"""Sauvegarde les recommandations en base"""
|
||
log("=== SAVE RECOMMENDATIONS ===")
|
||
|
||
conn = sqlite3.connect(DB_PATH)
|
||
cursor = conn.cursor()
|
||
|
||
today = datetime.now().strftime("%Y-%m-%d")
|
||
|
||
# Clear today's recommendations
|
||
cursor.execute("DELETE FROM recommendations WHERE date = ?", (today,))
|
||
|
||
for pred in predictions:
|
||
race_name = pred["race"]
|
||
|
||
# Add top 4
|
||
for i, horse in enumerate(pred["top4"]):
|
||
if i < 4: # TOP 4
|
||
cursor.execute(
|
||
"""
|
||
INSERT INTO recommendations
|
||
(date, race_name, type_pari, cheval1, numero1, cote, mise, confiance, justification)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
today,
|
||
race_name,
|
||
f"top{i + 1}",
|
||
horse["horse_name"],
|
||
horse["horse_number"],
|
||
horse["cote"],
|
||
2.0,
|
||
f"TOP {i + 1}",
|
||
f"Score: {horse['score']}, Rang scoring: {horse['rang_scoring']}",
|
||
),
|
||
)
|
||
|
||
# Add simple winner (top 1)
|
||
top1 = pred["top4"][0]
|
||
cursor.execute(
|
||
"""
|
||
INSERT INTO recommendations
|
||
(date, race_name, type_pari, cheval1, numero1, cote, mise, confiance, justification)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
""",
|
||
(
|
||
today,
|
||
race_name,
|
||
"simple_gagnant",
|
||
top1["horse_name"],
|
||
top1["horse_number"],
|
||
top1["cote"],
|
||
2.0,
|
||
"🔥 FORTE" if top1["score"] > 60 else "✅ BONNE",
|
||
f"Score: {top1['score']}, Favorite prediction",
|
||
),
|
||
)
|
||
|
||
conn.commit()
|
||
conn.close()
|
||
log(f"Saved {len(predictions)} race recommendations")
|
||
|
||
|
||
def notify_telegram(predictions):
|
||
"""Envoie les prédictions du jour via Telegram"""
|
||
import urllib.request
|
||
|
||
config_path = "/home/h3r7/turf_scraper/config_telegram.json"
|
||
if not os.path.exists(config_path):
|
||
log("No Telegram config found, skipping notification")
|
||
return
|
||
|
||
try:
|
||
with open(config_path, "r") as f:
|
||
cfg = json.load(f)
|
||
token = cfg.get("token", "")
|
||
chat_id = cfg.get("chat_id", "")
|
||
if not token or not chat_id:
|
||
log("Telegram config missing token or chat_id")
|
||
return
|
||
except Exception as e:
|
||
log(f"Error reading Telegram config: {e}")
|
||
return
|
||
|
||
today = datetime.now().strftime("%d/%m/%Y")
|
||
lines = [f"🏇 *Prédictions Turf — {today}*", ""]
|
||
|
||
if not predictions:
|
||
lines.append("Aucune prédiction disponible aujourd'hui.")
|
||
else:
|
||
for pred in predictions:
|
||
race_label = pred.get("race", "Course inconnue")
|
||
top4 = pred.get("top4", [])
|
||
lines.append(f"📍 *{race_label}*")
|
||
for i, horse in enumerate(top4, 1):
|
||
name = horse.get("horse_name", "?")
|
||
score = horse.get("score", 0)
|
||
cote = horse.get("cote", "?")
|
||
icon = "🥇" if i == 1 else ("🥈" if i == 2 else ("🥉" if i == 3 else "4️⃣"))
|
||
lines.append(f" {icon} {name} — score: {score} — cote: {cote}")
|
||
lines.append("")
|
||
|
||
text = "\n".join(lines)
|
||
|
||
url = f"https://api.telegram.org/bot{token}/sendMessage"
|
||
payload = json.dumps({
|
||
"chat_id": chat_id,
|
||
"text": text,
|
||
"parse_mode": "Markdown"
|
||
}).encode("utf-8")
|
||
|
||
try:
|
||
req = urllib.request.Request(
|
||
url,
|
||
data=payload,
|
||
headers={"Content-Type": "application/json"},
|
||
method="POST"
|
||
)
|
||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||
result = json.loads(resp.read().decode("utf-8"))
|
||
if result.get("ok"):
|
||
log(f"Telegram notification sent ({len(predictions)} races)")
|
||
else:
|
||
log(f"Telegram API error: {result}")
|
||
except Exception as e:
|
||
log(f"Error sending Telegram notification: {e}")
|
||
|
||
|
||
def run_backtest_yesterday():
|
||
"""Lance le backtest sur les résultats d'hier"""
|
||
log("=== RUN BACKTEST ===")
|
||
|
||
yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
|
||
|
||
# Check if we have results for yesterday
|
||
conn = sqlite3.connect(DB_PATH)
|
||
cursor = conn.cursor()
|
||
|
||
cursor.execute(
|
||
"""
|
||
SELECT COUNT(*) FROM pmu_partants
|
||
WHERE date_programme = ? AND ordre_arrivee > 0
|
||
""",
|
||
(yesterday,),
|
||
)
|
||
|
||
count = cursor.fetchone()[0]
|
||
conn.close()
|
||
|
||
if count > 0:
|
||
log(f"Running backtest for {yesterday}...")
|
||
# Run backtest script
|
||
cmd = f"cd /home/h3r7/turf_scraper && python3 backtest_analyzer.py --date {yesterday}"
|
||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
||
if result.returncode == 0:
|
||
log("Backtest completed")
|
||
else:
|
||
log(f"Backtest failed: {result.stderr}")
|
||
else:
|
||
log(f"No results for {yesterday}, skipping backtest")
|
||
|
||
|
||
def update_paris_results():
|
||
"""Met à jour les paris avec les résultats PMU"""
|
||
log("=== UPDATE PARIS RESULTS ===")
|
||
|
||
today = datetime.now().strftime("%Y-%m-%d")
|
||
|
||
cmd = (
|
||
f"cd /home/h3r7/turf_scraper && python3 update_paris_results.py --date {today}"
|
||
)
|
||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
||
|
||
if result.returncode == 0:
|
||
log(f"Paris updated: {result.stdout.strip()}")
|
||
else:
|
||
log(f"Paris update failed: {result.stderr}")
|
||
|
||
|
||
def main():
|
||
"""Point d'entrée principal"""
|
||
dry_run = "--dry-run" in sys.argv
|
||
|
||
log("=" * 50)
|
||
log("🤖 AGENT TURF AUTONOME - DEMARRAGE")
|
||
log("=" * 50)
|
||
|
||
try:
|
||
# 1. Sync database
|
||
if not dry_run:
|
||
sync_database()
|
||
|
||
# 2. Generate predictions
|
||
predictions = generate_predictions()
|
||
|
||
# 3. Save recommendations
|
||
if not dry_run and predictions:
|
||
save_recommendations(predictions)
|
||
|
||
# 3b. Send Telegram notification
|
||
if not dry_run:
|
||
notify_telegram(predictions)
|
||
|
||
# 4. Create paris from recommendations
|
||
if not dry_run and predictions:
|
||
update_paris_results()
|
||
|
||
# 5. Run backtest on yesterday
|
||
if not dry_run:
|
||
run_backtest_yesterday()
|
||
|
||
log("=" * 50)
|
||
log("✅ AGENT TURF - TERMINÉ")
|
||
log("=" * 50)
|
||
|
||
except Exception as e:
|
||
log(f"❌ ERREUR: {e}")
|
||
import traceback
|
||
|
||
traceback.print_exc()
|
||
sys.exit(1)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|