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>
593 lines
33 KiB
C#
593 lines
33 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Windows;
|
|
using AutoBidder.Utilities;
|
|
|
|
namespace AutoBidder
|
|
{
|
|
/// <summary>
|
|
/// Settings and configuration event handlers - REFACTORED
|
|
/// </summary>
|
|
public partial class MainWindow
|
|
{
|
|
/// <summary>
|
|
/// Carica TUTTE le impostazioni salvate nei controlli UI
|
|
/// </summary>
|
|
private void LoadDefaultSettings()
|
|
{
|
|
try
|
|
{
|
|
var settings = Utilities.SettingsManager.Load();
|
|
|
|
// Carica impostazioni predefinite aste
|
|
DefaultBidBeforeDeadlineMs.Text = settings.DefaultBidBeforeDeadlineMs.ToString();
|
|
Settings.AdaptiveLeadCheckBox.IsChecked = settings.AdaptiveLeadEnabled;
|
|
Settings.LeadMinMsTextBox.Text = settings.LeadMinMs.ToString();
|
|
Settings.LeadMaxMsTextBox.Text = settings.LeadMaxMs.ToString();
|
|
DefaultMinPrice.Text = settings.DefaultMinPrice.ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
|
|
DefaultMaxPrice.Text = settings.DefaultMaxPrice.ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
|
|
DefaultMaxClicks.Text = settings.DefaultMaxClicks.ToString();
|
|
|
|
// Carica limiti log
|
|
Settings.MaxLogLinesPerAuctionTextBox.Text = settings.MaxLogLinesPerAuction.ToString();
|
|
Settings.MaxGlobalLogLinesTextBox.Text = settings.MaxGlobalLogLines.ToString();
|
|
|
|
// ?? NUOVO: Carica limite storia puntate
|
|
Settings.MaxBidHistoryEntriesTextBox.Text = settings.MaxBidHistoryEntries.ToString();
|
|
|
|
// ?? NUOVO: Carica limite minimo puntate
|
|
MinimumRemainingBidsTextBox.Text = settings.MinimumRemainingBids.ToString();
|
|
|
|
// ?? NUOVO: Carica livello log
|
|
var logLevelErrorOnly = Settings.FindName("LogLevelErrorOnly") as System.Windows.Controls.RadioButton;
|
|
var logLevelNormal = Settings.FindName("LogLevelNormal") as System.Windows.Controls.RadioButton;
|
|
var logLevelInformational = Settings.FindName("LogLevelInformational") as System.Windows.Controls.RadioButton;
|
|
var logLevelDebug = Settings.FindName("LogLevelDebug") as System.Windows.Controls.RadioButton;
|
|
var logLevelTrace = Settings.FindName("LogLevelTrace") as System.Windows.Controls.RadioButton;
|
|
|
|
switch (settings.MinLogLevel)
|
|
{
|
|
case "ErrorOnly":
|
|
if (logLevelErrorOnly != null) logLevelErrorOnly.IsChecked = true;
|
|
break;
|
|
case "Informational":
|
|
if (logLevelInformational != null) logLevelInformational.IsChecked = true;
|
|
break;
|
|
case "Debug":
|
|
if (logLevelDebug != null) logLevelDebug.IsChecked = true;
|
|
break;
|
|
case "Trace":
|
|
if (logLevelTrace != null) logLevelTrace.IsChecked = true;
|
|
break;
|
|
case "Normal":
|
|
default:
|
|
if (logLevelNormal != null) logLevelNormal.IsChecked = true;
|
|
break;
|
|
}
|
|
|
|
// Motore di precisione
|
|
Settings.MaxRequestsPerSecondTextBox.Text = settings.MaxRequestsPerSecond.ToString("F0", System.Globalization.CultureInfo.InvariantCulture);
|
|
Settings.PrecisionTimerCheckBox.IsChecked = settings.PrecisionTimerEnabled;
|
|
|
|
RefreshEngineDiagnostics();
|
|
|
|
// Prodotti seguiti e catalogo
|
|
Settings.AutoAddEnabledCheckBox.IsChecked = settings.AutoAddProductsEnabled;
|
|
Settings.AutoAddState = settings.AutoAddNewAuctionState;
|
|
Settings.AutoAddScanSecondsTextBox.Text = settings.AutoAddScanSeconds.ToString();
|
|
Settings.AutoAddMaxAuctionsTextBox.Text = settings.AutoAddMaxAuctions.ToString();
|
|
Settings.AutoAddMaxStartMinutesTextBox.Text = settings.AutoAddMaxStartMinutes.ToString();
|
|
Settings.AutoAddOnlyNotStartedCheckBox.IsChecked = settings.AutoAddOnlyNotStarted;
|
|
Settings.SuggestedCoverageTextBox.Text = settings.SuggestedPriceCoveragePercent.ToString("0", System.Globalization.CultureInfo.CurrentCulture);
|
|
Settings.AverageBidCostTextBox.Text = settings.AverageBidCostEuro.ToString("0.00", System.Globalization.CultureInfo.CurrentCulture);
|
|
Settings.AutoAddScanDepthTextBox.Text = settings.AutoAddScanMaxAuctions.ToString();
|
|
Settings.CatalogMaxAuctionsTextBox.Text = settings.CatalogMaxAuctions.ToString();
|
|
Settings.CatalogAutoRefreshCheckBox.IsChecked = settings.CatalogAutoRefresh;
|
|
|
|
// Aste programmate, notifiche, cartelle
|
|
Settings.ScheduledBackoffCheckBox.IsChecked = settings.ScheduledAuctionBackoffEnabled;
|
|
Settings.ScheduledPollFarTextBox.Text = settings.ScheduledPollFarSeconds.ToString();
|
|
Settings.ScheduledPollMidTextBox.Text = settings.ScheduledPollMidSeconds.ToString();
|
|
Settings.ScheduledPollWakeTextBox.Text = settings.ScheduledPollWakeSeconds.ToString();
|
|
|
|
Settings.NotifyOnWinCheckBox.IsChecked = settings.NotifyOnWin;
|
|
Settings.NotifyOnLossCheckBox.IsChecked = settings.NotifyOnLoss;
|
|
|
|
// I percorsi si mostrano <b>risolti</b>, non come sono salvati: vuoto nel
|
|
// file significa "predefinito", ma a video il predefinito ha un nome preciso
|
|
// ed è quello che serve sapere.
|
|
RefreshDataFolderFields();
|
|
|
|
Settings.CatalogCacheSecondsTextBox.Text = settings.CatalogCacheSeconds.ToString();
|
|
|
|
Settings.QuietHoursCheckBox.IsChecked = settings.QuietHoursEnabled;
|
|
Settings.QuietHoursStartTextBox.Text = settings.QuietHoursStart.ToString();
|
|
Settings.QuietHoursEndTextBox.Text = settings.QuietHoursEnd.ToString();
|
|
|
|
Settings.AutoRemoveFinishedCheckBox.IsChecked = settings.AutoRemoveFinished;
|
|
Settings.AutoRemoveKeepMyBidsCheckBox.IsChecked = settings.AutoRemoveKeepWithMyBids;
|
|
Settings.AutoRemoveKeepWonCheckBox.IsChecked = settings.AutoRemoveKeepWon;
|
|
Settings.AutoRemoveKeepUnclearCheckBox.IsChecked = settings.AutoRemoveKeepUnclear;
|
|
|
|
Settings.WriteAppLogCheckBox.IsChecked = settings.WriteAppLog;
|
|
Settings.WriteFreeBidsLogCheckBox.IsChecked = settings.WriteFreeBidsLog;
|
|
Settings.WriteDossiersCheckBox.IsChecked = settings.RecordAuctions;
|
|
Settings.RawPollsCheckBox.IsChecked = settings.RecordPolls;
|
|
Settings.LogRetentionTextBox.Text = settings.LogRetentionDays.ToString();
|
|
Settings.DatabaseFolderTextBox.Text = AppPaths.DatabaseFolder;
|
|
|
|
Settings.BankrollEnabledCheckBox.IsChecked = settings.BankrollManagerEnabled;
|
|
Settings.MaxBidsPerAuctionTextBox.Text = settings.MaxBidsPerAuction.ToString();
|
|
Settings.MaxBidsPerSessionTextBox.Text = settings.MaxBidsPerSession.ToString();
|
|
Settings.DailyBudgetTextBox.Text = settings.DailyBudgetEuro.ToString("F2", System.Globalization.CultureInfo.CurrentCulture);
|
|
Settings.DailyStopLossTextBox.Text = settings.DailyStopLossEuro.ToString("F2", System.Globalization.CultureInfo.CurrentCulture);
|
|
Settings.MaxDrawdownTextBox.Text = settings.MaxDrawdownEuro.ToString("F2", System.Globalization.CultureInfo.CurrentCulture);
|
|
Settings.MaxConcurrentTextBox.Text = settings.MaxConcurrentActiveAuctions.ToString();
|
|
Settings.TransactionFeeTextBox.Text = settings.TransactionFeeEuro.ToString("F2", System.Globalization.CultureInfo.CurrentCulture);
|
|
Settings.HedgeCheckBox.IsChecked = settings.BuyNowHedgeEnabled;
|
|
Settings.RefreshRiskStatus();
|
|
|
|
// Aggiorna indicatore visivo
|
|
UpdateMinBidsIndicator(settings.MinimumRemainingBids);
|
|
|
|
// Carica stato iniziale aste
|
|
// ? NUOVO: Se RememberAuctionStates � attivo, seleziona "Ricorda Stato"
|
|
if (settings.RememberAuctionStates)
|
|
{
|
|
Settings.LoadAuctionsRemember.IsChecked = true;
|
|
}
|
|
else
|
|
{
|
|
// Altrimenti usa DefaultStartAuctionsOnLoad
|
|
switch (settings.DefaultStartAuctionsOnLoad)
|
|
{
|
|
case "Active":
|
|
Settings.LoadAuctionsActive.IsChecked = true;
|
|
break;
|
|
case "Paused":
|
|
Settings.LoadAuctionsPaused.IsChecked = true;
|
|
break;
|
|
case "Stopped":
|
|
default:
|
|
Settings.LoadAuctionsStopped.IsChecked = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (settings.DefaultNewAuctionState)
|
|
{
|
|
case "Active":
|
|
Settings.NewAuctionActive.IsChecked = true;
|
|
break;
|
|
case "Paused":
|
|
Settings.NewAuctionPaused.IsChecked = true;
|
|
break;
|
|
case "Stopped":
|
|
default:
|
|
Settings.NewAuctionStopped.IsChecked = true;
|
|
break;
|
|
}
|
|
|
|
Log($"[OK] Impostazioni caricate: Anticipo={settings.DefaultBidBeforeDeadlineMs}ms, LogAsta={settings.MaxLogLinesPerAuction}, LogGlobale={settings.MaxGlobalLogLines}, MinBids={settings.MinimumRemainingBids}", Utilities.LogLevel.Info);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"[ERRORE] Caricamento impostazioni predefinite: {ex.Message}", Utilities.LogLevel.Error);
|
|
}
|
|
}
|
|
|
|
private void SaveDefaultsButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
// ? Carica le impostazioni esistenti per non perdere gli altri valori
|
|
var settings = Utilities.SettingsManager.Load() ?? new Utilities.AppSettings();
|
|
|
|
// === SEZIONE DEFAULTS: Validazione e Salvataggio ===
|
|
if (int.TryParse(DefaultBidBeforeDeadlineMs.Text, out var bidMs) && bidMs >= 0 && bidMs <= 5000)
|
|
{
|
|
settings.DefaultBidBeforeDeadlineMs = bidMs;
|
|
}
|
|
else
|
|
{
|
|
Log("[ERRORE] Valore anticipo puntata non valido (deve essere 0-5000ms)", LogLevel.Error);
|
|
return;
|
|
}
|
|
|
|
// Paletti dell'anticipo adattivo: minimo sotto il massimo, entrambi sensati.
|
|
settings.AdaptiveLeadEnabled = Settings.AdaptiveLeadCheckBox.IsChecked == true;
|
|
settings.LeadMinMs = ReadBounded(Settings.LeadMinMsTextBox.Text, settings.LeadMinMs, 100, 5000, "anticipo minimo", " ms");
|
|
settings.LeadMaxMs = ReadBounded(Settings.LeadMaxMsTextBox.Text, settings.LeadMaxMs, 100, 5000, "anticipo massimo", " ms");
|
|
if (settings.LeadMaxMs < settings.LeadMinMs)
|
|
{
|
|
Log($"[ERRORE] Anticipo massimo ({settings.LeadMaxMs} ms) sotto il minimo ({settings.LeadMinMs} ms): riportato al minimo", LogLevel.Error);
|
|
settings.LeadMaxMs = settings.LeadMinMs;
|
|
}
|
|
|
|
|
|
if (double.TryParse(DefaultMinPrice.Text.Replace(',', '.'), System.Globalization.NumberStyles.Any,
|
|
System.Globalization.CultureInfo.InvariantCulture, out var minPrice))
|
|
{
|
|
settings.DefaultMinPrice = minPrice;
|
|
}
|
|
|
|
if (double.TryParse(DefaultMaxPrice.Text.Replace(',', '.'), System.Globalization.NumberStyles.Any,
|
|
System.Globalization.CultureInfo.InvariantCulture, out var maxPrice))
|
|
{
|
|
settings.DefaultMaxPrice = maxPrice;
|
|
}
|
|
|
|
if (int.TryParse(DefaultMaxClicks.Text, out var maxClicks))
|
|
{
|
|
settings.DefaultMaxClicks = maxClicks;
|
|
}
|
|
|
|
// === SEZIONE DEFAULTS: Limiti Log ===
|
|
if (int.TryParse(Settings.MaxLogLinesPerAuctionTextBox.Text, out var maxLogPerAuction) && maxLogPerAuction > 0)
|
|
{
|
|
settings.MaxLogLinesPerAuction = maxLogPerAuction;
|
|
}
|
|
else
|
|
{
|
|
Log("[ERRORE] Valore max log per asta non valido (deve essere > 0)", LogLevel.Error);
|
|
}
|
|
|
|
if (int.TryParse(Settings.MaxGlobalLogLinesTextBox.Text, out var maxGlobalLog) && maxGlobalLog > 0)
|
|
{
|
|
settings.MaxGlobalLogLines = maxGlobalLog;
|
|
}
|
|
else
|
|
{
|
|
Log("[ERRORE] Valore max log globale non valido (deve essere > 0)", LogLevel.Error);
|
|
}
|
|
|
|
// ?? NUOVO: Salva limite storia puntate
|
|
if (int.TryParse(Settings.MaxBidHistoryEntriesTextBox.Text, out var maxBidHistory) && maxBidHistory >= 0)
|
|
{
|
|
settings.MaxBidHistoryEntries = maxBidHistory;
|
|
|
|
if (maxBidHistory > 0)
|
|
{
|
|
Log($"[HISTORY] Impostato limite storia puntate: {maxBidHistory}", LogLevel.Info);
|
|
}
|
|
else
|
|
{
|
|
Log("[HISTORY] Limite storia puntate disabilitato (mostra tutte)", LogLevel.Info);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log("[ERRORE] Valore limite storia puntate non valido (deve essere >= 0)", LogLevel.Error);
|
|
}
|
|
|
|
// ?? NUOVO: Salva limite minimo puntate
|
|
if (int.TryParse(MinimumRemainingBidsTextBox.Text, out var minBids) && minBids >= 0)
|
|
{
|
|
settings.MinimumRemainingBids = minBids;
|
|
|
|
// Aggiorna indicatore visivo
|
|
UpdateMinBidsIndicator(minBids);
|
|
|
|
if (minBids > 0)
|
|
{
|
|
Log($"[LIMIT] Impostato limite minimo puntate: {minBids}", LogLevel.Info);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log("[ERRORE] Valore limite minimo puntate non valido (deve essere >= 0)", LogLevel.Error);
|
|
}
|
|
|
|
// ?? NUOVO: Salva livello log
|
|
var logLevelErrorOnly = Settings.FindName("LogLevelErrorOnly") as System.Windows.Controls.RadioButton;
|
|
var logLevelNormal = Settings.FindName("LogLevelNormal") as System.Windows.Controls.RadioButton;
|
|
var logLevelInformational = Settings.FindName("LogLevelInformational") as System.Windows.Controls.RadioButton;
|
|
var logLevelDebug = Settings.FindName("LogLevelDebug") as System.Windows.Controls.RadioButton;
|
|
var logLevelTrace = Settings.FindName("LogLevelTrace") as System.Windows.Controls.RadioButton;
|
|
|
|
string selectedLogLevel = "Normal"; // Default
|
|
if (logLevelErrorOnly?.IsChecked == true)
|
|
selectedLogLevel = "ErrorOnly";
|
|
else if (logLevelInformational?.IsChecked == true)
|
|
selectedLogLevel = "Informational";
|
|
else if (logLevelDebug?.IsChecked == true)
|
|
selectedLogLevel = "Debug";
|
|
else if (logLevelTrace?.IsChecked == true)
|
|
selectedLogLevel = "Trace";
|
|
else if (logLevelNormal?.IsChecked == true)
|
|
selectedLogLevel = "Normal";
|
|
|
|
settings.MinLogLevel = selectedLogLevel;
|
|
|
|
Log($"[LOG] Livello log impostato: {selectedLogLevel}", LogLevel.Info);
|
|
|
|
// === SEZIONE DEFAULTS: Stati Iniziali Aste ===
|
|
var loadAuctionsRemember = Settings.FindName("LoadAuctionsRemember") as System.Windows.Controls.RadioButton;
|
|
var loadAuctionsActive = Settings.FindName("LoadAuctionsActive") as System.Windows.Controls.RadioButton;
|
|
var loadAuctionsPaused = Settings.FindName("LoadAuctionsPaused") as System.Windows.Controls.RadioButton;
|
|
|
|
// ? NUOVO: Gestione "Ricorda Stato"
|
|
if (loadAuctionsRemember?.IsChecked == true)
|
|
{
|
|
// Attiva RememberAuctionStates
|
|
settings.RememberAuctionStates = true;
|
|
// DefaultStartAuctionsOnLoad diventa irrilevante, ma lo lasciamo a "Stopped" per compatibilit�
|
|
settings.DefaultStartAuctionsOnLoad = "Stopped";
|
|
}
|
|
else
|
|
{
|
|
// Disattiva RememberAuctionStates e usa DefaultStartAuctionsOnLoad
|
|
settings.RememberAuctionStates = false;
|
|
settings.DefaultStartAuctionsOnLoad = loadAuctionsActive?.IsChecked == true ? "Active" :
|
|
loadAuctionsPaused?.IsChecked == true ? "Paused" :
|
|
"Stopped";
|
|
}
|
|
|
|
var newAuctionActive = Settings.FindName("NewAuctionActive") as System.Windows.Controls.RadioButton;
|
|
var newAuctionPaused = Settings.FindName("NewAuctionPaused") as System.Windows.Controls.RadioButton;
|
|
|
|
settings.DefaultNewAuctionState = newAuctionActive?.IsChecked == true ? "Active" :
|
|
newAuctionPaused?.IsChecked == true ? "Paused" :
|
|
"Stopped";
|
|
|
|
// === SEZIONE: Motore di precisione ===
|
|
// Ogni cadenza ha un minimo sensato: sotto i 100 ms si spreca banda senza
|
|
// guadagnare precisione, perché la puntata la decide il cecchino, non il poll.
|
|
|
|
if (double.TryParse(Settings.MaxRequestsPerSecondTextBox.Text.Replace(',', '.'),
|
|
System.Globalization.NumberStyles.Any,
|
|
System.Globalization.CultureInfo.InvariantCulture, out var rps) && rps >= 1 && rps <= 200)
|
|
{
|
|
settings.MaxRequestsPerSecond = rps;
|
|
}
|
|
else
|
|
{
|
|
Log("[ERRORE] Limite richieste al secondo non valido (1-200): valore precedente mantenuto", LogLevel.Error);
|
|
}
|
|
|
|
settings.PrecisionTimerEnabled = Settings.PrecisionTimerCheckBox.IsChecked ?? true;
|
|
|
|
// === SEZIONE: Prodotti seguiti ===
|
|
settings.AutoAddProductsEnabled = Settings.AutoAddEnabledCheckBox.IsChecked ?? true;
|
|
settings.AutoAddNewAuctionState = Settings.AutoAddState;
|
|
settings.AutoAddScanSeconds = ReadBounded(Settings.AutoAddScanSecondsTextBox.Text,
|
|
settings.AutoAddScanSeconds, 30, 3600, "intervallo scansione prodotti seguiti", " s");
|
|
|
|
if (int.TryParse(Settings.AutoAddMaxAuctionsTextBox.Text, out var autoMax) && autoMax >= 0)
|
|
settings.AutoAddMaxAuctions = autoMax;
|
|
else
|
|
Log("[ERRORE] Massimo aste aggiunte automaticamente non valido (>= 0)", LogLevel.Error);
|
|
|
|
settings.AutoAddScanMaxAuctions = ReadBounded(Settings.AutoAddScanDepthTextBox.Text,
|
|
settings.AutoAddScanMaxAuctions, 50, 6000, "profondita ricerca prodotti seguiti", " aste");
|
|
|
|
// 0 = nessun orizzonte, ed e' una scelta vera: per questo il minimo del
|
|
// controllo di validita' e' zero e non uno.
|
|
settings.AutoAddOnlyNotStarted = Settings.AutoAddOnlyNotStartedCheckBox.IsChecked ?? false;
|
|
|
|
// Limiti consigliati per prodotto: due valori che entrano nel calcolo del
|
|
// tetto di prezzo. Si accetta la virgola oltre al punto, come nel resto
|
|
// dell'applicazione: chi scrive "0,20" non deve vedersi rifiutare il valore.
|
|
if (TryReadDouble(Settings.SuggestedCoverageTextBox.Text, out var copertura) &&
|
|
copertura is >= 10 and <= 99)
|
|
settings.SuggestedPriceCoveragePercent = copertura;
|
|
else
|
|
Log("[ERRORE] Copertura delle chiusure non valida (10-99%)", LogLevel.Error);
|
|
|
|
if (TryReadDouble(Settings.AverageBidCostTextBox.Text, out var costo) &&
|
|
costo is > 0 and <= 5)
|
|
settings.AverageBidCostEuro = costo;
|
|
else
|
|
Log("[ERRORE] Costo medio di una puntata non valido (0-5 EUR)", LogLevel.Error);
|
|
|
|
if (int.TryParse(Settings.AutoAddMaxStartMinutesTextBox.Text, out var orizzonte) && orizzonte >= 0)
|
|
settings.AutoAddMaxStartMinutes = orizzonte;
|
|
else
|
|
Log("[ERRORE] Orizzonte di aggiunta automatica non valido (>= 0 minuti)", LogLevel.Error);
|
|
|
|
if (settings.AutoAddProductsEnabled && settings.AutoAddNewAuctionState == "Active")
|
|
{
|
|
Log("[ATTENZIONE] Le aste seguite entreranno in stato Attiva: punteranno da sole, spendendo puntate reali.",
|
|
LogLevel.Warning);
|
|
}
|
|
|
|
// === SEZIONE: Catalogo ===
|
|
settings.CatalogMaxAuctions = ReadBounded(Settings.CatalogMaxAuctionsTextBox.Text,
|
|
settings.CatalogMaxAuctions, 20, 5000, "numero massimo aste catalogo", " aste");
|
|
settings.CatalogAutoRefresh = Settings.CatalogAutoRefreshCheckBox.IsChecked ?? false;
|
|
settings.CatalogCacheSeconds = ReadBounded(Settings.CatalogCacheSecondsTextBox.Text,
|
|
settings.CatalogCacheSeconds, 0, 3600, "cache catalogo", " s");
|
|
|
|
// === SEZIONE: Aste programmate ===
|
|
settings.ScheduledAuctionBackoffEnabled = Settings.ScheduledBackoffCheckBox.IsChecked ?? true;
|
|
settings.ScheduledPollFarSeconds = ReadBounded(Settings.ScheduledPollFarTextBox.Text,
|
|
settings.ScheduledPollFarSeconds, 60, 3600, "polling aste lontane", " s");
|
|
settings.ScheduledPollMidSeconds = ReadBounded(Settings.ScheduledPollMidTextBox.Text,
|
|
settings.ScheduledPollMidSeconds, 30, 1800, "polling aste vicine all'apertura", " s");
|
|
settings.ScheduledPollWakeSeconds = ReadBounded(Settings.ScheduledPollWakeTextBox.Text,
|
|
settings.ScheduledPollWakeSeconds, 30, 600, "margine di risveglio", " s");
|
|
|
|
// === SEZIONE: Notifiche ===
|
|
settings.NotifyOnWin = Settings.NotifyOnWinCheckBox.IsChecked ?? true;
|
|
settings.NotifyOnLoss = Settings.NotifyOnLossCheckBox.IsChecked ?? false;
|
|
|
|
// === SEZIONE: Cartelle dei dati ===
|
|
var previousDatabaseFolder = AppPaths.DatabaseFolder;
|
|
|
|
|
|
settings.QuietHoursEnabled = Settings.QuietHoursCheckBox.IsChecked ?? true;
|
|
if (int.TryParse(Settings.QuietHoursStartTextBox.Text?.Trim(), out var qStart) && qStart is >= 0 and <= 23)
|
|
settings.QuietHoursStart = qStart;
|
|
if (int.TryParse(Settings.QuietHoursEndTextBox.Text?.Trim(), out var qEnd) && qEnd is >= 0 and <= 24)
|
|
settings.QuietHoursEnd = qEnd;
|
|
|
|
settings.AutoRemoveFinished = Settings.AutoRemoveFinishedCheckBox.IsChecked ?? true;
|
|
settings.AutoRemoveKeepWithMyBids = Settings.AutoRemoveKeepMyBidsCheckBox.IsChecked ?? true;
|
|
settings.AutoRemoveKeepWon = Settings.AutoRemoveKeepWonCheckBox.IsChecked ?? true;
|
|
settings.AutoRemoveKeepUnclear = Settings.AutoRemoveKeepUnclearCheckBox.IsChecked ?? true;
|
|
|
|
settings.WriteAppLog = Settings.WriteAppLogCheckBox.IsChecked ?? true;
|
|
settings.WriteFreeBidsLog = Settings.WriteFreeBidsLogCheckBox.IsChecked ?? true;
|
|
settings.RecordAuctions = Settings.WriteDossiersCheckBox.IsChecked ?? true;
|
|
settings.RecordPolls = Settings.RawPollsCheckBox.IsChecked ?? true;
|
|
|
|
if (int.TryParse(Settings.LogRetentionTextBox.Text?.Trim(), out var retention) && retention >= 0)
|
|
settings.LogRetentionDays = retention;
|
|
|
|
settings.DatabaseFolder = NormalizeFolderChoice(Settings.DatabaseFolderTextBox.Text, AppPaths.DatabaseFolder, settings.DatabaseFolder);
|
|
|
|
settings.BankrollManagerEnabled = Settings.BankrollEnabledCheckBox.IsChecked ?? true;
|
|
settings.MaxBidsPerAuction = ReadBounded(Settings.MaxBidsPerAuctionTextBox.Text, settings.MaxBidsPerAuction, 0, 100000, "puntate massime per asta", "");
|
|
settings.MaxBidsPerSession = ReadBounded(Settings.MaxBidsPerSessionTextBox.Text, settings.MaxBidsPerSession, 0, 1000000, "puntate massime per sessione", "");
|
|
settings.DailyBudgetEuro = ReadEuro(Settings.DailyBudgetTextBox.Text, settings.DailyBudgetEuro, "tetto di spesa del giorno");
|
|
settings.DailyStopLossEuro = ReadEuro(Settings.DailyStopLossTextBox.Text, settings.DailyStopLossEuro, "stop-loss del giorno");
|
|
settings.MaxDrawdownEuro = ReadEuro(Settings.MaxDrawdownTextBox.Text, settings.MaxDrawdownEuro, "drawdown massimo");
|
|
settings.MaxConcurrentActiveAuctions = ReadBounded(Settings.MaxConcurrentTextBox.Text, settings.MaxConcurrentActiveAuctions, 0, 1000, "aste in gioco insieme", "");
|
|
settings.TransactionFeeEuro = ReadEuro(Settings.TransactionFeeTextBox.Text, settings.TransactionFeeEuro, "fee di transazione");
|
|
settings.BuyNowHedgeEnabled = Settings.HedgeCheckBox.IsChecked ?? true;
|
|
|
|
Utilities.SettingsManager.Save(settings);
|
|
|
|
ApplyDatabaseFolderSetting(settings, previousDatabaseFolder);
|
|
|
|
// Il limite di ritmo si applica a caldo, senza ricreare il trasporto.
|
|
_auctionMonitor.ApplyTransportSettings(settings);
|
|
ApplyProductWatchSettings(settings);
|
|
|
|
// L'interruttore in Esplora non deve dissentire dalle impostazioni, e il
|
|
// ciclo va fermato o riavviato di conseguenza.
|
|
Browser.SetAutoRefresh(settings.CatalogAutoRefresh);
|
|
if (settings.CatalogAutoRefresh) StartCatalogAutoRefresh();
|
|
else StopCatalogAutoRefresh();
|
|
RefreshEngineDiagnostics();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"[ERRORE] Salvataggio impostazioni: {ex.Message}", LogLevel.Error);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Legge un intero entro limiti, mantenendo il valore precedente se il testo non è
|
|
/// utilizzabile: un campo sbagliato non deve far ripartire il motore a caso.
|
|
/// </summary>
|
|
/// <summary>
|
|
/// Legge un numero con la virgola o col punto. Le impostazioni si scrivono a mano
|
|
/// in un'applicazione italiana: rifiutare "0,20" sarebbe un dispetto.
|
|
/// </summary>
|
|
private static bool TryReadDouble(string? text, out double value)
|
|
{
|
|
value = 0;
|
|
if (string.IsNullOrWhiteSpace(text)) return false;
|
|
|
|
return double.TryParse(text.Trim().Replace(',', '.'),
|
|
System.Globalization.NumberStyles.Any,
|
|
System.Globalization.CultureInfo.InvariantCulture, out value);
|
|
}
|
|
|
|
private int ReadBounded(string text, int current, int min, int max, string label, string unit = "ms")
|
|
{
|
|
if (int.TryParse(text, out var value) && value >= min && value <= max)
|
|
return value;
|
|
|
|
Log($"[ERRORE] Valore {label} non valido ({min}-{max}{unit}): mantenuto {current}{unit}", LogLevel.Error);
|
|
return current;
|
|
}
|
|
|
|
/// <summary>Un importo in euro, con virgola o punto; se non valido si tiene il valore attuale.</summary>
|
|
private double ReadEuro(string? text, double current, string label)
|
|
{
|
|
var t = (text ?? "").Trim().Replace(',', '.');
|
|
if (double.TryParse(t, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out var v) && v >= 0 && v < 1_000_000)
|
|
return Math.Round(v, 2);
|
|
|
|
Log($"[ERRORE] Valore {label} non valido: mantenuto {current:F2} €", LogLevel.Error);
|
|
return current;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Mostra lo stato del motore: aggancio all'orologio del server e traffico prodotto.
|
|
/// </summary>
|
|
private void RefreshEngineDiagnostics()
|
|
{
|
|
try
|
|
{
|
|
var box = Settings.EngineDiagnosticsText;
|
|
if (box == null) return;
|
|
|
|
var clock = _auctionMonitor.Clock;
|
|
var sent = _auctionMonitor.RequestsSent;
|
|
var failed = _auctionMonitor.RequestsFailed;
|
|
|
|
// Attenzione a come si presenta lo scarto: Bidoo dichiara i secondi interi,
|
|
// quindi i campioni si distribuiscono per forza su una finestra di circa
|
|
// 1000 ms. Non è l'errore della stima — la stima usa il minimo, che converge
|
|
// al confine reale del secondo. Un valore molto oltre i 1000 ms segnala
|
|
// invece una rete instabile.
|
|
var clockLine = clock.IsSynced
|
|
? $"Orologio server agganciato su {clock.SampleCount} campioni: le scadenze seguono il server, non l'orologio locale.\n" +
|
|
$"Finestra campioni {clock.SpreadMs:F0} ms (intorno a 1000 ms è normale: Bidoo dichiara i secondi interi)."
|
|
: $"Orologio server in sincronizzazione ({clock.SampleCount}/3 campioni): finché non è agganciato la scadenza è stimata localmente.";
|
|
|
|
box.Text = $"{clockLine}\n" +
|
|
$"Richieste inviate: {sent:N0} — fallite: {failed:N0}.\n" +
|
|
$"Motori attivi: {_auctionMonitor.ActiveRunners}.";
|
|
}
|
|
catch { /* la diagnostica non deve mai far fallire il salvataggio */ }
|
|
}
|
|
|
|
private void CancelDefaultsButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
// Ricarica defaults salvati
|
|
LoadDefaultSettings();
|
|
MessageBox.Show(this, "Impostazioni predefinite ripristinate.", "Annulla", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"[ERRORE] Ripristino impostazioni: {ex.Message}", LogLevel.Error);
|
|
MessageBox.Show(this, "Errore durante ripristino: " + ex.Message, "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
}
|
|
}
|
|
|
|
// === HANDLER PER PULSANTI UNIFICATI ===
|
|
|
|
private void SaveAllSettings_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
// Salva tutte le impostazioni (ora solo defaults, export rimosso)
|
|
SaveDefaultsButton_Click(sender, e);
|
|
|
|
MessageBox.Show(
|
|
"Tutte le impostazioni sono state salvate con successo.\n\nLe nuove impostazioni verranno applicate alle aste future.",
|
|
"Impostazioni Salvate",
|
|
MessageBoxButton.OK,
|
|
MessageBoxImage.Information
|
|
);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"[ERRORE] Salvataggio impostazioni: {ex.Message}", LogLevel.Error);
|
|
MessageBox.Show(this, "Errore durante salvataggio: " + ex.Message, "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
}
|
|
}
|
|
|
|
private void CancelAllSettings_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
// Annulla tutte le modifiche
|
|
LoadDefaultSettings();
|
|
MessageBox.Show(this, "Impostazioni ripristinate alle ultime salvate.", "Annulla", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"[ERRORE] Ripristino impostazioni: {ex.Message}", LogLevel.Error);
|
|
MessageBox.Show(this, "Errore durante ripristino: " + ex.Message, "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
}
|
|
}
|
|
}
|
|
}
|