Semplifica timing puntata, logging e controllo convenienza
- Timing di puntata ora gestito solo da offset fisso configurabile, rimosse strategie di compensazione latenza/jitter/offset dinamico - Aggiunto controllo convenienza: blocca puntate se il costo supera il "Compra Subito" oltre una soglia configurabile - Logging granulare: nuove opzioni per log selettivo (puntate, strategie, valore, competizione, timing, errori, stato, profiling avversari) - Persistenza stato browser aste (categoria, ricerca) tramite ApplicationStateService - Fix conteggio puntate per bidder, rimosso rilevamento "Last Second Sniper", aggiunta strategia "Price Momentum" - Refactoring e pulizia: rimozione codice obsoleto, migliorata documentazione e thread-safety
This commit is contained in:
@@ -65,10 +65,40 @@ namespace AutoBidder.Models
|
||||
[JsonPropertyName("BidsUsedOnThisAuction")]
|
||||
public int? BidsUsedOnThisAuction { get; set; }
|
||||
|
||||
|
||||
// Timestamp
|
||||
public DateTime AddedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? LastClickAt { get; set; }
|
||||
|
||||
// ?? NUOVO: Sistema timing basato su deadline
|
||||
/// <summary>
|
||||
/// Timestamp UTC preciso della scadenza dell'asta.
|
||||
/// Calcolato come: DateTime.UtcNow + Timer (quando riceviamo lo stato)
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public DateTime? DeadlineUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp UTC dell'ultimo aggiornamento della deadline.
|
||||
/// Usato per rilevare reset del timer.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public DateTime? LastDeadlineUpdateUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Timer raw dell'ultimo stato ricevuto (in secondi).
|
||||
/// Usato per rilevare cambiamenti nel timer.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public double LastRawTimer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True se la puntata è già stata schedulata per questo ciclo.
|
||||
/// Resettato quando il timer si resetta.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool BidScheduled { get; set; }
|
||||
|
||||
// Storico
|
||||
public List<BidHistory> BidHistory { get; set; } = new List<BidHistory>();
|
||||
public Dictionary<string, BidderInfo> BidderStats { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@page "/browser"
|
||||
@page "/browser"
|
||||
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
|
||||
@using AutoBidder.Models
|
||||
@using AutoBidder.Services
|
||||
@@ -220,18 +220,18 @@
|
||||
<h6 class="auction-name" title="@auction.Name">@auction.Name</h6>
|
||||
|
||||
<div class="auction-price">
|
||||
<span class="current-price">@auction.CurrentPrice.ToString("0.00") €</span>
|
||||
<span class="current-price">@auction.CurrentPrice.ToString("0.00") €</span>
|
||||
@if (auction.BuyNowPrice > 0)
|
||||
{
|
||||
<span class="buynow-price text-muted">
|
||||
<small>Compra: @auction.BuyNowPrice.ToString("0.00") €</small>
|
||||
<small>Compra: @auction.BuyNowPrice.ToString("0.00") €</small>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="auction-bidder">
|
||||
<i class="bi bi-person-fill text-muted me-1"></i>
|
||||
<span>@(string.IsNullOrEmpty(auction.LastBidder) ? "—" : auction.LastBidder)</span>
|
||||
<span>@(string.IsNullOrEmpty(auction.LastBidder) ? "—" : auction.LastBidder)</span>
|
||||
</div>
|
||||
|
||||
<div class="auction-timer @(auction.RemainingSeconds <= 3 ? "urgent" : "")">
|
||||
@@ -295,7 +295,14 @@
|
||||
private List<BidooCategoryInfo> categories = new();
|
||||
private List<BidooBrowserAuction> auctions = new();
|
||||
private List<BidooBrowserAuction> 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,10 +325,21 @@ private bool isUpdatingInBackground = false;
|
||||
{
|
||||
await LoadCategories();
|
||||
|
||||
// 🔥 Se c'è una categoria salvata, carica le aste
|
||||
if (categories.Count > 0)
|
||||
{
|
||||
// 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
|
||||
stateUpdateTimer = new System.Threading.Timer(async _ =>
|
||||
@@ -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();
|
||||
|
||||
@@ -181,80 +181,10 @@
|
||||
</h2>
|
||||
<div id="collapse-strategies" class="accordion-collapse collapse" aria-labelledby="heading-strategies" data-bs-parent="#settingsAccordion">
|
||||
<div class="accordion-body">
|
||||
<h6 class="fw-bold mb-3"><i class="bi bi-speedometer2"></i> Timing & Latenza</h6>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="adaptiveLatency" @bind="settings.AdaptiveLatencyEnabled" />
|
||||
<label class="form-check-label" for="adaptiveLatency">
|
||||
<strong>Compensazione latenza adattiva</strong>
|
||||
<div class="form-text">Misura e compensa automaticamente la latenza di rete</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="jitter" @bind="settings.JitterEnabled" />
|
||||
<label class="form-check-label" for="jitter">
|
||||
<strong>Jitter casuale</strong>
|
||||
<div class="form-text">Aggiunge variazione random per evitare sincronizzazione con altri bot</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<label class="form-label"><i class="bi bi-shuffle"></i> Range jitter (±ms)</label>
|
||||
<input type="number" class="form-control" @bind="settings.JitterRangeMs" />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="dynamicOffset" @bind="settings.DynamicOffsetEnabled" />
|
||||
<label class="form-check-label" for="dynamicOffset">
|
||||
<strong>Offset dinamico</strong>
|
||||
<div class="form-text">Adatta l'anticipo in base a heat, collisioni e volatilità</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Offset minimo (ms)</label>
|
||||
<input type="number" class="form-control" @bind="settings.MinimumOffsetMs" />
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Offset massimo (ms)</label>
|
||||
<input type="number" class="form-control" @bind="settings.MaximumOffsetMs" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="fw-bold mb-3"><i class="bi bi-robot"></i> Strategia Anti-AutoBid Bidoo</h6>
|
||||
<div class="alert alert-info mb-3">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
<strong>Come funziona:</strong> 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, <strong>risparmiando puntate</strong>.
|
||||
</div>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="waitAutoBid" @bind="settings.WaitForAutoBidEnabled" />
|
||||
<label class="form-check-label" for="waitAutoBid">
|
||||
<strong>Attendi auto-puntate Bidoo</strong>
|
||||
<div class="form-text">Aspetta che il timer scenda sotto la soglia prima di puntare (consigliato)</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Soglia attesa (secondi)</label>
|
||||
<input type="number" step="0.1" class="form-control" @bind="settings.WaitForAutoBidThresholdSeconds" />
|
||||
<div class="form-text">Punta solo quando timer < questo valore (default: 1.8s)</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-check form-switch mt-4">
|
||||
<input type="checkbox" class="form-check-input" id="logAutoBid" @bind="settings.LogAutoBidWaitSkips" />
|
||||
<label class="form-check-label" for="logAutoBid">
|
||||
<strong>Log attese</strong>
|
||||
<div class="form-text">Logga quando salta per aspettare (verbose)</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info border-0 shadow-sm mb-4">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
<strong>Nota:</strong> Le strategie decidono <strong>SE</strong> puntare, non <strong>QUANDO</strong>.
|
||||
Il timing è controllato solo dall'impostazione "Anticipo puntata" nelle Impostazioni Predefinite.
|
||||
</div>
|
||||
|
||||
<h6 class="fw-bold mb-3"><i class="bi bi-thermometer-half"></i> Rilevamento Competizione</h6>
|
||||
@@ -287,6 +217,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="fw-bold mb-3"><i class="bi bi-cash-coin"></i> Controllo Convenienza</h6>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="valueCheck" @bind="settings.ValueCheckEnabled" />
|
||||
<label class="form-check-label" for="valueCheck">
|
||||
<strong>Blocca puntate non convenienti</strong>
|
||||
<div class="form-text">Blocca se il costo totale supera il prezzo "Compra Subito" oltre la soglia</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Risparmio minimo richiesto (%)</label>
|
||||
<input type="number" step="0.1" class="form-control" @bind="settings.MinSavingsPercentage" />
|
||||
<div class="form-text">
|
||||
Negativo = tolleranza perdita. Es: -5 = permetti fino al 5% di perdita<br />
|
||||
Positivo = richiede risparmio. Es: 10 = richiedi almeno 10% di risparmio
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="fw-bold mb-3"><i class="bi bi-arrow-left-right"></i> Soft Retreat</h6>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12">
|
||||
@@ -308,6 +259,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<h6 class="fw-bold mb-3"><i class="bi bi-dice-5"></i> Puntata Probabilistica</h6>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12">
|
||||
@@ -421,25 +373,113 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LIMITI LOG -->
|
||||
<!-- LOGGING GRANULARE -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="heading-logs">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-logs" aria-expanded="false" aria-controls="collapse-logs">
|
||||
<i class="bi bi-journal-text me-2"></i> Limiti Log
|
||||
<i class="bi bi-journal-text me-2"></i> Logging e Limiti
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapse-logs" class="accordion-collapse collapse" aria-labelledby="heading-logs" data-bs-parent="#settingsAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="row g-3">
|
||||
<h6 class="fw-bold mb-3"><i class="bi bi-filter"></i> Cosa Loggare</h6>
|
||||
<p class="text-muted small mb-3">Scegli quali informazioni visualizzare nei log delle aste.</p>
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="logBids" @bind="settings.LogBids" />
|
||||
<label class="form-check-label" for="logBids">
|
||||
<strong>[BID] Puntate piazzate</strong>
|
||||
<div class="form-text">Log quando viene effettuata una puntata</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="logStrategy" @bind="settings.LogStrategyDecisions" />
|
||||
<label class="form-check-label" for="logStrategy">
|
||||
<strong>[STRATEGY] Decisioni strategie</strong>
|
||||
<div class="form-text">Log quando una strategia blocca la puntata</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="logStatus" @bind="settings.LogAuctionStatus" />
|
||||
<label class="form-check-label" for="logStatus">
|
||||
<strong>[STATUS] Stato asta</strong>
|
||||
<div class="form-text">Log quando l'asta termina, si resetta, ecc.</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="logErrors" @bind="settings.LogErrors" />
|
||||
<label class="form-check-label" for="logErrors">
|
||||
<strong>[ERROR/WARN] Errori e warning</strong>
|
||||
<div class="form-text">Log errori e avvisi importanti</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="fw-bold mb-3"><i class="bi bi-bug"></i> Debug Avanzato</h6>
|
||||
<div class="alert alert-warning border-0 shadow-sm mb-3">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
||||
<strong>Attenzione:</strong> Queste opzioni generano molti log. Attiva solo per debug!
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="logTiming" @bind="settings.LogTiming" />
|
||||
<label class="form-check-label" for="logTiming">
|
||||
<strong>[TIMING] Timing puntate</strong>
|
||||
<div class="form-text">Log dettagliato timing (molto verbose!)</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="logValue" @bind="settings.LogValueCalculations" />
|
||||
<label class="form-check-label" for="logValue">
|
||||
<strong>[VALUE] Calcoli valore</strong>
|
||||
<div class="form-text">Log calcoli convenienza prodotto</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="logCompetition" @bind="settings.LogCompetition" />
|
||||
<label class="form-check-label" for="logCompetition">
|
||||
<strong>[COMPETITION] Competizione</strong>
|
||||
<div class="form-text">Log rilevamento competizione e heat</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="logOpponent" @bind="settings.LogOpponentProfiling" />
|
||||
<label class="form-check-label" for="logOpponent">
|
||||
<strong>[OPPONENT] Profiling avversari</strong>
|
||||
<div class="form-text">Log analisi bidder aggressivi</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="fw-bold mb-3"><i class="bi bi-sliders"></i> Limiti</h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-md-4">
|
||||
<label class="form-label fw-bold"><i class="bi bi-list-ul"></i> Righe log globale</label>
|
||||
<input type="number" class="form-control" @bind="settings.MaxGlobalLogLines" />
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="col-12 col-md-4">
|
||||
<label class="form-label fw-bold"><i class="bi bi-list-check"></i> Righe log per asta</label>
|
||||
<input type="number" class="form-control" @bind="settings.MaxLogLinesPerAuction" />
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="col-12 col-md-4">
|
||||
<label class="form-label fw-bold"><i class="bi bi-clock-history"></i> Voci storia puntate</label>
|
||||
<input type="number" class="form-control" @bind="settings.MaxBidHistoryEntries" />
|
||||
</div>
|
||||
|
||||
@@ -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<AuctionInfo> auctions)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determina se l'asta è stata vinta dall'utente corrente
|
||||
/// Determina se l'asta è stata vinta dall'utente corrente
|
||||
/// </summary>
|
||||
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();
|
||||
// ?? Delay adattivo ULTRA-OTTIMIZZATO
|
||||
// Considera l'offset target più basso tra tutte le aste attive
|
||||
var settings = Utilities.SettingsManager.Load();
|
||||
|
||||
int delayMs = lowestTimer switch
|
||||
// ?? 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
|
||||
};
|
||||
if (a.IsPaused || !a.LastDeadlineUpdateUtc.HasValue) continue;
|
||||
|
||||
await Task.Delay(delayMs, token);
|
||||
// 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,117 +523,113 @@ namespace AutoBidder.Services
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
// ?? 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))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ? MOMENTO GIUSTO! Verifica strategie avanzate
|
||||
var session = _apiClient.GetSession();
|
||||
var decision = _bidStrategy.ShouldPlaceBid(auction, state, settings, session?.Username ?? "");
|
||||
|
||||
if (!decision.ShouldBid)
|
||||
{
|
||||
if (settings.LogStrategyDecisions)
|
||||
{
|
||||
auction.AddLog($"[STRATEGY] {decision.Reason}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 🕐 TIMER-BASED SCHEDULING (offset fisso, niente calcoli dinamici)
|
||||
if (timerMs > fixedOffsetMs)
|
||||
{
|
||||
// Timer ancora alto - Schedula puntata futura
|
||||
double delayMs = timerMs - fixedOffsetMs;
|
||||
// ?? PUNTA!
|
||||
auction.BidScheduled = true;
|
||||
|
||||
// Non schedulare se già c'è un task attivo per questa asta
|
||||
if (auction.IsAttackInProgress)
|
||||
if (settings.LogBids)
|
||||
{
|
||||
return;
|
||||
auction.AddLog($"[BID] Puntata a ~{estimatedRemaining:F0}ms dalla scadenza");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
else if (timerMs > 0 && timerMs <= fixedOffsetMs)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
// Se timer <= 0, asta già scaduta - Non fare nulla
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Esegue la puntata e registra metriche
|
||||
@@ -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
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private void MergeBidHistory(AuctionInfo auction, List<BidHistoryEntry> 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
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
var topBid = auction.RecentBids[0]; // Prima = più recente
|
||||
// ?? 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));
|
||||
|
||||
// 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))
|
||||
if (alreadyExists)
|
||||
{
|
||||
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 */ }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -17,84 +17,6 @@ namespace AutoBidder.Services
|
||||
private int _sessionTotalBids = 0;
|
||||
private DateTime _sessionStartedAt = DateTime.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// Calcola l'offset ottimale per una puntata considerando tutti i fattori
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calcola offset dinamico basato su heat, storico e volatilità
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aggiorna heat metric per un'asta
|
||||
/// </summary>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calcola la velocità di crescita del prezzo (€/secondo)
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rileva pattern bot analizzando i delta timing degli ultimi bid
|
||||
/// </summary>
|
||||
|
||||
@@ -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;
|
||||
|
||||
// ?? 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
// ???????????????????????????????????????????????????????????????
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public bool AdaptiveLatencyEnabled { get; set; } = true;
|
||||
public bool LogBids { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public bool JitterEnabled { get; set; } = true;
|
||||
public bool LogStrategyDecisions { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
/// </summary>
|
||||
public int JitterRangeMs { get; set; } = 50;
|
||||
public bool LogValueCalculations { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Abilita offset dinamico per asta basato su ping, storico e volatilità.
|
||||
/// Log rilevamento competizione e heat [COMPETITION]
|
||||
/// Default: false
|
||||
/// </summary>
|
||||
public bool LogCompetition { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Log timing e polling (molto verbose!) [TIMING]
|
||||
/// Default: false (attiva solo per debug timing)
|
||||
/// </summary>
|
||||
public bool LogTiming { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Log errori e warning [ERROR/WARN]
|
||||
/// Default: true
|
||||
/// </summary>
|
||||
public bool DynamicOffsetEnabled { get; set; } = true;
|
||||
public bool LogErrors { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Offset minimo garantito in ms (non scende mai sotto questo valore).
|
||||
/// Default: 80
|
||||
/// </summary>
|
||||
public int MinimumOffsetMs { get; set; } = 80;
|
||||
|
||||
/// <summary>
|
||||
/// Offset massimo in ms (non supera mai questo valore).
|
||||
/// Default: 500
|
||||
/// </summary>
|
||||
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.
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public bool WaitForAutoBidEnabled { get; set; } = true;
|
||||
public bool LogAuctionStatus { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public double WaitForAutoBidThresholdSeconds { get; set; } = 1.8;
|
||||
|
||||
/// <summary>
|
||||
/// Se true, logga quando salta una puntata per aspettare le auto-puntate.
|
||||
/// Default: false (per evitare spam nel log)
|
||||
/// </summary>
|
||||
public bool LogAutoBidWaitSkips { get; set; } = false;
|
||||
public bool LogOpponentProfiling { get; set; } = false;
|
||||
|
||||
// 🎯 STRATEGIE SEMPLIFICATE
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public bool UserExhaustionEnabled { get; set; } = true;
|
||||
|
||||
// 🎯 CONTROLLO CONVENIENZA PRODOTTO
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public bool ValueCheckEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
/// </summary>
|
||||
public double MinSavingsPercentage { get; set; } = -5.0;
|
||||
|
||||
// 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥
|
||||
// RILEVAMENTO COMPETIZIONE E HEAT METRIC
|
||||
// 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥
|
||||
|
||||
Reference in New Issue
Block a user