#!/usr/bin/env python3 """ Backtest route for API v1. GET /api/v1/backtest — Résultats backtest historiques (pro) """ from datetime import datetime, timedelta from flask import Blueprint, jsonify, request from api_v1.utils import ( get_db, table_exists, internal_error, bad_request, get_pagination_params, paginate_query, ) from auth import jwt_required_middleware, plan_required backtest_bp = Blueprint("v1_backtest", __name__, url_prefix="/api/v1") @backtest_bp.route("/backtest", methods=["GET"]) @jwt_required_middleware @plan_required("pro") def backtest(): """ Backtest historique --- tags: - Backtest summary: Résultats backtest historiques des paris simulés — accès pro uniquement security: - Bearer: [] parameters: - name: start in: query type: string format: date description: Date de début (YYYY-MM-DD), défaut = -30j - name: end in: query type: string format: date description: Date de fin (YYYY-MM-DD), défaut = aujourd'hui - name: limit in: query type: integer default: 50 - name: offset in: query type: integer default: 0 responses: 200: description: Résultats backtest 401: description: Token invalide 403: description: Plan insuffisant (pro requis) """ start = request.args.get("start") end = request.args.get("end") # Validate date formats for label, val in [("start", start), ("end", end)]: if val: try: datetime.strptime(val, "%Y-%m-%d") except ValueError: return bad_request( f"Paramètre '{label}' invalide, format attendu: YYYY-MM-DD" ) if not start: start = (datetime.now() - timedelta(days=30)).strftime("%Y-%m-%d") if not end: end = datetime.now().strftime("%Y-%m-%d") limit, offset = get_pagination_params(default_limit=50, max_limit=200) conn = get_db() try: if not table_exists(conn, "bet_results"): return jsonify( { "status": "ok", "period": {"start": start, "end": end}, "summary": { "total_bets": 0, "message": "Aucune donnée bet_results", }, "by_type": {}, "details": [], "pagination": { "total": 0, "limit": limit, "offset": offset, "has_more": False, }, } ), 200 # Summary summary_row = conn.execute( """SELECT COUNT(*) AS total, SUM(CASE WHEN resultat='GAGNE' THEN 1 ELSE 0 END) AS gagne, SUM(mise) AS mise, SUM(gain) AS gain FROM bet_results WHERE date BETWEEN ? AND ?""", (start, end), ).fetchone() total_bets = summary_row["total"] or 0 gagne = summary_row["gagne"] or 0 mise = float(summary_row["mise"] or 0) gain = float(summary_row["gain"] or 0) roi = round((gain - mise) / mise * 100, 1) if mise > 0 else 0.0 precision = round(gagne / total_bets * 100, 1) if total_bets > 0 else 0.0 # By type by_type_rows = conn.execute( """SELECT type_pari, COUNT(*) AS total, SUM(CASE WHEN resultat='GAGNE' THEN 1 ELSE 0 END) AS gagne, SUM(mise) AS mise, SUM(gain) AS gain FROM bet_results WHERE date BETWEEN ? AND ? GROUP BY type_pari""", (start, end), ).fetchall() by_type = {} for row in by_type_rows: t = row["total"] or 0 g = row["gagne"] or 0 m = float(row["mise"] or 0) gn = float(row["gain"] or 0) by_type[row["type_pari"]] = { "count": t, "gagne": g, "mise": round(m, 2), "gain": round(gn, 2), "roi": round((gn - m) / m * 100, 1) if m > 0 else 0.0, "precision": round(g / t * 100, 1) if t > 0 else 0.0, } # Paginated details count_row = conn.execute( "SELECT COUNT(*) AS cnt FROM bet_results WHERE date BETWEEN ? AND ?", (start, end), ).fetchone() detail_total = count_row["cnt"] if count_row else 0 detail_rows = conn.execute( """SELECT date, race_name, type_pari, horse_name, horse_number, COALESCE(cote, 0) AS cote, mise, resultat, gain FROM bet_results WHERE date BETWEEN ? AND ? ORDER BY date DESC, id DESC LIMIT ? OFFSET ?""", (start, end, limit, offset), ).fetchall() details = [dict(r) for r in detail_rows] pagination = paginate_query(details, detail_total, limit, offset) return jsonify( { "status": "ok", "period": {"start": start, "end": end}, "summary": { "total_bets": total_bets, "gagne": gagne, "perdu": total_bets - gagne, "precision": precision, "mise_totale": round(mise, 2), "gain_total": round(gain, 2), "roi": roi, }, "by_type": by_type, "details": details, **pagination, } ), 200 except Exception as e: return internal_error(str(e)) finally: conn.close()