pmupredict/
├── core/
│ ├── init.py
│ ├── config.py # Paramètres généraux (seuils, poids, etc.)
│ ├── data_manager.py # Gestion des données (CSV, SQLite, vectorDB)
│ ├── feature_engineering.py # Extraction des caractéristiques (XYZ)
│ ├── model_manager.py # Gestion du modèle SecretPMUPredictor
│ ├── probability_matrix.py # Calcul des probabilités (Plackett-Luce, Monte Carlo)
│ └── tracker.py # Traqueurs de chevaux performants
├── data/
│ ├── historical/ # CSV des 3 derniers mois
│ └── vector_db/ # Index vectoriel (Milvus/Pinecone)
├── api/
│ └── app.py # Serveur Flask/FastAPI pour Android
├── android/
│ ├── PmuPredictor.kt # Intégration Android
│ └── RetrofitService.kt # Appels API
└── scripts/
├── ingest_historical.py # Indexation des données historiques
└── train_model.py # Entraînement du modèle
Index Feature Description Plage
0 Musique moyenne (3 dernières) Moyenne des places 0-10
1 Tendance récente Pente des 5 dernières courses -5/+5
2 Poids normalisé (Poids - 53) / 7 0-1
3 Âge normalisé (Âge - 3) / 7 0-1
4 Gains normalisés Gains / 300 000 0-1
5 Driver score Hash du driver 0-1
6 Corde score 1-6 = 1, 7-12 = 0.5, 13-18 = 0 0-1
7 Spécialité Attelé 1 si attelé, 0 sinon 0/1
8 Spécialité Plat 1 si plat, 0 sinon 0/1
9 Spécialité Haie 1 si haie, 0 sinon 0/1
10 Ferrures (déferré) 1 si déferré, 0 sinon 0/1
11 Oeillères 1 si oeillères, 0 sinon 0/1
12 Gains récents Gains des 6 derniers mois 0-1
13 Podiums récents Nombre de podiums (5 dernières) 0-5
14 Appétence Ratio places / courses 0-1
15 Coefficient de forme Note entraîneur / driver 0-10
16 Coefficient de réussite % de victoires 0-10
17 Écart actuel Nombre de courses depuis dernière place 0-100
import pickle
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.neural_network import MLPRegressor
import joblib
class PmuPredictor:
def init(self):
self.scaler = StandardScaler()
self.pca = PCA(n_components=0.95)
self.rf = None
self.gb = None
self.mlp = None
self.trained = False
self.weights = None
self.bias = 0.0
# Paramètres de configuration
self.params = {
'voting_power': [0.35, 0.35, 0.30],
'feature_names': [
'musique_avg', 'tendance', 'poids_norm', 'age_norm',
'gains_norm', 'driver_score', 'corde_score',
'special_attelé', 'special_plat', 'special_haie',
'deferre', 'oeilleres', 'gains_recents', 'podiums_recents',
'appetence', 'coefficient_forme', 'coefficient_reussite',
'ecart_actuel'
]
}
# Traqueurs de chevaux
self.trackers = {
'top_gains': [], # Cheval avec les + gros gains
'top_forme': [], # Cheval avec la meilleure forme
'top_regulier': [], # Cheval le plus régulier
'top_corde': [], # Meilleur numéro à la corde
'top_driver': [] # Meilleur driver du jour
}
def extract_features(self, horse):
"""Extrait les 18 dimensions du vecteur XYZ"""
# Simuler l'extraction (à remplacer par vos vraies données)
return np.random.rand(18)
def train(self, X, y):
"""Entraîne le modèle sur les données historiques"""
X_scaled = self.scaler.fit_transform(X)
X_pca = self.pca.fit_transform(X_scaled)
self.rf = RandomForestRegressor(n_estimators=200, max_depth=12)
self.gb = GradientBoostingRegressor(n_estimators=150, learning_rate=0.05)
self.mlp = MLPRegressor(hidden_layer_sizes=(64,32), max_iter=500)
self.rf.fit(X_pca, y)
self.gb.fit(X_pca, y)
self.mlp.fit(X_pca, y)
self.trained = True
return self
def predict(self, X):
"""Prédiction avec softmax"""
if not self.trained:
raise ValueError("Modèle non entraîné")
X_scaled = self.scaler.transform(X)
X_pca = self.pca.transform(X_scaled)
pred_rf = self.rf.predict(X_pca)
pred_gb = self.gb.predict(X_pca)
pred_mlp = self.mlp.predict(X_pca)
pred = (self.params['voting_power'][0] * pred_rf +
self.params['voting_power'][1] * pred_gb +
self.params['voting_power'][2] * pred_mlp)
# Softmax pour obtenir des probabilités
exp_pred = np.exp(pred - np.max(pred))
return exp_pred / np.sum(exp_pred)
def save(self, path):
"""Sauvegarde le modèle"""
with open(path, 'wb') as f:
pickle.dump(self, f)
def load(self, path):
"""Charge le modèle"""
with open(path, 'rb') as f:
obj = pickle.load(f)
self.__dict__.update(obj.__dict__)
def compute_place_probabilities(scores, n_sim=10000):
"""
Calcule les probabilités de chaque place (1er à 5e) via Monte Carlo.
"""
n = len(scores)
weights = np.exp(scores - np.max(scores))
prob_win = weights / np.sum(weights)
# Simulation
win_counts = np.zeros(n)
top3_counts = np.zeros(n)
top5_counts = np.zeros(n)
place_counts = np.zeros((n, 5))
for _ in range(n_sim):
order = np.random.choice(n, size=n, replace=False, p=weights/np.sum(weights))
win_counts[order[0]] += 1
for idx in order[:3]:
top3_counts[idx] += 1
for idx in order[:5]:
top5_counts[idx] += 1
for pos, idx in enumerate(order[:5]):
place_counts[idx, pos] += 1
return {
'win': win_counts / n_sim,
'top3': top3_counts / n_sim,
'top5': top5_counts / n_sim,
'places': place_counts / n_sim
}
class HorseTracker:
"""
Suit les performances des chevaux sur différentes conditions.
"""
def init(self, db_connection):
self.db = db_connection
def track_horse(self, horse_id, race_data):
"""
Met à jour les statistiques d'un cheval après une course.
"""
# Mise à jour des gains, podiums, tendance, etc.
pass
def get_top_horses(self, condition='all', limit=10):
"""
Retourne les chevaux les plus performants selon un critère.
condition : 'gains', 'forme', 'regularite', 'corde', 'driver'
"""
query = f"SELECT * FROM horses ORDER BY {condition} DESC LIMIT {limit}"
return pd.read_sql(query, self.db)
def get_top_forme(self):
"""Chevaux avec la meilleure forme (moyenne des 3 dernières places)"""
return self.get_top_horses('forme', 5)
def get_top_gains(self):
"""Chevaux avec les gains les plus élevés"""
return self.get_top_horses('gains', 5)
def get_top_reguliers(self):
"""Chevaux avec le meilleur taux de places"""
return self.get_top_horses('regularite', 5)
import pinecone # ou pymilvus
class VectorDatabase:
def init(self, index_name='pmu_vectors'):
pinecone.init(api_key="your-api-key", environment="us-west1-gcp")
self.index = pinecone.Index(index_name)
def upsert_vectors(self, vectors):
"""
vectors : liste de tuples (id, vector, metadata)
"""
self.index.upsert(vectors=vectors)
def query_similar(self, vector, top_k=10):
"""
Retourne les chevaux les plus similaires à un vecteur donné.
"""
return self.index.query(vector=vector, top_k=top_k, include_metadata=True)
def build_historical_index(self, df):
"""
Indexe 3 mois de données historiques.
"""
for _, row in df.iterrows():
vector = row[['feature1', 'feature2', ...]].values.tolist()
metadata = {'nom': row['nom'], 'gains': row['gains'], 'hippodrome': row['hippodrome']}
self.upsert_vectors([(str(row['id']), vector, metadata)])
from flask import Flask, request, jsonify
from flask_cors import CORS
from pmupredict.core.model_manager import PmuPredictor
from pmupredict.core.probability_matrix import compute_place_probabilities
app = Flask(name)
CORS(app)
model = PmuPredictor()
model.load("secret_pmu_model.pkl")
@app.route("/predict", methods=["POST"])
def predict():
data = request.get_json()
horses = data.get("horses", [])
features = [model.extract_features(h) for h in horses]
X = np.array(features)
probs = model.predict(X)
prob_matrix = compute_place_probabilities(X.dot(model.weights) + model.bias)
result = []
for i, h in enumerate(horses):
result.append({
"numero": h["numero"],
"nom": h["nom"],
"probabilite": round(probs[i] * 100, 2),
"prob_win": round(prob_matrix['win'][i] * 100, 2),
"prob_top3": round(prob_matrix['top3'][i] * 100, 2),
"prob_top5": round(prob_matrix['top5'][i] * 100, 2),
"places": [round(p * 100, 2) for p in prob_matrix['places'][i]]
})
result.sort(key=lambda x: x["probabilite"], reverse=True)
return jsonify(result)
@app.route("/tracker/top_forme", methods=["GET"])
def top_forme():
tracker = HorseTracker(db)
return jsonify(tracker.get_top_forme().to_dict('records'))
@app.route("/tracker/top_gains", methods=["GET"])
def top_gains():
tracker = HorseTracker(db)
return jsonify(tracker.get_top_gains().to_dict('records'))
if name == "main":
app.run(host="0.0.0.0", port=5000)
import retrofit2.http.*
interface PmuApiService {
@post("predict")
suspend fun predict(@Body request: Map<String, List>): List
@GET("tracker/top_forme")
suspend fun getTopForme(): List<HorseStat>
@GET("tracker/top_gains")
suspend fun getTopGains(): List<HorseStat>
}
data class HorseRequest(
val musique: String,
val driver: String,
val poids: Double,
val age: Int,
val gains: Double,
val numero: Int,
val nom: String
)
data class PredictionResponse(
val numero: Int,
val nom: String,
val probabilite: Double,
val prob_win: Double,
val prob_top3: Double,
val prob_top5: Double,
val places: List
)
data class HorseStat(
val nom: String,
val score: Double,
val hippodrome: String,
val gains: Double
)
class PmuPredictor(private val context: Context) {
private val api = RetrofitInstance.api
private val viewModel = ViewModelProvider(context).get(PredictionViewModel::class.java)
suspend fun getPrediction(horses: List<HorseRequest>): List<PredictionResponse> {
return api.predict(mapOf("horses" to horses))
}
suspend fun getTopForme(): List<HorseStat> {
return api.getTopForme()
}
suspend fun getTopGains(): List<HorseStat> {
return api.getTopGains()
}
}
Traqueur Condition Utilisation
Top Gains Gains > 250 000 € Base solide
Top Forme Moyenne des 3 dernières places < 2 Favori
Top Régulier Ratio places / courses > 0.7 Outsider de valeur
Top Corde Numéro de corde 1 à 4 Avantage à la corde
Top Driver Jockey avec > 20% de victoires Plus-value
import pandas as pd
from pmupredict.core.data_manager import DataManager
from pmupredict.core.vector_database import VectorDatabase
Charger les 3 mois de données
df = pd.read_csv("historical_3months.csv")
Indexer dans la base vectorielle
db = VectorDatabase()
db.build_historical_index(df)
Entraîner le modèle
model = PmuPredictor()
X = df[['feature1', 'feature2', ...]].values
y = df['arrivee'].values
model.train(X, y)
model.save("secret_pmu_model_updated.pkl")
print("✅ Données indexées et modèle entraîné.")
// Dans votre fragment
val predictions = viewModel.predictions.value
recyclerView.adapter = PredictionAdapter(predictions)
// Affichage :
// 1. CHESS (n°1) – 24.5%
// Vainqueur : 22.1% | Podium : 58.3% | Top5 : 81.2%
// Places : 22.1% | 18.2% | 14.3% | 10.5% | 7.2%
pmupredict/
├── core/
│ ├── init.py
│ ├── config.py # Paramètres généraux (seuils, poids, etc.)
│ ├── data_manager.py # Gestion des données (CSV, SQLite, vectorDB)
│ ├── feature_engineering.py # Extraction des caractéristiques (XYZ)
│ ├── model_manager.py # Gestion du modèle SecretPMUPredictor
│ ├── probability_matrix.py # Calcul des probabilités (Plackett-Luce, Monte Carlo)
│ └── tracker.py # Traqueurs de chevaux performants
├── data/
│ ├── historical/ # CSV des 3 derniers mois
│ └── vector_db/ # Index vectoriel (Milvus/Pinecone)
├── api/
│ └── app.py # Serveur Flask/FastAPI pour Android
├── android/
│ ├── PmuPredictor.kt # Intégration Android
│ └── RetrofitService.kt # Appels API
└── scripts/
├── ingest_historical.py # Indexation des données historiques
└── train_model.py # Entraînement du modèle
Index Feature Description Plage
0 Musique moyenne (3 dernières) Moyenne des places 0-10
1 Tendance récente Pente des 5 dernières courses -5/+5
2 Poids normalisé (Poids - 53) / 7 0-1
3 Âge normalisé (Âge - 3) / 7 0-1
4 Gains normalisés Gains / 300 000 0-1
5 Driver score Hash du driver 0-1
6 Corde score 1-6 = 1, 7-12 = 0.5, 13-18 = 0 0-1
7 Spécialité Attelé 1 si attelé, 0 sinon 0/1
8 Spécialité Plat 1 si plat, 0 sinon 0/1
9 Spécialité Haie 1 si haie, 0 sinon 0/1
10 Ferrures (déferré) 1 si déferré, 0 sinon 0/1
11 Oeillères 1 si oeillères, 0 sinon 0/1
12 Gains récents Gains des 6 derniers mois 0-1
13 Podiums récents Nombre de podiums (5 dernières) 0-5
14 Appétence Ratio places / courses 0-1
15 Coefficient de forme Note entraîneur / driver 0-10
16 Coefficient de réussite % de victoires 0-10
17 Écart actuel Nombre de courses depuis dernière place 0-100
import pickle
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.neural_network import MLPRegressor
import joblib
class PmuPredictor:
def init(self):
self.scaler = StandardScaler()
self.pca = PCA(n_components=0.95)
self.rf = None
self.gb = None
self.mlp = None
self.trained = False
self.weights = None
self.bias = 0.0
def compute_place_probabilities(scores, n_sim=10000):
"""
Calcule les probabilités de chaque place (1er à 5e) via Monte Carlo.
"""
n = len(scores)
weights = np.exp(scores - np.max(scores))
prob_win = weights / np.sum(weights)
class HorseTracker:
"""
Suit les performances des chevaux sur différentes conditions.
"""
def init(self, db_connection):
self.db = db_connection
import pinecone # ou pymilvus
class VectorDatabase:
def init(self, index_name='pmu_vectors'):
pinecone.init(api_key="your-api-key", environment="us-west1-gcp")
self.index = pinecone.Index(index_name)
from flask import Flask, request, jsonify
from flask_cors import CORS
from pmupredict.core.model_manager import PmuPredictor
from pmupredict.core.probability_matrix import compute_place_probabilities
app = Flask(name)
CORS(app)
model = PmuPredictor()
model.load("secret_pmu_model.pkl")
@app.route("/predict", methods=["POST"])
def predict():
data = request.get_json()
horses = data.get("horses", [])
features = [model.extract_features(h) for h in horses]
X = np.array(features)
probs = model.predict(X)
prob_matrix = compute_place_probabilities(X.dot(model.weights) + model.bias)
@app.route("/tracker/top_forme", methods=["GET"])
def top_forme():
tracker = HorseTracker(db)
return jsonify(tracker.get_top_forme().to_dict('records'))
@app.route("/tracker/top_gains", methods=["GET"])
def top_gains():
tracker = HorseTracker(db)
return jsonify(tracker.get_top_gains().to_dict('records'))
if name == "main":
app.run(host="0.0.0.0", port=5000)
import retrofit2.http.*
interface PmuApiService {
@post("predict")
suspend fun predict(@Body request: Map<String, List>): List
}
data class HorseRequest(
val musique: String,
val driver: String,
val poids: Double,
val age: Int,
val gains: Double,
val numero: Int,
val nom: String
)
data class PredictionResponse(
val numero: Int,
val nom: String,
val probabilite: Double,
val prob_win: Double,
val prob_top3: Double,
val prob_top5: Double,
val places: List
)
data class HorseStat(
val nom: String,
val score: Double,
val hippodrome: String,
val gains: Double
)
class PmuPredictor(private val context: Context) {
private val api = RetrofitInstance.api
private val viewModel = ViewModelProvider(context).get(PredictionViewModel::class.java)
}
Traqueur Condition Utilisation
Top Gains Gains > 250 000 € Base solide
Top Forme Moyenne des 3 dernières places < 2 Favori
Top Régulier Ratio places / courses > 0.7 Outsider de valeur
Top Corde Numéro de corde 1 à 4 Avantage à la corde
Top Driver Jockey avec > 20% de victoires Plus-value
import pandas as pd
from pmupredict.core.data_manager import DataManager
from pmupredict.core.vector_database import VectorDatabase
Charger les 3 mois de données
df = pd.read_csv("historical_3months.csv")
Indexer dans la base vectorielle
db = VectorDatabase()
db.build_historical_index(df)
Entraîner le modèle
model = PmuPredictor()
X = df[['feature1', 'feature2', ...]].values
y = df['arrivee'].values
model.train(X, y)
model.save("secret_pmu_model_updated.pkl")
print("✅ Données indexées et modèle entraîné.")
// Dans votre fragment
val predictions = viewModel.predictions.value
recyclerView.adapter = PredictionAdapter(predictions)
// Affichage :
// 1. CHESS (n°1) – 24.5%
// Vainqueur : 22.1% | Podium : 58.3% | Top5 : 81.2%
// Places : 22.1% | 18.2% | 14.3% | 10.5% | 7.2%