@@ -295,7 +295,14 @@
private List categories = new();
private List auctions = new();
private List filteredAuctions = new();
-private int selectedCategoryIndex = 0;
+
+// 🔥 Usa stato persistente da AppState
+private int selectedCategoryIndex
+{
+ get => AppState.BrowserCategoryIndex;
+ set => AppState.BrowserCategoryIndex = value;
+}
+
private int currentPage = 0;
private bool isLoading = false;
@@ -303,8 +310,12 @@ private bool isLoadingMore = false;
private bool canLoadMore = true;
private string? errorMessage = null;
-// ? NUOVO: Ricerca
-private string searchQuery = "";
+// 🔥 Usa stato persistente per la ricerca
+private string searchQuery
+{
+ get => AppState.BrowserSearchQuery;
+ set => AppState.BrowserSearchQuery = value;
+}
private System.Threading.Timer? stateUpdateTimer;
private CancellationTokenSource? cts;
@@ -314,9 +325,20 @@ private bool isUpdatingInBackground = false;
{
await LoadCategories();
+ // 🔥 Se c'è una categoria salvata, carica le aste
if (categories.Count > 0)
{
- await LoadAuctions();
+ // Se selectedCategoryIndex è valido, carica quella categoria
+ if (selectedCategoryIndex >= 0 && selectedCategoryIndex < categories.Count)
+ {
+ await LoadAuctions();
+ }
+ else
+ {
+ // Altrimenti carica la prima categoria
+ selectedCategoryIndex = 0;
+ await LoadAuctions();
+ }
}
// Auto-update states every 500ms for real-time price updates
@@ -532,9 +554,30 @@ private bool isUpdatingInBackground = false;
{
if (browserAuction.IsMonitored) return;
- // ?? Carica impostazioni di default
+ // 🔥 Carica impostazioni di default
var settings = AutoBidder.Utilities.SettingsManager.Load();
+ // 🔥 Determina stato iniziale da impostazioni
+ bool isActive = false;
+ bool isPaused = false;
+
+ switch (settings.DefaultNewAuctionState)
+ {
+ case "Active":
+ isActive = true;
+ isPaused = false;
+ break;
+ case "Paused":
+ isActive = true;
+ isPaused = true;
+ break;
+ case "Stopped":
+ default:
+ isActive = false;
+ isPaused = false;
+ break;
+ }
+
var auctionInfo = new AuctionInfo
{
AuctionId = browserAuction.AuctionId,
@@ -542,7 +585,7 @@ private bool isUpdatingInBackground = false;
OriginalUrl = browserAuction.Url,
BuyNowPrice = (double)browserAuction.BuyNowPrice,
- // ?? FIX: Applica valori dalle impostazioni
+ // 🔥 Applica valori dalle impostazioni
BidBeforeDeadlineMs = settings.DefaultBidBeforeDeadlineMs,
CheckAuctionOpenBeforeBid = settings.DefaultCheckAuctionOpenBeforeBid,
MinPrice = settings.DefaultMinPrice,
@@ -550,8 +593,9 @@ private bool isUpdatingInBackground = false;
MinResets = settings.DefaultMinResets,
MaxResets = settings.DefaultMaxResets,
- IsActive = true,
- IsPaused = true, // Start paused
+ // 🔥 Usa stato da impostazioni invece di hardcoded
+ IsActive = isActive,
+ IsPaused = isPaused,
AddedAt = DateTime.UtcNow
};
@@ -565,7 +609,7 @@ private bool isUpdatingInBackground = false;
// Save to disk
AutoBidder.Utilities.PersistenceManager.SaveAuctions(AppState.Auctions.ToList());
- // ?? FIX CRITICO: Avvia il monitor se non gi attivo
+ // ?? FIX CRITICO: Avvia il monitor se non è già attivo
if (!AppState.IsMonitoringActive)
{
AuctionMonitor.Start();
diff --git a/Mimante/Pages/Settings.razor b/Mimante/Pages/Settings.razor
index 40beaa9..816fd4a 100644
--- a/Mimante/Pages/Settings.razor
+++ b/Mimante/Pages/Settings.razor
@@ -181,82 +181,12 @@
-
Timing & Latenza
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ Nota: Le strategie decidono SE puntare, non QUANDO.
+ Il timing controllato solo dall'impostazione "Anticipo puntata" nelle Impostazioni Predefinite.
-
Strategia Anti-AutoBid Bidoo
-
-
- Come funziona: Bidoo ha un sistema di auto-puntata integrato che si attiva a ~2 secondi.
- Aspettando che il timer scenda sotto questa soglia, lasciamo che gli utenti con auto-puntata attiva
- puntino prima di noi, risparmiando puntate.
-
-
-
-
-
-
-
-
-
-
-
-
Punta solo quando timer < questo valore (default: 1.8s)
-
-
-
-
-
-
-
-
-
Rilevamento Competizione
@@ -287,6 +217,27 @@
+
Controllo Convenienza
+
+
+
+
+
+
+
+
+
+
+
+ Negativo = tolleranza perdita. Es: -5 = permetti fino al 5% di perdita
+ Positivo = richiede risparmio. Es: 10 = richiedi almeno 10% di risparmio
+
+
+
+
Soft Retreat
@@ -308,6 +259,7 @@
+
Puntata Probabilistica
@@ -421,25 +373,113 @@
-
+
-
+
Cosa Loggare
+
Scegli quali informazioni visualizzare nei log delle aste.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Debug Avanzato
+
+
+ Attenzione: Queste opzioni generano molti log. Attiva solo per debug!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Limiti
+
+
-
+
-
+
diff --git a/Mimante/Services/ApplicationStateService.cs b/Mimante/Services/ApplicationStateService.cs
index 2ce728d..4da96e1 100644
--- a/Mimante/Services/ApplicationStateService.cs
+++ b/Mimante/Services/ApplicationStateService.cs
@@ -148,6 +148,47 @@ namespace AutoBidder.Services
}
}
+ // === STATO AUCTION BROWSER ===
+
+ private int _browserCategoryIndex = 0;
+ private string _browserSearchQuery = "";
+
+ public int BrowserCategoryIndex
+ {
+ get
+ {
+ lock (_lock)
+ {
+ return _browserCategoryIndex;
+ }
+ }
+ set
+ {
+ lock (_lock)
+ {
+ _browserCategoryIndex = value;
+ }
+ }
+ }
+
+ public string BrowserSearchQuery
+ {
+ get
+ {
+ lock (_lock)
+ {
+ return _browserSearchQuery;
+ }
+ }
+ set
+ {
+ lock (_lock)
+ {
+ _browserSearchQuery = value;
+ }
+ }
+ }
+
// === METODI GESTIONE ASTE ===
public void SetAuctions(List auctions)
diff --git a/Mimante/Services/AuctionMonitor.cs b/Mimante/Services/AuctionMonitor.cs
index f47986d..a8b9439 100644
--- a/Mimante/Services/AuctionMonitor.cs
+++ b/Mimante/Services/AuctionMonitor.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -98,7 +98,7 @@ namespace AutoBidder.Services
if (!_auctions.Any(a => a.AuctionId == auction.AuctionId))
{
_auctions.Add(auction);
- // ? RIMOSSO: Log ridondante - viene già loggato da MainWindow con defaults e stato
+ // ? RIMOSSO: Log ridondante - viene gi loggato da MainWindow con defaults e stato
// OnLog?.Invoke($"[+] Asta aggiunta: {auction.Name} (ID: {auction.AuctionId})");
}
}
@@ -111,20 +111,20 @@ namespace AutoBidder.Services
var auction = _auctions.FirstOrDefault(a => a.AuctionId == auctionId);
if (auction != null)
{
- // ?? Se l'asta è terminata, salva le statistiche prima di rimuoverla
+ // ?? Se l'asta terminata, salva le statistiche prima di rimuoverla
if (!auction.IsActive && auction.LastState != null)
{
OnLog?.Invoke($"[STATS] Asta terminata rilevata: {auction.Name} - Salvataggio statistiche in corso...");
try
{
- // Determina se è stata vinta dall'utente
+ // Determina se stata vinta dall'utente
var won = IsAuctionWonByUser(auction);
OnLog?.Invoke($"[STATS] Asta {auction.Name} - Stato: {(won ? "VINTA" : "PERSA")}");
// Emetti evento per salvare le statistiche
- // Questo trigger sarà gestito in Program.cs con scraping HTML
+ // Questo trigger sar gestito in Program.cs con scraping HTML
OnAuctionCompleted?.Invoke(auction, auction.LastState, won);
}
catch (Exception ex)
@@ -143,7 +143,7 @@ namespace AutoBidder.Services
}
///
- /// Determina se l'asta è stata vinta dall'utente corrente
+ /// Determina se l'asta stata vinta dall'utente corrente
///
private bool IsAuctionWonByUser(AuctionInfo auction)
{
@@ -154,7 +154,7 @@ namespace AutoBidder.Services
if (string.IsNullOrEmpty(username)) return false;
- // Controlla se l'ultimo puntatore è l'utente
+ // Controlla se l'ultimo puntatore l'utente
return auction.LastState.LastBidder?.Equals(username, StringComparison.OrdinalIgnoreCase) == true;
}
@@ -288,26 +288,41 @@ namespace AutoBidder.Services
continue;
}
- // Delay adattivo OTTIMIZZATO basato su timer più basso
- var lowestTimer = activeAuctions
- .Select(a => GetLastTimer(a))
- .Where(t => t > 0)
- .DefaultIfEmpty(999)
- .Min();
-
- int delayMs = lowestTimer switch
+ // ?? Delay adattivo ULTRA-OTTIMIZZATO
+ // Considera l'offset target pi basso tra tutte le aste attive
+ var settings = Utilities.SettingsManager.Load();
+
+ // ?? Polling VELOCE quando vicino alla scadenza
+ int minDelayMs = 500;
+
+ foreach (var a in activeAuctions)
{
- < 0.5 => 10, // Ultra-critico: polling ogni 10ms
- < 1 => 20, // Iper-veloce: polling ogni 20ms
- < 2 => 50, // Ultra-veloce: polling ogni 50ms
- < 3 => 100, // Molto veloce: polling ogni 100ms
- < 5 => 150, // Veloce: polling ogni 150ms
- < 10 => 300, // Medio: polling ogni 300ms
- < 30 => 500, // Lento: polling ogni 500ms
- _ => 1000 // Molto lento: polling ogni 1s
- };
-
- await Task.Delay(delayMs, token);
+ if (a.IsPaused || !a.LastDeadlineUpdateUtc.HasValue) continue;
+
+ // Calcola tempo stimato rimanente
+ var elapsed = (DateTime.UtcNow - a.LastDeadlineUpdateUtc.Value).TotalMilliseconds;
+ double estimatedRemaining = a.LastRawTimer - elapsed;
+
+ int offsetMs = a.BidBeforeDeadlineMs > 0
+ ? a.BidBeforeDeadlineMs
+ : settings.DefaultBidBeforeDeadlineMs;
+
+ double msToTarget = estimatedRemaining - offsetMs;
+
+ // Polling pi veloce quando vicino al target
+ int delay;
+ if (msToTarget < 100) delay = 5;
+ else if (msToTarget < 300) delay = 10;
+ else if (msToTarget < 500) delay = 20;
+ else if (msToTarget < 1000) delay = 50;
+ else if (msToTarget < 2000) delay = 100;
+ else if (msToTarget < 5000) delay = 200;
+ else delay = 400;
+
+ minDelayMs = Math.Min(minDelayMs, delay);
+ }
+
+ await Task.Delay(minDelayMs, token);
}
catch (OperationCanceledException)
{
@@ -355,7 +370,7 @@ namespace AutoBidder.Services
// ?? Aggiorna latenza con storico
auction.AddLatencyMeasurement(state.PollingLatencyMs);
- // ?? Segna tracking dall'inizio se è la prima volta
+ // ?? Segna tracking dall'inizio se la prima volta
if (!auction.IsTrackedFromStart && auction.BidHistory.Count == 0)
{
auction.IsTrackedFromStart = true;
@@ -368,7 +383,7 @@ namespace AutoBidder.Services
MergeBidHistory(auction, state.RecentBidsHistory);
}
- // 🔥 FIX: Aggiungi SEMPRE l'ultima puntata corrente allo storico (durante il monitoraggio)
+ // ?? FIX: Aggiungi SEMPRE l'ultima puntata corrente allo storico (durante il monitoraggio)
// Questo assicura che la puntata vincente sia sempre inclusa
if (!string.IsNullOrEmpty(state.LastBidder) && state.Price > 0)
{
@@ -391,7 +406,7 @@ namespace AutoBidder.Services
var lastBidTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var statePrice = (decimal)state.Price;
- // Verifica se questa puntata non è già presente
+ // Verifica se questa puntata non gi presente
var alreadyExists = auction.RecentBids.Any(b =>
Math.Abs(b.Price - statePrice) < 0.001m &&
b.Username.Equals(state.LastBidder, StringComparison.OrdinalIgnoreCase));
@@ -441,7 +456,7 @@ namespace AutoBidder.Services
if (state.Status == AuctionStatus.Running)
{
- // Log RIMOSSO per ridurre verbosità - polling continuo non necessita log
+ // Log RIMOSSO per ridurre verbosit - polling continuo non necessita log
// Solo eventi importanti (bid, reset, errori) vengono loggati
}
else if (state.Status == AuctionStatus.Paused)
@@ -457,11 +472,15 @@ namespace AutoBidder.Services
OnAuctionUpdated?.Invoke(state);
UpdateAuctionHistory(auction, state);
+
+
// ?? NUOVO: Calcola e aggiorna valore prodotto se disponibili dati
if (auction.BuyNowPrice.HasValue || auction.ShippingCost.HasValue)
{
try
{
+ var settings = Utilities.SettingsManager.Load();
+
var productValue = Utilities.ProductValueCalculator.Calculate(
auction,
state.Price,
@@ -470,9 +489,9 @@ namespace AutoBidder.Services
auction.CalculatedValue = productValue;
- // Log valore solo se cambia significativamente o ogni 10 polling
+ // Log valore solo se abilitato nelle impostazioni
bool shouldLogValue = false;
- if (auction.PollingLatencyMs % 10 == 0) // Ogni ~10 poll
+ if (settings.LogValueCalculations && auction.PollingLatencyMs % 10 == 0) // Ogni ~10 poll
{
shouldLogValue = true;
}
@@ -490,13 +509,10 @@ namespace AutoBidder.Services
}
}
- // NUOVA LOGICA: Punta solo se siamo vicini alla deadline E nessun altro ha appena puntato
- if (state.Status == AuctionStatus.Running && !auction.IsPaused && !auction.IsAttackInProgress)
+ // ?? NUOVA LOGICA: Controlla PRIMA il timing, POI le strategie
+ if (state.Status == AuctionStatus.Running && !auction.IsPaused)
{
- if (ShouldBid(auction, state))
- {
- await ExecuteBidStrategy(auction, state, token);
- }
+ await TryPlaceBid(auction, state, token);
}
}
catch (Exception ex)
@@ -507,116 +523,112 @@ namespace AutoBidder.Services
}
///
- /// Strategia di puntata SEMPLIFICATA
- /// Le strategie restituiscono solo un booleano (posso puntare?).
- /// Il timing è sempre fisso: offset globale o override per asta.
+ /// Sistema di puntata SEMPLICE.
+ /// Punta esattamente a `offset` ms dalla scadenza.
+ /// Le strategie decidono SE puntare, non QUANDO.
///
- private async Task ExecuteBidStrategy(AuctionInfo auction, AuctionState state, CancellationToken token)
+ private async Task TryPlaceBid(AuctionInfo auction, AuctionState state, CancellationToken token)
{
var settings = SettingsManager.Load();
- // Calcola il tempo rimanente in millisecondi
- double timerMs = state.Timer * 1000;
-
- // 🛑 CONTROLLO: Se sono già il vincitore, non fare nulla
- if (state.IsMyBid)
- {
- return;
- }
-
- // 🎯 OFFSET FISSO: Usa override asta se impostato, altrimenti globale
- int fixedOffsetMs = auction.BidBeforeDeadlineMs > 0
+ // Offset: millisecondi prima della scadenza
+ int offsetMs = auction.BidBeforeDeadlineMs > 0
? auction.BidBeforeDeadlineMs
: settings.DefaultBidBeforeDeadlineMs;
- // 🧠 VALUTAZIONE STRATEGIE (restituiscono solo bool)
- var session = _apiClient.GetSession();
- var currentUsername = session?.Username ?? "";
+ // Timer dall'API (in secondi, convertito in ms)
+ double timerMs = state.Timer * 1000;
- // Verifica se le strategie permettono di puntare
- var decision = _bidStrategy.ShouldPlaceBid(auction, state, settings, currentUsername);
+ // Skip se gi vincitore o timer scaduto
+ if (state.IsMyBid || timerMs <= 0) return;
- if (!decision.ShouldBid)
+ // ?? STIMA TEMPO RIMANENTE
+ // L'API d timer in secondi interi (1000, 2000, ecc.)
+ // Quando cambia, salvo il timestamp. Poi stimo localmente.
+
+ bool timerChanged = Math.Abs(auction.LastRawTimer - timerMs) > 500;
+
+ if (timerChanged || !auction.LastDeadlineUpdateUtc.HasValue)
+ {
+ auction.LastRawTimer = timerMs;
+ auction.LastDeadlineUpdateUtc = DateTime.UtcNow;
+ auction.BidScheduled = false;
+ }
+
+ // Calcola tempo stimato rimanente
+ var elapsed = (DateTime.UtcNow - auction.LastDeadlineUpdateUtc.Value).TotalMilliseconds;
+ double estimatedRemaining = timerMs - elapsed;
+
+ // Log timing solo se abilitato
+ if (settings.LogTiming)
+ {
+ auction.AddLog($"[TIMING] API={timerMs:F0}ms, Elapsed={elapsed:F0}ms, Stima={estimatedRemaining:F0}ms");
+ }
+
+ // ?? il momento di puntare?
+ // LOGICA MIGLIORATA: Punta quando siamo nell'ultimo secondo E stima <= offset
+ // Ma con un fallback: se timer=1000ms e stima < 300ms, punta comunque!
+ // Questo evita di perdere l'asta per polling troppo lento
+
+ bool shouldBidNow = false;
+
+ if (timerMs <= 1000 && timerMs > 0)
+ {
+ // Siamo nell'ultimo secondo
+ if (estimatedRemaining <= offsetMs)
+ {
+ // Stima sotto l'offset: PUNTA!
+ shouldBidNow = true;
+ }
+ else if (estimatedRemaining <= 300)
+ {
+ // Fallback: stima < 300ms ma sopra offset?
+ // Punta comunque per sicurezza (meglio puntare "presto" che perdere l'asta)
+ shouldBidNow = true;
+ }
+ }
+ else if (estimatedRemaining <= offsetMs && estimatedRemaining > -200)
+ {
+ // Logica originale per timer > 1 secondo
+ shouldBidNow = true;
+ }
+
+ if (!shouldBidNow) return;
+
+ // Protezione doppia puntata
+ if (auction.BidScheduled) return;
+
+ // Cooldown 1 secondo
+ if (auction.LastClickAt.HasValue && (DateTime.UtcNow - auction.LastClickAt.Value).TotalMilliseconds < 1000) return;
+
+ // ?? CONTROLLI FONDAMENTALI (prezzo, reset, limiti, puntate residue)
+ if (!ShouldBid(auction, state))
{
- auction.AddLog($"[STRATEGY] {decision.Reason}");
return;
}
- // 🕐 TIMER-BASED SCHEDULING (offset fisso, niente calcoli dinamici)
- if (timerMs > fixedOffsetMs)
+ // ? MOMENTO GIUSTO! Verifica strategie avanzate
+ var session = _apiClient.GetSession();
+ var decision = _bidStrategy.ShouldPlaceBid(auction, state, settings, session?.Username ?? "");
+
+ if (!decision.ShouldBid)
{
- // Timer ancora alto - Schedula puntata futura
- double delayMs = timerMs - fixedOffsetMs;
-
- // Non schedulare se già c'è un task attivo per questa asta
- if (auction.IsAttackInProgress)
+ if (settings.LogStrategyDecisions)
{
- return;
+ auction.AddLog($"[STRATEGY] {decision.Reason}");
}
-
- auction.IsAttackInProgress = true;
- auction.LastUsedOffsetMs = fixedOffsetMs;
-
- auction.AddLog($"[TIMING] Timer={timerMs:F0}ms - Puntata tra {delayMs:F0}ms (offset fisso={fixedOffsetMs}ms)");
-
- // Avvia task asincrono che attende e poi punta
- _ = Task.Run(async () =>
- {
- try
- {
- // Attendi il momento esatto
- await Task.Delay((int)delayMs, token);
-
- // Verifica che l'asta sia ancora attiva e non in pausa
- if (!auction.IsActive || auction.IsPaused || token.IsCancellationRequested)
- {
- auction.AddLog($"[TIMING] Task annullato (asta inattiva/pausa)");
- return;
- }
-
- auction.AddLog($"[TIMING] Eseguo puntata!");
-
- // Esegui la puntata
- await ExecuteBid(auction, state, token);
- }
- catch (OperationCanceledException)
- {
- auction.AddLog($"[TIMING] Task cancellato");
- }
- catch (Exception ex)
- {
- auction.AddLog($"[TIMING ERROR] {ex.Message}");
- }
- finally
- {
- auction.IsAttackInProgress = false;
- }
- }, token);
+ return;
}
- else if (timerMs > 0 && timerMs <= fixedOffsetMs)
+
+ // ?? PUNTA!
+ auction.BidScheduled = true;
+
+ if (settings.LogBids)
{
- // Timer già nella finestra - Punta SUBITO senza delay
- if (auction.IsAttackInProgress)
- {
- return;
- }
-
- auction.IsAttackInProgress = true;
- auction.LastUsedOffsetMs = fixedOffsetMs;
-
- try
- {
- auction.AddLog($"[TIMING] Timer in finestra ({timerMs:F0}ms <= {fixedOffsetMs}ms) - PUNTA SUBITO!");
-
- // Esegui la puntata
- await ExecuteBid(auction, state, token);
- }
- finally
- {
- auction.IsAttackInProgress = false;
- }
+ auction.AddLog($"[BID] Puntata a ~{estimatedRemaining:F0}ms dalla scadenza");
}
- // Se timer <= 0, asta già scaduta - Non fare nulla
+
+ await ExecuteBid(auction, state, token);
}
///
@@ -639,7 +651,7 @@ namespace AutoBidder.Services
_bidStrategy.RecordTimerExpired(auction);
}
- // 🔥 FIX: Aggiorna contatore puntate
+ // ?? FIX: Aggiorna contatore puntate
if (result.Success)
{
if (result.RemainingBids.HasValue)
@@ -681,7 +693,7 @@ namespace AutoBidder.Services
Timer = state.Timer,
LatencyMs = result.LatencyMs,
Success = result.Success,
- Notes = result.Success ? $"EUR{result.NewPrice:F2}" : result.Error
+ Notes = result.Success ? $"EUR{result.NewPrice:F2}" : (result.Error ?? "Errore sconosciuto")
});
}
catch (Exception ex)
@@ -695,40 +707,33 @@ namespace AutoBidder.Services
{
var settings = Utilities.SettingsManager.Load();
- // ?? CONTROLLO ANTI-AUTOBID BIDOO (PRIORITÀ MASSIMA)
- // Bidoo ha un sistema di auto-puntata che si attiva a ~2 secondi.
- // Aspettiamo che il timer scenda sotto la soglia per lasciare che
- // gli altri utenti con auto-puntata attiva puntino prima di noi.
- // Questo ci fa risparmiare puntate perché non puntiamo "troppo presto".
- if (settings.WaitForAutoBidEnabled && state.Timer > settings.WaitForAutoBidThresholdSeconds)
+ if (settings.LogTiming)
{
- // Timer ancora sopra la soglia - aspetta che le auto-puntate si attivino
- if (settings.LogAutoBidWaitSkips)
- {
- auction.AddLog($"[AUTOBID] Timer {state.Timer:F2}s > soglia {settings.WaitForAutoBidThresholdSeconds}s - Aspetto auto-puntate Bidoo");
- }
- return false;
+ auction.AddLog($"[DEBUG] === INIZIO CONTROLLI PUNTATA ===");
}
- // ?? CONTROLLO 0: Verifica convenienza (se dati disponibili)
- // IMPORTANTE: Applica solo se BuyNowPrice è valido (> 0)
- // Se BuyNowPrice == 0, significa errore scraping - non bloccare le puntate
- if (auction.BuyNowPrice.HasValue &&
+ // ?? CONTROLLO 0: Verifica convenienza (se abilitato e dati disponibili)
+ if (settings.ValueCheckEnabled &&
+ auction.BuyNowPrice.HasValue &&
auction.BuyNowPrice.Value > 0 &&
auction.CalculatedValue != null &&
auction.CalculatedValue.Savings.HasValue &&
!auction.CalculatedValue.IsWorthIt)
{
- // Permetti comunque di puntare se il risparmio è ancora positivo (anche se piccolo)
- // Blocca solo se sta andando in perdita significativa (< -5%)
+ // Usa la percentuale configurabile dall'utente
if (auction.CalculatedValue.SavingsPercentage.HasValue &&
- auction.CalculatedValue.SavingsPercentage.Value < -5)
+ auction.CalculatedValue.SavingsPercentage.Value < settings.MinSavingsPercentage)
{
- auction.AddLog($"[VALUE] Puntata bloccata: perdita {auction.CalculatedValue.SavingsPercentage.Value:F1}% (troppo costoso)");
+ auction.AddLog($"[VALUE] Puntata bloccata: risparmio {auction.CalculatedValue.SavingsPercentage.Value:F1}% < {settings.MinSavingsPercentage:F1}% richiesto");
return false;
}
}
+ if (settings.LogTiming && settings.ValueCheckEnabled)
+ {
+ auction.AddLog($"[DEBUG] ? Controllo convenienza OK");
+ }
+
// ?? CONTROLLO ANTI-COLLISIONE: Rileva aste troppo "affollate"
// Se negli ultimi 10 secondi ci sono state 3+ puntate di utenti diversi, evita
var recentBidsThreshold = 10; // secondi
@@ -746,9 +751,14 @@ namespace AutoBidder.Services
.Distinct(StringComparer.OrdinalIgnoreCase)
.Count();
+ if (settings.LogTiming)
+ {
+ auction.AddLog($"[DEBUG] Bidder attivi ultimi {recentBidsThreshold}s: {activeBidders}/{maxActiveBidders}");
+ }
+
if (activeBidders >= maxActiveBidders)
{
- // Controlla se l'ultimo bidder sono io - se sì, posso continuare
+ // Controlla se l'ultimo bidder sono io - se s, posso continuare
var session = _apiClient.GetSession();
var lastBid = recentBids.OrderByDescending(b => b.Timestamp).FirstOrDefault();
@@ -762,6 +772,11 @@ namespace AutoBidder.Services
}
catch { /* Ignora errori nel controllo competizione */ }
+ if (settings.LogTiming)
+ {
+ auction.AddLog($"[DEBUG] ? Controllo competizione OK");
+ }
+
// ?? CONTROLLO 1: Limite minimo puntate residue
if (settings.MinimumRemainingBids > 0)
{
@@ -771,27 +786,44 @@ namespace AutoBidder.Services
auction.AddLog($"[LIMIT] Puntata bloccata: puntate residue ({session.RemainingBids}) <= limite ({settings.MinimumRemainingBids})");
return false;
}
+
+ if (settings.LogTiming && session != null)
+ {
+ auction.AddLog($"[DEBUG] ? Puntate residue OK ({session.RemainingBids} > {settings.MinimumRemainingBids})");
+ }
}
- // ? CONTROLLO 2: Non puntare se sono già il vincitore corrente
+ // ? CONTROLLO 2: Non puntare se sono gi il vincitore corrente
if (state.IsMyBid)
{
+ if (settings.LogTiming)
+ {
+ auction.AddLog($"[DEBUG] Sono gi vincitore");
+ }
return false;
}
// ?? CONTROLLO 3: MinPrice/MaxPrice
if (auction.MinPrice > 0 && state.Price < auction.MinPrice)
{
- auction.AddLog($"[PRICE] Prezzo troppo basso: €{state.Price:F2} < Min €{auction.MinPrice:F2}");
+ auction.AddLog($"[PRICE] Prezzo troppo basso: {state.Price:F2} < Min {auction.MinPrice:F2}");
return false;
}
if (auction.MaxPrice > 0 && state.Price > auction.MaxPrice)
{
- auction.AddLog($"[PRICE] Prezzo troppo alto: €{state.Price:F2} > Max €{auction.MaxPrice:F2}");
+ auction.AddLog($"[PRICE] Prezzo troppo alto: {state.Price:F2} > Max {auction.MaxPrice:F2}");
return false;
}
+ if (settings.LogTiming)
+ {
+ if (auction.MinPrice > 0 || auction.MaxPrice > 0)
+ {
+ auction.AddLog($"[DEBUG] ? Range prezzo OK ({state.Price:F2} in [{auction.MinPrice:F2}, {auction.MaxPrice:F2}])");
+ }
+ }
+
// ?? CONTROLLO 4: MinResets/MaxResets
if (auction.MinResets > 0 && auction.ResetCount < auction.MinResets)
{
@@ -805,16 +837,34 @@ namespace AutoBidder.Services
return false;
}
+ if (settings.LogTiming)
+ {
+ if (auction.MinResets > 0 || auction.MaxResets > 0)
+ {
+ auction.AddLog($"[DEBUG] ? Range reset OK ({auction.ResetCount} in [{auction.MinResets}, {auction.MaxResets}])");
+ }
+ }
+
+
// ?? CONTROLLO 6: Cooldown (evita puntate multiple ravvicinate)
if (auction.LastClickAt.HasValue)
{
var timeSinceLastClick = DateTime.UtcNow - auction.LastClickAt.Value;
if (timeSinceLastClick.TotalMilliseconds < 800)
{
+ if (settings.LogTiming)
+ {
+ auction.AddLog($"[DEBUG] Cooldown attivo ({timeSinceLastClick.TotalMilliseconds:F0}ms < 800ms)");
+ }
return false;
}
}
+ if (settings.LogTiming)
+ {
+ auction.AddLog($"[DEBUG] === TUTTI I CONTROLLI SUPERATI ===");
+ }
+
return true;
}
@@ -867,20 +917,8 @@ namespace AutoBidder.Services
Notes = $"Puntata: EUR{state.Price:F2}"
});
- // Aggiorna statistiche bidder
- if (!string.IsNullOrEmpty(state.LastBidder))
- {
- if (!auction.BidderStats.ContainsKey(state.LastBidder))
- {
- auction.BidderStats[state.LastBidder] = new BidderInfo
- {
- Username = state.LastBidder
- };
- }
-
- auction.BidderStats[state.LastBidder].BidCount++;
- auction.BidderStats[state.LastBidder].LastBidTime = DateTime.UtcNow;
- }
+ // ? RIMOSSO: Non incrementare qui - gi gestito da UpdateBidderStatsFromRecentBids
+ // L'incremento doppio causava conteggi gonfiati
OnResetCountChanged?.Invoke(auction.AuctionId);
}
@@ -894,8 +932,8 @@ namespace AutoBidder.Services
///
/// Unisce la storia puntate ricevuta dall'API con quella esistente,
- /// mantenendo le puntate più vecchie e aggiungendo solo le nuove.
- /// Le puntate sono ordinate con le più RECENTI in CIMA.
+ /// mantenendo le puntate pi vecchie e aggiungendo solo le nuove.
+ /// Le puntate sono ordinate con le pi RECENTI in CIMA.
///
private void MergeBidHistory(AuctionInfo auction, List newBids)
{
@@ -908,7 +946,7 @@ namespace AutoBidder.Services
// ?? FIX: Usa lock per thread-safety
lock (auction.RecentBids)
{
- // Se la lista esistente è vuota, semplicemente copia le nuove
+ // Se la lista esistente vuota, semplicemente copia le nuove
if (auction.RecentBids.Count == 0)
{
auction.RecentBids = newBids.ToList();
@@ -953,7 +991,7 @@ namespace AutoBidder.Services
.ThenByDescending(b => b.Price)
.ToList();
- // Limita al numero massimo di puntate (mantieni le più recenti = prime della lista)
+ // Limita al numero massimo di puntate (mantieni le pi recenti = prime della lista)
if (maxEntries > 0 && auction.RecentBids.Count > maxEntries)
{
auction.RecentBids = auction.RecentBids
@@ -975,7 +1013,7 @@ namespace AutoBidder.Services
///
/// Assicura che la puntata corrente (quella vincente) sia sempre presente nello storico.
/// Questo risolve il problema dell'API che a volte non include l'ultima puntata.
- /// IMPORTANTE: Aggiunge solo se è una NUOVA puntata (prezzo/utente diverso dall'ultima registrata).
+ /// IMPORTANTE: Aggiunge solo se una NUOVA puntata (prezzo/utente diverso dall'ultima registrata).
///
private void EnsureCurrentBidInHistory(AuctionInfo auction, AuctionState state)
{
@@ -984,22 +1022,18 @@ namespace AutoBidder.Services
var statePrice = (decimal)state.Price;
var currentBidder = state.LastBidder;
- // 🔥 VERIFICA: Controlla se è la stessa puntata che abbiamo già in cima
- // Evitiamo di aggiungere continuamente la stessa puntata ad ogni polling
- if (auction.RecentBids.Count > 0)
+ // ?? VERIFICA: Controlla se questa puntata gi presente nella lista
+ // Evitiamo duplicati controllando prezzo + utente in TUTTA la lista
+ var alreadyExists = auction.RecentBids.Any(b =>
+ Math.Abs(b.Price - statePrice) < 0.001m &&
+ b.Username.Equals(currentBidder, StringComparison.OrdinalIgnoreCase));
+
+ if (alreadyExists)
{
- var topBid = auction.RecentBids[0]; // Prima = più recente
-
- // Se la puntata in cima è identica (stesso prezzo + stesso utente), salta
- if (Math.Abs(topBid.Price - statePrice) < 0.001m &&
- topBid.Username.Equals(currentBidder, StringComparison.OrdinalIgnoreCase))
- {
- return; // Già presente in cima, non serve aggiungere
- }
+ return; // Gi presente, non serve aggiungere
}
- // 🔥 NUOVA PUNTATA: Aggiungi solo se diversa dall'ultima
- // Questo significa che c'è stata una nuova puntata che l'API non ha ancora segnalato
+ // ?? NUOVA PUNTATA: Aggiungi solo se non esiste gi
var lastBidTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
auction.RecentBids.Insert(0, new BidHistoryEntry
@@ -1010,52 +1044,45 @@ namespace AutoBidder.Services
BidType = "Auto"
});
- // Aggiorna anche le statistiche bidder
- if (!auction.BidderStats.ContainsKey(currentBidder))
- {
- auction.BidderStats[currentBidder] = new BidderInfo
- {
- Username = currentBidder,
- BidCount = 1,
- RecentBidCount = 1,
- LastBidTime = DateTime.UtcNow
- };
- }
- else
- {
- var bidder = auction.BidderStats[currentBidder];
- bidder.BidCount++;
- bidder.RecentBidCount++;
- bidder.LastBidTime = DateTime.UtcNow;
- }
+ // ? RIMOSSO: Non incrementare BidderStats qui
+ // gestito SOLO da UpdateBidderStatsFromRecentBids per evitare duplicazioni
+ // La puntata stata aggiunta a RecentBids, sar contata al prossimo aggiornamento
}
catch { /* Silenzioso */ }
}
///
- /// Aggiorna le statistiche dei bidder in modo CUMULATIVO.
- /// BidCount è il totale dall'inizio del monitoraggio.
- /// RecentBidCount è il conteggio nella finestra corrente di RecentBids.
- /// I bidder NON vengono mai rimossi, il contatore è infinito.
+ /// Aggiorna le statistiche dei bidder basandosi SOLO su RecentBids.
+ /// QUESTO L'UNICO POSTO che aggiorna i conteggi.
+ /// IMPORTANTE: Il conteggio basato SOLO sulle puntate in RecentBids, non cumulativo.
///
private void UpdateBidderStatsFromRecentBids(AuctionInfo auction)
{
try
{
- // Raggruppa puntate per username nella finestra corrente
+ // ?? FIX: Conta direttamente le puntate in RecentBids per ogni utente
+ // Non usare delta, conta sempre da zero basandosi sulla lista attuale
var bidsByUser = auction.RecentBids
.GroupBy(b => b.Username, StringComparer.OrdinalIgnoreCase)
.ToDictionary(
g => g.Key,
g => new
{
- RecentCount = g.Count(),
+ Count = g.Count(),
LastBidTime = DateTimeOffset.FromUnixTimeSeconds(g.Max(b => b.Timestamp)).DateTime
},
StringComparer.OrdinalIgnoreCase
);
- // Aggiorna o crea BidderInfo per ogni utente
+ // Resetta tutti i conteggi e ricalcola da zero
+ // Questo evita accumuli errati dovuti a delta sbagliati
+ foreach (var existing in auction.BidderStats.Values)
+ {
+ existing.BidCount = 0;
+ existing.RecentBidCount = 0;
+ }
+
+ // Aggiorna con i conteggi reali da RecentBids
foreach (var kvp in bidsByUser)
{
var username = kvp.Key;
@@ -1063,37 +1090,22 @@ namespace AutoBidder.Services
if (!auction.BidderStats.ContainsKey(username))
{
- // Nuovo bidder - inizializza con i conteggi attuali
auction.BidderStats[username] = new BidderInfo
{
Username = username,
- BidCount = stats.RecentCount, // Primo conteggio
- RecentBidCount = stats.RecentCount,
+ BidCount = stats.Count,
+ RecentBidCount = stats.Count,
LastBidTime = stats.LastBidTime
};
}
else
{
var existing = auction.BidderStats[username];
-
- // Calcola delta: quante nuove puntate rispetto all'ultimo check
- int previousRecent = existing.RecentBidCount;
- int currentRecent = stats.RecentCount;
-
- // Se il conteggio è aumentato, aggiungi la differenza al totale cumulativo
- if (currentRecent > previousRecent)
- {
- existing.BidCount += (currentRecent - previousRecent);
- }
-
- // Aggiorna conteggio finestra corrente e ultimo timestamp
- existing.RecentBidCount = currentRecent;
+ existing.BidCount = stats.Count;
+ existing.RecentBidCount = stats.Count;
existing.LastBidTime = stats.LastBidTime;
}
}
-
- // NON rimuovere i bidder che non sono più in RecentBids!
- // Il conteggio totale è cumulativo e persistente.
}
catch (Exception ex)
{
diff --git a/Mimante/Services/BidStrategyService.cs b/Mimante/Services/BidStrategyService.cs
index e295dae..6ac3019 100644
--- a/Mimante/Services/BidStrategyService.cs
+++ b/Mimante/Services/BidStrategyService.cs
@@ -17,84 +17,6 @@ namespace AutoBidder.Services
private int _sessionTotalBids = 0;
private DateTime _sessionStartedAt = DateTime.UtcNow;
- ///
- /// Calcola l'offset ottimale per una puntata considerando tutti i fattori
- ///
- public BidTimingResult CalculateOptimalTiming(AuctionInfo auction, AppSettings settings)
- {
- var result = new BidTimingResult
- {
- BaseOffsetMs = auction.BidBeforeDeadlineMs,
- FinalOffsetMs = auction.BidBeforeDeadlineMs,
- ShouldBid = true
- };
-
- // 1. Adaptive Latency Compensation
- if (settings.AdaptiveLatencyEnabled)
- {
- result.LatencyCompensationMs = (int)auction.AverageLatencyMs;
- result.FinalOffsetMs += result.LatencyCompensationMs;
- }
-
- // 2. Dynamic Offset (basato su heat, storico, volatilit)
- if (settings.DynamicOffsetEnabled)
- {
- var dynamicAdjustment = CalculateDynamicOffset(auction, settings);
- result.DynamicAdjustmentMs = dynamicAdjustment;
- result.FinalOffsetMs += dynamicAdjustment;
- }
-
- // 3. Jitter (randomizzazione)
- if (settings.JitterEnabled || (auction.JitterEnabledOverride ?? settings.JitterEnabled))
- {
- result.JitterMs = _random.Next(-settings.JitterRangeMs, settings.JitterRangeMs + 1);
- result.FinalOffsetMs += result.JitterMs;
- }
-
- // 4. Clamp ai limiti
- result.FinalOffsetMs = Math.Clamp(result.FinalOffsetMs, settings.MinimumOffsetMs, settings.MaximumOffsetMs);
-
- // Salva offset calcolato nell'asta
- auction.DynamicOffsetMs = result.FinalOffsetMs;
-
- return result;
- }
-
- ///
- /// Calcola offset dinamico basato su heat, storico e volatilit
- ///
- private int CalculateDynamicOffset(AuctionInfo auction, AppSettings settings)
- {
- int adjustment = 0;
-
- // Pi l'asta "calda", pi anticipo serve
- if (auction.HeatMetric > 50)
- {
- adjustment += (auction.HeatMetric - 50) / 2; // +0-25ms per heat 50-100
- }
-
- // Se ci sono state collisioni recenti, anticipa di pi
- if (auction.ConsecutiveCollisions > 0)
- {
- adjustment += auction.ConsecutiveCollisions * 10; // +10ms per ogni collisione
- }
-
- // Se la latenza volatile (alta deviazione), aggiungi margine
- if (auction.LatencyHistory.Count >= 5)
- {
- var avg = auction.LatencyHistory.Average();
- var variance = auction.LatencyHistory.Sum(x => Math.Pow(x - avg, 2)) / auction.LatencyHistory.Count;
- var stdDev = Math.Sqrt(variance);
-
- if (stdDev > 20) // Alta variabilit
- {
- adjustment += (int)(stdDev / 2);
- }
- }
-
- return adjustment;
- }
-
///
/// Aggiorna heat metric per un'asta
///
@@ -360,9 +282,43 @@ namespace AutoBidder.Services
}
}
+ // ? RIMOSSO: DetectLastSecondSniper - causava falsi positivi
+ // In un duello, TUTTI i bidder hanno pattern regolari (ogni reset del timer)
+ // Questa strategia bloccava puntate legittime e faceva perdere aste
+
+ // ?? 7. STRATEGIA: Price Momentum (con soglia pi alta)
+ // Se il prezzo sta salendo TROPPO velocemente, pausa
+ var priceVelocity = CalculatePriceVelocity(auction);
+ if (priceVelocity > 0.10) // +10 centesimi/secondo = MOLTO veloce
+ {
+ decision.ShouldBid = false;
+ decision.Reason = $"Prezzo sale troppo veloce ({priceVelocity:F3}/s)";
+ return decision;
+ }
+
return decision;
}
+ ///
+ /// Calcola la velocit di crescita del prezzo (/secondo)
+ ///
+ private double CalculatePriceVelocity(AuctionInfo auction)
+ {
+ if (auction.RecentBids.Count < 5) return 0;
+
+ var recentBids = auction.RecentBids.Take(10).ToList();
+ if (recentBids.Count < 2) return 0;
+
+ var first = recentBids.Last();
+ var last = recentBids.First();
+
+ var timeDiffSeconds = last.Timestamp - first.Timestamp;
+ if (timeDiffSeconds <= 0) return 0;
+
+ var priceDiff = last.Price - first.Price;
+ return (double)priceDiff / timeDiffSeconds;
+ }
+
///
/// Rileva pattern bot analizzando i delta timing degli ultimi bid
///
diff --git a/Mimante/Utilities/ProductValueCalculator.cs b/Mimante/Utilities/ProductValueCalculator.cs
index ca8ef7c..302ce24 100644
--- a/Mimante/Utilities/ProductValueCalculator.cs
+++ b/Mimante/Utilities/ProductValueCalculator.cs
@@ -41,7 +41,7 @@ namespace AutoBidder.Utilities
}
// Calcola risparmio rispetto al prezzo "Compra Subito"
- if (auctionInfo.BuyNowPrice.HasValue)
+ if (auctionInfo.BuyNowPrice.HasValue && auctionInfo.BuyNowPrice.Value > 0)
{
var buyNowTotal = auctionInfo.BuyNowPrice.Value;
if (auctionInfo.ShippingCost.HasValue)
@@ -50,12 +50,24 @@ namespace AutoBidder.Utilities
}
value.Savings = buyNowTotal - value.TotalCostIfWin;
- value.SavingsPercentage = (value.Savings.Value / buyNowTotal) * 100.0;
+
+ // ?? FIX: Evita divisione per zero
+ if (buyNowTotal > 0)
+ {
+ value.SavingsPercentage = (value.Savings.Value / buyNowTotal) * 100.0;
+ }
+ else
+ {
+ // Se il buyNowTotal 0, imposta un valore fittizio negativo per indicare perdita
+ value.SavingsPercentage = -100.0;
+ }
+
value.IsWorthIt = value.Savings.Value > 0;
}
else
{
- // Senza prezzo "Compra Subito", consideriamo sempre conveniente
+ // Senza prezzo "Compra Subito" valido, consideriamo sempre conveniente
+ // Questo permette di puntare su aste senza dati di riferimento
value.IsWorthIt = true;
}
diff --git a/Mimante/Utilities/SettingsManager.cs b/Mimante/Utilities/SettingsManager.cs
index c07797b..a98e405 100644
--- a/Mimante/Utilities/SettingsManager.cs
+++ b/Mimante/Utilities/SettingsManager.cs
@@ -7,7 +7,7 @@ namespace AutoBidder.Utilities
public class AppSettings
{
// NUOVE IMPOSTAZIONI PREDEFINITE PER LE ASTE
- public int DefaultBidBeforeDeadlineMs { get; set; } = 200;
+ public int DefaultBidBeforeDeadlineMs { get; set; } = 800;
public bool DefaultCheckAuctionOpenBeforeBid { get; set; } = false;
public double DefaultMinPrice { get; set; } = 0;
public double DefaultMaxPrice { get; set; } = 0;
@@ -104,71 +104,64 @@ namespace AutoBidder.Utilities
// 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
+ // ???????????????????????????????????????????????????????????????
+
///
- /// Abilita compensazione adattiva della latenza.
- /// Misura latenza reale per ogni asta e adatta l'anticipo automaticamente.
+ /// Log quando viene piazzata una puntata [BID]
/// Default: true
///
- public bool AdaptiveLatencyEnabled { get; set; } = true;
+ public bool LogBids { get; set; } = true;
///
- /// Abilita jitter casuale sull'offset per evitare sincronizzazione con altri bot.
- /// Aggiunge ±JitterRangeMs al timing di puntata.
+ /// Log quando una strategia blocca la puntata [STRATEGY]
/// Default: true
///
- public bool JitterEnabled { get; set; } = true;
+ public bool LogStrategyDecisions { get; set; } = true;
///
- /// Range massimo del jitter casuale in millisecondi (±X ms).
- /// Default: 50 (range -50ms a +50ms)
+ /// Log calcoli valore prodotto [VALUE]
+ /// Default: false (attiva per debug)
///
- public int JitterRangeMs { get; set; } = 50;
+ public bool LogValueCalculations { get; set; } = false;
///
- /// Abilita offset dinamico per asta basato su ping, storico e volatilità.
+ /// 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 DynamicOffsetEnabled { get; set; } = true;
+ public bool LogErrors { get; set; } = true;
///
- /// Offset minimo garantito in ms (non scende mai sotto questo valore).
- /// Default: 80
- ///
- public int MinimumOffsetMs { get; set; } = 80;
-
- ///
- /// Offset massimo in ms (non supera mai questo valore).
- /// Default: 500
- ///
- public int MaximumOffsetMs { get; set; } = 500;
-
- // ?? STRATEGIA ANTI-AUTOBID BIDOO
- // Bidoo ha un sistema di auto-puntata integrato che si attiva a ~2 secondi.
- // Aspettando che questa soglia venga superata, lasciamo che gli altri
- // utenti con auto-puntata attiva puntino prima di noi, risparmiando puntate.
-
- ///
- /// Abilita la strategia di attesa per le auto-puntate di Bidoo.
- /// Se true, aspetta che il timer scenda sotto la soglia prima di puntare.
+ /// Log stato asta (terminata, reset, ecc.) [STATUS]
/// Default: true
///
- public bool WaitForAutoBidEnabled { get; set; } = true;
+ public bool LogAuctionStatus { get; set; } = true;
///
- /// Soglia in secondi sotto la quale si può puntare.
- /// Bidoo attiva le auto-puntate a ~2 secondi, quindi aspettiamo che passino.
- /// Default: 1.8 (punta solo quando timer < 1.8s, dopo che le auto-puntate si sono attivate)
+ /// Log profiling avversari [OPPONENT]
+ /// Default: false
///
- public double WaitForAutoBidThresholdSeconds { get; set; } = 1.8;
-
- ///
- /// Se true, logga quando salta una puntata per aspettare le auto-puntate.
- /// Default: false (per evitare spam nel log)
- ///
- public bool LogAutoBidWaitSkips { get; set; } = 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.
@@ -190,6 +183,26 @@ namespace AutoBidder.Utilities
///
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;
+
// 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥
// RILEVAMENTO COMPETIZIONE E HEAT METRIC
// 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥