Compare commits
2 Commits
c8c674dd1c
...
fc46e56b82
| Author | SHA1 | Date | |
|---|---|---|---|
| fc46e56b82 | |||
| 8fe93de673 |
@@ -58,42 +58,6 @@
|
||||
<Reference Include="CsvHelper, Version=33.0.0.0, Culture=neutral, PublicKeyToken=8c4a6d608ce8f59c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CsvHelper.33.0.1\lib\net481\CsvHelper.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.10.0.0-preview.4.25258.110\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bcl.HashCode, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bcl.HashCode.6.0.0\lib\net461\Microsoft.Bcl.HashCode.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Bcl.Numerics, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bcl.Numerics.10.0.0-preview.4.25258.110\lib\net462\Microsoft.Bcl.Numerics.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.ML.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.ML.CpuMath, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ML.CpuMath.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.CpuMath.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.ML.Data, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.Data.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.ML.DataView, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ML.DataView.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.DataView.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.ML.FastTree, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ML.FastTree.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.FastTree.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.ML.KMeansClustering, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.KMeansClustering.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.ML.PCA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.PCA.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.ML.StandardTrainers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.StandardTrainers.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.ML.Transforms, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.Transforms.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
@@ -194,10 +158,6 @@
|
||||
<Compile Include="HorseRacing\Main.cs" />
|
||||
<Compile Include="Horses\Calculator.cs" />
|
||||
<Compile Include="Horses\Files\Maps\Punters.cs" />
|
||||
<Compile Include="Horses\ML\HorseRacePrediction.cs" />
|
||||
<Compile Include="Horses\ML\MachineLearningService.cs" />
|
||||
<Compile Include="Horses\ML\ModelInput.cs" />
|
||||
<Compile Include="Horses\ML\ModelOutput.cs" />
|
||||
<Compile Include="Manager\API.cs" />
|
||||
<Compile Include="Manager\FileReader.cs" />
|
||||
<Compile Include="Manager\Database.cs" />
|
||||
@@ -215,7 +175,6 @@
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<None Include="Dati\Result.csv" />
|
||||
<None Include="packages.config" />
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
@@ -231,11 +190,7 @@
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Query\ScanFavoriteHorseWeights.sql" />
|
||||
<Content Include="Query\PredictionScanResults.sql" />
|
||||
<Content Include="Query\GetFavoriteHorses.sql" />
|
||||
<Content Include="Query\GetValidHorses.sql" />
|
||||
<Content Include="Query\Races.sql" />
|
||||
<Folder Include="Themes\" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets" Condition="Exists('..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" />
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -153,6 +153,7 @@ namespace HorseRacingPredictor.Football.Database
|
||||
l.country AS Paese,
|
||||
l.name AS Campionato,
|
||||
f.date AS [Data / Ora],
|
||||
f.timestamp AS unix_ts,
|
||||
f.status AS Stato,
|
||||
th.name AS Casa,
|
||||
ta.name AS Trasferta,
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
||||
using RestSharp;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Data.SqlClient;
|
||||
|
||||
namespace HorseRacingPredictor.Football
|
||||
@@ -317,7 +318,13 @@ namespace HorseRacingPredictor.Football
|
||||
{
|
||||
try
|
||||
{
|
||||
var jsonObject = JsonNode.Parse(jsonResponse);
|
||||
// Parse JSON using System.Text.Json and convert to Newtonsoft.JToken where needed
|
||||
var jsonNode = JsonNode.Parse(jsonResponse);
|
||||
var jsonObject = JObject.Parse(jsonNode.ToJsonString());
|
||||
|
||||
// Helper to enumerate array-like tokens (compat replacement for JsonNode.AsArray())
|
||||
var responseArray = jsonObject["response"] as JArray;
|
||||
Func<JToken, IEnumerable<JToken>> asArray = t => t is JArray a ? a : (t != null ? t.Children() : Enumerable.Empty<JToken>());
|
||||
|
||||
_database.ExecuteTransactionalQuery("l'elaborazione dei dati calcistici", (connection, transaction) =>
|
||||
{
|
||||
@@ -327,7 +334,7 @@ namespace HorseRacingPredictor.Football
|
||||
((Manager.Database)_database).DisableAllConstraints(connection, transaction);
|
||||
|
||||
// FASE 1: Inserisci le leghe
|
||||
foreach (var responseItem in jsonObject["response"].AsArray())
|
||||
foreach (var responseItem in asArray(jsonObject["response"]))
|
||||
{
|
||||
var league = responseItem["league"];
|
||||
if (league != null && league["id"] != null)
|
||||
@@ -337,11 +344,11 @@ namespace HorseRacingPredictor.Football
|
||||
}
|
||||
|
||||
// FASE 2: Inserisci i bookmakers e i tipi di scommessa
|
||||
foreach (var responseItem in jsonObject["response"].AsArray())
|
||||
foreach (var responseItem in asArray(jsonObject["response"]))
|
||||
{
|
||||
if (responseItem["bookmakers"] != null)
|
||||
{
|
||||
foreach (var bookmaker in responseItem["bookmakers"].AsArray())
|
||||
foreach (var bookmaker in asArray(responseItem["bookmakers"]))
|
||||
{
|
||||
if (bookmaker["id"] != null)
|
||||
{
|
||||
@@ -350,7 +357,7 @@ namespace HorseRacingPredictor.Football
|
||||
// Tipi di scommessa
|
||||
if (bookmaker["bets"] != null)
|
||||
{
|
||||
foreach (var bet in bookmaker["bets"].AsArray())
|
||||
foreach (var bet in asArray(bookmaker["bets"]))
|
||||
{
|
||||
if (bet["id"] != null && bet["name"] != null)
|
||||
{
|
||||
@@ -364,7 +371,7 @@ namespace HorseRacingPredictor.Football
|
||||
}
|
||||
|
||||
// FASE 3: Inserisci le squadre (senza relazione con fixture)
|
||||
foreach (var responseItem in jsonObject["response"].AsArray())
|
||||
foreach (var responseItem in asArray(jsonObject["response"]))
|
||||
{
|
||||
var teams = responseItem["teams"];
|
||||
if (teams != null && teams["home"] != null && teams["away"] != null &&
|
||||
@@ -376,7 +383,7 @@ namespace HorseRacingPredictor.Football
|
||||
}
|
||||
|
||||
// FASE 4: Inserisci i fixture e venue
|
||||
foreach (var responseItem in jsonObject["response"].AsArray())
|
||||
foreach (var responseItem in asArray(jsonObject["response"]))
|
||||
{
|
||||
var fixture = responseItem["fixture"];
|
||||
if (fixture != null && fixture["id"] != null)
|
||||
@@ -386,7 +393,7 @@ namespace HorseRacingPredictor.Football
|
||||
}
|
||||
|
||||
// FASE 5: Inserisci relazioni tra entità e dati dipendenti
|
||||
foreach (var responseItem in jsonObject["response"].AsArray())
|
||||
foreach (var responseItem in asArray(jsonObject["response"]))
|
||||
{
|
||||
int? fixtureId = null;
|
||||
try
|
||||
@@ -394,7 +401,7 @@ namespace HorseRacingPredictor.Football
|
||||
var fixture = responseItem["fixture"];
|
||||
if (fixture != null && fixture["id"] != null)
|
||||
{
|
||||
fixtureId = fixture["id"].GetValue<int>();
|
||||
fixtureId = fixture["id"].Value<int>();
|
||||
|
||||
// Relazioni fixture-team
|
||||
var teams = responseItem["teams"];
|
||||
@@ -408,7 +415,7 @@ namespace HorseRacingPredictor.Football
|
||||
var league = responseItem["league"];
|
||||
if (league != null && league["id"] != null)
|
||||
{
|
||||
int leagueId = league["id"].GetValue<int>();
|
||||
int leagueId = league["id"].Value<int>();
|
||||
_fixtureLeagueRepository.Upsert(connection, transaction, fixtureId.Value, leagueId, league);
|
||||
}
|
||||
|
||||
@@ -435,7 +442,7 @@ namespace HorseRacingPredictor.Football
|
||||
}
|
||||
|
||||
// FASE 6: Inserisci dati che richiedono fixture e teams: quote e previsioni
|
||||
foreach (var responseItem in jsonObject["response"].AsArray())
|
||||
foreach (var responseItem in asArray(jsonObject["response"]))
|
||||
{
|
||||
int? fixtureId = null;
|
||||
try
|
||||
@@ -443,7 +450,7 @@ namespace HorseRacingPredictor.Football
|
||||
var fixture = responseItem["fixture"];
|
||||
if (fixture != null && fixture["id"] != null)
|
||||
{
|
||||
fixtureId = fixture["id"].GetValue<int>();
|
||||
fixtureId = fixture["id"].Value<int>();
|
||||
|
||||
// Quote
|
||||
if (responseItem["bookmakers"] != null)
|
||||
@@ -478,15 +485,15 @@ namespace HorseRacingPredictor.Football
|
||||
}
|
||||
|
||||
// Head-to-head
|
||||
if (predictions["h2h"] != null && predictions["h2h"].AsArray().Any())
|
||||
if (predictions["h2h"] != null && asArray(predictions["h2h"]).Any())
|
||||
{
|
||||
_h2hRepository.DeleteForPrediction(connection, predictionId.Value);
|
||||
|
||||
foreach (var h2hFixture in predictions["h2h"].AsArray())
|
||||
foreach (var h2hFixture in asArray(predictions["h2h"]))
|
||||
{
|
||||
if (h2hFixture["fixture"] != null && h2hFixture["fixture"]["id"] != null)
|
||||
{
|
||||
_h2hRepository.Insert(connection, predictionId.Value, h2hFixture["fixture"]["id"].GetValue<int>());
|
||||
_h2hRepository.Insert(connection, predictionId.Value, h2hFixture["fixture"]["id"].Value<int>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -498,7 +505,7 @@ namespace HorseRacingPredictor.Football
|
||||
// Home team stats
|
||||
if (teams["home"] != null && teams["home"]["id"] != null && predictions["teams"]["home"] != null)
|
||||
{
|
||||
int homeTeamId = teams["home"]["id"].GetValue<int>();
|
||||
int homeTeamId = teams["home"]["id"].Value<int>();
|
||||
|
||||
if (predictions["teams"]["home"]["team"] != null)
|
||||
{
|
||||
@@ -516,7 +523,7 @@ namespace HorseRacingPredictor.Football
|
||||
// Away team stats
|
||||
if (teams["away"] != null && teams["away"]["id"] != null && predictions["teams"]["away"] != null)
|
||||
{
|
||||
int awayTeamId = teams["away"]["id"].GetValue<int>();
|
||||
int awayTeamId = teams["away"]["id"].Value<int>();
|
||||
|
||||
if (predictions["teams"]["away"]["team"] != null)
|
||||
{
|
||||
|
||||
@@ -5,17 +5,15 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using HorseRacingPredictor.Horses.ML;
|
||||
|
||||
namespace HorseRacingPredictor.Horses
|
||||
{
|
||||
internal class Calculator
|
||||
{
|
||||
private readonly MachineLearningService _mlService;
|
||||
|
||||
public Calculator()
|
||||
{
|
||||
_mlService = new MachineLearningService();
|
||||
// Machine learning service removed or unavailable in this build.
|
||||
// Keep constructor minimal.
|
||||
}
|
||||
|
||||
public int CalculateRating1(DataRow row)
|
||||
@@ -168,52 +166,15 @@ namespace HorseRacingPredictor.Horses
|
||||
/// </summary>
|
||||
public int CalculateMLRating(DataRow row)
|
||||
{
|
||||
// Machine learning component removed: fall back to deterministic rating
|
||||
try
|
||||
{
|
||||
// Prepara l'input per il modello ML
|
||||
var input = new ModelInput
|
||||
{
|
||||
Age = GetFloatValueOrDefault(row, "Age"),
|
||||
HandicapRating = GetFloatValueOrDefault(row, "Handicap Rating"),
|
||||
Weight = GetFloatValueOrDefault(row, "Weight"),
|
||||
WeightCarried = GetFloatValueOrDefault(row, "Weight Carried"),
|
||||
Barrier = GetFloatValueOrDefault(row, "Barrier"),
|
||||
CareerRuns = GetFloatValueOrDefault(row, "Career Runs"),
|
||||
CareerWins = GetFloatValueOrDefault(row, "Career Wins"),
|
||||
CareerStrikeRate = GetFloatValueOrDefault(row, "Career Strike Rate"),
|
||||
CareerROI = GetFloatValueOrDefault(row, "Career ROI"),
|
||||
ThisTrackRuns = GetFloatValueOrDefault(row, "This Track Runs"),
|
||||
ThisTrackWins = GetFloatValueOrDefault(row, "This Track Wins"),
|
||||
ThisTrackStrikeRate = GetFloatValueOrDefault(row, "This Track Strike Rate"),
|
||||
ThisDistanceRuns = GetFloatValueOrDefault(row, "This Distance Runs"),
|
||||
ThisDistanceWins = GetFloatValueOrDefault(row, "This Distance Wins"),
|
||||
ThisDistanceStrikeRate = GetFloatValueOrDefault(row, "This Distance Strike Rate"),
|
||||
JockeyLast100Wins = GetFloatValueOrDefault(row, "Jockey Last 100 Wins"),
|
||||
JockeyLast100StrikeRate = GetFloatValueOrDefault(row, "Jockey Last 100 Strike Rate"),
|
||||
TrainerLast100Wins = GetFloatValueOrDefault(row, "Trainer Last 100 Wins"),
|
||||
TrainerLast100StrikeRate = GetFloatValueOrDefault(row, "Trainer Last 100 Strike Rate"),
|
||||
BestFixedOdds = GetFloatValueOrDefault(row, "Best Fixed Odds")
|
||||
};
|
||||
|
||||
// Effettua la previsione usando il modello ML
|
||||
var output = _mlService.PredictFinishPosition(input);
|
||||
|
||||
// Il rating ML è inversamente proporzionale alla posizione prevista
|
||||
// Migliore è la posizione prevista (più vicina a 1), più alto sarà il rating
|
||||
double mlRating = 100 * (1.0 / Math.Max(1, output.PredictedPosition));
|
||||
|
||||
// Aggiusta il rating in base alla probabilità di vittoria
|
||||
mlRating = mlRating * (0.6 + (output.WinProbability * 0.4));
|
||||
|
||||
return (int)Math.Min(100, Math.Max(0, mlRating));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Errore nel calcolo del rating ML: {ex.Message}");
|
||||
|
||||
// In caso di errore, ritorna un rating basato sul metodo standard
|
||||
return CalculateRating1(row);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private double GetValueOrDefault(DataRow row, string columnName)
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace HorseRacingPredictor.Horses.ML
|
||||
{
|
||||
/// <summary>
|
||||
/// Classe che contiene il risultato della previsione per un singolo cavallo in una corsa.
|
||||
/// </summary>
|
||||
public class HorseRacePrediction
|
||||
{
|
||||
// Informazioni identificative
|
||||
public string HorseId { get; set; }
|
||||
public string HorseName { get; set; }
|
||||
public string RaceMeeting { get; set; }
|
||||
public int RaceNumber { get; set; }
|
||||
public DateTime RaceDate { get; set; }
|
||||
|
||||
// Risultati della previsione
|
||||
public float PredictedPosition { get; set; }
|
||||
public float WinProbability { get; set; }
|
||||
public float PlaceProbability { get; set; }
|
||||
|
||||
// Rating ML calcolato (combinazione di probabilità e altri fattori)
|
||||
public int MLRating { get; set; }
|
||||
|
||||
// Quota attuale per valutazione valore
|
||||
public float CurrentOdds { get; set; }
|
||||
|
||||
// Valore calcolato (rapporto tra probabilità e quota)
|
||||
public float Value => WinProbability > 0 ? (WinProbability * 100) / (1 / CurrentOdds) : 0;
|
||||
|
||||
// Indicazione se questo cavallo è considerato di valore
|
||||
public bool IsValueBet => Value > 1.1f;
|
||||
|
||||
// Posizione reale del cavallo
|
||||
public string ActualPosition { get; set; }
|
||||
|
||||
// Indicazione se la previsione è accurata
|
||||
public bool IsAccuratePrediction { get; set; }
|
||||
|
||||
// Metodo per valutare l'accuratezza della previsione
|
||||
public void EvaluateAccuracy()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ActualPosition) && int.TryParse(ActualPosition, out int actualPos))
|
||||
{
|
||||
IsAccuratePrediction = Math.Abs(actualPos - PredictedPosition) <= 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,573 +0,0 @@
|
||||
using Microsoft.ML;
|
||||
using Microsoft.ML.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Data.SqlClient;
|
||||
|
||||
namespace HorseRacingPredictor.Horses.ML
|
||||
{
|
||||
/// <summary>
|
||||
/// Servizio principale per l'implementazione del machine learning
|
||||
/// </summary>
|
||||
public class MachineLearningService
|
||||
{
|
||||
private readonly MLContext _mlContext;
|
||||
private ITransformer _trainedModel;
|
||||
private readonly string _modelPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Models", "HorseRaceModel.zip");
|
||||
|
||||
public string ConnectionString { get; set; }
|
||||
|
||||
public MachineLearningService(string connectionString = null)
|
||||
{
|
||||
_mlContext = new MLContext(seed: 42);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(_modelPath));
|
||||
ConnectionString = connectionString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Carica un modello ML esistente da file
|
||||
/// </summary>
|
||||
public void LoadModel()
|
||||
{
|
||||
if (!File.Exists(_modelPath))
|
||||
throw new FileNotFoundException($"[ML] Modello non trovato: {_modelPath}");
|
||||
|
||||
Console.WriteLine($"[ML] Caricamento modello da: {_modelPath}");
|
||||
_trainedModel = _mlContext.Model.Load(_modelPath, out _);
|
||||
Console.WriteLine("[ML] Modello ML caricato correttamente.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Salva il modello addestrato su file
|
||||
/// </summary>
|
||||
public void SaveModel(ITransformer model)
|
||||
{
|
||||
Console.WriteLine($"[ML] Salvataggio modello in: {_modelPath}");
|
||||
_mlContext.Model.Save(model, null, _modelPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene i dati di training dal database e addestra un modello ML
|
||||
/// </summary>
|
||||
public void TrainModel(string connectionString)
|
||||
{
|
||||
// Imposta a 'true' per ricreare sempre il modello (default).
|
||||
// Per NON ricreare il modello ad ogni esecuzione, commenta la riga seguente:
|
||||
bool alwaysRetrain = true;
|
||||
// bool alwaysRetrain = false;
|
||||
|
||||
if (!alwaysRetrain && File.Exists(_modelPath))
|
||||
{
|
||||
Console.WriteLine("[ML] Modello già esistente. Caricamento modello da file...");
|
||||
LoadModel();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Console.WriteLine("[ML] Inizio caricamento dati di training dal database...");
|
||||
var trainingData = LoadTrainingDataFromDatabase(connectionString);
|
||||
|
||||
if (trainingData.Count == 0)
|
||||
{
|
||||
Console.WriteLine("[ML][ERRORE] Nessun dato di training disponibile nel database.");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine($"[ML] Numero record caricati per il training: {trainingData.Count}");
|
||||
|
||||
var dataView = _mlContext.Data.LoadFromEnumerable(trainingData);
|
||||
|
||||
// Suddividi i dati in train e test (80% train, 20% test)
|
||||
var split = _mlContext.Data.TrainTestSplit(dataView, testFraction: 0.2, seed: 42);
|
||||
|
||||
// Costruisci la pipeline di training
|
||||
var pipeline = _mlContext.Transforms.Concatenate("Features",
|
||||
"Age", "HandicapRating", "Weight", "WeightCarried", "Barrier",
|
||||
"CareerRuns", "CareerWins", "CareerStrikeRate", "CareerROI",
|
||||
"ThisTrackRuns", "ThisTrackWins", "ThisTrackStrikeRate",
|
||||
"ThisDistanceRuns", "ThisDistanceWins", "ThisDistanceStrikeRate",
|
||||
"JockeyLast100Wins", "JockeyLast100StrikeRate",
|
||||
"TrainerLast100Wins", "TrainerLast100StrikeRate", "BestFixedOdds",
|
||||
"Rating1", "Rating2", "Rating3", "Rating4", "Rating5"
|
||||
// Qui puoi aggiungere nuove feature create o derivate
|
||||
)
|
||||
.Append(_mlContext.Transforms.NormalizeMinMax("Features"))
|
||||
.Append(_mlContext.Regression.Trainers.FastTree(
|
||||
labelColumnName: "FinishPosition",
|
||||
featureColumnName: "Features",
|
||||
numberOfTrees: 200, // Aumentato per maggiore accuratezza
|
||||
numberOfLeaves: 32, // Più foglie per maggiore complessità
|
||||
minimumExampleCountPerLeaf: 5, // Più sensibile ai dettagli
|
||||
learningRate: 0.15f // Puoi sperimentare anche questo parametro
|
||||
));
|
||||
|
||||
Console.WriteLine("[ML] Inizio addestramento del modello...");
|
||||
_trainedModel = pipeline.Fit(split.TrainSet);
|
||||
Console.WriteLine("[ML] Addestramento completato.");
|
||||
|
||||
// Valuta sul training set
|
||||
var trainMetrics = _mlContext.Regression.Evaluate(_trainedModel.Transform(split.TrainSet), labelColumnName: "FinishPosition");
|
||||
// Valuta sul test set
|
||||
var testMetrics = _mlContext.Regression.Evaluate(_trainedModel.Transform(split.TestSet), labelColumnName: "FinishPosition");
|
||||
|
||||
// Cross-validation
|
||||
var cvResults = _mlContext.Regression.CrossValidate(data: dataView, estimator: pipeline, numberOfFolds: 5, labelColumnName: "FinishPosition");
|
||||
var avgRSquared = cvResults.Average(fold => fold.Metrics.RSquared);
|
||||
Console.WriteLine($"RSquared medio (cross-validation): {avgRSquared:F4}");
|
||||
|
||||
SaveModel(_trainedModel);
|
||||
Console.WriteLine($"[ML] Modello salvato in: {_modelPath}");
|
||||
|
||||
Console.WriteLine("[ML] Metriche di valutazione (TRAIN):");
|
||||
Console.WriteLine($"[ML] - RSquared: {trainMetrics.RSquared:F4}");
|
||||
Console.WriteLine($"[ML] - MAE: {trainMetrics.MeanAbsoluteError:F4}");
|
||||
Console.WriteLine($"[ML] - MSE: {trainMetrics.MeanSquaredError:F4}");
|
||||
Console.WriteLine($"[ML] - RMSE: {trainMetrics.RootMeanSquaredError:F4}");
|
||||
|
||||
Console.WriteLine("[ML] Metriche di valutazione (TEST):");
|
||||
Console.WriteLine($"[ML] - RSquared: {testMetrics.RSquared:F4}");
|
||||
Console.WriteLine($"[ML] - MAE: {testMetrics.MeanAbsoluteError:F4}");
|
||||
Console.WriteLine($"[ML] - MSE: {testMetrics.MeanSquaredError:F4}");
|
||||
Console.WriteLine($"[ML] - RMSE: {testMetrics.RootMeanSquaredError:F4}");
|
||||
|
||||
var predictions = _trainedModel.Transform(split.TestSet);
|
||||
var actuals = _mlContext.Data.CreateEnumerable<ModelInput>(split.TestSet, false).ToList();
|
||||
var preds = _mlContext.Data.CreateEnumerable<ModelOutput>(predictions, false).ToList();
|
||||
|
||||
int top3Correct = actuals.Zip(preds, (a, p) => (a, p))
|
||||
.Count(x => x.a.FinishPosition <= 3 && x.p.PredictedPosition <= 3);
|
||||
double accuracyTop3 = (double)top3Correct / actuals.Count;
|
||||
Console.WriteLine($"Percentuale cavalli previsti nei primi 3: {accuracyTop3:P2}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[ML][ERRORE] Errore durante l'addestramento del modello: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Carica i dati di training dal database
|
||||
/// </summary>
|
||||
private List<ModelInput> LoadTrainingDataFromDatabase(string connectionString)
|
||||
{
|
||||
var trainingData = new List<ModelInput>();
|
||||
var calculator = new HorseRacingPredictor.Horses.Calculator();
|
||||
int skippedRows = 0;
|
||||
int rating1Errors = 0, rating2Errors = 0, rating3Errors = 0, rating4Errors = 0, rating5Errors = 0;
|
||||
|
||||
try
|
||||
{
|
||||
using (var connection = new SqlConnection(connectionString))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
string query = @"
|
||||
SELECT
|
||||
CASE WHEN ISNUMERIC(FinishResult) = 1 THEN CAST(FinishResult AS FLOAT) ELSE 999 END AS FinishPosition,
|
||||
COALESCE(Age, 0) AS Age,
|
||||
COALESCE(HandicapRating, 0) AS HandicapRating,
|
||||
COALESCE(Weight, 0) AS Weight,
|
||||
COALESCE(WeightCarried, 0) AS WeightCarried,
|
||||
COALESCE(Barrier, 0) AS Barrier,
|
||||
COALESCE(CareerRuns, 0) AS CareerRuns,
|
||||
COALESCE(CareerWins, 0) AS CareerWins,
|
||||
COALESCE(CareerStrikeRate, 0) AS CareerStrikeRate,
|
||||
COALESCE(CareerROI, 0) AS CareerROI,
|
||||
COALESCE(ThisTrackRuns, 0) AS ThisTrackRuns,
|
||||
COALESCE(ThisTrackWins, 0) AS ThisTrackWins,
|
||||
COALESCE(ThisTrackStrikeRate, 0) AS ThisTrackStrikeRate,
|
||||
COALESCE(ThisDistanceRuns, 0) AS ThisDistanceRuns,
|
||||
COALESCE(ThisDistanceWins, 0) AS ThisDistanceWins,
|
||||
COALESCE(ThisDistanceStrikeRate, 0) AS ThisDistanceStrikeRate,
|
||||
COALESCE(JockeyLast100Wins, 0) AS JockeyLast100Wins,
|
||||
COALESCE(JockeyLast100StrikeRate, 0) AS JockeyLast100StrikeRate,
|
||||
COALESCE(TrainerLast100Wins, 0) AS TrainerLast100Wins,
|
||||
COALESCE(TrainerLast100StrikeRate, 0) AS TrainerLast100StrikeRate,
|
||||
COALESCE(BestFixedOdds, 0) AS BestFixedOdds,
|
||||
COALESCE(Gender, '') AS Gender
|
||||
FROM Races
|
||||
WHERE ISNUMERIC(FinishResult) = 1
|
||||
AND FinishResult IS NOT NULL
|
||||
AND FinishResult <> ''";
|
||||
|
||||
using (var command = new SqlCommand(query, connection))
|
||||
{
|
||||
using (var adapter = new SqlDataAdapter(command))
|
||||
{
|
||||
var table = new System.Data.DataTable();
|
||||
adapter.Fill(table);
|
||||
|
||||
foreach (System.Data.DataRow row in table.Rows)
|
||||
{
|
||||
try {
|
||||
float finishPos = Convert.ToSingle(row["FinishPosition"]);
|
||||
if (finishPos >= 15 || finishPos <= 0) {
|
||||
skippedRows++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calcola i rating con gestione degli errori e sostituzione con valori predefiniti
|
||||
float rating1 = 0, rating2 = 0, rating3 = 0, rating4 = 0, rating5 = 0;
|
||||
|
||||
try {
|
||||
rating1 = (float)calculator.CalculateRating1(row);
|
||||
if (float.IsNaN(rating1) || float.IsInfinity(rating1)) {
|
||||
rating1 = 0;
|
||||
rating1Errors++;
|
||||
}
|
||||
} catch (Exception) {
|
||||
rating1Errors++;
|
||||
}
|
||||
|
||||
try {
|
||||
rating2 = (float)calculator.CalculateRating2(row);
|
||||
if (float.IsNaN(rating2) || float.IsInfinity(rating2)) {
|
||||
rating2 = 0;
|
||||
rating2Errors++;
|
||||
}
|
||||
} catch (Exception) {
|
||||
rating2Errors++;
|
||||
}
|
||||
|
||||
try {
|
||||
rating3 = (float)calculator.CalculateRating3(row);
|
||||
if (float.IsNaN(rating3) || float.IsInfinity(rating3)) {
|
||||
rating3 = 0;
|
||||
rating3Errors++;
|
||||
}
|
||||
} catch (Exception) {
|
||||
rating3Errors++;
|
||||
}
|
||||
|
||||
try {
|
||||
rating4 = (float)calculator.CalculateRating4(row);
|
||||
if (float.IsNaN(rating4) || float.IsInfinity(rating4)) {
|
||||
rating4 = 0;
|
||||
rating4Errors++;
|
||||
}
|
||||
} catch (Exception) {
|
||||
rating4Errors++;
|
||||
}
|
||||
|
||||
try {
|
||||
rating5 = (float)calculator.CalculateRating5(row);
|
||||
if (float.IsNaN(rating5) || float.IsInfinity(rating5)) {
|
||||
rating5 = 0;
|
||||
rating5Errors++;
|
||||
}
|
||||
} catch (Exception) {
|
||||
rating5Errors++;
|
||||
}
|
||||
|
||||
// Usa SafeConvert per tutte le conversioni
|
||||
trainingData.Add(new ModelInput
|
||||
{
|
||||
FinishPosition = finishPos,
|
||||
Age = SafeConvert.ToSingle(row["Age"]),
|
||||
HandicapRating = SafeConvert.ToSingle(row["HandicapRating"]),
|
||||
Weight = SafeConvert.ToSingle(row["Weight"]),
|
||||
WeightCarried = SafeConvert.ToSingle(row["WeightCarried"]),
|
||||
Barrier = SafeConvert.ToSingle(row["Barrier"]),
|
||||
CareerRuns = SafeConvert.ToSingle(row["CareerRuns"]),
|
||||
CareerWins = SafeConvert.ToSingle(row["CareerWins"]),
|
||||
CareerStrikeRate = SafeConvert.ToSingle(row["CareerStrikeRate"]),
|
||||
CareerROI = SafeConvert.ToSingle(row["CareerROI"]),
|
||||
ThisTrackRuns = SafeConvert.ToSingle(row["ThisTrackRuns"]),
|
||||
ThisTrackWins = SafeConvert.ToSingle(row["ThisTrackWins"]),
|
||||
ThisTrackStrikeRate = SafeConvert.ToSingle(row["ThisTrackStrikeRate"]),
|
||||
ThisDistanceRuns = SafeConvert.ToSingle(row["ThisDistanceRuns"]),
|
||||
ThisDistanceWins = SafeConvert.ToSingle(row["ThisDistanceWins"]),
|
||||
ThisDistanceStrikeRate = SafeConvert.ToSingle(row["ThisDistanceStrikeRate"]),
|
||||
JockeyLast100Wins = SafeConvert.ToSingle(row["JockeyLast100Wins"]),
|
||||
JockeyLast100StrikeRate = SafeConvert.ToSingle(row["JockeyLast100StrikeRate"]),
|
||||
TrainerLast100Wins = SafeConvert.ToSingle(row["TrainerLast100Wins"]),
|
||||
TrainerLast100StrikeRate = SafeConvert.ToSingle(row["TrainerLast100StrikeRate"]),
|
||||
BestFixedOdds = SafeConvert.ToSingle(row["BestFixedOdds"]),
|
||||
Rating1 = rating1,
|
||||
Rating2 = rating2,
|
||||
Rating3 = rating3,
|
||||
Rating4 = rating4,
|
||||
Rating5 = rating5
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
skippedRows++;
|
||||
}
|
||||
}
|
||||
Console.WriteLine($"[ML] Record letti dal database: {table.Rows.Count}");
|
||||
Console.WriteLine($"[ML] Record saltati: {skippedRows}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[ML][ERRORE] Errore durante il caricamento dei dati dal database: {ex.Message}");
|
||||
}
|
||||
|
||||
Console.WriteLine($"[ML] Caricati {trainingData.Count} record per l'addestramento.");
|
||||
return trainingData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Costruisce la pipeline di addestramento del modello
|
||||
/// </summary>
|
||||
private IEstimator<ITransformer> BuildTrainingPipeline()
|
||||
{
|
||||
// Crea la pipeline per addestramento
|
||||
// 1. Concatena tutte le feature in un unico vettore
|
||||
// 2. Normalizza i dati di input
|
||||
// 3. Addestra un modello FastTree con FastTreeRegressionTrainer
|
||||
var pipeline = _mlContext.Transforms.Concatenate("Features",
|
||||
"Age", "HandicapRating", "Weight", "WeightCarried", "Barrier",
|
||||
"CareerRuns", "CareerWins", "CareerStrikeRate", "CareerROI",
|
||||
"ThisTrackRuns", "ThisTrackWins", "ThisTrackStrikeRate",
|
||||
"ThisDistanceRuns", "ThisDistanceWins", "ThisDistanceStrikeRate",
|
||||
"JockeyLast100Wins", "JockeyLast100StrikeRate",
|
||||
"TrainerLast100Wins", "TrainerLast100StrikeRate", "BestFixedOdds",
|
||||
"Rating1", "Rating2", "Rating3", "Rating4", "Rating5")
|
||||
.Append(_mlContext.Transforms.NormalizeMinMax("Features"))
|
||||
.Append(_mlContext.Regression.Trainers.FastTree(
|
||||
labelColumnName: "FinishPosition",
|
||||
featureColumnName: "Features",
|
||||
numberOfTrees: 100,
|
||||
numberOfLeaves: 20,
|
||||
minimumExampleCountPerLeaf: 10));
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Valuta il modello addestrato
|
||||
/// </summary>
|
||||
private RegressionMetrics EvaluateModel(IDataView dataView, ITransformer model)
|
||||
{
|
||||
var predictions = model.Transform(dataView);
|
||||
return _mlContext.Regression.Evaluate(predictions, labelColumnName: "FinishPosition");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Predice la posizione finale per un cavallo
|
||||
/// </summary>
|
||||
public ModelOutput PredictFinishPosition(ModelInput input)
|
||||
{
|
||||
if (_trainedModel == null)
|
||||
{
|
||||
Console.WriteLine("Il modello non è stato addestrato o caricato.");
|
||||
|
||||
// Restituisci un oggetto vuoto invece di lanciare un'eccezione
|
||||
return new ModelOutput
|
||||
{
|
||||
PredictedPosition = 0,
|
||||
WinProbability = 0,
|
||||
PlaceProbability = 0
|
||||
};
|
||||
}
|
||||
|
||||
// Crea il motore di previsione
|
||||
var predEngine = _mlContext.Model.CreatePredictionEngine<ModelInput, ModelOutput>(_trainedModel);
|
||||
|
||||
// Effettua la previsione
|
||||
var prediction = predEngine.Predict(input);
|
||||
|
||||
// Calcola le probabilità (implementazione semplificata)
|
||||
prediction.WinProbability = CalculateWinProbability(prediction.PredictedPosition);
|
||||
prediction.PlaceProbability = CalculatePlaceProbability(prediction.PredictedPosition);
|
||||
|
||||
return prediction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calcola la probabilità di vittoria in base alla posizione prevista
|
||||
/// </summary>
|
||||
private float CalculateWinProbability(float predictedPosition)
|
||||
{
|
||||
// Implementazione semplice: più il valore è vicino a 1, maggiore è la probabilità di vittoria
|
||||
if (predictedPosition <= 1) return 0.9f;
|
||||
if (predictedPosition <= 2) return 0.5f;
|
||||
if (predictedPosition <= 3) return 0.3f;
|
||||
if (predictedPosition <= 5) return 0.1f;
|
||||
return 0.05f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calcola la probabilità di piazzamento (primi 3) in base alla posizione prevista
|
||||
/// </summary>
|
||||
private float CalculatePlaceProbability(float predictedPosition)
|
||||
{
|
||||
// Implementazione semplice: più il valore è vicino a 1, 2 o 3, maggiore è la probabilità di piazzamento
|
||||
if (predictedPosition <= 1) return 0.95f;
|
||||
if (predictedPosition <= 2) return 0.85f;
|
||||
if (predictedPosition <= 3) return 0.75f;
|
||||
if (predictedPosition <= 4) return 0.4f;
|
||||
if (predictedPosition <= 6) return 0.2f;
|
||||
return 0.1f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Analizza un'intera corsa e prevede il risultato per tutti i cavalli
|
||||
/// </summary>
|
||||
public List<HorseRacePrediction> PredictRaceResult(string meeting, int raceNumber, DateTime raceDate, string connectionString)
|
||||
{
|
||||
var predictions = new List<HorseRacePrediction>();
|
||||
var calculator = new HorseRacingPredictor.Horses.Calculator(); // Inizializza il calculator qui
|
||||
|
||||
try
|
||||
{
|
||||
using (var connection = new SqlConnection(connectionString))
|
||||
{
|
||||
connection.Open();
|
||||
string query = @"
|
||||
SELECT
|
||||
Num, HorseName,
|
||||
COALESCE(Age, 0) AS Age,
|
||||
COALESCE(HandicapRating, 0) AS HandicapRating,
|
||||
COALESCE(Weight, 0) AS Weight,
|
||||
COALESCE(WeightCarried, 0) AS WeightCarried,
|
||||
COALESCE(Barrier, 0) AS Barrier,
|
||||
COALESCE(CareerRuns, 0) AS CareerRuns,
|
||||
COALESCE(CareerWins, 0) AS CareerWins,
|
||||
COALESCE(CareerStrikeRate, 0) AS CareerStrikeRate,
|
||||
COALESCE(CareerROI, 0) AS CareerROI,
|
||||
COALESCE(ThisTrackRuns, 0) AS ThisTrackRuns,
|
||||
COALESCE(ThisTrackWins, 0) AS ThisTrackWins,
|
||||
COALESCE(ThisTrackStrikeRate, 0) AS ThisTrackStrikeRate,
|
||||
COALESCE(ThisDistanceRuns, 0) AS ThisDistanceRuns,
|
||||
COALESCE(ThisDistanceWins, 0) AS ThisDistanceWins,
|
||||
COALESCE(ThisDistanceStrikeRate, 0) AS ThisDistanceStrikeRate,
|
||||
COALESCE(JockeyLast100Wins, 0) AS JockeyLast100Wins,
|
||||
COALESCE(JockeyLast100StrikeRate, 0) AS JockeyLast100StrikeRate,
|
||||
COALESCE(TrainerLast100Wins, 0) AS TrainerLast100Wins,
|
||||
COALESCE(TrainerLast100StrikeRate, 0) AS TrainerLast100StrikeRate,
|
||||
COALESCE(BestFixedOdds, 0) AS BestFixedOdds,
|
||||
COALESCE(Gender, '') AS Gender
|
||||
FROM Races
|
||||
WHERE Meeting = @Meeting AND Race = @Race AND Data = @Data";
|
||||
|
||||
using (var command = new SqlCommand(query, connection))
|
||||
{
|
||||
command.Parameters.AddWithValue("@Meeting", meeting);
|
||||
command.Parameters.AddWithValue("@Race", raceNumber);
|
||||
command.Parameters.AddWithValue("@Data", raceDate);
|
||||
|
||||
using (var reader = command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
// Converti i dati del reader in una DataRow per usare il calculator
|
||||
DataTable dt = new DataTable();
|
||||
DataRow row = dt.NewRow();
|
||||
for (int i = 0; i < reader.FieldCount; i++)
|
||||
{
|
||||
if (!dt.Columns.Contains(reader.GetName(i)))
|
||||
dt.Columns.Add(reader.GetName(i));
|
||||
row[reader.GetName(i)] = reader.GetValue(i);
|
||||
}
|
||||
dt.Rows.Add(row);
|
||||
|
||||
// Prepara l'input per il modello
|
||||
var input = new ModelInput
|
||||
{
|
||||
Age = Convert.ToSingle(reader["Age"]),
|
||||
HandicapRating = Convert.ToSingle(reader["HandicapRating"]),
|
||||
Weight = Convert.ToSingle(reader["Weight"]),
|
||||
WeightCarried = Convert.ToSingle(reader["WeightCarried"]),
|
||||
Barrier = Convert.ToSingle(reader["Barrier"]),
|
||||
CareerRuns = Convert.ToSingle(reader["CareerRuns"]),
|
||||
CareerWins = Convert.ToSingle(reader["CareerWins"]),
|
||||
CareerStrikeRate = Convert.ToSingle(reader["CareerStrikeRate"]),
|
||||
CareerROI = Convert.ToSingle(reader["CareerROI"]),
|
||||
ThisTrackRuns = Convert.ToSingle(reader["ThisTrackRuns"]),
|
||||
ThisTrackWins = Convert.ToSingle(reader["ThisTrackWins"]),
|
||||
ThisTrackStrikeRate = Convert.ToSingle(reader["ThisTrackStrikeRate"]),
|
||||
ThisDistanceRuns = Convert.ToSingle(reader["ThisDistanceRuns"]),
|
||||
ThisDistanceWins = Convert.ToSingle(reader["ThisDistanceWins"]),
|
||||
ThisDistanceStrikeRate = Convert.ToSingle(reader["ThisDistanceStrikeRate"]),
|
||||
JockeyLast100Wins = Convert.ToSingle(reader["JockeyLast100Wins"]),
|
||||
JockeyLast100StrikeRate = Convert.ToSingle(reader["JockeyLast100StrikeRate"]),
|
||||
TrainerLast100Wins = Convert.ToSingle(reader["TrainerLast100Wins"]),
|
||||
TrainerLast100StrikeRate = Convert.ToSingle(reader["TrainerLast100StrikeRate"]),
|
||||
BestFixedOdds = Convert.ToSingle(reader["BestFixedOdds"]),
|
||||
// Aggiungi i rating calcolati
|
||||
Rating1 = (float)calculator.CalculateRating1(row),
|
||||
Rating2 = (float)calculator.CalculateRating2(row),
|
||||
Rating3 = (float)calculator.CalculateRating3(row),
|
||||
Rating4 = (float)calculator.CalculateRating4(row),
|
||||
Rating5 = (float)calculator.CalculateRating5(row)
|
||||
};
|
||||
|
||||
// Effettua la previsione
|
||||
var output = PredictFinishPosition(input);
|
||||
|
||||
// Calcola un rating ML basato sulla posizione prevista
|
||||
int mlRating = CalculateMLRating(output);
|
||||
|
||||
// Aggiungi alla lista delle previsioni
|
||||
predictions.Add(new HorseRacePrediction
|
||||
{
|
||||
HorseId = Convert.ToString(reader["Num"]),
|
||||
HorseName = Convert.ToString(reader["HorseName"]),
|
||||
RaceMeeting = meeting,
|
||||
RaceNumber = raceNumber,
|
||||
RaceDate = raceDate,
|
||||
PredictedPosition = output.PredictedPosition,
|
||||
WinProbability = output.WinProbability,
|
||||
PlaceProbability = output.PlaceProbability,
|
||||
MLRating = mlRating,
|
||||
CurrentOdds = Convert.ToSingle(reader["BestFixedOdds"])
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ordina per posizione prevista (dal migliore al peggiore)
|
||||
return predictions.OrderBy(p => p.PredictedPosition).ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[ML][ERRORE] Errore durante la previsione della corsa: {ex.Message}");
|
||||
return predictions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calcola un rating ML basato sulla posizione prevista e altre metriche
|
||||
/// </summary>
|
||||
private int CalculateMLRating(ModelOutput output)
|
||||
{
|
||||
// Formula per il rating ML (convertito in un numero intero da 0 a 100)
|
||||
double baseRating = (1 / Math.Max(0.1, output.PredictedPosition)) * 100;
|
||||
double winProbabilityFactor = output.WinProbability * 100;
|
||||
double placeProbabilityFactor = output.PlaceProbability * 50;
|
||||
|
||||
// Combina i fattori con pesi diversi
|
||||
double combinedRating = (baseRating * 0.4) + (winProbabilityFactor * 0.4) + (placeProbabilityFactor * 0.2);
|
||||
|
||||
// Limita il valore tra 0 e 100
|
||||
return (int)Math.Min(100, Math.Max(0, combinedRating));
|
||||
}
|
||||
|
||||
// Metodo di utilità per convertire in modo sicuro i valori
|
||||
private static class SafeConvert
|
||||
{
|
||||
public static float ToSingle(object value, float defaultValue = 0)
|
||||
{
|
||||
if (value == null || value == DBNull.Value)
|
||||
return defaultValue;
|
||||
|
||||
try {
|
||||
float result = Convert.ToSingle(value);
|
||||
return float.IsNaN(result) || float.IsInfinity(result) ? defaultValue : result;
|
||||
}
|
||||
catch {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
using Microsoft.ML.Data;
|
||||
|
||||
namespace HorseRacingPredictor.Horses.ML
|
||||
{
|
||||
/// <summary>
|
||||
/// Classe che rappresenta i dati di input per l'addestramento del modello ML.
|
||||
/// Contiene le feature selezionate dalle corse passate.
|
||||
/// </summary>
|
||||
public class ModelInput
|
||||
{
|
||||
// Etichetta da prevedere: posizione finale (1 = vincitore, 2 = secondo posto, ecc.)
|
||||
[LoadColumn(0)]
|
||||
[ColumnName("FinishPosition")]
|
||||
public float FinishPosition { get; set; }
|
||||
|
||||
// Feature principali sul cavallo
|
||||
[LoadColumn(1)]
|
||||
[ColumnName("Age")]
|
||||
public float Age { get; set; }
|
||||
|
||||
[LoadColumn(2)]
|
||||
[ColumnName("HandicapRating")]
|
||||
public float HandicapRating { get; set; }
|
||||
|
||||
[LoadColumn(3)]
|
||||
[ColumnName("Weight")]
|
||||
public float Weight { get; set; }
|
||||
|
||||
[LoadColumn(4)]
|
||||
[ColumnName("WeightCarried")]
|
||||
public float WeightCarried { get; set; }
|
||||
|
||||
[LoadColumn(5)]
|
||||
[ColumnName("Barrier")]
|
||||
public float Barrier { get; set; }
|
||||
|
||||
// Statistiche di carriera
|
||||
[LoadColumn(6)]
|
||||
[ColumnName("CareerRuns")]
|
||||
public float CareerRuns { get; set; }
|
||||
|
||||
[LoadColumn(7)]
|
||||
[ColumnName("CareerWins")]
|
||||
public float CareerWins { get; set; }
|
||||
|
||||
[LoadColumn(8)]
|
||||
[ColumnName("CareerStrikeRate")]
|
||||
public float CareerStrikeRate { get; set; }
|
||||
|
||||
[LoadColumn(9)]
|
||||
[ColumnName("CareerROI")]
|
||||
public float CareerROI { get; set; }
|
||||
|
||||
// Statistiche di pista
|
||||
[LoadColumn(10)]
|
||||
[ColumnName("ThisTrackRuns")]
|
||||
public float ThisTrackRuns { get; set; }
|
||||
|
||||
[LoadColumn(11)]
|
||||
[ColumnName("ThisTrackWins")]
|
||||
public float ThisTrackWins { get; set; }
|
||||
|
||||
[LoadColumn(12)]
|
||||
[ColumnName("ThisTrackStrikeRate")]
|
||||
public float ThisTrackStrikeRate { get; set; }
|
||||
|
||||
[LoadColumn(13)]
|
||||
[ColumnName("ThisDistanceRuns")]
|
||||
public float ThisDistanceRuns { get; set; }
|
||||
|
||||
[LoadColumn(14)]
|
||||
[ColumnName("ThisDistanceWins")]
|
||||
public float ThisDistanceWins { get; set; }
|
||||
|
||||
[LoadColumn(15)]
|
||||
[ColumnName("ThisDistanceStrikeRate")]
|
||||
public float ThisDistanceStrikeRate { get; set; }
|
||||
|
||||
// Statistiche fantino
|
||||
[LoadColumn(16)]
|
||||
[ColumnName("JockeyLast100Wins")]
|
||||
public float JockeyLast100Wins { get; set; }
|
||||
|
||||
[LoadColumn(17)]
|
||||
[ColumnName("JockeyLast100StrikeRate")]
|
||||
public float JockeyLast100StrikeRate { get; set; }
|
||||
|
||||
// Statistiche allenatore
|
||||
[LoadColumn(18)]
|
||||
[ColumnName("TrainerLast100Wins")]
|
||||
public float TrainerLast100Wins { get; set; }
|
||||
|
||||
[LoadColumn(19)]
|
||||
[ColumnName("TrainerLast100StrikeRate")]
|
||||
public float TrainerLast100StrikeRate { get; set; }
|
||||
|
||||
// Quota iniziale (può essere predittiva)
|
||||
[LoadColumn(20)]
|
||||
[ColumnName("BestFixedOdds")]
|
||||
public float BestFixedOdds { get; set; }
|
||||
|
||||
// Cambia il tipo da int a float
|
||||
public float Rating1 { get; set; }
|
||||
public float Rating2 { get; set; }
|
||||
public float Rating3 { get; set; }
|
||||
public float Rating4 { get; set; }
|
||||
public float Rating5 { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using Microsoft.ML.Data;
|
||||
|
||||
namespace HorseRacingPredictor.Horses.ML
|
||||
{
|
||||
/// <summary>
|
||||
/// Classe che rappresenta l'output della previsione del modello ML.
|
||||
/// </summary>
|
||||
public class ModelOutput
|
||||
{
|
||||
// Previsione: il risultato della classificazione (1 = vincitore, 2 = secondo posto, ecc.)
|
||||
[ColumnName("Score")]
|
||||
public float PredictedPosition { get; set; }
|
||||
|
||||
[NoColumn] // Indica che questa proprietà non esiste nei dati
|
||||
public float WinProbability { get; set; }
|
||||
|
||||
[NoColumn] // Indica che questa proprietà non esiste nei dati
|
||||
public float PlaceProbability { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,8 @@
|
||||
Loaded="Window_Loaded">
|
||||
|
||||
<Window.Resources>
|
||||
<!-- =========================================================
|
||||
CATPPUCCIN MOCHA PALETTE
|
||||
========================================================= -->
|
||||
<!-- Inlined Global Styles (avoids relying on external resource resolution at runtime) -->
|
||||
<!-- CATPPUCCIN MOCHA PALETTE (shared) -->
|
||||
<Color x:Key="CBase">#1E1E2E</Color>
|
||||
<Color x:Key="CMantle">#181825</Color>
|
||||
<Color x:Key="CCrust">#11111B</Color>
|
||||
@@ -41,9 +40,21 @@
|
||||
<SolidColorBrush x:Key="BrPeach" Color="{StaticResource CPeach}"/>
|
||||
<SolidColorBrush x:Key="BrLavender" Color="{StaticResource CLavender}"/>
|
||||
|
||||
<!-- =========================================================
|
||||
NAV BUTTON STYLE (icon-only sidebar)
|
||||
========================================================= -->
|
||||
<!-- Force ComboBox text to black for readability on light backgrounds -->
|
||||
<Style TargetType="ComboBox">
|
||||
<Setter Property="Foreground" Value="Black"/>
|
||||
</Style>
|
||||
<Style TargetType="ComboBoxItem">
|
||||
<Setter Property="Foreground" Value="Black"/>
|
||||
</Style>
|
||||
|
||||
<!-- Acrylic-like background (semi-transparent fallback) -->
|
||||
<SolidColorBrush x:Key="AcrylicBackgroundBrush" Color="#0F000000"/>
|
||||
|
||||
<!-- Subtle shadow effect for elevation -->
|
||||
<DropShadowEffect x:Key="SubtleDropShadow" BlurRadius="12" ShadowDepth="2" Color="#50000000"/>
|
||||
|
||||
<!-- NAV BUTTON STYLE (icon-only sidebar) -->
|
||||
<Style x:Key="NavBtn" TargetType="RadioButton">
|
||||
<Setter Property="Width" Value="48"/>
|
||||
<Setter Property="Height" Value="48"/>
|
||||
@@ -72,9 +83,7 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- =========================================================
|
||||
ACCENT BUTTON
|
||||
========================================================= -->
|
||||
<!-- ACCENT BUTTON -->
|
||||
<Style x:Key="AccentBtn" TargetType="Button">
|
||||
<Setter Property="Foreground" Value="#181825"/>
|
||||
<Setter Property="FontFamily" Value="Segoe UI Semibold"/>
|
||||
@@ -102,9 +111,7 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- =========================================================
|
||||
FLAT TEXTBOX
|
||||
========================================================= -->
|
||||
<!-- FLAT TEXTBOX -->
|
||||
<Style x:Key="FlatTb" TargetType="TextBox">
|
||||
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
|
||||
@@ -134,9 +141,7 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- =========================================================
|
||||
PASSWORD BOX
|
||||
========================================================= -->
|
||||
<!-- PASSWORD BOX -->
|
||||
<Style x:Key="FlatPb" TargetType="PasswordBox">
|
||||
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
|
||||
@@ -166,9 +171,7 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- =========================================================
|
||||
PROGRESS BAR
|
||||
========================================================= -->
|
||||
<!-- PROGRESS BAR -->
|
||||
<Style x:Key="ModernPb" TargetType="ProgressBar">
|
||||
<Setter Property="Height" Value="4"/>
|
||||
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
|
||||
@@ -189,9 +192,7 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- =========================================================
|
||||
DATAGRID
|
||||
========================================================= -->
|
||||
<!-- DATAGRID -->
|
||||
<Style x:Key="ModernDg" TargetType="DataGrid">
|
||||
<Setter Property="Background" Value="#282A3A"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
|
||||
@@ -211,6 +212,7 @@
|
||||
<Setter Property="FontFamily" Value="Segoe UI"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="DataGridColumnHeader">
|
||||
<Setter Property="Background" Value="#23243A"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource BrBlue}"/>
|
||||
@@ -220,6 +222,7 @@
|
||||
<Setter Property="BorderBrush" Value="#37394E"/>
|
||||
<Setter Property="BorderThickness" Value="0,0,0,1"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="DataGridCell">
|
||||
<Setter Property="Padding" Value="8,6"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
@@ -239,6 +242,7 @@
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="Margin" Value="0"/>
|
||||
<Style.Triggers>
|
||||
@@ -247,6 +251,14 @@
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<!-- Card style for lists / rows -->
|
||||
<Style x:Key="CardStyle" TargetType="Border">
|
||||
<Setter Property="CornerRadius" Value="8"/>
|
||||
<Setter Property="Background" Value="#23232A"/>
|
||||
<Setter Property="Padding" Value="12"/>
|
||||
<Setter Property="Margin" Value="6"/>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<!-- ================================================================
|
||||
@@ -288,12 +300,7 @@
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="20"
|
||||
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=RadioButton}}"/>
|
||||
</RadioButton>
|
||||
<RadioButton x:Name="navInfo" Style="{StaticResource NavBtn}"
|
||||
ToolTip="Informazioni"
|
||||
Checked="navInfo_Checked">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="20"
|
||||
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=RadioButton}}"/>
|
||||
</RadioButton>
|
||||
<!-- Info tab removed -->
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
@@ -313,28 +320,29 @@
|
||||
<Grid Background="{StaticResource BrBase}">
|
||||
|
||||
<!-- ==== PAGE: FOOTBALL ==== -->
|
||||
<Grid x:Name="pageFootball" Margin="24,16">
|
||||
<Grid x:Name="pageFootball" Margin="24,16" Panel.ZIndex="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<!-- Toolbar -->
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,12">
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,12" Panel.ZIndex="20">
|
||||
<DatePicker x:Name="dpFootball" Width="160"
|
||||
Background="{StaticResource BrSurface0}"
|
||||
Foreground="{StaticResource BrText}"
|
||||
FontSize="13" VerticalContentAlignment="Center"/>
|
||||
FontSize="13" VerticalContentAlignment="Center" IsHitTestVisible="True"/>
|
||||
<Button x:Name="btnDownloadFb" Content="Scarica Partite"
|
||||
Style="{StaticResource AccentBtn}"
|
||||
Background="{StaticResource BrBlue}" Margin="12,0,0,0"
|
||||
Click="btnDownloadFb_Click"/>
|
||||
Click="btnDownloadFb_Click" IsHitTestVisible="True"/>
|
||||
<Button x:Name="btnExportFbCsv" Content="Esporta CSV"
|
||||
Style="{StaticResource AccentBtn}"
|
||||
Background="{StaticResource BrGreen}" Margin="8,0,0,0"
|
||||
IsEnabled="False"
|
||||
Click="btnExportFbCsv_Click"/>
|
||||
Click="btnExportFbCsv_Click" IsHitTestVisible="True"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Status -->
|
||||
<StackPanel Grid.Row="1" Margin="0,0,0,8">
|
||||
<ProgressBar x:Name="pbFootball" Style="{StaticResource ModernPb}" Margin="0,0,0,4"/>
|
||||
@@ -352,21 +360,35 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<!-- Toolbar -->
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,12">
|
||||
<ComboBox x:Name="cmbDay" Width="140"
|
||||
Background="{StaticResource BrSurface0}"
|
||||
Foreground="{StaticResource BrText}"
|
||||
FontSize="13" VerticalContentAlignment="Center"/>
|
||||
<Button x:Name="btnDownloadRc" Content="Scarica Corse"
|
||||
Style="{StaticResource AccentBtn}"
|
||||
Background="{StaticResource BrBlue}" Margin="12,0,0,0"
|
||||
Click="btnDownloadRc_Click"/>
|
||||
<Button x:Name="btnExportRcCsv" Content="Esporta CSV"
|
||||
Style="{StaticResource AccentBtn}"
|
||||
Background="{StaticResource BrGreen}" Margin="8,0,0,0"
|
||||
IsEnabled="False"
|
||||
Click="btnExportRcCsv_Click"/>
|
||||
<!-- Source selector: API or CSV -->
|
||||
<StackPanel Grid.Row="0" Margin="0,0,0,12">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
|
||||
<RadioButton x:Name="rbRcApi" Content="API" IsChecked="True" Foreground="{StaticResource BrText}" VerticalAlignment="Center" GroupName="RcSource" Checked="rbRcSource_Checked"/>
|
||||
<RadioButton x:Name="rbRcCsv" Content="CSV (Punters)" Foreground="{StaticResource BrText}" VerticalAlignment="Center" Margin="16,0,0,0" GroupName="RcSource" Checked="rbRcSource_Checked"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<!-- API controls -->
|
||||
<ComboBox x:Name="cmbDay" Width="140"
|
||||
Background="{StaticResource BrSurface0}"
|
||||
Foreground="{StaticResource BrText}"
|
||||
FontSize="13" VerticalContentAlignment="Center"/>
|
||||
<Button x:Name="btnDownloadRc" Content="Scarica Corse"
|
||||
Style="{StaticResource AccentBtn}"
|
||||
Background="{StaticResource BrBlue}" Margin="12,0,0,0"
|
||||
Click="btnDownloadRc_Click"/>
|
||||
<!-- CSV controls -->
|
||||
<Button x:Name="btnBrowseCsvRc" Content="Seleziona cartella CSV..."
|
||||
Style="{StaticResource AccentBtn}"
|
||||
Background="{StaticResource BrBlue}" Margin="12,0,0,0"
|
||||
Visibility="Collapsed"
|
||||
Click="btnBrowseCsvRc_Click"/>
|
||||
<!-- Export (always visible) -->
|
||||
<Button x:Name="btnExportRcCsv" Content="Esporta"
|
||||
Style="{StaticResource AccentBtn}"
|
||||
Background="{StaticResource BrGreen}" Margin="8,0,0,0"
|
||||
IsEnabled="False"
|
||||
Click="btnExportRcCsv_Click"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<!-- Status -->
|
||||
<StackPanel Grid.Row="1" Margin="0,0,0,8">
|
||||
@@ -375,15 +397,13 @@
|
||||
FontSize="12" Foreground="{StaticResource BrSubtext0}"/>
|
||||
</StackPanel>
|
||||
<!-- Grid -->
|
||||
<DataGrid x:Name="dgRacing" Grid.Row="2" Style="{StaticResource ModernDg}"/>
|
||||
<DataGrid x:Name="dgRacing" Grid.Row="2" Style="{StaticResource ModernDg}" AutoGeneratingColumn="dgRacing_AutoGeneratingColumn"/>
|
||||
</Grid>
|
||||
|
||||
<!-- ==== PAGE: SETTINGS ==== -->
|
||||
<ScrollViewer x:Name="pageSettings" Visibility="Collapsed"
|
||||
VerticalScrollBarVisibility="Auto" Padding="24,16">
|
||||
<StackPanel MaxWidth="600" HorizontalAlignment="Left">
|
||||
|
||||
<!-- FOOTBALL API -->
|
||||
<TextBlock Text="Calcio — API" FontSize="16" FontFamily="Segoe UI Semibold"
|
||||
Foreground="{StaticResource BrBlue}" Margin="0,0,0,8"/>
|
||||
<Border Background="#282A3A" CornerRadius="10" Padding="20" Margin="0,0,0,20">
|
||||
@@ -391,6 +411,39 @@
|
||||
<TextBlock Text="API Key (api-football)" Foreground="{StaticResource BrText}"
|
||||
FontSize="13" Margin="0,0,0,6"/>
|
||||
<TextBox x:Name="txtApiKey" Style="{StaticResource FlatTb}"/>
|
||||
<!-- File name now built from prefix + date + suffix -->
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="Prefisso" Foreground="{StaticResource BrText}" FontSize="13" Margin="0,0,0,6"/>
|
||||
<TextBox x:Name="txtFbPrefix" Style="{StaticResource FlatTb}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Margin="8,0,0,0">
|
||||
<TextBlock Text="Suffisso" Foreground="{StaticResource BrText}" FontSize="13" Margin="0,0,0,6"/>
|
||||
<TextBox x:Name="txtFbSuffix" Style="{StaticResource FlatTb}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
|
||||
<CheckBox x:Name="chkFbIncludeDate" IsChecked="True" Foreground="{StaticResource BrText}" VerticalAlignment="Center">Includi data</CheckBox>
|
||||
<ComboBox x:Name="cmbFbDateFormat" Width="180" Margin="12,0,0,0" Background="{StaticResource BrSurface0}" Foreground="Black">
|
||||
<ComboBoxItem Content="yyyy-MM-dd"/>
|
||||
<ComboBoxItem Content="dd-MM-yyyy"/>
|
||||
<ComboBoxItem Content="yyyyMMdd"/>
|
||||
<ComboBoxItem Content="ddMMyyyy"/>
|
||||
<ComboBoxItem Content="yyyy-MM-dd_HH-mm"/>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<TextBlock Text="Anteprima nome file:" Foreground="{StaticResource BrSubtext0}" Margin="0,8,0,4"/>
|
||||
<TextBlock x:Name="txtFbPreview" FontFamily="Segoe UI" FontSize="13" Foreground="{StaticResource BrText}" />
|
||||
<TextBlock Text="Formato esportazione" Foreground="{StaticResource BrText}" FontSize="13" Margin="0,12,0,6"/>
|
||||
<ComboBox x:Name="cmbFbFormat" Width="160" SelectedIndex="0" Background="{StaticResource BrSurface0}" Foreground="Black">
|
||||
<ComboBoxItem Content="CSV"/>
|
||||
<ComboBoxItem Content="JSON"/>
|
||||
<ComboBoxItem Content="XML"/>
|
||||
</ComboBox>
|
||||
<TextBlock Text="Cartella esportazione CSV" Foreground="{StaticResource BrText}"
|
||||
FontSize="13" Margin="0,14,0,6"/>
|
||||
<Grid>
|
||||
@@ -409,12 +462,45 @@
|
||||
|
||||
<!-- HORSE RACING API -->
|
||||
<TextBlock Text="Corse Cavalli — API" FontSize="16" FontFamily="Segoe UI Semibold"
|
||||
Foreground="{StaticResource BrBlue}" Margin="0,0,0,8"/>
|
||||
Foreground="{StaticResource BrBlue}" Margin="0,32,0,8"/>
|
||||
<Border Background="#282A3A" CornerRadius="10" Padding="20" Margin="0,0,0,20">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Username" Foreground="{StaticResource BrText}"
|
||||
FontSize="13" Margin="0,0,0,6"/>
|
||||
<TextBox x:Name="txtRacingUser" Style="{StaticResource FlatTb}"/>
|
||||
<!-- File name now built from prefix + date + suffix -->
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="Prefisso" Foreground="{StaticResource BrText}" FontSize="13" Margin="0,0,0,6"/>
|
||||
<TextBox x:Name="txtRcPrefix" Style="{StaticResource FlatTb}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Margin="8,0,0,0">
|
||||
<TextBlock Text="Suffisso" Foreground="{StaticResource BrText}" FontSize="13" Margin="0,0,0,6"/>
|
||||
<TextBox x:Name="txtRcSuffix" Style="{StaticResource FlatTb}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
|
||||
<CheckBox x:Name="chkRcIncludeDate" IsChecked="True" Foreground="{StaticResource BrText}" VerticalAlignment="Center">Includi data</CheckBox>
|
||||
<ComboBox x:Name="cmbRcDateFormat" Width="180" Margin="12,0,0,0" Background="{StaticResource BrSurface0}" Foreground="Black">
|
||||
<ComboBoxItem Content="yyyy-MM-dd"/>
|
||||
<ComboBoxItem Content="dd-MM-yyyy"/>
|
||||
<ComboBoxItem Content="yyyyMMdd"/>
|
||||
<ComboBoxItem Content="ddMMyyyy"/>
|
||||
<ComboBoxItem Content="yyyy-MM-dd_HH-mm"/>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<TextBlock Text="Anteprima nome file:" Foreground="{StaticResource BrSubtext0}" Margin="0,8,0,4"/>
|
||||
<TextBlock x:Name="txtRcPreview" FontFamily="Segoe UI" FontSize="13" Foreground="{StaticResource BrText}" />
|
||||
<TextBlock Text="Formato esportazione" Foreground="{StaticResource BrText}" FontSize="13" Margin="0,12,0,6"/>
|
||||
<ComboBox x:Name="cmbRcFormat" Width="160" SelectedIndex="0" Background="{StaticResource BrSurface0}" Foreground="Black">
|
||||
<ComboBoxItem Content="CSV"/>
|
||||
<ComboBoxItem Content="JSON"/>
|
||||
<ComboBoxItem Content="XML"/>
|
||||
</ComboBox>
|
||||
<TextBlock Text="Password" Foreground="{StaticResource BrText}"
|
||||
FontSize="13" Margin="0,14,0,6"/>
|
||||
<PasswordBox x:Name="txtRacingPass" Style="{StaticResource FlatPb}"/>
|
||||
@@ -435,7 +521,7 @@
|
||||
</Border>
|
||||
|
||||
<!-- SAVE -->
|
||||
<Button Content="Salva impostazioni"
|
||||
<Button x:Name="btnSaveSettings" Content="Salva Impostazioni"
|
||||
Style="{StaticResource AccentBtn}"
|
||||
Background="{StaticResource BrGreen}"
|
||||
HorizontalAlignment="Left" Margin="0,4,0,24"
|
||||
@@ -443,33 +529,9 @@
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- ==== PAGE: INFO ==== -->
|
||||
<Grid x:Name="pageInfo" Visibility="Collapsed" Margin="24,16">
|
||||
<Border Background="#282A3A" CornerRadius="12" Padding="32"
|
||||
VerticalAlignment="Top" HorizontalAlignment="Left"
|
||||
MaxWidth="520">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Betting Predictor" FontSize="26"
|
||||
FontFamily="Segoe UI Black"
|
||||
Foreground="{StaticResource BrBlue}"/>
|
||||
<TextBlock Text="Versione 2.0.0" FontSize="14"
|
||||
Foreground="{StaticResource BrSubtext0}" Margin="0,4,0,16"/>
|
||||
<TextBlock TextWrapping="Wrap" FontSize="13" Foreground="{StaticResource BrText}"
|
||||
LineHeight="22">
|
||||
Applicazione per lo scaricamento e l'analisi di dati sportivi
|
||||
tramite API esterne. Supporta calcio e corse dei cavalli
|
||||
con esportazione in CSV.
|
||||
</TextBlock>
|
||||
<TextBlock Text=".NET Framework 4.8.1 - WPF"
|
||||
FontSize="12" Foreground="{StaticResource BrSubtext0}" Margin="0,16,0,0"/>
|
||||
<TextBlock FontSize="12" Foreground="{StaticResource BrOverlay0}" Margin="0,8,0,0">
|
||||
<Run Text="© 2025 - Tutti i diritti riservati"/>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
<!-- Info page removed -->
|
||||
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
</Window>
|
||||
@@ -1,7 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -23,6 +26,139 @@ namespace HorseRacingPredictor
|
||||
InitializeComponent();
|
||||
_footballManager = new Football.Main();
|
||||
_racingManager = new HorseRacing.Main(DefaultRacingUser, DefaultRacingPass);
|
||||
// Wire preview update events
|
||||
txtFbPrefix.TextChanged += (s, e) => UpdateFbPreview();
|
||||
txtFbSuffix.TextChanged += (s, e) => UpdateFbPreview();
|
||||
chkFbIncludeDate.Checked += (s, e) => UpdateFbPreview();
|
||||
chkFbIncludeDate.Unchecked += (s, e) => UpdateFbPreview();
|
||||
cmbFbDateFormat.SelectionChanged += (s, e) => UpdateFbPreview();
|
||||
cmbFbFormat.SelectionChanged += (s, e) => UpdateFbPreview();
|
||||
dpFootball.SelectedDateChanged += (s, e) => UpdateFbPreview();
|
||||
|
||||
txtRcPrefix.TextChanged += (s, e) => UpdateRcPreview();
|
||||
txtRcSuffix.TextChanged += (s, e) => UpdateRcPreview();
|
||||
chkRcIncludeDate.Checked += (s, e) => UpdateRcPreview();
|
||||
chkRcIncludeDate.Unchecked += (s, e) => UpdateRcPreview();
|
||||
cmbRcDateFormat.SelectionChanged += (s, e) => UpdateRcPreview();
|
||||
cmbRcFormat.SelectionChanged += (s, e) => UpdateRcPreview();
|
||||
}
|
||||
|
||||
private void dgRacing_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
|
||||
{
|
||||
// Hide the "Inizio" column in the racing grid if it appears
|
||||
if (e.PropertyName.Equals("Inizio", StringComparison.OrdinalIgnoreCase) || e.Column.Header?.ToString() == "Inizio")
|
||||
{
|
||||
e.Cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure the row number 'No' column is visible and placed first
|
||||
if (e.PropertyName.Equals("No", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// make header readable
|
||||
e.Column.Header = "No";
|
||||
// set width small
|
||||
e.Column.Width = 60;
|
||||
}
|
||||
}
|
||||
|
||||
private void ExportToJson(DataTable data, string folder, string defaultName, Action<string> setStatus)
|
||||
{
|
||||
if (data == null || data.Rows.Count == 0) { MessageBox.Show("Nessun dato da esportare.", "Nessun dato", MessageBoxButton.OK, MessageBoxImage.Warning); return; }
|
||||
// ensure file name has .json
|
||||
defaultName = EnsureFileExtension(string.IsNullOrWhiteSpace(defaultName) ? "export.json" : defaultName, ".json");
|
||||
string filePath;
|
||||
if (!string.IsNullOrEmpty(folder) && Directory.Exists(folder)) filePath = Path.Combine(folder, defaultName);
|
||||
else
|
||||
{
|
||||
var dlg = new Microsoft.Win32.SaveFileDialog { Filter = "File JSON|*.json", FileName = defaultName, AddExtension = true };
|
||||
if (dlg.ShowDialog() != true) return;
|
||||
filePath = dlg.FileName;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var list = new System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, object>>();
|
||||
foreach (DataRow r in data.Rows)
|
||||
{
|
||||
var dict = new System.Collections.Generic.Dictionary<string, object>();
|
||||
foreach (DataColumn c in data.Columns)
|
||||
dict[c.ColumnName] = r[c] == DBNull.Value ? null : r[c];
|
||||
list.Add(dict);
|
||||
}
|
||||
var json = System.Text.Json.JsonSerializer.Serialize(list, new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
|
||||
File.WriteAllText(filePath, json, Encoding.UTF8);
|
||||
setStatus?.Invoke($"JSON esportato: {Path.GetFileName(filePath)}");
|
||||
MessageBox.Show($"Esportate {data.Rows.Count} righe in:\n{filePath}", "Esportazione completata", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Errore durante l'esportazione JSON:\n{ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// display count somewhere: update status label if provided
|
||||
setStatus?.Invoke($"Esportate {data.Rows.Count} righe");
|
||||
}
|
||||
}
|
||||
|
||||
private void ExportToXml(DataTable data, string folder, string defaultName, Action<string> setStatus)
|
||||
{
|
||||
if (data == null || data.Rows.Count == 0) { MessageBox.Show("Nessun dato da esportare.", "Nessun dato", MessageBoxButton.OK, MessageBoxImage.Warning); return; }
|
||||
// ensure file name has .xml
|
||||
defaultName = EnsureFileExtension(string.IsNullOrWhiteSpace(defaultName) ? "export.xml" : defaultName, ".xml");
|
||||
string filePath;
|
||||
if (!string.IsNullOrEmpty(folder) && Directory.Exists(folder)) filePath = Path.Combine(folder, defaultName);
|
||||
else
|
||||
{
|
||||
var dlg = new Microsoft.Win32.SaveFileDialog { Filter = "File XML|*.xml", FileName = defaultName, AddExtension = true };
|
||||
if (dlg.ShowDialog() != true) return;
|
||||
filePath = dlg.FileName;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Build a simple XML without schema to avoid assembly/type resolution issues
|
||||
var doc = new System.Xml.Linq.XDocument();
|
||||
var root = new System.Xml.Linq.XElement("Rows");
|
||||
foreach (DataRow r in data.Rows)
|
||||
{
|
||||
var rowEl = new System.Xml.Linq.XElement("Row");
|
||||
foreach (DataColumn c in data.Columns)
|
||||
{
|
||||
var val = r[c] == DBNull.Value ? string.Empty : r[c].ToString();
|
||||
rowEl.Add(new System.Xml.Linq.XElement(XmlConvertName(c.ColumnName), val));
|
||||
}
|
||||
root.Add(rowEl);
|
||||
}
|
||||
doc.Add(root);
|
||||
doc.Save(filePath);
|
||||
|
||||
setStatus?.Invoke($"XML esportato: {Path.GetFileName(filePath)}");
|
||||
MessageBox.Show($"Esportate {data.Rows.Count} righe in:\n{filePath}", "Esportazione completata", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Errore durante l'esportazione XML:\n{ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
setStatus?.Invoke($"Esportate {data.Rows.Count} righe");
|
||||
}
|
||||
}
|
||||
|
||||
private string XmlConvertName(string name)
|
||||
{
|
||||
// Ensure XML element name is valid: replace spaces and illegal chars with underscore
|
||||
if (string.IsNullOrEmpty(name)) return "Column";
|
||||
var sb = new StringBuilder();
|
||||
foreach (var ch in name)
|
||||
{
|
||||
if (char.IsLetterOrDigit(ch) || ch == '_' || ch == '-') sb.Append(ch); else sb.Append('_');
|
||||
}
|
||||
// cannot start with digit
|
||||
if (char.IsDigit(sb[0])) return "C_" + sb.ToString();
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
// ???????????????????? LIFECYCLE ????????????????????
|
||||
@@ -44,7 +180,6 @@ namespace HorseRacingPredictor
|
||||
if (pageFootball != null) pageFootball.Visibility = name == "football" ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (pageRacing != null) pageRacing.Visibility = name == "racing" ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (pageSettings != null) pageSettings.Visibility = name == "settings" ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (pageInfo != null) pageInfo.Visibility = name == "info" ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
// Update title if available
|
||||
if (lblTitle != null)
|
||||
@@ -54,7 +189,6 @@ namespace HorseRacingPredictor
|
||||
case "football": lblTitle.Text = "Calcio"; break;
|
||||
case "racing": lblTitle.Text = "Corse Cavalli"; break;
|
||||
case "settings": lblTitle.Text = "Impostazioni"; break;
|
||||
case "info": lblTitle.Text = "Informazioni"; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +196,6 @@ namespace HorseRacingPredictor
|
||||
private void navFootball_Checked(object sender, RoutedEventArgs e) => ShowPage("football");
|
||||
private void navRacing_Checked(object sender, RoutedEventArgs e) => ShowPage("racing");
|
||||
private void navSettings_Checked(object sender, RoutedEventArgs e) => ShowPage("settings");
|
||||
private void navInfo_Checked(object sender, RoutedEventArgs e) => ShowPage("info");
|
||||
|
||||
// ???????????????????? FOOTBALL ????????????????????
|
||||
|
||||
@@ -89,6 +222,10 @@ namespace HorseRacingPredictor
|
||||
_footballManager.GetTodayFixtures(date, progress, status));
|
||||
|
||||
_footballData = table;
|
||||
|
||||
// Ensure the start time column exists and populate it (no timezone label)
|
||||
InjectRomeStartTimeColumn(_footballData, "Inizio");
|
||||
|
||||
dgFootball.ItemsSource = _footballData?.DefaultView;
|
||||
|
||||
if (_footballData != null && _footballData.Rows.Count > 0)
|
||||
@@ -117,18 +254,224 @@ namespace HorseRacingPredictor
|
||||
|
||||
private void btnExportFbCsv_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ExportToCsv(_footballData, txtFbExportPath.Text,
|
||||
$"Partite_{dpFootball.SelectedDate:yyyy-MM-dd}.csv",
|
||||
s => lblStatusFb.Text = s);
|
||||
var format = (cmbFbFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV";
|
||||
var filename = BuildFilename(txtFbPrefix?.Text, chkFbIncludeDate?.IsChecked == true ? GetSelectedDateString(cmbFbDateFormat, dpFootball.SelectedDate ?? DateTime.Today) : null, txtFbSuffix?.Text, null, $"Partite_{dpFootball.SelectedDate:yyyy-MM-dd}.{format.ToLower()}");
|
||||
filename = EnsureFileExtension(SanitizeFileName(filename), "." + format.ToLower());
|
||||
|
||||
switch (format.ToUpper())
|
||||
{
|
||||
case "CSV":
|
||||
ExportToCsv(_footballData, txtFbExportPath.Text, filename, s => lblStatusFb.Text = s);
|
||||
break;
|
||||
case "JSON":
|
||||
ExportToJson(_footballData, txtFbExportPath.Text, filename, s => lblStatusFb.Text = s);
|
||||
break;
|
||||
case "XML":
|
||||
ExportToXml(_footballData, txtFbExportPath.Text, filename, s => lblStatusFb.Text = s);
|
||||
break;
|
||||
default:
|
||||
ExportToCsv(_footballData, txtFbExportPath.Text, filename, s => lblStatusFb.Text = s);
|
||||
break;
|
||||
}
|
||||
// show total rows extracted
|
||||
lblStatusFb.Text = _footballData == null ? "Nessuna riga" : $"Righe estratte: {_footballData.Rows.Count}";
|
||||
}
|
||||
|
||||
// ???????????????????? HORSE RACING ????????????????????
|
||||
|
||||
private void rbRcSource_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Toggle visibility of API vs CSV controls
|
||||
if (cmbDay == null || btnDownloadRc == null || btnBrowseCsvRc == null) return;
|
||||
bool isApi = rbRcApi.IsChecked == true;
|
||||
cmbDay.Visibility = isApi ? Visibility.Visible : Visibility.Collapsed;
|
||||
btnDownloadRc.Visibility = isApi ? Visibility.Visible : Visibility.Collapsed;
|
||||
btnBrowseCsvRc.Visibility = isApi ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
private async void btnDownloadRc_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await DownloadRacecardsAsync();
|
||||
}
|
||||
|
||||
private void btnBrowseCsvRc_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
using (var dlg = new System.Windows.Forms.FolderBrowserDialog())
|
||||
{
|
||||
dlg.Description = "Seleziona la cartella con i file CSV Punters";
|
||||
if (dlg.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;
|
||||
|
||||
try
|
||||
{
|
||||
lblStatusRc.Text = "Caricamento file CSV…";
|
||||
pbRacing.Value = 0;
|
||||
btnExportRcCsv.IsEnabled = false;
|
||||
|
||||
var csvFiles = Directory.GetFiles(dlg.SelectedPath, "*.csv", SearchOption.AllDirectories)
|
||||
.OrderBy(f => f)
|
||||
.ToList();
|
||||
|
||||
if (csvFiles.Count == 0)
|
||||
{
|
||||
lblStatusRc.Text = "Nessun file CSV trovato nella cartella selezionata";
|
||||
return;
|
||||
}
|
||||
|
||||
// Merge all CSV files into a single DataTable preserving all original columns
|
||||
var table = new DataTable();
|
||||
table.Columns.Add("Meeting", typeof(string));
|
||||
table.Columns.Add("Race", typeof(int));
|
||||
|
||||
int processed = 0;
|
||||
foreach (var file in csvFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Extract meeting name and race number from filename pattern YYYYMMDD-meeting-rXX.csv
|
||||
string fileName = Path.GetFileNameWithoutExtension(file);
|
||||
string meetingName = fileName;
|
||||
int raceNumber = 0;
|
||||
var m = Regex.Match(Path.GetFileName(file), @"^\d{8}-(.+)-r(\d+)\.csv$", RegexOptions.IgnoreCase);
|
||||
if (m.Success)
|
||||
{
|
||||
meetingName = string.Join(" ", m.Groups[1].Value.Split('-')
|
||||
.Select(s => s.Length > 0 ? char.ToUpper(s[0]) + s.Substring(1).ToLower() : s));
|
||||
int.TryParse(m.Groups[2].Value, out raceNumber);
|
||||
}
|
||||
|
||||
// Read CSV with simple parser (comma-delimited, first row = header)
|
||||
var lines = File.ReadAllLines(file, Encoding.UTF8);
|
||||
if (lines.Length < 2) { processed++; continue; }
|
||||
|
||||
var headers = ParseCsvLine(lines[0]);
|
||||
|
||||
// Ensure all columns exist in the merged DataTable
|
||||
foreach (var h in headers)
|
||||
{
|
||||
string colName = h.Trim();
|
||||
if (string.IsNullOrWhiteSpace(colName)) continue;
|
||||
if (!table.Columns.Contains(colName))
|
||||
table.Columns.Add(colName, typeof(string));
|
||||
}
|
||||
|
||||
// Parse data rows
|
||||
for (int i = 1; i < lines.Length; i++)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(lines[i])) continue;
|
||||
var values = ParseCsvLine(lines[i]);
|
||||
var row = table.NewRow();
|
||||
row["Meeting"] = meetingName;
|
||||
row["Race"] = raceNumber;
|
||||
for (int c = 0; c < headers.Length && c < values.Length; c++)
|
||||
{
|
||||
string colName = headers[c].Trim();
|
||||
if (string.IsNullOrWhiteSpace(colName)) continue;
|
||||
if (table.Columns.Contains(colName))
|
||||
row[colName] = values[c]?.Trim() ?? "";
|
||||
}
|
||||
table.Rows.Add(row);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Errore CSV {file}: {ex.Message}");
|
||||
}
|
||||
processed++;
|
||||
pbRacing.Value = (int)((double)processed / csvFiles.Count * 100);
|
||||
}
|
||||
|
||||
// Add row numbers
|
||||
InjectRowNumbers(table);
|
||||
|
||||
_racingData = table;
|
||||
dgRacing.ItemsSource = _racingData?.DefaultView;
|
||||
|
||||
if (_racingData.Rows.Count > 0)
|
||||
{
|
||||
btnExportRcCsv.IsEnabled = true;
|
||||
lblStatusRc.Text = $"Caricati {_racingData.Rows.Count} cavalli da {csvFiles.Count} file CSV";
|
||||
}
|
||||
else
|
||||
{
|
||||
lblStatusRc.Text = "Nessun cavallo trovato nei file CSV";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Errore durante il caricamento CSV:\n{ex.Message}",
|
||||
"Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
lblStatusRc.Text = "Errore nel caricamento CSV";
|
||||
pbRacing.Value = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a single CSV line respecting quoted fields (comma delimiter).
|
||||
/// </summary>
|
||||
private static string[] ParseCsvLine(string line)
|
||||
{
|
||||
var fields = new List<string>();
|
||||
bool inQuotes = false;
|
||||
var sb = new StringBuilder();
|
||||
for (int i = 0; i < line.Length; i++)
|
||||
{
|
||||
char c = line[i];
|
||||
if (inQuotes)
|
||||
{
|
||||
if (c == '"')
|
||||
{
|
||||
if (i + 1 < line.Length && line[i + 1] == '"')
|
||||
{
|
||||
sb.Append('"');
|
||||
i++; // skip escaped quote
|
||||
}
|
||||
else
|
||||
{
|
||||
inQuotes = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(c);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (c == '"')
|
||||
{
|
||||
inQuotes = true;
|
||||
}
|
||||
else if (c == ',')
|
||||
{
|
||||
fields.Add(sb.ToString());
|
||||
sb.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
fields.Add(sb.ToString());
|
||||
return fields.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a "No" row-number column as the first column in the DataTable.
|
||||
/// </summary>
|
||||
private static void InjectRowNumbers(DataTable table)
|
||||
{
|
||||
if (table == null || table.Rows.Count == 0) return;
|
||||
if (table.Columns.Contains("No")) table.Columns.Remove("No");
|
||||
var col = new DataColumn("No", typeof(int));
|
||||
table.Columns.Add(col);
|
||||
col.SetOrdinal(0);
|
||||
int n = 1;
|
||||
foreach (DataRow r in table.Rows)
|
||||
r[col] = n++;
|
||||
}
|
||||
|
||||
private async Task DownloadRacecardsAsync()
|
||||
{
|
||||
try
|
||||
@@ -148,6 +491,10 @@ namespace HorseRacingPredictor
|
||||
_racingManager.GetRacecards(day, progress, status));
|
||||
|
||||
_racingData = table;
|
||||
|
||||
// Add only row numbers for racing (do not add an "Inizio" column — meeting name already contains time)
|
||||
InjectRomeStartTimeColumn(_racingData, null);
|
||||
|
||||
dgRacing.ItemsSource = _racingData?.DefaultView;
|
||||
|
||||
if (_racingData != null && _racingData.Rows.Count > 0)
|
||||
@@ -177,9 +524,27 @@ namespace HorseRacingPredictor
|
||||
private void btnExportRcCsv_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
string dayLabel = cmbDay.SelectedIndex == 0 ? "oggi" : "domani";
|
||||
ExportToCsv(_racingData, txtRcExportPath.Text,
|
||||
$"Corse_{dayLabel}_{DateTime.Now:yyyy-MM-dd}.csv",
|
||||
s => lblStatusRc.Text = s);
|
||||
var format = (cmbRcFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV";
|
||||
var defaultName = $"Corse_{dayLabel}_{DateTime.Now:yyyy-MM-dd}.{format.ToLower()}";
|
||||
var filename = BuildFilename(txtRcPrefix?.Text, chkRcIncludeDate?.IsChecked == true ? GetSelectedDateString(cmbRcDateFormat, DateTime.Now) : null, txtRcSuffix?.Text, null, defaultName);
|
||||
filename = EnsureFileExtension(SanitizeFileName(filename), "." + format.ToLower());
|
||||
|
||||
switch (format.ToUpper())
|
||||
{
|
||||
case "CSV":
|
||||
ExportToCsv(_racingData, txtRcExportPath.Text, filename, s => lblStatusRc.Text = s);
|
||||
break;
|
||||
case "JSON":
|
||||
ExportToJson(_racingData, txtRcExportPath.Text, filename, s => lblStatusRc.Text = s);
|
||||
break;
|
||||
case "XML":
|
||||
ExportToXml(_racingData, txtRcExportPath.Text, filename, s => lblStatusRc.Text = s);
|
||||
break;
|
||||
default:
|
||||
ExportToCsv(_racingData, txtRcExportPath.Text, filename, s => lblStatusRc.Text = s);
|
||||
break;
|
||||
}
|
||||
lblStatusRc.Text = _racingData == null ? "Nessuna riga" : $"Righe estratte: {_racingData.Rows.Count}";
|
||||
}
|
||||
|
||||
// ???????????????????? SHARED CSV EXPORT ????????????????????
|
||||
@@ -193,6 +558,8 @@ namespace HorseRacingPredictor
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure filename has .csv extension
|
||||
defaultName = EnsureFileExtension(defaultName, ".csv");
|
||||
string filePath;
|
||||
if (!string.IsNullOrEmpty(folder) && Directory.Exists(folder))
|
||||
{
|
||||
@@ -203,7 +570,8 @@ namespace HorseRacingPredictor
|
||||
var dlg = new Microsoft.Win32.SaveFileDialog
|
||||
{
|
||||
Filter = "File CSV|*.csv",
|
||||
FileName = defaultName
|
||||
FileName = defaultName,
|
||||
AddExtension = true
|
||||
};
|
||||
if (dlg.ShowDialog() != true) return;
|
||||
filePath = dlg.FileName;
|
||||
@@ -240,6 +608,10 @@ namespace HorseRacingPredictor
|
||||
MessageBox.Show($"Errore durante l'esportazione CSV:\n{ex.Message}",
|
||||
"Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
setStatus?.Invoke($"Esportate {data.Rows.Count} righe");
|
||||
}
|
||||
}
|
||||
|
||||
// ???????????????????? FOLDER BROWSE ????????????????????
|
||||
@@ -248,12 +620,14 @@ namespace HorseRacingPredictor
|
||||
{
|
||||
var path = BrowseFolder("Seleziona la cartella di esportazione per Calcio");
|
||||
if (path != null) txtFbExportPath.Text = path;
|
||||
UpdateFbPreview();
|
||||
}
|
||||
|
||||
private void btnBrowseRcExport_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var path = BrowseFolder("Seleziona la cartella di esportazione per Corse Cavalli");
|
||||
if (path != null) txtRcExportPath.Text = path;
|
||||
UpdateRcPreview();
|
||||
}
|
||||
|
||||
private static string BrowseFolder(string description)
|
||||
@@ -289,16 +663,251 @@ namespace HorseRacingPredictor
|
||||
|
||||
if (key == "ApiKey") txtApiKey.Text = val;
|
||||
else if (key == "FbExportPath") txtFbExportPath.Text = val;
|
||||
else if (key == "FbPrefix") txtFbPrefix.Text = val;
|
||||
else if (key == "FbSuffix") txtFbSuffix.Text = val;
|
||||
else if (key == "FbIncludeDate") chkFbIncludeDate.IsChecked = val == "1" || val.Equals("true", StringComparison.OrdinalIgnoreCase);
|
||||
else if (key == "FbDateFormat") { try { SetComboBoxSelectionByContent(cmbFbDateFormat, val); } catch { } }
|
||||
else if (key == "FbFormat") { try { SetComboBoxSelectionByContent(cmbFbFormat, val); } catch { } }
|
||||
else if (key == "RcExportPath") txtRcExportPath.Text = val;
|
||||
else if (key == "RcPrefix") txtRcPrefix.Text = val;
|
||||
else if (key == "RcSuffix") txtRcSuffix.Text = val;
|
||||
else if (key == "RcIncludeDate") chkRcIncludeDate.IsChecked = val == "1" || val.Equals("true", StringComparison.OrdinalIgnoreCase);
|
||||
else if (key == "RcDateFormat") { try { SetComboBoxSelectionByContent(cmbRcDateFormat, val); } catch { } }
|
||||
else if (key == "RcFormat") { try { SetComboBoxSelectionByContent(cmbRcFormat, val); } catch { } }
|
||||
else if (key == "RacingUser") txtRacingUser.Text = val;
|
||||
else if (key == "RacingPass") txtRacingPass.Password = val;
|
||||
}
|
||||
|
||||
// Update preview UI after loading values
|
||||
UpdateFbPreview();
|
||||
UpdateRcPreview();
|
||||
|
||||
_racingManager.UpdateCredentials(txtRacingUser.Text, txtRacingPass.Password);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void SetComboBoxSelectionByContent(ComboBox combo, string content)
|
||||
{
|
||||
if (combo == null) return;
|
||||
for (int i = 0; i < combo.Items.Count; i++)
|
||||
{
|
||||
var item = combo.Items[i] as ComboBoxItem;
|
||||
if (item != null && string.Equals(item.Content?.ToString(), content, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
combo.SelectedIndex = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string EnsureFileExtension(string fileName, string extension)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(fileName)) return "";
|
||||
if (!extension.StartsWith(".")) extension = "." + extension;
|
||||
if (fileName.EndsWith(extension, StringComparison.OrdinalIgnoreCase)) return fileName;
|
||||
return fileName + extension;
|
||||
}
|
||||
|
||||
private static string BuildFilename(string prefix, string datePart, string suffix, string explicitName, string fallback)
|
||||
{
|
||||
// If user provided explicit full filename, use it
|
||||
if (!string.IsNullOrWhiteSpace(explicitName)) return SanitizeFileName(explicitName.Trim());
|
||||
|
||||
prefix = prefix ?? "";
|
||||
suffix = suffix ?? "";
|
||||
// The user may include underscores in prefix/suffix as desired
|
||||
|
||||
string middle = string.IsNullOrWhiteSpace(datePart) ? "" : datePart;
|
||||
|
||||
string name = (prefix ?? string.Empty) + middle + (suffix ?? string.Empty);
|
||||
if (string.IsNullOrWhiteSpace(name)) return fallback;
|
||||
return SanitizeFileName(name);
|
||||
}
|
||||
|
||||
private static string GetSelectedDateString(ComboBox combo, DateTime? date)
|
||||
{
|
||||
if (date == null) return null;
|
||||
var fmt = (combo?.SelectedItem as ComboBoxItem)?.Content?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(fmt)) fmt = "yyyy-MM-dd";
|
||||
try { return date.Value.ToString(fmt); } catch { return date.Value.ToString("yyyy-MM-dd"); }
|
||||
}
|
||||
|
||||
private static string SanitizeFileName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)) return name;
|
||||
var invalid = Path.GetInvalidFileNameChars();
|
||||
var sb = new StringBuilder();
|
||||
foreach (var ch in name)
|
||||
{
|
||||
if (Array.IndexOf(invalid, ch) >= 0)
|
||||
continue; // remove invalid characters
|
||||
sb.Append(ch);
|
||||
}
|
||||
// trim whitespace
|
||||
return sb.ToString().Trim();
|
||||
}
|
||||
|
||||
private void UpdateFbPreview()
|
||||
{
|
||||
try
|
||||
{
|
||||
var format = (cmbFbFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV";
|
||||
var datePart = chkFbIncludeDate?.IsChecked == true ? GetSelectedDateString(cmbFbDateFormat, dpFootball.SelectedDate ?? DateTime.Today) : null;
|
||||
var name = BuildFilename(txtFbPrefix?.Text, datePart, txtFbSuffix?.Text, null, $"Partite_{(dpFootball.SelectedDate ?? DateTime.Today):yyyy-MM-dd}.{format.ToLower()}");
|
||||
name = SanitizeFileName(name);
|
||||
name = EnsureFileExtension(name, "." + format.ToLower());
|
||||
if (txtFbPreview != null) txtFbPreview.Text = name;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void UpdateRcPreview()
|
||||
{
|
||||
try
|
||||
{
|
||||
var format = (cmbRcFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV";
|
||||
var datePart = chkRcIncludeDate?.IsChecked == true ? GetSelectedDateString(cmbRcDateFormat, DateTime.Now) : null;
|
||||
var defaultName = $"Corse_{(cmbDay.SelectedIndex==0?"oggi":"domani")}_{DateTime.Now:yyyy-MM-dd}.{format.ToLower()}";
|
||||
var name = BuildFilename(txtRcPrefix?.Text, datePart, txtRcSuffix?.Text, null, defaultName);
|
||||
name = SanitizeFileName(name);
|
||||
name = EnsureFileExtension(name, "." + format.ToLower());
|
||||
if (txtRcPreview != null) txtRcPreview.Text = name;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure a visible column with the fixture/race start time converted to Rome timezone exists in the DataTable.
|
||||
/// It attempts to use a unix timestamp column (unix_ts) or a date column (Data / Ora / date) and adds a formatted column.
|
||||
/// </summary>
|
||||
private void InjectRomeStartTimeColumn(DataTable table, string columnName)
|
||||
{
|
||||
if (table == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
// If no columnName specified, only add/populate row number column and return
|
||||
if (string.IsNullOrWhiteSpace(columnName))
|
||||
{
|
||||
if (table.Columns.Contains("No")) table.Columns.Remove("No");
|
||||
var rowOnly = new DataColumn("No", typeof(int));
|
||||
table.Columns.Add(rowOnly);
|
||||
rowOnly.SetOrdinal(0);
|
||||
int rnOnly = 1;
|
||||
foreach (DataRow r in table.Rows)
|
||||
{
|
||||
try { r[rowOnly] = rnOnly++; } catch { }
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove any existing columns with the same names to avoid duplicates
|
||||
if (table.Columns.Contains(columnName)) table.Columns.Remove(columnName);
|
||||
if (table.Columns.Contains("No")) table.Columns.Remove("No");
|
||||
|
||||
// Add row number column as first column
|
||||
var rowNoCol = new DataColumn("No", typeof(int));
|
||||
table.Columns.Add(rowNoCol);
|
||||
rowNoCol.SetOrdinal(0);
|
||||
|
||||
// Add new column as string for formatted display and place it after 'No'
|
||||
var col = new DataColumn(columnName, typeof(string));
|
||||
table.Columns.Add(col);
|
||||
col.SetOrdinal(1);
|
||||
|
||||
// Populate row numbers immediately
|
||||
int rn = 1;
|
||||
foreach (DataRow r in table.Rows)
|
||||
{
|
||||
try { r[rowNoCol] = rn++; } catch { }
|
||||
}
|
||||
|
||||
// Determine timezone for Rome (Windows TZ id). Fallback to UTC+1 offset if not found.
|
||||
TimeZoneInfo romeTz = null;
|
||||
try
|
||||
{
|
||||
romeTz = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
|
||||
}
|
||||
catch
|
||||
{
|
||||
try { romeTz = TimeZoneInfo.FindSystemTimeZoneById("Central Europe Standard Time"); } catch { romeTz = null; }
|
||||
}
|
||||
|
||||
// Helper to convert DateTimeOffset to Rome local time and format
|
||||
Func<DateTimeOffset, string> fmt = dto =>
|
||||
{
|
||||
DateTimeOffset rome;
|
||||
if (romeTz != null)
|
||||
rome = TimeZoneInfo.ConvertTime(dto, romeTz);
|
||||
else
|
||||
rome = dto.ToOffset(TimeSpan.FromHours(1)); // fallback UTC+1
|
||||
|
||||
return rome.ToString("yyyy-MM-dd HH:mm");
|
||||
};
|
||||
|
||||
// Try unix timestamp first
|
||||
if (table.Columns.Contains("unix_ts"))
|
||||
{
|
||||
foreach (DataRow r in table.Rows)
|
||||
{
|
||||
try
|
||||
{
|
||||
var obj = r["unix_ts"];
|
||||
if (obj == DBNull.Value) { r[col] = string.Empty; continue; }
|
||||
long ts = 0;
|
||||
if (obj is long) ts = (long)obj;
|
||||
else if (obj is int) ts = Convert.ToInt64(obj);
|
||||
else ts = Convert.ToInt64(obj);
|
||||
|
||||
var dto = DateTimeOffset.FromUnixTimeSeconds(ts);
|
||||
r[col] = fmt(dto);
|
||||
}
|
||||
catch { r[col] = string.Empty; }
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise try common date columns
|
||||
string[] candidates = new[] { "Data / Ora", "date", "Date", "start", "kickoff" };
|
||||
foreach (var c in candidates)
|
||||
{
|
||||
if (!table.Columns.Contains(c)) continue;
|
||||
foreach (DataRow r in table.Rows)
|
||||
{
|
||||
try
|
||||
{
|
||||
var v = r[c];
|
||||
if (v == DBNull.Value) { r[col] = string.Empty; continue; }
|
||||
|
||||
DateTimeOffset dto;
|
||||
if (v is DateTime dt)
|
||||
{
|
||||
// treat as UTC if unspecified
|
||||
dto = new DateTimeOffset(DateTime.SpecifyKind(dt, DateTimeKind.Utc));
|
||||
}
|
||||
else
|
||||
{
|
||||
// parse string including offset
|
||||
dto = DateTimeOffset.Parse(v.ToString());
|
||||
}
|
||||
|
||||
r[col] = fmt(dto);
|
||||
}
|
||||
catch { r[col] = string.Empty; }
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If no source found, leave column empty
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Non-fatal: log to debug output
|
||||
System.Diagnostics.Debug.WriteLine("InjectRomeStartTimeColumn error: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void btnSaveSettings_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -306,11 +915,25 @@ namespace HorseRacingPredictor
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"ApiKey={txtApiKey.Text.Trim()}");
|
||||
sb.AppendLine($"FbExportPath={txtFbExportPath.Text.Trim()}");
|
||||
sb.AppendLine($"FbPrefix={txtFbPrefix.Text.Trim()}");
|
||||
sb.AppendLine($"FbSuffix={txtFbSuffix.Text.Trim()}");
|
||||
sb.AppendLine($"FbIncludeDate={(chkFbIncludeDate.IsChecked==true?"1":"0")}");
|
||||
sb.AppendLine($"FbDateFormat={(cmbFbDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd"}");
|
||||
sb.AppendLine($"FbFormat={(cmbFbFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV"}");
|
||||
sb.AppendLine($"RcExportPath={txtRcExportPath.Text.Trim()}");
|
||||
sb.AppendLine($"RcPrefix={txtRcPrefix.Text.Trim()}");
|
||||
sb.AppendLine($"RcSuffix={txtRcSuffix.Text.Trim()}");
|
||||
sb.AppendLine($"RcIncludeDate={(chkRcIncludeDate.IsChecked==true?"1":"0")}");
|
||||
sb.AppendLine($"RcDateFormat={(cmbRcDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd"}");
|
||||
sb.AppendLine($"RcFormat={(cmbRcFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV"}");
|
||||
sb.AppendLine($"RacingUser={txtRacingUser.Text.Trim()}");
|
||||
sb.AppendLine($"RacingPass={txtRacingPass.Password.Trim()}");
|
||||
File.WriteAllText(SettingsFilePath, sb.ToString(), Encoding.UTF8);
|
||||
|
||||
// update previews after save
|
||||
UpdateFbPreview();
|
||||
UpdateRcPreview();
|
||||
|
||||
_racingManager.UpdateCredentials(txtRacingUser.Text.Trim(), txtRacingPass.Password.Trim());
|
||||
|
||||
MessageBox.Show("Impostazioni salvate con successo.",
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
/****** Object: StoredProcedure [dbo].[GetFavoriteHorses] Script Date: 17/07/2025 09:09:35 ******/
|
||||
SET ANSI_NULLS ON
|
||||
GO
|
||||
|
||||
SET QUOTED_IDENTIFIER ON
|
||||
GO
|
||||
|
||||
ALTER PROCEDURE [dbo].[GetFavoriteHorses]
|
||||
@Date DATETIME = NULL,
|
||||
@Meeting NVARCHAR(100) = NULL,
|
||||
@RaceNumber INT = NULL,
|
||||
@TopN INT = 3,
|
||||
@Weight_HandicapRating DECIMAL(10,2) = 1,
|
||||
@Weight_CareerWins DECIMAL(10,2) = 1,
|
||||
@Weight_CareerStrikeRate DECIMAL(10,2) = 1,
|
||||
@Weight_JockeyLast100StrikeRate DECIMAL(10,2) = 1,
|
||||
@Weight_TrainerLast100StrikeRate DECIMAL(10,2) = 1,
|
||||
@Weight_BestFixedOdds DECIMAL(10,2) = 1,
|
||||
@Weight_Age DECIMAL(10,2) = 1,
|
||||
@Weight_CareerPlacings DECIMAL(10,2) = 1,
|
||||
@Weight_DryTrackStrikeRate DECIMAL(10,2) = 1,
|
||||
@Weight_ThisTrackStrikeRate DECIMAL(10,2) = 1,
|
||||
@Weight_LastStartFinishPosition DECIMAL(10,2) = 1,
|
||||
@Weight_WeightCarried DECIMAL(10,2) = 1
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
-- Crea tabella temporanea per i risultati
|
||||
CREATE TABLE #Results (
|
||||
DataCorsa NVARCHAR(10),
|
||||
LuogoCorsa NVARCHAR(100),
|
||||
NumeroCorsa INT,
|
||||
NomeCavallo NVARCHAR(100),
|
||||
Punteggio DECIMAL(18,4),
|
||||
RisultatoFinale NVARCHAR(50),
|
||||
PosizioneArrivo INT,
|
||||
PrevistoVincente BIT
|
||||
);
|
||||
|
||||
WITH Scoring AS (
|
||||
SELECT
|
||||
Data,
|
||||
Meeting,
|
||||
Race,
|
||||
HorseName,
|
||||
-- Calcolo del punteggio pesato con le nuove statistiche
|
||||
(
|
||||
ISNULL(HandicapRating,0) * @Weight_HandicapRating +
|
||||
ISNULL(CareerWins,0) * @Weight_CareerWins +
|
||||
ISNULL(CareerStrikeRate,0) * @Weight_CareerStrikeRate +
|
||||
ISNULL(JockeyLast100StrikeRate,0) * @Weight_JockeyLast100StrikeRate +
|
||||
ISNULL(TrainerLast100StrikeRate,0) * @Weight_TrainerLast100StrikeRate +
|
||||
ISNULL(BestFixedOdds,0) * @Weight_BestFixedOdds +
|
||||
ISNULL(Age,0) * @Weight_Age +
|
||||
ISNULL(CareerPlacings,0) * @Weight_CareerPlacings +
|
||||
ISNULL(DryTrackStrikeRate,0) * @Weight_DryTrackStrikeRate +
|
||||
ISNULL(ThisTrackStrikeRate,0) * @Weight_ThisTrackStrikeRate +
|
||||
-- Per LastStartFinishPosition, valori più bassi sono migliori, quindi invertiamo il segno
|
||||
(CASE WHEN TRY_CAST(LastStartFinishPosition AS INT) > 0 THEN (1.0 / TRY_CAST(LastStartFinishPosition AS FLOAT)) ELSE 0 END) * @Weight_LastStartFinishPosition +
|
||||
ISNULL(WeightCarried,0) * @Weight_WeightCarried
|
||||
) AS Score,
|
||||
FinishResult
|
||||
FROM Races
|
||||
WHERE (FinishResult IS NOT NULL AND FinishResult <> '')
|
||||
),
|
||||
Ranked AS (
|
||||
SELECT *,
|
||||
ROW_NUMBER() OVER (PARTITION BY Data, Meeting, Race ORDER BY Score DESC) AS RankByScore
|
||||
FROM Scoring
|
||||
)
|
||||
INSERT INTO #Results
|
||||
SELECT
|
||||
CONVERT(VARCHAR(10), Data, 120),
|
||||
Meeting,
|
||||
Race,
|
||||
HorseName,
|
||||
Score,
|
||||
FinishResult,
|
||||
CASE WHEN ISNUMERIC(FinishResult) = 1 THEN CAST(FinishResult AS INT) ELSE NULL END,
|
||||
CASE WHEN RankByScore = 1 THEN 1 ELSE 0 END
|
||||
FROM Ranked
|
||||
WHERE RankByScore = 1;
|
||||
|
||||
SELECT * FROM #Results;
|
||||
|
||||
DROP TABLE #Results;
|
||||
END
|
||||
@@ -1,27 +0,0 @@
|
||||
/****** Object: View [dbo].[GetValidHorses] Script Date: 17/07/2025 09:10:07 ******/
|
||||
SET ANSI_NULLS ON
|
||||
GO
|
||||
|
||||
SET QUOTED_IDENTIFIER ON
|
||||
GO
|
||||
|
||||
CREATE VIEW [dbo].[GetValidHorses]
|
||||
AS
|
||||
SELECT Data, Meeting, Race, Num, HorseName, Age, Gender, HandicapRating, CareerRuns, CareerWins, CareerStrikeRate, CareerROI, CareerPlacings, CareerPlaceStrikeRate, DryTrackRuns, DryTrackWins, DryTrackStrikeRate,
|
||||
DryTrackROI, WetTrackRuns, WetTrackWins, WetTrackStrikeRate, WetTrackROI, AveragePrizeMoney, CareerPrizeMoney, BestFixedOdds, BetEasyOdds, Weight, WeightCarried, Barrier, PrizeMoney, ThisTrackRuns,
|
||||
ThisTrackWins, ThisTrackStrikeRate, ThisTrackROI, ThisTrackPlaces, ThisTrackPlaceStrikeRate, ThisDistanceRuns, ThisDistanceWins, ThisDistanceStrikeRate, ThisDistanceROI, ThisDistancePlaces,
|
||||
ThisDistancePlaceStrikeRate, ThisTrackDistanceRuns, ThisTrackDistanceWins, ThisTrackDistanceStrikeRate, ThisTrackDistanceROI, ThisTrackDistancePlaces, ThisTrackDistancePlaceStrikeRate, ThisConditionRuns,
|
||||
ThisConditionWins, ThisConditionStrikeRate, ThisConditionROI, ThisConditionPlaces, ThisConditionPlaceStrikeRate, Jockey, Apprentice, JockeyWeightClaim, JockeyLast100HorseEarnings, JockeyLast100AvgHorseEarnings,
|
||||
JockeyLast100Starts, JockeyLast100Wins, JockeyLast100StrikeRate, JockeyLast100ROI, JockeyLast100Places, JockeyLast100PlaceStrikeRate, Jockey12MonthHorseEarnings, Jockey12MonthAvgHorseEarnings,
|
||||
Jockey12MonthsStarts, Jockey12MonthsWins, Jockey12MonthsStrikeRate, Jockey12MonthsROI, Jockey12MonthsPlaces, Jockey12MonthsPlaceStrikeRate, JockeyThisSeasonHorseEarnings,
|
||||
JockeyThisSeasonAvgHorseEarnings, JockeyThisSeasonStarts, JockeyThisSeasonWins, JockeyThisSeasonStrikeRate, JockeyThisSeasonROI, JockeyThisSeasonPlaces, JockeyThisSeasonPlaceStrikeRate,
|
||||
JockeyLastSeasonHorseEarnings, JockeyLastSeasonAvgHorseEarnings, JockeyLastSeasonStarts, JockeyLastSeasonWins, JockeyLastSeasonStrikeRate, JockeyLastSeasonROI, JockeyLastSeasonPlaces,
|
||||
JockeyLastSeasonPlaceStrikeRate, Trainer, TrainerLast100HorseEarnings, TrainerLast100AvgHorseEarnings, TrainerLast100Starts, TrainerLast100Wins, TrainerLast100StrikeRate, TrainerLast100ROI, TrainerLast100Places,
|
||||
TrainerLast100PlaceStrikeRate, Trainer12MonthHorseEarnings, Trainer12MonthAvgHorseEarnings, Trainer12MonthsStarts, Trainer12MonthsWins, Trainer12MonthsStrikeRate, Trainer12MonthsROI, Trainer12MonthsPlaces,
|
||||
Trainer12MonthsPlaceStrikeRate, TrainerThisSeasonHorseEarnings, TrainerThisSeasonAvgHorseEarnings, TrainerThisSeasonStarts, TrainerThisSeasonWins, TrainerThisSeasonStrikeRate, TrainerThisSeasonROI,
|
||||
TrainerThisSeasonPlaces, TrainerThisSeasonPlaceStrikeRate, TrainerLastSeasonHorseEarnings, TrainerLastSeasonAvgHorseEarnings, TrainerLastSeasonStarts, TrainerLastSeasonWins, TrainerLastSeasonStrikeRate,
|
||||
TrainerLastSeasonROI, TrainerLastSeasonPlaces, TrainerLastSeasonPlaceStrikeRate, LastStartFinishPosition, LastStartMargin, LastStartDistance, LastStartDistanceChange, LastStartPrizeMoney, FinishResult
|
||||
FROM dbo.Races
|
||||
WHERE (FinishResult <> '') OR
|
||||
(FinishResult IS NOT NULL)
|
||||
GO
|
||||
@@ -1,20 +0,0 @@
|
||||
CREATE TABLE [dbo].[PredictionScanResults] (
|
||||
ScanId INT IDENTITY(1,1) PRIMARY KEY,
|
||||
SessionId UNIQUEIDENTIFIER NOT NULL,
|
||||
ScanDate DATETIME NOT NULL DEFAULT GETDATE(),
|
||||
Weight_HandicapRating DECIMAL(10,2) NOT NULL,
|
||||
Weight_CareerWins DECIMAL(10,2) NOT NULL,
|
||||
Weight_CareerStrikeRate DECIMAL(10,2) NOT NULL,
|
||||
Weight_JockeyLast100StrikeRate DECIMAL(10,2) NOT NULL,
|
||||
Weight_TrainerLast100StrikeRate DECIMAL(10,2) NOT NULL,
|
||||
Weight_BestFixedOdds DECIMAL(10,2) NOT NULL,
|
||||
Weight_Age DECIMAL(10,2) NOT NULL,
|
||||
Weight_CareerPlacings DECIMAL(10,2) NOT NULL,
|
||||
Weight_DryTrackStrikeRate DECIMAL(10,2) NOT NULL,
|
||||
Weight_ThisTrackStrikeRate DECIMAL(10,2) NOT NULL,
|
||||
Weight_LastStartFinishPosition DECIMAL(10,2) NOT NULL,
|
||||
Weight_WeightCarried DECIMAL(10,2) NOT NULL,
|
||||
TotalRaces INT NOT NULL,
|
||||
CorrectPredictions INT NOT NULL,
|
||||
WinPercentage DECIMAL(5,2) NOT NULL
|
||||
)
|
||||
@@ -1,152 +0,0 @@
|
||||
/****** Object: Table [dbo].[Races] Script Date: 16/07/2025 23:27:12 ******/
|
||||
SET ANSI_NULLS ON
|
||||
GO
|
||||
|
||||
SET QUOTED_IDENTIFIER ON
|
||||
GO
|
||||
|
||||
CREATE TABLE [dbo].[Races](
|
||||
[Data] [datetime] NOT NULL,
|
||||
[Meeting] [nvarchar](100) NOT NULL,
|
||||
[Race] [int] NOT NULL,
|
||||
[Num] [int] NOT NULL,
|
||||
[FileName] [nvarchar](255) NULL,
|
||||
[HorseName] [nvarchar](100) NULL,
|
||||
[Age] [int] NULL,
|
||||
[Gender] [nvarchar](10) NULL,
|
||||
[HandicapRating] [decimal](10, 2) NULL,
|
||||
[CareerRuns] [int] NULL,
|
||||
[CareerWins] [int] NULL,
|
||||
[CareerStrikeRate] [decimal](10, 2) NULL,
|
||||
[CareerROI] [decimal](10, 2) NULL,
|
||||
[CareerPlacings] [int] NULL,
|
||||
[CareerPlaceStrikeRate] [decimal](10, 2) NULL,
|
||||
[DryTrackRuns] [int] NULL,
|
||||
[DryTrackWins] [int] NULL,
|
||||
[DryTrackStrikeRate] [decimal](10, 2) NULL,
|
||||
[DryTrackROI] [decimal](10, 2) NULL,
|
||||
[WetTrackRuns] [int] NULL,
|
||||
[WetTrackWins] [int] NULL,
|
||||
[WetTrackStrikeRate] [decimal](10, 2) NULL,
|
||||
[WetTrackROI] [decimal](10, 2) NULL,
|
||||
[AveragePrizeMoney] [decimal](15, 2) NULL,
|
||||
[CareerPrizeMoney] [decimal](15, 2) NULL,
|
||||
[BestFixedOdds] [decimal](10, 2) NULL,
|
||||
[BetEasyOdds] [decimal](10, 2) NULL,
|
||||
[Weight] [decimal](10, 2) NULL,
|
||||
[WeightCarried] [decimal](10, 2) NULL,
|
||||
[Barrier] [nvarchar](10) NULL,
|
||||
[PrizeMoney] [decimal](15, 2) NULL,
|
||||
[ThisTrackRuns] [int] NULL,
|
||||
[ThisTrackWins] [int] NULL,
|
||||
[ThisTrackStrikeRate] [decimal](10, 2) NULL,
|
||||
[ThisTrackROI] [decimal](10, 2) NULL,
|
||||
[ThisTrackPlaces] [int] NULL,
|
||||
[ThisTrackPlaceStrikeRate] [decimal](10, 2) NULL,
|
||||
[ThisDistanceRuns] [int] NULL,
|
||||
[ThisDistanceWins] [int] NULL,
|
||||
[ThisDistanceStrikeRate] [decimal](10, 2) NULL,
|
||||
[ThisDistanceROI] [decimal](10, 2) NULL,
|
||||
[ThisDistancePlaces] [int] NULL,
|
||||
[ThisDistancePlaceStrikeRate] [decimal](10, 2) NULL,
|
||||
[ThisTrackDistanceRuns] [int] NULL,
|
||||
[ThisTrackDistanceWins] [int] NULL,
|
||||
[ThisTrackDistanceStrikeRate] [decimal](10, 2) NULL,
|
||||
[ThisTrackDistanceROI] [decimal](10, 2) NULL,
|
||||
[ThisTrackDistancePlaces] [int] NULL,
|
||||
[ThisTrackDistancePlaceStrikeRate] [decimal](10, 2) NULL,
|
||||
[ThisConditionRuns] [int] NULL,
|
||||
[ThisConditionWins] [int] NULL,
|
||||
[ThisConditionStrikeRate] [decimal](10, 2) NULL,
|
||||
[ThisConditionROI] [decimal](10, 2) NULL,
|
||||
[ThisConditionPlaces] [int] NULL,
|
||||
[ThisConditionPlaceStrikeRate] [decimal](10, 2) NULL,
|
||||
[Jockey] [nvarchar](100) NULL,
|
||||
[Apprentice] [nvarchar](10) NULL,
|
||||
[JockeyWeightClaim] [decimal](10, 2) NULL,
|
||||
[JockeyLast100HorseEarnings] [decimal](15, 2) NULL,
|
||||
[JockeyLast100AvgHorseEarnings] [decimal](15, 2) NULL,
|
||||
[JockeyLast100Starts] [int] NULL,
|
||||
[JockeyLast100Wins] [int] NULL,
|
||||
[JockeyLast100StrikeRate] [decimal](10, 2) NULL,
|
||||
[JockeyLast100ROI] [decimal](10, 2) NULL,
|
||||
[JockeyLast100Places] [int] NULL,
|
||||
[JockeyLast100PlaceStrikeRate] [decimal](10, 2) NULL,
|
||||
[Jockey12MonthHorseEarnings] [decimal](15, 2) NULL,
|
||||
[Jockey12MonthAvgHorseEarnings] [decimal](15, 2) NULL,
|
||||
[Jockey12MonthsStarts] [int] NULL,
|
||||
[Jockey12MonthsWins] [int] NULL,
|
||||
[Jockey12MonthsStrikeRate] [decimal](10, 2) NULL,
|
||||
[Jockey12MonthsROI] [decimal](10, 2) NULL,
|
||||
[Jockey12MonthsPlaces] [int] NULL,
|
||||
[Jockey12MonthsPlaceStrikeRate] [decimal](10, 2) NULL,
|
||||
[JockeyThisSeasonHorseEarnings] [decimal](15, 2) NULL,
|
||||
[JockeyThisSeasonAvgHorseEarnings] [decimal](15, 2) NULL,
|
||||
[JockeyThisSeasonStarts] [int] NULL,
|
||||
[JockeyThisSeasonWins] [int] NULL,
|
||||
[JockeyThisSeasonStrikeRate] [decimal](10, 2) NULL,
|
||||
[JockeyThisSeasonROI] [decimal](10, 2) NULL,
|
||||
[JockeyThisSeasonPlaces] [int] NULL,
|
||||
[JockeyThisSeasonPlaceStrikeRate] [decimal](10, 2) NULL,
|
||||
[JockeyLastSeasonHorseEarnings] [decimal](15, 2) NULL,
|
||||
[JockeyLastSeasonAvgHorseEarnings] [decimal](15, 2) NULL,
|
||||
[JockeyLastSeasonStarts] [int] NULL,
|
||||
[JockeyLastSeasonWins] [int] NULL,
|
||||
[JockeyLastSeasonStrikeRate] [decimal](10, 2) NULL,
|
||||
[JockeyLastSeasonROI] [decimal](10, 2) NULL,
|
||||
[JockeyLastSeasonPlaces] [int] NULL,
|
||||
[JockeyLastSeasonPlaceStrikeRate] [decimal](10, 2) NULL,
|
||||
[Trainer] [nvarchar](100) NULL,
|
||||
[TrainerLast100HorseEarnings] [decimal](15, 2) NULL,
|
||||
[TrainerLast100AvgHorseEarnings] [decimal](15, 2) NULL,
|
||||
[TrainerLast100Starts] [int] NULL,
|
||||
[TrainerLast100Wins] [int] NULL,
|
||||
[TrainerLast100StrikeRate] [decimal](10, 2) NULL,
|
||||
[TrainerLast100ROI] [decimal](10, 2) NULL,
|
||||
[TrainerLast100Places] [int] NULL,
|
||||
[TrainerLast100PlaceStrikeRate] [decimal](10, 2) NULL,
|
||||
[Trainer12MonthHorseEarnings] [decimal](15, 2) NULL,
|
||||
[Trainer12MonthAvgHorseEarnings] [decimal](15, 2) NULL,
|
||||
[Trainer12MonthsStarts] [int] NULL,
|
||||
[Trainer12MonthsWins] [int] NULL,
|
||||
[Trainer12MonthsStrikeRate] [decimal](10, 2) NULL,
|
||||
[Trainer12MonthsROI] [decimal](10, 2) NULL,
|
||||
[Trainer12MonthsPlaces] [int] NULL,
|
||||
[Trainer12MonthsPlaceStrikeRate] [decimal](10, 2) NULL,
|
||||
[TrainerThisSeasonHorseEarnings] [decimal](15, 2) NULL,
|
||||
[TrainerThisSeasonAvgHorseEarnings] [decimal](15, 2) NULL,
|
||||
[TrainerThisSeasonStarts] [int] NULL,
|
||||
[TrainerThisSeasonWins] [int] NULL,
|
||||
[TrainerThisSeasonStrikeRate] [decimal](10, 2) NULL,
|
||||
[TrainerThisSeasonROI] [decimal](10, 2) NULL,
|
||||
[TrainerThisSeasonPlaces] [int] NULL,
|
||||
[TrainerThisSeasonPlaceStrikeRate] [decimal](10, 2) NULL,
|
||||
[TrainerLastSeasonHorseEarnings] [decimal](15, 2) NULL,
|
||||
[TrainerLastSeasonAvgHorseEarnings] [decimal](15, 2) NULL,
|
||||
[TrainerLastSeasonStarts] [int] NULL,
|
||||
[TrainerLastSeasonWins] [int] NULL,
|
||||
[TrainerLastSeasonStrikeRate] [decimal](10, 2) NULL,
|
||||
[TrainerLastSeasonROI] [decimal](10, 2) NULL,
|
||||
[TrainerLastSeasonPlaces] [int] NULL,
|
||||
[TrainerLastSeasonPlaceStrikeRate] [decimal](10, 2) NULL,
|
||||
[LastStartFinishPosition] [nvarchar](10) NULL,
|
||||
[LastStartMargin] [decimal](10, 2) NULL,
|
||||
[LastStartDistance] [decimal](10, 2) NULL,
|
||||
[LastStartDistanceChange] [decimal](10, 2) NULL,
|
||||
[LastStartPrizeMoney] [decimal](15, 2) NULL,
|
||||
[FormGuideUrl] [nvarchar](500) NULL,
|
||||
[HorseProfileUrl] [nvarchar](500) NULL,
|
||||
[JockeyProfileUrl] [nvarchar](500) NULL,
|
||||
[TrainerProfileUrl] [nvarchar](500) NULL,
|
||||
[FinishResult] [nvarchar](50) NULL,
|
||||
[Created] [datetime] NOT NULL,
|
||||
[Updated] [datetime] NOT NULL,
|
||||
CONSTRAINT [PK_Races] PRIMARY KEY CLUSTERED
|
||||
(
|
||||
[Data] ASC,
|
||||
[Meeting] ASC,
|
||||
[Race] ASC,
|
||||
[Num] ASC
|
||||
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
|
||||
) ON [PRIMARY]
|
||||
GO
|
||||
@@ -1,228 +0,0 @@
|
||||
ALTER PROCEDURE [dbo].[ScanFavoriteHorseWeights]
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DECLARE @MinValue INT = 1
|
||||
DECLARE @MaxValue INT = 3 -- Modifica per range più ampi
|
||||
DECLARE @SessionId UNIQUEIDENTIFIER = NEWID()
|
||||
DECLARE @ScanDate DATETIME = GETDATE()
|
||||
|
||||
-- Parametri
|
||||
DECLARE @Weight_HandicapRating INT
|
||||
DECLARE @Weight_CareerWins INT
|
||||
DECLARE @Weight_CareerStrikeRate INT
|
||||
DECLARE @Weight_JockeyLast100StrikeRate INT
|
||||
DECLARE @Weight_TrainerLast100StrikeRate INT
|
||||
DECLARE @Weight_BestFixedOdds INT
|
||||
DECLARE @Weight_Age INT
|
||||
DECLARE @Weight_CareerPlacings INT
|
||||
DECLARE @Weight_DryTrackStrikeRate INT
|
||||
DECLARE @Weight_ThisTrackStrikeRate INT
|
||||
DECLARE @Weight_LastStartFinishPosition INT
|
||||
DECLARE @Weight_WeightCarried INT
|
||||
|
||||
DECLARE @TotalRaces INT, @CorrectPredictions INT, @WinPercentage DECIMAL(5,2)
|
||||
DECLARE @CombinazioniTotali BIGINT
|
||||
DECLARE @CombinazioneCorrente BIGINT = 0
|
||||
|
||||
-- Carica tutti i cavalli validi in memoria
|
||||
IF OBJECT_ID('tempdb..#AllHorses') IS NOT NULL DROP TABLE #AllHorses
|
||||
SELECT
|
||||
Data,
|
||||
Meeting,
|
||||
Race,
|
||||
HorseName,
|
||||
HandicapRating,
|
||||
CareerWins,
|
||||
CareerStrikeRate,
|
||||
JockeyLast100StrikeRate,
|
||||
TrainerLast100StrikeRate,
|
||||
BestFixedOdds,
|
||||
Age,
|
||||
CareerPlacings,
|
||||
DryTrackStrikeRate,
|
||||
ThisTrackStrikeRate,
|
||||
LastStartFinishPosition,
|
||||
WeightCarried,
|
||||
FinishResult
|
||||
INTO #AllHorses
|
||||
FROM Races
|
||||
WHERE (FinishResult IS NOT NULL AND FinishResult <> '')
|
||||
|
||||
-- Conta il numero di corse distinte
|
||||
SELECT @TotalRaces = COUNT(DISTINCT
|
||||
CONVERT(NVARCHAR(20), Data, 120) + '_' + Meeting + '_' + CAST(Race AS NVARCHAR(10))
|
||||
)
|
||||
FROM #AllHorses
|
||||
|
||||
-- Calcola il numero totale di combinazioni
|
||||
SET @CombinazioniTotali = POWER(@MaxValue - @MinValue + 1, 12)
|
||||
|
||||
-- Inizializza la sessione con un record "vuoto"
|
||||
INSERT INTO PredictionScanResults (
|
||||
SessionId, ScanDate,
|
||||
Weight_HandicapRating, Weight_CareerWins, Weight_CareerStrikeRate, Weight_JockeyLast100StrikeRate,
|
||||
Weight_TrainerLast100StrikeRate, Weight_BestFixedOdds, Weight_Age, Weight_CareerPlacings,
|
||||
Weight_DryTrackStrikeRate, Weight_ThisTrackStrikeRate, Weight_LastStartFinishPosition, Weight_WeightCarried,
|
||||
TotalRaces, CorrectPredictions, WinPercentage
|
||||
) VALUES (
|
||||
@SessionId, @ScanDate,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0
|
||||
)
|
||||
|
||||
SET @Weight_HandicapRating = @MinValue
|
||||
WHILE @Weight_HandicapRating <= @MaxValue
|
||||
BEGIN
|
||||
SET @Weight_CareerWins = @MinValue
|
||||
WHILE @Weight_CareerWins <= @MaxValue
|
||||
BEGIN
|
||||
SET @Weight_CareerStrikeRate = @MinValue
|
||||
WHILE @Weight_CareerStrikeRate <= @MaxValue
|
||||
BEGIN
|
||||
SET @Weight_JockeyLast100StrikeRate = @MinValue
|
||||
WHILE @Weight_JockeyLast100StrikeRate <= @MaxValue
|
||||
BEGIN
|
||||
SET @Weight_TrainerLast100StrikeRate = @MinValue
|
||||
WHILE @Weight_TrainerLast100StrikeRate <= @MaxValue
|
||||
BEGIN
|
||||
SET @Weight_BestFixedOdds = @MinValue
|
||||
WHILE @Weight_BestFixedOdds <= @MaxValue
|
||||
BEGIN
|
||||
SET @Weight_Age = @MinValue
|
||||
WHILE @Weight_Age <= @MaxValue
|
||||
BEGIN
|
||||
SET @Weight_CareerPlacings = @MinValue
|
||||
WHILE @Weight_CareerPlacings <= @MaxValue
|
||||
BEGIN
|
||||
SET @Weight_DryTrackStrikeRate = @MinValue
|
||||
WHILE @Weight_DryTrackStrikeRate <= @MaxValue
|
||||
BEGIN
|
||||
SET @Weight_ThisTrackStrikeRate = @MinValue
|
||||
WHILE @Weight_ThisTrackStrikeRate <= @MaxValue
|
||||
BEGIN
|
||||
SET @Weight_LastStartFinishPosition = @MinValue
|
||||
WHILE @Weight_LastStartFinishPosition <= @MaxValue
|
||||
BEGIN
|
||||
SET @Weight_WeightCarried = @MinValue
|
||||
WHILE @Weight_WeightCarried <= @MaxValue
|
||||
BEGIN
|
||||
SET @CombinazioneCorrente = @CombinazioneCorrente + 1
|
||||
|
||||
-- Calcolo in memoria: trova il cavallo con punteggio massimo per ogni corsa
|
||||
IF OBJECT_ID('tempdb..#Results') IS NOT NULL DROP TABLE #Results
|
||||
SELECT
|
||||
Data,
|
||||
Meeting,
|
||||
Race,
|
||||
HorseName,
|
||||
(
|
||||
ISNULL(HandicapRating,0) * @Weight_HandicapRating +
|
||||
ISNULL(CareerWins,0) * @Weight_CareerWins +
|
||||
ISNULL(CareerStrikeRate,0) * @Weight_CareerStrikeRate +
|
||||
ISNULL(JockeyLast100StrikeRate,0) * @Weight_JockeyLast100StrikeRate +
|
||||
ISNULL(TrainerLast100StrikeRate,0) * @Weight_TrainerLast100StrikeRate +
|
||||
ISNULL(BestFixedOdds,0) * @Weight_BestFixedOdds +
|
||||
ISNULL(Age,0) * @Weight_Age +
|
||||
ISNULL(CareerPlacings,0) * @Weight_CareerPlacings +
|
||||
ISNULL(DryTrackStrikeRate,0) * @Weight_DryTrackStrikeRate +
|
||||
ISNULL(ThisTrackStrikeRate,0) * @Weight_ThisTrackStrikeRate +
|
||||
(CASE WHEN TRY_CAST(LastStartFinishPosition AS INT) > 0 THEN (1.0 / TRY_CAST(LastStartFinishPosition AS FLOAT)) ELSE 0 END) * @Weight_LastStartFinishPosition +
|
||||
ISNULL(WeightCarried,0) * @Weight_WeightCarried
|
||||
) AS Score,
|
||||
FinishResult
|
||||
INTO #Results
|
||||
FROM #AllHorses
|
||||
|
||||
-- Seleziona il cavallo con punteggio massimo per ogni corsa
|
||||
;WITH Ranked AS (
|
||||
SELECT *,
|
||||
ROW_NUMBER() OVER (PARTITION BY Data, Meeting, Race ORDER BY Score DESC) AS RankByScore
|
||||
FROM #Results
|
||||
)
|
||||
SELECT *
|
||||
INTO #TopResults
|
||||
FROM Ranked
|
||||
WHERE RankByScore = 1
|
||||
|
||||
-- Calcola le statistiche
|
||||
SELECT
|
||||
@CorrectPredictions = SUM(CASE WHEN ISNUMERIC(FinishResult) = 1 AND CAST(FinishResult AS INT) = 1 THEN 1 ELSE 0 END)
|
||||
FROM #TopResults
|
||||
|
||||
-- @TotalRaces già calcolato all'inizio
|
||||
|
||||
IF @TotalRaces > 0
|
||||
SET @WinPercentage = CAST(@CorrectPredictions AS DECIMAL(5,2)) * 100.0 / @TotalRaces
|
||||
ELSE
|
||||
SET @WinPercentage = 0
|
||||
|
||||
-- Aggiorna solo se la percentuale è migliore
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM PredictionScanResults
|
||||
WHERE SessionId = @SessionId
|
||||
AND WinPercentage < @WinPercentage
|
||||
)
|
||||
BEGIN
|
||||
UPDATE PredictionScanResults
|
||||
SET
|
||||
Weight_HandicapRating = @Weight_HandicapRating,
|
||||
Weight_CareerWins = @Weight_CareerWins,
|
||||
Weight_CareerStrikeRate = @Weight_CareerStrikeRate,
|
||||
Weight_JockeyLast100StrikeRate = @Weight_JockeyLast100StrikeRate,
|
||||
Weight_TrainerLast100StrikeRate = @Weight_TrainerLast100StrikeRate,
|
||||
Weight_BestFixedOdds = @Weight_BestFixedOdds,
|
||||
Weight_Age = @Weight_Age,
|
||||
Weight_CareerPlacings = @Weight_CareerPlacings,
|
||||
Weight_DryTrackStrikeRate = @Weight_DryTrackStrikeRate,
|
||||
Weight_ThisTrackStrikeRate = @Weight_ThisTrackStrikeRate,
|
||||
Weight_LastStartFinishPosition = @Weight_LastStartFinishPosition,
|
||||
Weight_WeightCarried = @Weight_WeightCarried,
|
||||
TotalRaces = @TotalRaces,
|
||||
CorrectPredictions = @CorrectPredictions,
|
||||
WinPercentage = @WinPercentage
|
||||
WHERE SessionId = @SessionId
|
||||
END
|
||||
|
||||
-- Output avanzamento
|
||||
PRINT CONCAT(
|
||||
'Combinazione ', @CombinazioneCorrente, '/', @CombinazioniTotali,
|
||||
' (', CAST((@CombinazioneCorrente * 100.0 / @CombinazioniTotali) AS DECIMAL(5,2)), '%) - ',
|
||||
'Pesi: ', @Weight_HandicapRating, ',', @Weight_CareerWins, ',', @Weight_CareerStrikeRate, ',',
|
||||
@Weight_JockeyLast100StrikeRate, ',', @Weight_TrainerLast100StrikeRate, ',', @Weight_BestFixedOdds, ',',
|
||||
@Weight_Age, ',', @Weight_CareerPlacings, ',', @Weight_DryTrackStrikeRate, ',', @Weight_ThisTrackStrikeRate, ',',
|
||||
@Weight_LastStartFinishPosition, ',', @Weight_WeightCarried,
|
||||
' - Win%: ', @WinPercentage
|
||||
)
|
||||
|
||||
DROP TABLE IF EXISTS #Results
|
||||
DROP TABLE IF EXISTS #TopResults
|
||||
|
||||
SET @Weight_WeightCarried = @Weight_WeightCarried + 1
|
||||
END
|
||||
SET @Weight_LastStartFinishPosition = @Weight_LastStartFinishPosition + 1
|
||||
END
|
||||
SET @Weight_ThisTrackStrikeRate = @Weight_ThisTrackStrikeRate + 1
|
||||
END
|
||||
SET @Weight_DryTrackStrikeRate = @Weight_DryTrackStrikeRate + 1
|
||||
END
|
||||
SET @Weight_CareerPlacings = @Weight_CareerPlacings + 1
|
||||
END
|
||||
SET @Weight_Age = @Weight_Age + 1
|
||||
END
|
||||
SET @Weight_BestFixedOdds = @Weight_BestFixedOdds + 1
|
||||
END
|
||||
SET @Weight_TrainerLast100StrikeRate = @Weight_TrainerLast100StrikeRate + 1
|
||||
END
|
||||
SET @Weight_JockeyLast100StrikeRate = @Weight_JockeyLast100StrikeRate + 1
|
||||
END
|
||||
SET @Weight_CareerStrikeRate = @Weight_CareerStrikeRate + 1
|
||||
END
|
||||
SET @Weight_CareerWins = @Weight_CareerWins + 1
|
||||
END
|
||||
SET @Weight_HandicapRating = @Weight_HandicapRating + 1
|
||||
END
|
||||
END
|
||||
249
HorseRacingPredictor/Styles/GlobalStyles.xaml
Normal file
249
HorseRacingPredictor/Styles/GlobalStyles.xaml
Normal file
@@ -0,0 +1,249 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<!-- =========================================================
|
||||
CATPPUCCIN MOCHA PALETTE (shared)
|
||||
========================================================= -->
|
||||
<Color x:Key="CBase">#1E1E2E</Color>
|
||||
<Color x:Key="CMantle">#181825</Color>
|
||||
<Color x:Key="CCrust">#11111B</Color>
|
||||
<Color x:Key="CSurface0">#313244</Color>
|
||||
<Color x:Key="CSurface1">#45475A</Color>
|
||||
<Color x:Key="CSurface2">#585B70</Color>
|
||||
<Color x:Key="COverlay0">#6C7086</Color>
|
||||
<Color x:Key="CText">#CDD6F4</Color>
|
||||
<Color x:Key="CSubtext0">#A6ADC8</Color>
|
||||
<Color x:Key="CSubtext1">#BAC2DE</Color>
|
||||
<Color x:Key="CBlue">#89B4FA</Color>
|
||||
<Color x:Key="CGreen">#A6E3A1</Color>
|
||||
<Color x:Key="CRed">#F38BA8</Color>
|
||||
<Color x:Key="CPeach">#FAB387</Color>
|
||||
<Color x:Key="CLavender">#B4BEFE</Color>
|
||||
|
||||
<SolidColorBrush x:Key="BrBase" Color="{StaticResource CBase}"/>
|
||||
<SolidColorBrush x:Key="BrMantle" Color="{StaticResource CMantle}"/>
|
||||
<SolidColorBrush x:Key="BrSurface0" Color="{StaticResource CSurface0}"/>
|
||||
<SolidColorBrush x:Key="BrSurface1" Color="{StaticResource CSurface1}"/>
|
||||
<SolidColorBrush x:Key="BrSurface2" Color="{StaticResource CSurface2}"/>
|
||||
<SolidColorBrush x:Key="BrOverlay0" Color="{StaticResource COverlay0}"/>
|
||||
<SolidColorBrush x:Key="BrText" Color="{StaticResource CText}"/>
|
||||
<SolidColorBrush x:Key="BrSubtext0" Color="{StaticResource CSubtext0}"/>
|
||||
<SolidColorBrush x:Key="BrBlue" Color="{StaticResource CBlue}"/>
|
||||
<SolidColorBrush x:Key="BrGreen" Color="{StaticResource CGreen}"/>
|
||||
<SolidColorBrush x:Key="BrRed" Color="{StaticResource CRed}"/>
|
||||
<SolidColorBrush x:Key="BrPeach" Color="{StaticResource CPeach}"/>
|
||||
<SolidColorBrush x:Key="BrLavender" Color="{StaticResource CLavender}"/>
|
||||
|
||||
<!-- Acrylic-like background (semi-transparent fallback) -->
|
||||
<SolidColorBrush x:Key="AcrylicBackgroundBrush" Color="#0F000000"/>
|
||||
|
||||
<!-- Subtle shadow effect for elevation -->
|
||||
<DropShadowEffect x:Key="SubtleDropShadow" BlurRadius="12" ShadowDepth="2" Color="#50000000"/>
|
||||
|
||||
<!-- NAV BUTTON STYLE (icon-only sidebar) -->
|
||||
<Style x:Key="NavBtn" TargetType="RadioButton">
|
||||
<Setter Property="Width" Value="48"/>
|
||||
<Setter Property="Height" Value="48"/>
|
||||
<Setter Property="Margin" Value="6,4"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource BrSubtext0}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="RadioButton">
|
||||
<Grid>
|
||||
<Border x:Name="Bg" CornerRadius="10" Background="{TemplateBinding Background}"/>
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bg" Property="Background" Value="#28283A"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="Bg" Property="Background" Value="{StaticResource BrBlue}"/>
|
||||
<Setter Property="Foreground" Value="#181825"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- ACCENT BUTTON -->
|
||||
<Style x:Key="AccentBtn" TargetType="Button">
|
||||
<Setter Property="Foreground" Value="#181825"/>
|
||||
<Setter Property="FontFamily" Value="Segoe UI Semibold"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="Padding" Value="18,7"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd" CornerRadius="8"
|
||||
Background="{TemplateBinding Background}"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Opacity" Value="0.85"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="Bd" Property="Opacity" Value="0.40"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- FLAT TEXTBOX -->
|
||||
<Style x:Key="FlatTb" TargetType="TextBox">
|
||||
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
|
||||
<Setter Property="CaretBrush" Value="{StaticResource BrText}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BrSurface1}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="10,7"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="FontFamily" Value="Segoe UI"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TextBox">
|
||||
<Border x:Name="Bd" CornerRadius="6"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ScrollViewer x:Name="PART_ContentHost"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsFocused" Value="True">
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource BrBlue}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- PASSWORD BOX -->
|
||||
<Style x:Key="FlatPb" TargetType="PasswordBox">
|
||||
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
|
||||
<Setter Property="CaretBrush" Value="{StaticResource BrText}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BrSurface1}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="10,7"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="FontFamily" Value="Segoe UI"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="PasswordBox">
|
||||
<Border x:Name="Bd" CornerRadius="6"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ScrollViewer x:Name="PART_ContentHost"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsFocused" Value="True">
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource BrBlue}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- PROGRESS BAR -->
|
||||
<Style x:Key="ModernPb" TargetType="ProgressBar">
|
||||
<Setter Property="Height" Value="4"/>
|
||||
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource BrBlue}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ProgressBar">
|
||||
<Grid>
|
||||
<Border CornerRadius="2" Background="{TemplateBinding Background}"/>
|
||||
<Border x:Name="PART_Indicator" CornerRadius="2"
|
||||
Background="{TemplateBinding Foreground}"
|
||||
HorizontalAlignment="Left"/>
|
||||
<Border x:Name="PART_Track"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- DATAGRID -->
|
||||
<Style x:Key="ModernDg" TargetType="DataGrid">
|
||||
<Setter Property="Background" Value="#282A3A"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="RowBackground" Value="#282A3A"/>
|
||||
<Setter Property="AlternatingRowBackground" Value="#2D2F42"/>
|
||||
<Setter Property="GridLinesVisibility" Value="Horizontal"/>
|
||||
<Setter Property="HorizontalGridLinesBrush" Value="#37394E"/>
|
||||
<Setter Property="HeadersVisibility" Value="Column"/>
|
||||
<Setter Property="RowHeaderWidth" Value="0"/>
|
||||
<Setter Property="AutoGenerateColumns" Value="True"/>
|
||||
<Setter Property="IsReadOnly" Value="True"/>
|
||||
<Setter Property="SelectionMode" Value="Single"/>
|
||||
<Setter Property="CanUserAddRows" Value="False"/>
|
||||
<Setter Property="CanUserDeleteRows" Value="False"/>
|
||||
<Setter Property="CanUserResizeRows" Value="False"/>
|
||||
<Setter Property="FontFamily" Value="Segoe UI"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="DataGridColumnHeader">
|
||||
<Setter Property="Background" Value="#23243A"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource BrBlue}"/>
|
||||
<Setter Property="FontFamily" Value="Segoe UI Semibold"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="Padding" Value="10,8"/>
|
||||
<Setter Property="BorderBrush" Value="#37394E"/>
|
||||
<Setter Property="BorderThickness" Value="0,0,0,1"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="DataGridCell">
|
||||
<Setter Property="Padding" Value="8,6"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="DataGridCell">
|
||||
<Border Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}">
|
||||
<ContentPresenter VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="Background" Value="#3C3F58"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource BrBlue}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="Margin" Value="0"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="Background" Value="#3C3F58"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<!-- Card style for lists / rows -->
|
||||
<Style x:Key="CardStyle" TargetType="Border">
|
||||
<Setter Property="CornerRadius" Value="8"/>
|
||||
<Setter Property="Background" Value="#23232A"/>
|
||||
<Setter Property="Padding" Value="12"/>
|
||||
<Setter Property="Margin" Value="6"/>
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
Reference in New Issue
Block a user