Compare commits

...

4 Commits

Author SHA1 Message Date
97951ac6f0 Aggiunta sezione Calcio Virtuale con WebView2 e statistiche
Introdotta una nuova pagina "Calcio Virtuale" con browser WebView2 integrato per navigare siti di virtual football (default Bet365). Aggiunta la possibilità di inserire manualmente i risultati delle partite virtuali, visualizzarli in lista, calcolare statistiche (1/X/2, media gol, over 2.5) e ricevere suggerimenti di puntata dinamici. Creata la classe VirtualMatch per rappresentare i risultati. Aggiornate le dipendenze e l'interfaccia senza impattare le altre sezioni.
2026-03-11 23:28:05 +01:00
0abb8467f2 Aggiorna riferimenti NuGet in BettingPredictor.csproj
Aggiornati i riferimenti a Microsoft.ML, CsvHelper, Newtonsoft.Json, RestSharp e vari pacchetti System.* nel file di progetto alla versioni più recenti. Modificati i percorsi <HintPath> e le condizioni di errore per riflettere i nuovi numeri di versione.
2026-03-03 08:55:08 +01:00
fc46e56b82 Esportazione avanzata e UI migliorata
Aggiunta esportazione dati in JSON/XML oltre a CSV, con nome file personalizzabile (prefisso, suffisso, formato data, anteprima). Nuove impostazioni per esportazione e preview dinamica. Gestione sorgente corse cavalli da API o CSV Punters, con aggregazione automatica. Migliorati stili DataGrid e numerazione righe. Rimossa pagina info, stili globali consolidati, ComboBox più leggibili. Migliorata gestione errori e messaggi utente.
2026-03-01 13:40:19 +01:00
8fe93de673 Rimozione completa della componente Machine Learning
Sono state eliminate tutte le dipendenze, file sorgente e script SQL relativi al machine learning (Microsoft.ML, Horses\ML, procedure e tabelle per rating automatico). Il calcolo dei rating ora è deterministico. Aggiornato il parsing JSON in Main.cs con Newtonsoft.Json. Il progetto risulta semplificato e più focalizzato sulle funzionalità manuali.
2026-02-25 21:17:14 +01:00
21 changed files with 584486 additions and 42027 deletions

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Microsoft.ML.FastTree.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.FastTree.props" Condition="Exists('..\packages\Microsoft.ML.FastTree.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.FastTree.props')" />
<Import Project="..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.props" Condition="Exists('..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.props')" />
<Import Project="..\packages\Microsoft.ML.CpuMath.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.CpuMath.props" Condition="Exists('..\packages\Microsoft.ML.CpuMath.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.CpuMath.props')" />
<Import Project="..\packages\Microsoft.ML.FastTree.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.FastTree.props" Condition="Exists('..\packages\Microsoft.ML.FastTree.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.FastTree.props')" />
<Import Project="..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.props" Condition="Exists('..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.props')" />
<Import Project="..\packages\Microsoft.ML.CpuMath.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.CpuMath.props" Condition="Exists('..\packages\Microsoft.ML.CpuMath.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.CpuMath.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -56,49 +56,22 @@
</PropertyGroup>
<ItemGroup>
<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>
<HintPath>..\packages\CsvHelper.33.1.0\lib\net48\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 Include="Microsoft.Web.WebView2.Core, Version=1.0.3800.47, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Web.WebView2.1.0.3800.47\lib\net462\Microsoft.Web.WebView2.Core.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 Include="Microsoft.Web.WebView2.Wpf, Version=1.0.3800.47, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Web.WebView2.1.0.3800.47\lib\net462\Microsoft.Web.WebView2.Wpf.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 Include="Microsoft.Web.WebView2.WinForms, Version=1.0.3800.47, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Web.WebView2.1.0.3800.47\lib\net462\Microsoft.Web.WebView2.WinForms.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>
<HintPath>..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="RestSharp, Version=112.1.1.0, Culture=neutral, PublicKeyToken=598062e77f915f75, processorArchitecture=MSIL">
<HintPath>..\packages\RestSharp.112.1.0\lib\net481\RestSharp.dll</HintPath>
<HintPath>..\packages\RestSharp.112.1.1-alpha.0.4\lib\net48\RestSharp.dll</HintPath>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
@@ -109,21 +82,21 @@
<HintPath>..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.CodeDom, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.CodeDom.10.0.0-preview.4.25258.110\lib\net462\System.CodeDom.dll</HintPath>
<HintPath>..\packages\System.CodeDom.10.0.0-rc.1.25451.107\lib\net462\System.CodeDom.dll</HintPath>
</Reference>
<Reference Include="System.Collections.Immutable, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.10.0.0-preview.4.25258.110\lib\net462\System.Collections.Immutable.dll</HintPath>
<HintPath>..\packages\System.Collections.Immutable.10.0.0-rc.1.25451.107\lib\net462\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.Core" />
<Reference Include="System.IO.Pipelines, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.IO.Pipelines.10.0.0-preview.4.25258.110\lib\net462\System.IO.Pipelines.dll</HintPath>
<HintPath>..\packages\System.IO.Pipelines.10.0.0-rc.1.25451.107\lib\net462\System.IO.Pipelines.dll</HintPath>
</Reference>
<Reference Include="System.Memory, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Tensors, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Tensors.10.0.0-preview.4.25258.110\lib\net462\System.Numerics.Tensors.dll</HintPath>
<HintPath>..\packages\System.Numerics.Tensors.10.0.0-rc.1.25451.107\lib\net462\System.Numerics.Tensors.dll</HintPath>
</Reference>
<Reference Include="System.Numerics.Vectors, Version=4.1.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll</HintPath>
@@ -132,13 +105,13 @@
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Text.Encodings.Web, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Encodings.Web.10.0.0-preview.4.25258.110\lib\net462\System.Text.Encodings.Web.dll</HintPath>
<HintPath>..\packages\System.Text.Encodings.Web.10.0.0-rc.1.25451.107\lib\net462\System.Text.Encodings.Web.dll</HintPath>
</Reference>
<Reference Include="System.Text.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Json.10.0.0-preview.4.25258.110\lib\net462\System.Text.Json.dll</HintPath>
<HintPath>..\packages\System.Text.Json.10.0.0-rc.1.25451.107\lib\net462\System.Text.Json.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Channels, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Channels.10.0.0-preview.4.25258.110\lib\net462\System.Threading.Channels.dll</HintPath>
<HintPath>..\packages\System.Threading.Channels.10.0.0-rc.1.25451.107\lib\net462\System.Threading.Channels.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.4.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll</HintPath>
@@ -158,6 +131,7 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="VirtualFootball\VirtualMatch.cs" />
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
@@ -194,10 +168,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 +185,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 +200,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')" />
@@ -244,10 +209,11 @@
<ErrorText>Questo progetto fa riferimento a uno o pi<70> pacchetti NuGet che non sono presenti in questo computer. Usare lo strumento di ripristino dei pacchetti NuGet per scaricarli. Per altre informazioni, vedere http://go.microsoft.com/fwlink/?LinkID=322105. Il file mancante <20> {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.CpuMath.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.CpuMath.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.CpuMath.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.CpuMath.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.FastTree.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.FastTree.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.FastTree.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.FastTree.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.CpuMath.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.CpuMath.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.CpuMath.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.CpuMath.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.FastTree.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.FastTree.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.FastTree.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.FastTree.props'))" />
</Target>
<Import Project="..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.targets" Condition="Exists('..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.targets')" />
<Import Project="..\packages\Microsoft.Web.WebView2.1.0.3800.47\build\Microsoft.Web.WebView2.targets" Condition="Exists('..\packages\Microsoft.Web.WebView2.1.0.3800.47\build\Microsoft.Web.WebView2.targets')" />
<Import Project="..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.targets" Condition="Exists('..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.targets')" />
</Project>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -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,

View File

@@ -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)
{

View File

@@ -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)

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -1,16 +1,16 @@
<Window x:Class="HorseRacingPredictor.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Betting Predictor" Width="1100" Height="680"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
Title="Betting Predictor" Width="1100" Height="680"
MinWidth="900" MinHeight="540"
WindowStartupLocation="CenterScreen"
Background="#1E1E2E"
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 +41,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 +84,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 +112,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 +142,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 +172,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 +193,7 @@
</Setter>
</Style>
<!-- =========================================================
DATAGRID
========================================================= -->
<!-- DATAGRID -->
<Style x:Key="ModernDg" TargetType="DataGrid">
<Setter Property="Background" Value="#282A3A"/>
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
@@ -211,6 +213,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 +223,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 +243,7 @@
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="DataGridRow">
<Setter Property="Margin" Value="0"/>
<Style.Triggers>
@@ -247,6 +252,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>
<!-- ================================================================
@@ -282,18 +295,19 @@
<TextBlock Text="&#xE7C1;" FontFamily="Segoe MDL2 Assets" FontSize="20"
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=RadioButton}}"/>
</RadioButton>
<RadioButton x:Name="navVirtualFb" Style="{StaticResource NavBtn}"
ToolTip="Calcio Virtuale"
Checked="navVirtualFb_Checked">
<TextBlock Text="&#xE7FC;" FontFamily="Segoe MDL2 Assets" FontSize="20"
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=RadioButton}}"/>
</RadioButton>
<RadioButton x:Name="navSettings" Style="{StaticResource NavBtn}"
ToolTip="Impostazioni"
Checked="navSettings_Checked">
<TextBlock Text="&#xE713;" 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="&#xE946;" FontFamily="Segoe MDL2 Assets" FontSize="20"
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=RadioButton}}"/>
</RadioButton>
<!-- Info tab removed -->
</StackPanel>
</DockPanel>
</Border>
@@ -313,28 +327,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 +367,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 +404,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 &#x2014; 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 +418,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 +469,45 @@
<!-- HORSE RACING API -->
<TextBlock Text="Corse Cavalli &#x2014; 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 +528,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 +536,129 @@
</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="&#x00A9; 2025 - Tutti i diritti riservati"/>
</TextBlock>
</StackPanel>
<!-- ==== PAGE: VIRTUAL FOOTBALL ==== -->
<Grid x:Name="pageVirtualFb" Margin="0" Visibility="Collapsed">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="400"/>
<ColumnDefinition Width="360"/>
</Grid.ColumnDefinitions>
<!-- LEFT: WebBrowser -->
<Border Grid.Column="0" Background="#181825" Margin="8">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="8,8,8,4">
<Button x:Name="btnVfbNavigate" Content="Vai"
Style="{StaticResource AccentBtn}" Background="{StaticResource BrBlue}"
Margin="0,0,8,0" Click="btnVfbNavigate_Click"/>
<TextBox x:Name="txtVfbUrl" Style="{StaticResource FlatTb}"
Text="https://www.bet365.it/#/IP/B151"
Width="500" VerticalContentAlignment="Center"/>
<Button x:Name="btnVfbRefresh" Content="&#xE72C;"
FontFamily="Segoe MDL2 Assets" FontSize="14"
Style="{StaticResource AccentBtn}" Background="{StaticResource BrSurface2}"
Foreground="{StaticResource BrText}" Margin="8,0,0,0"
Click="btnVfbRefresh_Click" ToolTip="Aggiorna"/>
</StackPanel>
<wv2:WebView2 x:Name="wbVirtualFb" Margin="8,0,8,8"/>
</DockPanel>
</Border>
<!-- RIGHT: Results Panel -->
<Border Grid.Column="1" Background="#282A3A" CornerRadius="10" Margin="0,8,8,8" Padding="12">
<DockPanel>
<TextBlock DockPanel.Dock="Top" Text="Ultimi Risultati"
FontFamily="Segoe UI Semibold" FontSize="16"
Foreground="{StaticResource BrBlue}" Margin="0,0,0,8"/>
<!-- Add result form -->
<Border DockPanel.Dock="Top" Background="#313244" CornerRadius="8" Padding="10" Margin="0,0,0,8">
<StackPanel>
<TextBlock Text="Aggiungi Risultato" FontSize="13"
Foreground="{StaticResource BrSubtext0}" Margin="0,0,0,6"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="txtVfbHome" Style="{StaticResource FlatTb}"
Text="Casa" FontSize="12"/>
<TextBox x:Name="txtVfbHomeGoals" Grid.Column="1" Style="{StaticResource FlatTb}"
Text="0" FontSize="12" TextAlignment="Center" Margin="4,0"/>
<TextBlock Grid.Column="2" Text="-" Foreground="{StaticResource BrText}"
FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBox x:Name="txtVfbAwayGoals" Grid.Column="3" Style="{StaticResource FlatTb}"
Text="0" FontSize="12" TextAlignment="Center" Margin="4,0"/>
<TextBox x:Name="txtVfbAway" Grid.Column="4" Style="{StaticResource FlatTb}"
Text="Ospite" FontSize="12"/>
</Grid>
<Button x:Name="btnVfbAddResult" Content="Aggiungi"
Style="{StaticResource AccentBtn}" Background="{StaticResource BrBlue}"
HorizontalAlignment="Left" Margin="0,8,0,0"
Click="btnVfbAddResult_Click"/>
</StackPanel>
</Border>
<!-- Suggested bet -->
<Border DockPanel.Dock="Top" Background="#313244" CornerRadius="8" Padding="10" Margin="0,0,0,8">
<StackPanel>
<TextBlock Text="Puntata Consigliata" FontSize="13"
FontFamily="Segoe UI Semibold" Foreground="{StaticResource BrPeach}" Margin="0,0,0,4"/>
<TextBlock x:Name="lblVfbSuggestion" Text="Inserisci almeno 5 risultati"
FontSize="12" Foreground="{StaticResource BrText}" TextWrapping="Wrap"/>
</StackPanel>
</Border>
<!-- Stats -->
<Border DockPanel.Dock="Top" Background="#313244" CornerRadius="8" Padding="10" Margin="0,0,0,8">
<StackPanel>
<TextBlock Text="Statistiche" FontSize="13"
FontFamily="Segoe UI Semibold" Foreground="{StaticResource BrLavender}" Margin="0,0,0,4"/>
<TextBlock x:Name="lblVfbStats" Text="Nessun dato"
FontSize="12" Foreground="{StaticResource BrText}" TextWrapping="Wrap"/>
</StackPanel>
</Border>
<!-- Results list -->
<ListBox x:Name="lbVfbResults" Background="Transparent" BorderThickness="0"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<Border CornerRadius="6" Padding="10,6" Margin="0,2"
Background="{Binding RowColor}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Time}"
FontSize="11" Foreground="#A6ADC8"
VerticalAlignment="Center" Margin="0,0,8,0"/>
<TextBlock Grid.Column="1" FontSize="12" VerticalAlignment="Center">
<Run Text="{Binding Home, Mode=OneWay}" Foreground="#CDD6F4"/>
<Run Text=" "/>
<Run Text="{Binding Score, Mode=OneWay}" Foreground="White" FontFamily="Segoe UI Semibold"/>
<Run Text=" "/>
<Run Text="{Binding Away, Mode=OneWay}" Foreground="#CDD6F4"/>
</TextBlock>
<TextBlock Grid.Column="2" Text="{Binding Outcome}"
FontSize="12" FontFamily="Segoe UI Semibold"
Foreground="White" VerticalAlignment="Center" Margin="8,0,0,0"/>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</Border>
</Grid>
<!-- Info page removed -->
</Grid>
</DockPanel>
</Grid>
</Window>
</Window>

View File

@@ -1,10 +1,15 @@
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.Collections.ObjectModel;
using System.Windows.Controls;
using Microsoft.Web.WebView2.Core;
namespace HorseRacingPredictor
{
@@ -15,6 +20,9 @@ namespace HorseRacingPredictor
private DataTable _footballData;
private DataTable _racingData;
// Virtual Football
private readonly ObservableCollection<VirtualFootball.VirtualMatch> _vfbResults = new ObservableCollection<VirtualFootball.VirtualMatch>();
private const string DefaultRacingUser = "qi1mHOHPquDY9KNDASAeGipy";
private const string DefaultRacingPass = "RXNFU1YX27R9rTnk8Vop8ZfH";
@@ -23,6 +31,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,17 +185,17 @@ 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;
if (pageVirtualFb != null) pageVirtualFb.Visibility = name == "virtualfb" ? Visibility.Visible : Visibility.Collapsed;
// Update title if available
if (lblTitle != null)
{
switch (name)
{
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;
case "football": lblTitle.Text = "Calcio"; break;
case "racing": lblTitle.Text = "Corse Cavalli"; break;
case "settings": lblTitle.Text = "Impostazioni"; break;
case "virtualfb": lblTitle.Text = "Calcio Virtuale"; break;
}
}
}
@@ -62,7 +203,55 @@ 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");
private bool _vfbInitialized;
private async void navVirtualFb_Checked(object sender, RoutedEventArgs e)
{
ShowPage("virtualfb");
// Bind results list once
if (lbVfbResults.ItemsSource == null)
lbVfbResults.ItemsSource = _vfbResults;
if (!_vfbInitialized)
{
_vfbInitialized = true;
try
{
// Persistent user-data folder so cookies, localStorage and session survive
var userDataFolder = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"BettingPredictor", "WebView2Data");
var env = await CoreWebView2Environment.CreateAsync(
browserExecutableFolder: null,
userDataFolder: userDataFolder,
options: new CoreWebView2EnvironmentOptions());
await wbVirtualFb.EnsureCoreWebView2Async(env);
// Match the real Chrome User-Agent from the HAR capture
wbVirtualFb.CoreWebView2.Settings.UserAgent =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
"(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36";
// Allow everything the SPA needs
wbVirtualFb.CoreWebView2.Settings.IsScriptEnabled = true;
wbVirtualFb.CoreWebView2.Settings.IsWebMessageEnabled = true;
wbVirtualFb.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = true;
wbVirtualFb.CoreWebView2.Settings.IsStatusBarEnabled = false;
wbVirtualFb.CoreWebView2.Navigate(txtVfbUrl.Text);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[VFB] WebView2 init error: {ex.Message}");
MessageBox.Show(
$"Impossibile inizializzare WebView2.\n\n" +
$"Assicurati che il Microsoft Edge WebView2 Runtime sia installato.\n\n{ex.Message}",
"Errore WebView2", MessageBoxButton.OK, MessageBoxImage.Warning);
_vfbInitialized = false;
}
}
}
// ???????????????????? FOOTBALL ????????????????????
@@ -89,6 +278,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 +310,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 +547,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 +580,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 +614,8 @@ namespace HorseRacingPredictor
return;
}
// Ensure filename has .csv extension
defaultName = EnsureFileExtension(defaultName, ".csv");
string filePath;
if (!string.IsNullOrEmpty(folder) && Directory.Exists(folder))
{
@@ -203,7 +626,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 +664,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 +676,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 +719,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 +971,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.",
@@ -322,5 +1001,114 @@ namespace HorseRacingPredictor
"Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
// ???????????????????????? VIRTUAL FOOTBALL ????????????????????????
private void btnVfbNavigate_Click(object sender, RoutedEventArgs e)
{
try
{
var url = txtVfbUrl.Text?.Trim();
if (!string.IsNullOrEmpty(url) && wbVirtualFb.CoreWebView2 != null)
wbVirtualFb.CoreWebView2.Navigate(url);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[VFB] Navigate error: {ex.Message}");
}
}
private void btnVfbRefresh_Click(object sender, RoutedEventArgs e)
{
try { wbVirtualFb.CoreWebView2?.Reload(); }
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[VFB] Refresh error: {ex.Message}");
}
}
private void btnVfbAddResult_Click(object sender, RoutedEventArgs e)
{
if (!int.TryParse(txtVfbHomeGoals.Text, out int hg)) hg = 0;
if (!int.TryParse(txtVfbAwayGoals.Text, out int ag)) ag = 0;
var match = new VirtualFootball.VirtualMatch
{
Time = DateTime.Now.ToString("HH:mm"),
Home = string.IsNullOrWhiteSpace(txtVfbHome.Text) ? "Casa" : txtVfbHome.Text.Trim(),
HomeGoals = hg,
AwayGoals = ag,
Away = string.IsNullOrWhiteSpace(txtVfbAway.Text) ? "Ospite" : txtVfbAway.Text.Trim()
};
_vfbResults.Insert(0, match);
// Reset input
txtVfbHomeGoals.Text = "0";
txtVfbAwayGoals.Text = "0";
UpdateVfbStats();
UpdateVfbSuggestion();
}
private void UpdateVfbStats()
{
if (_vfbResults.Count == 0) { lblVfbStats.Text = "Nessun dato"; return; }
int total = _vfbResults.Count;
int draws = _vfbResults.Count(m => m.Outcome == "X");
int home = _vfbResults.Count(m => m.Outcome == "1");
int away = _vfbResults.Count(m => m.Outcome == "2");
double drawPct = (double)draws / total * 100;
double homePct = (double)home / total * 100;
double awayPct = (double)away / total * 100;
int totalGoals = _vfbResults.Sum(m => m.HomeGoals + m.AwayGoals);
double avgGoals = (double)totalGoals / total;
int over25 = _vfbResults.Count(m => m.HomeGoals + m.AwayGoals > 2);
lblVfbStats.Text = $"Partite: {total}\n" +
$"1: {home} ({homePct:F1}%) X: {draws} ({drawPct:F1}%) 2: {away} ({awayPct:F1}%)\n" +
$"Media gol: {avgGoals:F1} Over 2.5: {over25} ({(double)over25 / total * 100:F1}%)";
}
private void UpdateVfbSuggestion()
{
if (_vfbResults.Count < 5)
{
lblVfbSuggestion.Text = "Inserisci almeno 5 risultati";
return;
}
// Count consecutive non-draw results (most recent first)
int streak = 0;
foreach (var m in _vfbResults)
{
if (m.Outcome != "X") streak++;
else break;
}
// Simple strategy: after a long streak without draws, suggest betting on draw
if (streak >= 8)
{
lblVfbSuggestion.Text = $"\u26A0 PUNTA X (pareggio) \u2014 {streak} partite consecutive senza pareggio!\n" +
"Puntata alta consigliata.";
}
else if (streak >= 5)
{
lblVfbSuggestion.Text = $"\u2705 Punta X (pareggio) \u2014 {streak} partite senza pareggio.\n" +
"Puntata media consigliata.";
}
else if (streak >= 3)
{
lblVfbSuggestion.Text = $"\u23F3 Possibile X \u2014 {streak} partite senza pareggio.\n" +
"Puntata bassa o attendi.";
}
else
{
double drawPct = (double)_vfbResults.Count(m => m.Outcome == "X") / _vfbResults.Count * 100;
lblVfbSuggestion.Text = $"Pareggio recente \u2014 attendi una serie senza X.\n" +
$"Frequenza X attuale: {drawPct:F1}%";
}
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
)

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,30 @@
namespace HorseRacingPredictor.VirtualFootball
{
/// <summary>
/// Represents a single virtual football match result displayed in the results panel.
/// </summary>
public class VirtualMatch
{
public string Time { get; set; }
public string Home { get; set; }
public int HomeGoals { get; set; }
public int AwayGoals { get; set; }
public string Away { get; set; }
public string Score => $"{HomeGoals} - {AwayGoals}";
/// <summary>1, X, or 2</summary>
public string Outcome
{
get
{
if (HomeGoals > AwayGoals) return "1";
if (HomeGoals < AwayGoals) return "2";
return "X";
}
}
/// <summary>Row background colour: green for draw, red for 1/2.</summary>
public string RowColor => Outcome == "X" ? "#2A4A3A" : "#4A2A2A";
}
}

View File

@@ -9,6 +9,7 @@
<package id="Microsoft.ML.CpuMath" version="5.0.0-preview.25503.2" targetFramework="net481" />
<package id="Microsoft.ML.DataView" version="5.0.0-preview.25503.2" targetFramework="net481" />
<package id="Microsoft.ML.FastTree" version="5.0.0-preview.25503.2" targetFramework="net481" />
<package id="Microsoft.Web.WebView2" version="1.0.3800.47" targetFramework="net481" />
<package id="Newtonsoft.Json" version="13.0.4" targetFramework="net481" />
<package id="RestSharp" version="112.1.1-alpha.0.4" targetFramework="net481" />
<package id="System.Buffers" version="4.6.1" targetFramework="net481" />

View 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>

Binary file not shown.