using System; using System.IO; using System.Text.Json; using System.Threading; namespace AutoBidder.Utilities { public class AppSettings { /// /// Millisecondi di anticipo sulla scadenza con cui parte la puntata. /// /// È il parametro che decide quanto costa vincere, e i dossier /// raccolti dicono esattamente dove sta l'ottimo. Il motore punta solo nei cicli /// che arrivano fino a questo anticipo senza che nessun altro abbia puntato: /// più l'anticipo è largo, più cicli lo raggiungono, più puntate si spendono. /// /// Rigiocando 2.095 dossier (1.007.850 cicli) con backtest.ps1, i cicli /// che scendono fino a una data soglia sono: /// /// sotto 1 s - 0,6% dei cicli, mediana 1 puntata per asta /// sotto 2 s - 5,9%, mediana 15 puntate /// sotto 3 s - 20,4%, mediana 46 puntate /// sotto 4 s - 79,5%, mediana 97 puntate /// /// /// Il grosso degli avversari punta con circa tre secondi ancora sul cronometro: /// il 58,8% dei cicli si ferma esattamente li'. Sotto i due secondi la concorrenza si /// dirada, ed e' il motivo per cui l'anticipo va tenuto stretto - allargarlo di un /// secondo moltiplica per dieci le puntate spese. /// /// Dal lato opposto c'e' la rete: ping mediano 56 ms, p99 135 ms, p99,9 /// 444 ms, con punte oltre i due secondi. Sotto i 600 ms di anticipo basta un picco /// perche' la puntata arrivi a giochi chiusi, e una puntata tardiva costa l'asta intera. /// /// Predefinito 1000 ms: il valore piu' largo che resta nella fascia da /// una puntata per asta, e piu' del doppio del p99,9 del ping. Sotto il secondo i /// dossier non possono dire di piu', perche' Bidoo dichiara la scadenza al secondo /// intero. /// /// /// Aggiornato a 500 ms. Le due ragioni per stare larghi sono cadute /// entrambe. La prima — il p99,9 del ping a 444 ms — era misurata quando il tetto di /// 40 richieste al secondo faceva accodare le chiamate: con il tetto tolto il ping /// misurato sui dossier nuovi sta fra 47 e 78 ms, mediana 55. La seconda era la /// paura della concorrenza fitta sotto i due secondi, ma i tipi di puntata dicono /// il contrario: il 75,3% delle puntate avversarie è automatica e scatta a 2 s /// esatti, quindi sotto quella soglia resta solo chi punta a mano. /// /// A 500 ms la puntata parte con circa nove volte il ping di margine, e cade /// nella metà di secondo in cui l'autopuntata del sito ha già sparato. Più in là non /// conviene andare: Bidoo dichiara la scadenza al secondo intero, quindi sotto i /// 500 ms si starebbe scommettendo sull'arrotondamento. /// public int DefaultBidBeforeDeadlineMs { get; set; } = 500; public double DefaultMinPrice { get; set; } = 0; public double DefaultMaxPrice { get; set; } = 0; public int DefaultMaxClicks { get; set; } = 0; public int DefaultMinResets { get; set; } = 0; public int DefaultMaxResets { get; set; } = 0; /// /// Mostra avviso quando una puntata arriva troppo tardi (timer scaduto). /// Suggerisce all'utente di aumentare il tempo di puntata. /// Default: true /// public bool ShowLateBidWarning { get; set; } = true; // ═══════════════════════════════════════════════════════════════════ // MOTORE DI PRECISIONE — CADENZA POLLING ADATTIVA // ═══════════════════════════════════════════════════════════════════ // Ogni asta ha il proprio anello di polling, che si stringe man mano che // la scadenza si avvicina: un'asta a otto minuti non ha bisogno di quattro // chiamate al secondo, una a tre secondi sì. /// Cadenza polling oltre i 60 s dalla scadenza. Default: 2000 ms. public int PollIntervalFarMs { get; set; } = 2000; /// Cadenza polling tra 10 e 60 s dalla scadenza. Default: 900 ms. public int PollIntervalMidMs { get; set; } = 900; /// Cadenza polling sotto i 10 s dalla scadenza. Default: 400 ms. public int PollIntervalNearMs { get; set; } = 400; /// /// Cadenza polling nella finestra critica, solo per le aste in stato Attiva. /// In sola osservazione non c'è puntata da azzeccare e si risparmiano chiamate. /// Default: 220 ms. /// public int PollIntervalCriticalMs { get; set; } = 220; /// Ampiezza della finestra critica prima della scadenza. Default: 4000 ms. public int CriticalWindowMs { get; set; } = 4000; /// /// Tetto complessivo di richieste al secondo verso Bidoo (le puntate non sono soggette /// al limite: hanno corsia preferenziale). Default: 40. /// /// /// Tetto di richieste al secondo verso Bidoo. 0 = nessun tetto, ed è il /// predefinito: il ritmo lo detta la rete, e a frenare è il server con 429/503. /// /// Il vecchio predefinito era 40, ed era il vero collo di bottiglia. Misurato /// sui registri di tre giorni: con fino a 31 aste seguite insieme la distribuzione /// delle richieste era tagliata netta sul tetto (p90 39, p99 41), e ogni asta /// veniva interrogata ogni ~596 ms invece dei 220 configurati per la finestra /// critica. Con il timer a un secondo restava un solo sguardo prima della /// scadenza: da lì le puntate perse e il tempo che non scorreva. /// public double MaxRequestsPerSecond { get; set; } = 0; /// /// Porta la granularità del timer di sistema a 1 ms mentre il monitor è in funzione. /// Senza, ogni attesa può sforare di ~15 ms. Default: true. /// public bool PrecisionTimerEnabled { get; set; } = true; // ═══════════════════════════════════════════════════════════════════ // ASTE PROGRAMMATE — attesa dell'inizio // ═══════════════════════════════════════════════════════════════════ // Un'asta che comincia fra tre ore non va interrogata ogni due secondi, ma // all'apertura il motore dev'essere già pronto. La cadenza si allarga in // proporzione all'attesa e si stringe man mano che l'inizio si avvicina. /// Attiva la cadenza rilassata per le aste non ancora cominciate. public bool ScheduledAuctionBackoffEnabled { get; set; } = true; /// Cadenza quando l'inizio è oltre un'ora. Default: 20 minuti. public int ScheduledPollFarSeconds { get; set; } = 1200; /// Cadenza quando l'inizio è fra 10 e 60 minuti. Default: 2 minuti. public int ScheduledPollMidSeconds { get; set; } = 120; /// /// Quanto prima dell'inizio si torna alla cadenza normale. Default: 120 s — /// un margine ampio garantisce di non perdere l'apertura. /// public int ScheduledPollWakeSeconds { get; set; } = 120; // ═══════════════════════════════════════════════════════════════════ // ANTICIPO PUNTATA — misura e taratura // ═══════════════════════════════════════════════════════════════════ /// /// Registra l'anticipo effettivo di ogni puntata, per capire se il valore /// impostato è quello giusto. Default: true — è solo una misura, non cambia nulla. /// public bool BidLeadTrackingEnabled { get; set; } = true; /// /// Propone (non applica) una correzione dell'anticipo quando i dati raccolti /// mostrano uno scarto sistematico. Default: true. /// public bool BidLeadSuggestionsEnabled { get; set; } = true; /// Misure necessarie prima di azzardare una proposta. Default: 15. public int BidLeadMinSamples { get; set; } = 15; // ═══════════════════════════════════════════════════════════════════ // NOTIFICHE // ═══════════════════════════════════════════════════════════════════ /// Mostra una notifica di Windows quando un'asta viene vinta. public bool NotifyOnWin { get; set; } = true; /// Notifica anche quando un'asta seguita viene persa. Default: false. public bool NotifyOnLoss { get; set; } = false; // ═══════════════════════════════════════════════════════════════════ // CARTELLE DEI DATI // ═══════════════════════════════════════════════════════════════════ // Vuoto = posizione predefinita sotto %LocalAppData%\AutoBidder. /// Dati d'esercizio: aste monitorate e prodotti seguiti. public string DataFolder { get; set; } = ""; /// /// Statistiche ed esportazioni. Separata perché cresce nel tempo e serve /// soprattutto per le analisi fatte fuori dall'applicazione. /// public string StatsFolder { get; set; } = ""; /// /// Registri di testo: log applicativo, riscatto puntate, dossier delle aste. /// Vuoto = Dati\Registri. /// public string LogFolder { get; set; } = ""; // ═══════════════════════════════════════════════════════════════════ // REGISTRI SU FILE // ═══════════════════════════════════════════════════════════════════ /// Scrive il log applicativo anche su file, un file per giorno. public bool WriteAppLogFile { get; set; } = true; /// Scrive su file il registro del riscatto puntate. public bool WriteFreeBidsLogFile { get; set; } = true; /// /// Registra un dossier per ogni asta seguita: intestazione, eventi e riepilogo, in /// formato JSON Lines. È il materiale su cui si studiano le strategie, e va raccolto /// mentre l'asta è in corso — a posteriori Bidoo non espone nulla di tutto questo. /// public bool WriteAuctionDossiers { get; set; } = true; /// /// Registra nel dossier ogni singola interrogazione a Bidoo, non solo gli /// eventi. È la risoluzione massima — prezzo, timer e ping a ogni poll — e il prezzo /// da pagare è la dimensione: nella finestra critica sono quattro righe al secondo, /// quindi un'asta lunga vale qualche megabyte. /// public bool DossierIncludeRawPolls { get; set; } = true; /// /// Dopo quanti giorni cancellare i registri. 0 = tenerli per sempre. /// Predefinito 90: con i poll grezzi accesi lo spazio va tenuto d'occhio. /// public int LogRetentionDays { get; set; } = 90; /// /// Registra una scheda dettagliata per ogni asta conclusa (serie dei prezzi, /// puntate per utente, durata). È la materia prima per migliorare le strategie. /// public bool DetailedStatsEnabled { get; set; } = true; // ═══════════════════════════════════════════════════════════════════ // CATALOGO — cache // ═══════════════════════════════════════════════════════════════════ /// /// Per quanti secondi riusare le aste già scaricate di una categoria. /// Ogni caricamento costa una richiesta ogni cinquanta aste: senza cache, /// tornare avanti e indietro fra due categorie le rifà tutte ogni volta. /// Default: 60 s. 0 = nessuna cache. /// public int CatalogCacheSeconds { get; set; } = 60; // ═══════════════════════════════════════════════════════════════════ // ESPLORA — CATALOGO // ═══════════════════════════════════════════════════════════════════ /// /// Quante aste caricare al massimo per categoria. Il listato si sfoglia a pagine /// di cinquanta, in ordine di scadenza: il tetto taglia via le aste più lontane, /// mai quelle imminenti. Le categorie piccole finiscono prima e si fermano da sole. /// Default: 500 (dieci pagine). /// public int CatalogMaxAuctions { get; set; } = 500; /// /// Aggiorna prezzi e timer delle schede mentre le guardi. /// Default: false — con l'aggiornamento acceso la pagina cambia sotto gli occhi /// mentre si legge, quindi va acceso solo quando serve davvero. /// public bool CatalogAutoRefresh { get; set; } = false; // ═══════════════════════════════════════════════════════════════════ // AGGIUNTA AUTOMATICA DEI PRODOTTI SEGUITI // ═══════════════════════════════════════════════════════════════════ // Bidoo rimette all'asta lo stesso articolo di continuo: seguendo il prodotto // (stellina in Esplora) le aste nuove entrano nel monitor da sole. /// Attiva la scansione periodica del catalogo per i prodotti seguiti. public bool AutoAddProductsEnabled { get; set; } = true; /// Ogni quanti secondi cercare aste nuove dei prodotti seguiti. Default: 90. public int AutoAddScanSeconds { get; set; } = 90; /// /// Quante aste percorrere nella scansione dei prodotti seguiti. /// /// È un valore a parte da quello del catalogo, e più alto, per una /// ragione precisa: il listato è ordinato per scadenza, quindi le aste che devono /// ancora cominciare stanno in fondo. Con il tetto del catalogo (500) un'asta che /// apre fra tre ore resta invisibile finché non si avvicina — ed è esattamente /// quella che si vorrebbe avere nel monitor prima che parta. /// /// Predefinito 2500: una cinquantina di richieste per scansione, che con la /// cadenza predefinita sono meno di una al secondo. /// public int AutoAddScanMaxAuctions { get; set; } = 2500; /// /// Stato con cui entrano le aste aggiunte automaticamente. /// Valori: "Stopped", "Watch" (predefinito), "Active". /// /// Il valore predefinito è deliberatamente Osserva: un'asta che entra da sola /// e comincia subito a puntare spenderebbe puntate vere senza che tu l'abbia vista. /// public string AutoAddNewAuctionState { get; set; } = "Watch"; /// /// Tetto di aste aggiunte automaticamente presenti insieme nel monitor /// (0 = nessun limite). Default: 20. /// public int AutoAddMaxAuctions { get; set; } = 20; /// /// Orizzonte temporale della sorveglianza: un'asta che comincia fra più di /// tanti minuti non entra da sola nel monitor. 0 = nessun limite. /// /// La ricerca scende in fondo al listato apposta per trovare le aste /// programmate, ma "programmata" su Bidoo vuol dire anche fra dodici ore: senza un /// orizzonte il monitor si riempie di aste che non succederanno per mezza giornata, /// consumando il tetto di e nascondendo quelle /// vicine. A mano si può sempre aggiungere qualunque asta: questo limite vale solo /// per l'aggiunta automatica. /// /// Predefinito 60 minuti. Un'asta in corso ha per costruzione un timer di /// pochi secondi, quindi non è mai toccata dal filtro. /// public int AutoAddMaxStartMinutes { get; set; } = 60; /// /// Aggiungi da solo solo le aste che non sono ancora cominciate, cioè quelle /// su cui nessuno ha ancora puntato. /// /// Un'asta presa a metà si può seguire, ma la sua storia è già scritta per /// buona parte: non si sa quanto è durata davvero, chi c'era prima, come è salito il /// prezzo. Le statistiche che ne escono sono monche, e i dossier — che sono il /// materiale su cui si tarano le strategie — raccontano solo la seconda metà. /// Accendendo questa opzione entrano nel monitor soltanto le aste che si potranno /// seguire dal primo istante all'ultimo. /// /// Il prezzo da pagare è che si perdono le occasioni già in corso. Predefinito /// false: chi vuole puntare, e non solo misurare, di solito le vuole entrambe. /// public bool AutoAddOnlyNotStarted { get; set; } = false; // RISCATTO AUTOMATICO DELLE PUNTATE GRATIS // Bidoo lascia le ricompense dei giorni scorsi in attesa sulla pagina dedicata: // un controllo periodico le raccoglie senza doverci pensare. /// Attiva il controllo periodico delle ricompense da riscuotere. public bool FreeBidsAutoClaimEnabled { get; set; } = true; /// /// Ogni quanti minuti controllare. Il minimo applicato è 5: le ricompense sono /// giornaliere, e interrogare il sito più spesso sarebbe traffico sprecato. /// Default: 30. /// public int FreeBidsCheckMinutes { get; set; } = 30; // LIMITI LOG /// /// Numero massimo di righe di log da mantenere per ogni singola asta (default: 500) /// public int MaxLogLinesPerAuction { get; set; } = 500; /// /// Numero massimo di righe di log da mantenere nel log globale (default: 1000) /// public int MaxGlobalLogLines { get; set; } = 1000; // NUOVE IMPOSTAZIONI STATO INIZIALE ASTE /// /// Determina se all'apertura dell'applicazione le aste salvate devono essere avviate automaticamente. /// Valori: "Active" = avvia, "Paused" = in pausa, "Stopped" = fermate (default) /// public string DefaultStartAuctionsOnLoad { get; set; } = "Stopped"; /// /// Determina lo stato iniziale di una nuova asta quando viene aggiunta. /// Valori: "Active" = attiva, "Paused" = in pausa, "Stopped" = fermata (default) /// public string DefaultNewAuctionState { get; set; } = "Stopped"; /// /// Se TRUE, salva e ripristina lo stato effettivo (attiva/pausa/ferma) di ogni asta. /// Se FALSE, applica DefaultStartAuctionsOnLoad a tutte le aste al caricamento. /// Default: false (usa DefaultStartAuctionsOnLoad) /// public bool RememberAuctionStates { get; set; } = false; // ? NUOVO: LIMITE MINIMO PUNTATE /// /// Numero minimo di puntate residue da mantenere sull'account. /// Se impostato > 0, il sistema non punterà se le puntate residue scenderebbero sotto questa soglia. /// Default: 0 (nessun limite) /// public int MinimumRemainingBids { get; set; } = 0; // ?? NUOVO: LIMITE STORIA PUNTATE /// /// Numero massimo di puntate da visualizzare nella scheda "Storia Puntate". /// Se impostato > 0, mostra solo le ultime N puntate dall'API. /// Default: 20 (ultime 20 puntate) /// public int MaxBidHistoryEntries { get; set; } = 20; // ?? NUOVO: LIVELLO MINIMO LOG /// /// Livello minimo di log da visualizzare. /// Valori: "ErrorOnly" (solo errori), "Normal" (errori e warning), /// "Informational" (info standard), "Debug" (dettagli sviluppo), "Trace" (tutto). /// Default: "Normal" (uso giornaliero - errori e warning) /// public string MinLogLevel { get; set; } = "Normal"; // ??????????????????????????????????????????????????????????????? // IMPOSTAZIONI DATABASE // ??????????????????????????????????????????????????????????????? /// /// Abilita il salvataggio automatico delle aste completate nel database. /// Default: true (consigliato per statistiche) /// public bool DatabaseAutoSaveEnabled { get; set; } = true; /// /// Esegue pulizia automatica duplicati all'avvio dell'applicazione. /// Default: true (consigliato per mantenere database pulito) /// public bool DatabaseAutoCleanupDuplicates { get; set; } = true; /// /// Esegue pulizia automatica record incompleti all'avvio. /// Default: false (può rimuovere dati utili in caso di errori temporanei) /// public bool DatabaseAutoCleanupIncomplete { get; set; } = false; /// /// Numero massimo di giorni da mantenere nei risultati aste. /// Record più vecchi vengono eliminati automaticamente. /// Default: 180 (6 mesi), 0 = disabilitato /// public int DatabaseMaxRetentionDays { get; set; } = 180; // ??????????????????????????????????????????????????????????????? // STRATEGIE AVANZATE DI PUNTATA // ??????????????????????????????????????????????????????????????? // ❌ RIMOSSO: Jitter, Offset Dinamico, Latenza Adattiva // Il timing è gestito SOLO da DefaultBidBeforeDeadlineMs // Le strategie decidono SE puntare, non QUANDO // 🎯 LOGGING GRANULARE // ??????????????????????????????????????????????????????????????? /// /// Log quando viene piazzata una puntata [BID] /// Default: true /// public bool LogBids { get; set; } = true; /// /// Log quando una strategia blocca la puntata [STRATEGY] /// Default: true /// public bool LogStrategyDecisions { get; set; } = true; /// /// Log calcoli valore prodotto [VALUE] /// Default: false (attiva per debug) /// public bool LogValueCalculations { get; set; } = false; /// /// Log rilevamento competizione e heat [COMPETITION] /// Default: false /// public bool LogCompetition { get; set; } = false; /// /// Log timing e polling (molto verbose!) [TIMING] /// Default: false (attiva solo per debug timing) /// public bool LogTiming { get; set; } = false; /// /// Log errori e warning [ERROR/WARN] /// Default: true /// public bool LogErrors { get; set; } = true; /// /// Applica automaticamente i limiti salvati nel prodotto quando si aggiunge una nuova asta. /// Se TRUE e il prodotto ha valori di default salvati, li applica automaticamente. /// Default: true (consigliato per coerenza) /// public bool AutoApplyProductDefaults { get; set; } = true; /// /// Scelta priorità limiti quando si aggiunge un'asta per un prodotto già salvato: /// - "ProductStats": Usa i limiti personalizzati salvati nelle statistiche prodotto (UserDefaultMinPrice, ecc.) /// - "GlobalDefaults": Usa sempre i limiti globali (DefaultMinPrice, DefaultMaxPrice, ecc.) /// Default: "ProductStats" (consigliato per usare limiti specifici per prodotto) /// public string NewAuctionLimitsPriority { get; set; } = "ProductStats"; /// /// Log stato asta (terminata, reset, ecc.) [STATUS] /// Default: true /// public bool LogAuctionStatus { get; set; } = true; /// /// Log profiling avversari [OPPONENT] /// Default: false /// public bool LogOpponentProfiling { get; set; } = false; // 🎯 STRATEGIE SEMPLIFICATE /// /// Entry Point: Usato SOLO per calcolare i limiti consigliati (70% del MaxPrice storico). /// NON blocca le puntate! I limiti MinPrice/MaxPrice impostati dall'utente sono RIGIDI. /// Default: true (per calcolo limiti consigliati) /// public bool EntryPointEnabled { get; set; } = true; /// /// Anti-bot: rinuncia al ciclo quando l'ultimo puntatore punta a cadenza fissa. /// /// Predefinito cambiato a false. Le marche temporali di Bidoo sono in /// secondi interi, quindi la regola non puo' davvero distinguere un automatismo da /// una persona regolare: si riduce a "le ultime pause dell'avversario sono identiche /// al secondo", cosa comunissima. Rigiocando i dossier con backtest.ps1 /// rifiutava fra il 4% e il 9% delle puntate, a seconda dell'anticipo, e la /// premessa era comunque rovesciata: un avversario a cadenza fissa punta con secondi /// di anticipo, quindi e' il piu' facile da battere aspettando l'ultimo istante. /// /// Per rivedere il confronto con i propri dati: /// $env:AUTOBIDDER_BACKTEST_LEGACY=1 prima di backtest.ps1. /// public bool AntiBotDetectionEnabled { get; set; } = false; /// /// User Exhaustion: Sfrutta utenti stanchi (oltre 50 puntate) /// quando ci sono pochi altri bidder attivi. /// Default: true /// public bool UserExhaustionEnabled { get; set; } = true; // 🎯 CONTROLLO CONVENIENZA PRODOTTO /// /// Abilita il controllo di convenienza basato sul valore del prodotto. /// Se attivo, blocca le puntate quando il costo totale supera il prezzo "Compra Subito" /// di una percentuale superiore a MinSavingsPercentage. /// Default: true /// public bool ValueCheckEnabled { get; set; } = true; /// /// Percentuale minima di risparmio richiesta per continuare a puntare. /// Valori negativi = tolleranza alla perdita. /// Es: -5 = permetti fino al 5% di perdita rispetto al "Compra Subito" /// 0 = blocca se costa uguale o più del "Compra Subito" /// 10 = richiedi almeno 10% di risparmio /// Default: -5 (permetti fino al 5% di perdita) /// public double MinSavingsPercentage { get; set; } = -5.0; /// /// Abilita il controllo anti-collisione hardcoded. /// Se attivo, blocca le puntate quando ci sono 3+ bidder attivi negli ultimi 10 secondi. /// ATTENZIONE: Questo controllo può far perdere aste competitive! /// Default: false (DISABILITATO - non blocca mai) /// public bool HardcodedAntiCollisionEnabled { get; set; } = false; // ── Fascia oraria sospesa ──────────────────────────────────────── // Vedi BiddingHours per i numeri: alle 0 e alle 9 la stessa asta costa quasi il // doppio che fra le 10 e le 13. // ── Apprendimento ──────────────────────────────────────────────── // Vedi Ml/LearningService. Sempre acceso: impara da ogni asta chiusa. /// /// Lasciar fermare una puntata al modello quando il valore atteso è negativo. /// Spento, il modello parla nel registro ma non decide. /// public bool LearningGateEnabled { get; set; } = true; /// /// Aste apprese sotto le quali il modello non ferma niente. Un modello appena nato /// direbbe cose a caso, e a caso fermerebbe le puntate. Predefinito 300. /// public int LearningMinAuctions { get; set; } = 300; /// /// Moltiplica il costo della puntata nel confronto col valore atteso: 1 è il /// valore atteso puro, 2 pretende che una puntata renda il doppio del suo costo. /// Più alto = più selettivo = meno puntate. /// public double LearningEvMultiplier { get; set; } = 1.0; /// /// Secondi per avvio dedicati a leggere i dossier non ancora appresi, in /// sottofondo. La prima volta ne servono diversi avvii; poi resta solo l'asta /// appena chiusa. /// public int LearningBootstrapSecondsPerStart { get; set; } = 120; // ── Rimozione automatica delle aste concluse ───────────────────── // Vedi FinishedAuctionCleanup: si toglie di serie, si trattiene cio' su cui c'e' // stato un esborso. /// Togliere da sole dall'elenco le aste concluse. Acceso di serie. public bool AutoRemoveFinished { get; set; } = true; /// Trattenere le aste su cui hai puntato, per controllarle a mano. public bool AutoRemoveKeepWithMyBids { get; set; } = true; /// Trattenere le aste vinte: c'è da confermare l'acquisto su Bidoo. public bool AutoRemoveKeepWon { get; set; } = true; /// Trattenere le aste di cui non si è vista la fine: esito da controllare. public bool AutoRemoveKeepUnclear { get; set; } = true; /// /// Versione dello schema delle impostazioni, per le migrazioni una tantum. /// /// Un file scritto da una versione precedente non ha questo campo e vale /// quindi 0. Serve perché un predefinito nuovo non arriva a chi ha già un /// file salvato: il valore su disco vince sempre, ed è giusto che sia così, tranne /// quando il vecchio valore veniva da una misura che si è poi rivelata sbagliata. /// Vedi . /// public int SettingsSchemaVersion { get; set; } /// /// Sospendere le puntate nella fascia indicata. Acceso di serie. /// L'asta resta comunque Attiva e riprende da sola: vedi . /// public bool QuietHoursEnabled { get; set; } = true; /// Prima ora della fascia sospesa, inclusa. public int QuietHoursStart { get; set; } = BiddingHours.DefaultStart; /// Prima ora in cui si torna a puntare. public int QuietHoursEnd { get; set; } = BiddingHours.DefaultEnd; /// /// Ritirarsi quando dall'altra parte c'è un'autopuntata armata. Acceso di serie. /// Vedi per il meccanismo e i numeri misurati. /// public bool AutoBidDuelWithdrawEnabled { get; set; } = true; /// /// Risposte automatiche di fila oltre le quali ci si ritira. Predefinito 5. /// Più basso risparmia prima ma rischia di scambiare per macchina un avversario /// umano regolare; più alto è prudente e costa puntate. /// public int AutoBidDuelResponses { get; set; } = AutoBidDuel.RispostePerConcludere; // 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥 // RILEVAMENTO COMPETIZIONE E HEAT METRIC // 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥 /// /// Abilita rilevamento competizione e heat metric. /// Conta bidder attivi e collisioni per determinare il "calore" dell'asta. /// Default: true /// public bool CompetitionDetectionEnabled { get; set; } = true; /// /// Finestra temporale in secondi per contare bidder attivi. /// Default: 30 (ultimi 30 secondi) /// public int CompetitionWindowSeconds { get; set; } = 30; /// /// Numero minimo di bidder attivi per considerare l'asta "affollata". /// Se >= a questa soglia, applica logica di evitamento. /// Default: 3 /// public int CompetitionThreshold { get; set; } = 3; /// /// Abilita auto-pausa per aste troppo competitive. /// Default: false (solo warning, non pausa automatica) /// public bool AutoPauseHotAuctions { get; set; } = false; /// /// Soglia heat metric per auto-pausa (0-100). /// Default: 80 (pausa se heat >= 80%) /// public int HeatThresholdForPause { get; set; } = 80; // ??????????????????????????????????????????????????????????????? // SOFT RETREAT E COLLISION MANAGEMENT // ??????????????????????????????????????????????????????????????? /// /// Abilita soft retreat automatico dopo N collisioni consecutive. /// Default: true /// public bool SoftRetreatEnabled { get; set; } = true; /// /// Numero di collisioni consecutive per attivare soft retreat. /// Default: 3 /// public int SoftRetreatAfterCollisions { get; set; } = 3; /// /// Durata del ritiro, in secondi. /// /// Ridotta da 30 a 12: il timer delle aste osservate si azzera ogni 8-12 /// secondi, quindi trenta secondi di pausa sono tre cicli interi — nella pratica /// l'asta è persa. Dodici secondi bastano a spezzare una serie di collisioni /// senza consegnare la partita. /// public int SoftRetreatDurationSeconds { get; set; } = 12; /// /// Blocca la puntata se il prezzo sale più in fretta di tanti euro al secondo. /// 0 = controllo spento (predefinito). /// /// Prima era una costante nel codice fissata a 0,10 €/s, cioè dieci puntate /// al secondo: su oltre 40.000 valutazioni riprese dai dossier non è mai scattata, /// e la velocità massima mai osservata è 0,016 €/s. Se lo si vuole usare davvero, /// un valore sensato sta intorno a 0,01-0,02 €/s. /// public double PriceVelocityBlockPerSecond { get; set; } = 0; // ??????????????????????????????????????????????????????????????? // PROBABILISTIC BIDDING // ??????????????????????????????????????????????????????????????? /// /// Abilita policy di puntata probabilistica. /// Decide se puntare con probabilità p basata su competizione e ROI. /// Default: false (richiede tuning) /// public bool ProbabilisticBiddingEnabled { get; set; } = false; /// /// Probabilità base di puntata (0.0 - 1.0). /// Default: 0.8 (80%) /// public double BaseBidProbability { get; set; } = 0.8; /// /// Fattore di riduzione probabilità per ogni bidder attivo extra. /// Default: 0.1 (riduce del 10% per ogni bidder oltre la soglia) /// public double ProbabilityReductionPerBidder { get; set; } = 0.1; // ??????????????????????????????????????????????????????????????? // OPPONENT PROFILING // ??????????????????????????????????????????????????????????????? /// /// Abilita profiling degli avversari. /// Identifica utenti aggressivi e applica regole specifiche. /// Default: true /// public bool OpponentProfilingEnabled { get; set; } = true; /// /// Soglia puntate per considerare un utente "aggressivo". /// Default: 10 (se un utente ha fatto >= 10 puntate in un'asta) /// public int AggressiveBidderThreshold { get; set; } = 10; /// /// Dimensione finestra scorrevole per analisi bidder aggressivi. /// Analizza le ultime N puntate invece del conteggio totale. /// Default: 30 (ultime 30 puntate) /// public int AggressiveBidderWindowSize { get; set; } = 30; /// /// Soglia percentuale per considerare un utente "aggressivo". /// Se un utente ha più di X% delle puntate nella finestra, è aggressivo. /// Default: 40 (40% delle puntate) /// public double AggressiveBidderPercentageThreshold { get; set; } = 40.0; /// /// Dimensione finestra per rilevamento situazioni di duello. /// Default: 20 (ultime 20 puntate) /// public int DuelDetectionWindowSize { get; set; } = 20; /// /// Azione da intraprendere con bidder aggressivi. /// "Avoid" = evita l'asta, "Compete" = continua normalmente, "Outbid" = punta più aggressivamente /// Default: "Compete" (cambiato da Avoid per essere meno restrittivo) /// public string AggressiveBidderAction { get; set; } = "Compete"; // ??????????????????????????????????????????????????????????????? // BANKROLL & SAFETY MANAGER // ??????????????????????????????????????????????????????????????? /// /// Abilita gestione bankroll per limitare spese. /// Default: true /// public bool BankrollManagerEnabled { get; set; } = true; /// /// Limite massimo puntate per sessione (0 = illimitato). /// Default: 0 /// public int MaxBidsPerSession { get; set; } = 0; /// /// Limite massimo puntate per singola asta (0 = illimitato). /// Default: 0 /// public int MaxBidsPerAuction { get; set; } = 0; /// /// Budget massimo giornaliero in euro (0 = illimitato). /// Calcolato come: puntate usate × costo medio puntata. /// Default: 0 /// public double DailyBudgetEuro { get; set; } = 0; /// /// Costo medio di una puntata, in euro. Entra nel budget e nel tetto di prezzo /// consigliato per ogni prodotto. Predefinito 0,20: è il valore che il motore usa /// nei calcoli di convenienza durante l'asta, e averne due diversi voleva dire /// vedere due conti diversi sullo stesso articolo. /// public double AverageBidCostEuro { get; set; } = 0.20; /// /// Quota delle chiusure passate che il prezzo massimo consigliato punta a coprire. /// /// Alzarla vuol dire restare in gioco in più aste ma spingersi su prezzi più /// alti; abbassarla vuol dire puntare solo sulle occasioni buone e lasciar perdere /// le altre. Predefinito 85%: la sola moda — il prezzo a cui l'articolo si /// aggiudica più spesso — coprirebbe fra il 15% e il 44% delle aste a seconda del /// prodotto, cioè si resterebbe fuori dalla maggior parte delle occasioni. /// /// Il consiglio resta comunque limitato dal budget: vedi /// . /// public double SuggestedPriceCoveragePercent { get; set; } = 85; // ??????????????????????????????????????????????????????????????? // LOGGING AVANZATO // ??????????????????????????????????????????????????????????????? /// /// Abilita logging avanzato con metriche dettagliate. /// Include: collisioni, timer scaduto, latenza, heat metric. /// Default: true /// public bool AdvancedLoggingEnabled { get; set; } = true; /// /// Salva metriche per ogni puntata nel database. /// Default: true /// public bool SaveBidMetricsToDatabase { get; set; } = true; // ═══════════════════════════════════════════════════════════════════ // INTERFACCIA // ═══════════════════════════════════════════════════════════════════ /// /// Abilita la modalità scura dell'interfaccia. /// Default: true (modalità scura) /// public bool DarkMode { get; set; } = true; } /// /// Impostazioni su file, con una copia in memoria. /// /// è su un percorso caldissimo: ogni anello di polling e ogni giro /// del cecchino la chiamano, quindi decine di volte al secondo per ogni asta seguita. /// Per questo la lettura del caso comune è senza lucchetto: si legge un /// riferimento volatile, che in .NET è atomico. Il lucchetto serve solo a chi ricarica /// da disco, cioè una volta ogni due secondi al massimo. /// public static class SettingsManager { // Le impostazioni stanno nella radice fissa: contengono il percorso degli altri // dati, quindi non possono a loro volta dipendere da quel percorso. private static string _folder => AppPaths.ConfigRoot; private static string _file => AppPaths.SettingsFile; private static readonly object _reloadLock = new(); private static volatile AppSettings? _cached; /// Scadenza della copia in memoria, in tick UTC. private static long _cacheExpiryTicks; private const int CACHE_TTL_MS = 2000; /// Schema corrente. Alzarlo fa girare una volta sola. public const int SchemaCorrente = 1; /// /// Porta avanti un file scritto da una versione precedente. Restituisce true se /// qualcosa è cambiato, così chi chiama sa che va risalvato. /// /// Perché serve. Il valore su disco vince sui predefiniti, ed è /// giusto: sono scelte dell'utente. Ma due di quei valori non erano scelte, erano /// conseguenze di misure sbagliate — il tetto di 40 richieste al secondo e /// l'anticipo di 1000 ms erano stati tarati quando il tetto stesso accodava le /// chiamate e gonfiava il ping a 444 ms. Senza migrazione, chi aggiorna si ritrova /// le correzioni di oggi già annullate dal proprio file, e conclude che non /// funzionano. /// /// Si tocca solo chi ha ancora esattamente il vecchio valore. Chi lo aveva /// già cambiato di suo se lo tiene: quella è una scelta, e non spetta a un /// aggiornamento ribaltarla. /// public static bool Migra(AppSettings s) { if (s == null || s.SettingsSchemaVersion >= SchemaCorrente) return false; if (Math.Abs(s.MaxRequestsPerSecond - 40) < 0.001) s.MaxRequestsPerSecond = 0; if (s.DefaultBidBeforeDeadlineMs == 1000) s.DefaultBidBeforeDeadlineMs = 500; s.SettingsSchemaVersion = SchemaCorrente; return true; } public static AppSettings Load() { // Percorso comune: nessun lucchetto, nessuna allocazione. var cached = _cached; if (cached != null && DateTime.UtcNow.Ticks < Interlocked.Read(ref _cacheExpiryTicks)) return cached; lock (_reloadLock) { // Un altro thread potrebbe aver già ricaricato mentre aspettavamo. cached = _cached; if (cached != null && DateTime.UtcNow.Ticks < Interlocked.Read(ref _cacheExpiryTicks)) return cached; AppSettings loaded; var daRisalvare = false; try { var esiste = File.Exists(_file); loaded = esiste ? JsonSerializer.Deserialize(File.ReadAllText(_file)) ?? new AppSettings() : new AppSettings(); // Un file nuovo nasce già allo schema corrente: la migrazione riguarda // solo chi arriva da una versione precedente. if (!esiste) loaded.SettingsSchemaVersion = SchemaCorrente; else daRisalvare = Migra(loaded); } catch { // File illeggibile o corrotto: si tiene quello che c'era, o i predefiniti. loaded = _cached ?? new AppSettings(); } // Scritto qui e non dentro Save: Load è sul percorso caldo e non deve // toccare il disco, ma una migrazione capita una volta sola nella vita // del file, e lasciarla non salvata la farebbe ripetere a ogni avvio. if (daRisalvare) { try { File.WriteAllText(_file, JsonSerializer.Serialize(loaded, new JsonSerializerOptions { WriteIndented = true })); } catch { /* si riproverà al prossimo avvio */ } } _cached = loaded; Interlocked.Exchange(ref _cacheExpiryTicks, DateTime.UtcNow.AddMilliseconds(CACHE_TTL_MS).Ticks); return loaded; } } public static void Save(AppSettings settings) { try { if (!Directory.Exists(_folder)) Directory.CreateDirectory(_folder); var txt = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true }); File.WriteAllText(_file, txt); // Pubblica subito i nuovi valori: il motore li vede al giro successivo. _cached = settings; Interlocked.Exchange(ref _cacheExpiryTicks, DateTime.UtcNow.AddMilliseconds(CACHE_TTL_MS).Ticks); } catch { } } } }