Initial commit: existing turf_saas codebase
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
347
agent_turf.py
Executable file
347
agent_turf.py
Executable file
@@ -0,0 +1,347 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user