Tutto ciò che l'applicazione registra sta ora in due file SQLite in %LocalAppData%\AutoBidder\Database: autobidder.sqlite per le osservazioni e esercizio.sqlite per i documenti d'esercizio (aste nel monitor, prodotti, promozioni, modelli appresi con prefisso ml/) e per i registri applicativo e del riscatto puntate. Il motore comune è SqliteDatabase; i file JSON e la cartella Apprendimento delle versioni precedenti vengono importati la prima volta e lasciati dove sono. La cartella si cambia dalle Impostazioni: al salvataggio si chiede se spostare i file, e si riavvia. Le cartelle Dati/Statistiche/Registri, i registri su file, le misure dell'anticipo e le impostazioni morte (LogBids, AutoApplyProductDefaults, NewAuctionLimitsPriority…) non esistono più. Il kill-switch se ne va: al suo posto un solo interruttore in barra, accanto ad Avvia/Osserva/Ferma, che spegne l'apprendimento. Spento, il motore punta solo entro i limiti dell'utente (prezzo, puntate, budget, fascia oraria, rischio) con l'anticipo fisso: niente valore atteso, regime, duello, bandit, anticipo adattivo. Le aste vengono comunque registrate e studiate. Tre difetti visti osservando il motore dal vivo per un'ora, in Osserva: - la copertura «Compralo Ora» con V lasciato al listino azzerava la perdita coperta e il motore diceva BID a ogni scadenza con P dell'1%: ora vale solo per i prodotti con «Valore reale €» scritto, altrimenti la puntata si conta persa e P decide; - la chiusura di un'asta arrivava due volte (fine, poi rimozione dal monitor) e di nuovo al riavvio: modello, profilo, bandit e statistiche per prodotto contavano ogni asta due volte. Il monitor la comunica una volta sola, l'apprendimento salta le aste già apprese, e le schede prodotto vengono ricostruite una volta dallo storico; - la pagina delle ricompense veniva scambiata per la pagina di accesso perché porta un collegamento a /login.php: prima si guarda se è la pagina delle ricompense. Interfaccia: la scheda Esporta e tutto il suo codice sono tolti; le esportazioni chiedono sempre dove salvare. Prodotti, Storico e Apprendimento hanno barre a sole icone con tooltip che dicono esattamente cosa succede, colorate come il monitor, con i nuovi pulsanti di pulizia (spegni tutte le stelline, togli i non seguiti, pulizia completa; seleziona tutte / elimina le selezionate / svuota lo storico). Impostazioni: sezione Database, Manutenzione con «Elimina tutti i dati (tranne la login)» e «Impostazioni di fabbrica», esportazione dei registri in testo. Modifiche.txt, il prompt ormai realizzato, esce dal repository. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
15 KiB
AutoBidder — il sistema di decisione e apprendimento
Questo documento descrive come AutoBidder osserva, impara e decide. Vale come specifica:
il codice in Data/, Ml/, Services/BidStrategyService.cs, Services/RiskManager.cs e
Engine/AuctionRunner.cs la implementa, i test in Tests/ la verificano. Nessuna libreria
esterna: SQLite arriva da winsqlite3.dll di Windows, rete neurale, bandit, simulatore e
agente di rinforzo sono C# puro.
0. Avvertenze, prima di tutto
Termini e condizioni di Bidoo. I termini (it.bidoo.com/terms.php) vietano «l'uso di software di monitoraggio dell'offerta» e la creazione di account multipli, e consentono un solo account per persona. Questo programma è software di monitoraggio e puntata: il suo uso può comportare il blocco o la chiusura dell'account e la perdita delle puntate acquistate. Non implementa e non implementerà la creazione di account multipli.
Quadro legale. In Italia le aste al centesimo sono in una zona grigia: non classificate come gioco d'azzardo (art. 721 c.p. richiede aleatorietà pressoché totale; qui c'è un elemento di abilità), ma trattate dall'AGCM come pratica commerciale ai sensi del Codice del Consumo. Nel 2015 (provvedimenti PS8243 e PS9030) l'AGCM ha sanzionato due operatori del settore per un milione di euro complessivo. Non risulta alcuna azione contro Bidoo, ma ciò non è una garanzia.
Realtà economica. Augenblick (2016, Review of Economic Studies 83(1)), su 166.000 aste: il margine medio del banco supera il 50% del valore del bene; il 75% dei puntatori abbandona prima di 50 puntate, l'86% prima di 100. Platt, Price e Tappen (2013) e Hinnosaar (2016): in ogni equilibrio che non finisce subito, il profitto atteso di ogni puntata è zero. Un vantaggio, se esiste, viene solo dall'irrazionalità altrui (costo affondato, gusto per il rischio), non dalla struttura del gioco.
Il sistema è quindi scettico per costruzione: ogni stima di profittabilità si confronta con il null-model (ROI per puntata = 0), e l'esito atteso onesto è astenersi dalla grande maggioranza delle aste. Lo stato Osserva è la modalità shadow: registra ogni decisione che avrebbe preso senza puntare. L'utente ha scelto di non avere una shadow globale obbligatoria; le aste in Attiva puntano davvero, dentro i paletti del gestore del rischio.
1. Cosa si registra: il database
Due file SQLite in %LocalAppData%\AutoBidder\Database (cartella cambiabile dalle
Impostazioni, con spostamento dei file e riavvio; mai una cartella sincronizzata):
autobidder.sqlite— le osservazioni: le tabelle qui sotto.esercizio.sqlite— l'esercizio: i documenti JSON dell'applicazione (aste nel monitor, prodotti seguiti, statistiche per prodotto, promozioni riscattate, modelli appresi con prefissoml/) e i registri applicativo e del riscatto puntate. I file JSON delle versioni precedenti vengono importati la prima volta e lasciati dove sono.
Le scritture vanno in coda e un thread dedicato per database le svuota a blocchi in
transazioni: nulla sul percorso della puntata aspetta il disco. Le letture usano una
seconda connessione con giornale WAL. Il motore comune è SqliteDatabase.
| tabella | cosa contiene |
|---|---|
items |
il prodotto: chiave, titolo, «Compralo Ora», valore reale V scelto dall'utente, spedizione |
auctions |
una riga per asta, aggiornata dall'apertura alla chiusura: esito, prezzo finale, vincitore, puntate mie e del vincitore, copertura dell'osservazione, rete, motore, serie dei prezzi, puntate per utente, configurazione |
bids |
ogni puntata di ogni utente: prezzo dopo, tipo (Auto/Manuale), ora del server, secondi residui quando è arrivata, mia/altrui |
polls |
le interrogazioni, coalescenti (quando cambia qualcosa, e almeno una al secondo): prezzo, timer, ultimo puntatore, ping, scarto dell'orologio |
resets |
ogni azzeramento del timer con la profondità del ciclo (fin dove era sceso) e la sua durata |
my_bids |
le nostre puntate: anticipo voluto ed effettivo, ping, andata e ritorno, esito, id della decisione |
bot_decisions |
ogni decisione del motore, in shadow e dal vivo: azione, P(vittoria), EV, policy, regime, reason_detail, e l'esito scritto alla chiusura |
network_metrics |
una riga ogni dieci secondi: mediana e p95 del ping, jitter, scarto dell'orologio |
bidders |
chi punta: aste, puntate, quota di automatiche, vittorie |
sessions, risk_ledger, meta |
sessioni dell'applicazione; uscite ed entrate in euro per il gestore del rischio; stato HALT |
Lo storico compatto (CompletedAuctionsStore), le schede dettagliate (AuctionDetailStore),
l'apprendimento, la rigiocata e le esportazioni leggono tutti da qui. I vecchi dossier
JSON Lines restano leggibili (DossierReader.Read, DossierTrainingReader.Read) per
un'eventuale importazione, ma non vengono più scritti.
Esiti delle decisioni. Alla chiusura dell'asta ogni decisione riceve un esito: se il
prezzo non è più salito dopo quell'istante, una puntata lì sarebbe stata l'ultima
(won/would_win; missed se non si è puntato), altrimenti sarebbe stata coperta
(answered/would_be_answered; right_skip). È quello che permette di giudicare le policy
sugli stessi istanti (§5).
2. Il modello matematico — Ml/Theory.cs
Valore atteso di una puntata (§3.1 del documento di progetto, nella forma corretta: la puntata si paga in entrambi i rami):
EV = P · (V − (p + Δ) − fee − spedizione − c) − (1 − P) · c_perdita
con V il valore reale del bene (colonna «Valore reale €» nella scheda Prodotti; se vuota,
il «Compralo Ora»), Δ = 0,01 €, fee ≈ 1 €, c il costo della puntata.
Copertura «Compralo Ora». Chi perde può comprare al listino L recuperando le puntate
spese come credito: la perdita finale, se si esercita, è L − V qualunque sia il numero di
puntate. Quindi c_perdita = c finché spese + c ≤ L − V, zero dopo. Con V = L la
copertura è totale dal primo centesimo e si punterebbe sempre, a qualunque probabilità: in
shadow è successo (BID con P dell'1%). Per questo la copertura è un'impostazione
(Copertura «Compralo Ora») e vale solo per i prodotti con «Valore reale €» scritto:
con V lasciato al listino la puntata si conta persa, come senza copertura.
Null-model. Profitto atteso per puntata = 0; numero critico di puntatori V/c
(Hinnosaar). Sopravvivenza empirica: dalla tabella resets, P(vittoria) a un dato
anticipo = quota dei cicli scesi almeno fin lì che hanno chiuso l'asta; curva di
Kaplan-Meier della profondità dei cicli. Arrivi di Poisson: P(nessuna puntata in τ) = exp(−λτ) con λ dagli intervalli recenti.
3. La pipeline di decisione — Services/BidStrategyService.cs
Cinque passi, sempre nello stesso ordine, una volta per ciclo del timer, ~60 ms prima dell'istante di fuoco:
RISCHIO ──▶ PALETTI ──▶ DUELLO ──▶ VALORE ATTESO ──▶ REGIME ──▶ punta?
(solo dal (utente) (sessione) (storico + (sessione)
vivo) sessione) │
QUANDO: anticipo adattivo
-
Rischio —
RiskManager.Authorize: HALT persistente, stop-loss del giorno, drawdown dal picco, tetto di spesa del giorno, aste in gioco insieme. Lo stop-loss e il drawdown mettono in HALT, che sopravvive al riavvio e si toglie a mano. La contabilità è inrisk_ledger: ogni puntata riuscita un'uscita, ogni vincita un'entrata al valore reale meno prezzo, fee e spedizione. -
Paletti — fascia oraria sospesa (globale, per prodotto, per asta), tetti di puntate.
L'interruttore dell'apprendimento (il robot nella barra del monitor, accanto ad Avvia/Osserva/Ferma;
LearningEnabled) taglia qui la pipeline: spento, i passi 3–5 non girano e l'anticipo è quello fisso delle impostazioni. Si punta solo entro i limiti dell'utente — prezzo minimo e massimo, puntate, budget, fascia oraria, rischio — come un cecchino senza cervello. Le aste vengono comunque registrate e studiate: quando lo si riaccende il modello non ha perso niente. -
Duello — cinque risposte di fila con la firma dell'autopuntata (6 ± 2,5 s) = macchina armata dall'altra parte; si smette e si informa il regime.
-
Valore atteso — il modello dà P(senza risposta);
Theoryla trasforma in euro. Il modello parla sempre, decide dopoLearningMinAuctionsaste apprese. -
Regime —
CompetitionRegime: Calmo / Sfogo / Sondaggio con isteresi e pazienza che raddoppia a ogni sondaggio coperto. Uscire è facile, rientrare è difficile.
Quando — LatencyModel: anticipo = margine + p99 della latenza di sessione, fra
LeadMinMs e LeadMaxMs; una puntata tardiva alza il margine subito, venti in tempo lo
abbassano piano. Un anticipo scritto a mano su un'asta vince sempre.
Shadow. In Osserva il cecchino arriva allo stesso istante e chiama la stessa pipeline (saltando il gestore del rischio, che riguarda solo i soldi veri), registra la decisione e non punta. Ogni decisione porta anche il parere del bandit (§4.3) sullo stesso istante.
4. Cosa impara, e come — Ml/
4.1 P(senza risposta): campione e sfidante
Due modelli imparano dagli stessi esempi — ogni puntata di ogni asta chiusa, etichettata
«è rimasta l'ultima?» — con le stesse variabili a fasce (BidFeatures: durata del ciclo,
quota di autopuntate, puntatori distinti, prezzo sul valore, ora, giorno, ora di apertura,
tipo di puntata, profondità, prodotto in 32 cassetti):
- campione: regressione logistica online (
OnlineLogit, SGD, L2, calibrazione in linea); - sfidante: rete neurale 70→32→16→1 (
NeuralNet, ReLU, Adam, L2, stessa calibrazione).
A ogni esempio, prima di imparare, entrambi prevedono; il quadrato dell'errore va in una media mobile (20.000 esempi): è il punteggio di Brier prequenziale di ciascuno. Risponde al motore chi ce l'ha più basso; la rete non comanda prima di 5.000 esempi. La scheda Apprendimento mostra chi risponde e i due punteggi.
Perché non le nostre vittorie come etichetta: sono troppo poche (l'utente non ne ha ancora). Il comportamento avversario non dipende da chi ha puntato: questo rende utilizzabili i milioni di puntate altrui.
4.2 Profilo per prodotto e ora
AuctionProfileStats: puntate del vincitore e prezzo di chiusura in frazione del valore, per
prodotto × fascia oraria × feriale/festivo, con restringimento gerarchico dove i dati
scarseggiano. Alimentato asta per asta.
4.3 Bandit contestuale (Thompson)
ThompsonBandit: per ogni contesto (ciclo, prezzo/valore, fascia, puntatori, autopuntate)
una Beta(α, β) su P(senza risposta). Decide estraendo p dalla Beta e applicando la regola
del valore atteso: esplora dove ha visto poco, si stringe con i dati. Impara dagli esiti
delle decisioni — informazione completa, anche dove non si è puntato. È la seconda policy,
sempre in shadow, confrontata con la prima sugli stessi istanti.
4.4 Regime e latenza
Sessione in corso, non storico: CompetitionRegime per asta, LatencyModel per la rete.
Vedi §3.
4.5 Simulatore e agente di rinforzo
PennyAuctionEnv: aste sintetiche con quattro profili di avversario (aggressivo, cecchino,
autopuntata a 2 s, principiante), ognuno con credito e persistenza crescente con lo speso
(costo affondato, Augenblick). Taratura dal database: puntatori per asta, istogramma della
profondità dei cicli, quota di autopuntate, chiusura mediana in frazione del valore.
QLearningAgent: funzione Q su rete neurale, replay delle esperienze, ε-greedy decrescente,
penalità conservativa sulle azioni poco viste (nello spirito di CQL). Si addestra solo nel
simulatore: non si esplora con denaro vero.
SimulationLab mette a confronto su aste identiche (stessi semi): non punta mai, punta
sempre, valore atteso con P empirica, bandit che impara giocando, agente Q. Il simulatore
non è Bidoo: i numeri assoluti non contano, il confronto sì. È l'unico posto dove si vede
come reagirebbero gli altri a una nostra puntata.
5. Come si giudica — ShadowReport, LearningEvaluator, rigiocata
- Shadow (
ShadowReport): sugli istanti di decisione con esito noto, per ogni policy: puntate, vincenti, spesi, incassati al valore reale, ROI per puntata con estremo inferiore dell'intervallo al 95%. Il null-model è battuto solo se l'estremo inferiore è sopra zero, su abbastanza puntate. - Prequenziale (
LearningEvaluator, «Valuta ora»): addestra sulle aste più vecchie, giudica sulle più recenti, poi ogni asta prima prevista e poi appresa; sollevamento, calibrazione per fasce, punteggio di Brier, cosa avrebbe fatto il cancello sulle nostre puntate vere. - Rigiocata (
BacktestReport): cosa avrebbe fatto il motore su ogni asta registrata, anticipo per anticipo; misura i costi, non l'esito. - Esportazione: «Esporta decisioni (CSV)» — punto e virgola, colonna
reason_detail.
Criteri di promozione e di stop (dal documento di progetto): una policy passa dal shadow al vivo solo con ROI per puntata > 0 ed estremo inferiore > 0 su almeno quattro settimane, calibrazione buona, drawdown simulato entro il limite. Se dopo un periodo di vita reale il ROI cumulato non batte il null-model, il sistema resta strumento analitico e si spegne il vivo. La posizione di default è che un vantaggio consistente sia improbabile.
6. Dove si vede
Scheda Apprendimento: stato e pesi del modello, ultime decisioni, profilo per prodotto, valutazione, autonomia sul momento (latenza e regime per asta), shadow (policy a confronto, campione/sfidante, bandit), simulatore. Barra del monitor: l'interruttore dell'apprendimento. Scheda Impostazioni › Gestione del rischio: paletti, HALT e contabilità; Impostazioni › Database e Manutenzione: cartella dei database, «Elimina tutti i dati (tranne la login)», «Impostazioni di fabbrica». Scheda Prodotti: colonna «Valore reale €».
Nel registro di ogni asta: [SHADOW] Avrei puntato…, [REGIME] Calmo → Sfogo…, [TIMING] … anticipo adattivo…, ⛔ Strategia blocca: ….
7. Cosa non c'è, e perché
- Feed DOM (Playwright): gli endpoint HTTP di Bidoo (
data.php,get_auction_updates) sono verificati e usati da mesi; un browser sarebbe più lento e più fragile. - Dataset Swoopo (2008-09): sito e anni diversi; il database si riempie da solo.
- Modelli sequenziali (LSTM/Transformer): rimandati; le variabili a fasce con la rete MLP coprono le interazioni che contano, e i dati per una sequenza profonda non ci sono.
- Account multipli: mai.
8. Milestone
| stato | |
|---|---|
| M1 rischio, HALT, interruttore dell'apprendimento, shadow per asta | fatto |
| M2 database SQLite (osservazioni + esercizio), registrazione, sessioni, misure di rete | fatto (si parte da zero) |
| M3 teoria, simulatore con avversari tarati, rigiocata | fatto |
| M4 modelli: logistico, rete neurale, profilo, hazard/Kaplan-Meier, Brier | fatto |
| M5 policy: valore atteso + regime, bandit Thompson, agente Q conservativo | fatto (Q solo nel simulatore) |
| M6 valutazione shadow ≥ 4 settimane, confronto fra policy | strumenti pronti; servono le settimane |
| M7 esecuzione e latenza | fatto (anticipo adattivo) |
| M8 monitoraggio e riaddestramento | scheda pronta; riaddestramento a ogni asta chiusa; deriva: da fare |