Le aste non si salvano più in un file JSON per asta più tre archivi da tenere allineati: un solo database SQLite (winsqlite3.dll di Windows via P/Invoke, nessun pacchetto) con prodotti, aste, puntate, interrogazioni coalescenti, reset con profondità del ciclo, nostre puntate, decisioni del motore, misure di rete, puntatori, sessioni e contabilità. Scrittura in coda su un thread dedicato, letture in WAL. Per scelta dell'utente si parte da zero: il database si riempie man mano che si osservano le aste; i vecchi dossier restano leggibili per un'importazione futura ma non si scrivono più. Storico, schede, apprendimento, rigiocata ed esportazioni leggono tutti da lì. Il documento di progetto (Modifiche.txt) tradotto in C# senza librerie: - RiskManager: kill-switch (file KILL_SWITCH, anche dalla barra), HALT persistente per stop-loss e drawdown, tetto del giorno, aste in gioco insieme, contabilità in euro. - Theory: valore atteso con fee, spedizione, valore reale per prodotto (nuova colonna nella scheda Prodotti) e copertura «Compralo Ora»; null-model; sopravvivenza empirica e Kaplan-Meier dai reset; arrivi di Poisson. - Shadow per asta: in Osserva il cecchino arriva allo stesso istante, registra cosa avrebbe fatto e non punta; alla chiusura ogni decisione riceve l'esito, e ShadowReport confronta le policy sugli stessi istanti con intervallo di confidenza sul ROI. - NeuralNet (MLP, Adam) come sfidante del logistico, scelto dal Brier prequenziale; ThompsonBandit come seconda policy che impara dagli esiti delle decisioni; PennyAuctionEnv con avversari tarati sul database e QLearningAgent conservativo; SimulationLab per il confronto fra policy; esportazione CSV delle decisioni con reason_detail. Tutto nella scheda Apprendimento. Ml/LEGGIMI.md riscritto con le avvertenze su termini d'uso, quadro legale e realtà economica, lo schema del database e lo stato delle milestone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
59 lines
2.2 KiB
C#
59 lines
2.2 KiB
C#
using System;
|
|
using System.IO;
|
|
using AutoBidder.Ml;
|
|
using Xunit;
|
|
using Xunit.Abstractions;
|
|
|
|
namespace AutoBidder.Tests;
|
|
|
|
/// <summary>
|
|
/// Valuta il modello sui dossier veri. La logica sta in <see cref="LearningEvaluator"/>,
|
|
/// condivisa con la scheda dell'applicazione: qui si aggiunge solo l'attivazione da
|
|
/// variabili d'ambiente e la condizione minima.
|
|
///
|
|
/// <list type="bullet">
|
|
/// <item><c>AUTOBIDDER_ML_DB</c> — file del database SQLite;</item>
|
|
/// <item><c>AUTOBIDDER_ML_MAX</c> — quante aste al massimo (0 = tutte);</item>
|
|
/// <item><c>AUTOBIDDER_ML_OUT</c> — dove scrivere il rapporto;</item>
|
|
/// <item><c>AUTOBIDDER_ML_ME</c> — il nostro nome utente, per i dossier vecchi.</item>
|
|
/// </list>
|
|
/// </summary>
|
|
public class MlModelBacktest
|
|
{
|
|
private readonly ITestOutputHelper _output;
|
|
|
|
public MlModelBacktest(ITestOutputHelper output) => _output = output;
|
|
|
|
[Fact]
|
|
public void Valuta_il_modello_sulle_aste_reali()
|
|
{
|
|
var dbPath = Environment.GetEnvironmentVariable("AUTOBIDDER_ML_DB");
|
|
|
|
if (string.IsNullOrWhiteSpace(dbPath) || !File.Exists(dbPath))
|
|
{
|
|
_output.WriteLine("AUTOBIDDER_ML_DB non impostata o inesistente: valutazione saltata.");
|
|
return;
|
|
}
|
|
|
|
var max = int.TryParse(Environment.GetEnvironmentVariable("AUTOBIDDER_ML_MAX"), out var m) ? m : 0;
|
|
var me = Environment.GetEnvironmentVariable("AUTOBIDDER_ML_ME");
|
|
|
|
using var db = AutoBidder.Data.AuctionDatabase.Open(dbPath);
|
|
var report = LearningEvaluator.Run(db, max, me, _output.WriteLine);
|
|
_output.WriteLine(report.Text);
|
|
|
|
var outFile = Environment.GetEnvironmentVariable("AUTOBIDDER_ML_OUT");
|
|
if (!string.IsNullOrWhiteSpace(outFile))
|
|
{
|
|
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outFile))!);
|
|
File.WriteAllText(outFile, report.Text);
|
|
}
|
|
|
|
if (!report.Enough) return;
|
|
|
|
// La condizione minima perche' il modello serva a qualcosa: nella cima della
|
|
// classifica le puntate finali devono essere piu' frequenti che a caso.
|
|
Assert.True(report.Lift5 > 1.0, $"il modello non separa: sollevamento al 5% = {report.Lift5:N2}");
|
|
}
|
|
}
|