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")]
|
[JsonPropertyName("BidsUsedOnThisAuction")]
|
||||||
public int? BidsUsedOnThisAuction { get; set; }
|
public int? BidsUsedOnThisAuction { get; set; }
|
||||||
|
|
||||||
|
|
||||||
// Timestamp
|
// Timestamp
|
||||||
public DateTime AddedAt { get; set; } = DateTime.UtcNow;
|
public DateTime AddedAt { get; set; } = DateTime.UtcNow;
|
||||||
public DateTime? LastClickAt { get; set; }
|
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
|
// Storico
|
||||||
public List<BidHistory> BidHistory { get; set; } = new List<BidHistory>();
|
public List<BidHistory> BidHistory { get; set; } = new List<BidHistory>();
|
||||||
public Dictionary<string, BidderInfo> BidderStats { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
public Dictionary<string, BidderInfo> BidderStats { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@page "/browser"
|
@page "/browser"
|
||||||
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
|
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
|
||||||
@using AutoBidder.Models
|
@using AutoBidder.Models
|
||||||
@using AutoBidder.Services
|
@using AutoBidder.Services
|
||||||
@@ -220,18 +220,18 @@
|
|||||||
<h6 class="auction-name" title="@auction.Name">@auction.Name</h6>
|
<h6 class="auction-name" title="@auction.Name">@auction.Name</h6>
|
||||||
|
|
||||||
<div class="auction-price">
|
<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)
|
@if (auction.BuyNowPrice > 0)
|
||||||
{
|
{
|
||||||
<span class="buynow-price text-muted">
|
<span class="buynow-price text-muted">
|
||||||
<small>Compra: @auction.BuyNowPrice.ToString("0.00") €</small>
|
<small>Compra: @auction.BuyNowPrice.ToString("0.00") €</small>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="auction-bidder">
|
<div class="auction-bidder">
|
||||||
<i class="bi bi-person-fill text-muted me-1"></i>
|
<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>
|
||||||
|
|
||||||
<div class="auction-timer @(auction.RemainingSeconds <= 3 ? "urgent" : "")">
|
<div class="auction-timer @(auction.RemainingSeconds <= 3 ? "urgent" : "")">
|
||||||
@@ -295,7 +295,14 @@
|
|||||||
private List<BidooCategoryInfo> categories = new();
|
private List<BidooCategoryInfo> categories = new();
|
||||||
private List<BidooBrowserAuction> auctions = new();
|
private List<BidooBrowserAuction> auctions = new();
|
||||||
private List<BidooBrowserAuction> filteredAuctions = 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 int currentPage = 0;
|
||||||
|
|
||||||
private bool isLoading = false;
|
private bool isLoading = false;
|
||||||
@@ -303,8 +310,12 @@ private bool isLoadingMore = false;
|
|||||||
private bool canLoadMore = true;
|
private bool canLoadMore = true;
|
||||||
private string? errorMessage = null;
|
private string? errorMessage = null;
|
||||||
|
|
||||||
// ? NUOVO: Ricerca
|
// 🔥 Usa stato persistente per la ricerca
|
||||||
private string searchQuery = "";
|
private string searchQuery
|
||||||
|
{
|
||||||
|
get => AppState.BrowserSearchQuery;
|
||||||
|
set => AppState.BrowserSearchQuery = value;
|
||||||
|
}
|
||||||
|
|
||||||
private System.Threading.Timer? stateUpdateTimer;
|
private System.Threading.Timer? stateUpdateTimer;
|
||||||
private CancellationTokenSource? cts;
|
private CancellationTokenSource? cts;
|
||||||
@@ -314,10 +325,21 @@ private bool isUpdatingInBackground = false;
|
|||||||
{
|
{
|
||||||
await LoadCategories();
|
await LoadCategories();
|
||||||
|
|
||||||
|
// 🔥 Se c'è una categoria salvata, carica le aste
|
||||||
if (categories.Count > 0)
|
if (categories.Count > 0)
|
||||||
|
{
|
||||||
|
// Se selectedCategoryIndex è valido, carica quella categoria
|
||||||
|
if (selectedCategoryIndex >= 0 && selectedCategoryIndex < categories.Count)
|
||||||
{
|
{
|
||||||
await LoadAuctions();
|
await LoadAuctions();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Altrimenti carica la prima categoria
|
||||||
|
selectedCategoryIndex = 0;
|
||||||
|
await LoadAuctions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-update states every 500ms for real-time price updates
|
// Auto-update states every 500ms for real-time price updates
|
||||||
stateUpdateTimer = new System.Threading.Timer(async _ =>
|
stateUpdateTimer = new System.Threading.Timer(async _ =>
|
||||||
@@ -532,9 +554,30 @@ private bool isUpdatingInBackground = false;
|
|||||||
{
|
{
|
||||||
if (browserAuction.IsMonitored) return;
|
if (browserAuction.IsMonitored) return;
|
||||||
|
|
||||||
// ?? Carica impostazioni di default
|
// 🔥 Carica impostazioni di default
|
||||||
var settings = AutoBidder.Utilities.SettingsManager.Load();
|
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
|
var auctionInfo = new AuctionInfo
|
||||||
{
|
{
|
||||||
AuctionId = browserAuction.AuctionId,
|
AuctionId = browserAuction.AuctionId,
|
||||||
@@ -542,7 +585,7 @@ private bool isUpdatingInBackground = false;
|
|||||||
OriginalUrl = browserAuction.Url,
|
OriginalUrl = browserAuction.Url,
|
||||||
BuyNowPrice = (double)browserAuction.BuyNowPrice,
|
BuyNowPrice = (double)browserAuction.BuyNowPrice,
|
||||||
|
|
||||||
// ?? FIX: Applica valori dalle impostazioni
|
// 🔥 Applica valori dalle impostazioni
|
||||||
BidBeforeDeadlineMs = settings.DefaultBidBeforeDeadlineMs,
|
BidBeforeDeadlineMs = settings.DefaultBidBeforeDeadlineMs,
|
||||||
CheckAuctionOpenBeforeBid = settings.DefaultCheckAuctionOpenBeforeBid,
|
CheckAuctionOpenBeforeBid = settings.DefaultCheckAuctionOpenBeforeBid,
|
||||||
MinPrice = settings.DefaultMinPrice,
|
MinPrice = settings.DefaultMinPrice,
|
||||||
@@ -550,8 +593,9 @@ private bool isUpdatingInBackground = false;
|
|||||||
MinResets = settings.DefaultMinResets,
|
MinResets = settings.DefaultMinResets,
|
||||||
MaxResets = settings.DefaultMaxResets,
|
MaxResets = settings.DefaultMaxResets,
|
||||||
|
|
||||||
IsActive = true,
|
// 🔥 Usa stato da impostazioni invece di hardcoded
|
||||||
IsPaused = true, // Start paused
|
IsActive = isActive,
|
||||||
|
IsPaused = isPaused,
|
||||||
AddedAt = DateTime.UtcNow
|
AddedAt = DateTime.UtcNow
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -565,7 +609,7 @@ private bool isUpdatingInBackground = false;
|
|||||||
// Save to disk
|
// Save to disk
|
||||||
AutoBidder.Utilities.PersistenceManager.SaveAuctions(AppState.Auctions.ToList());
|
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)
|
if (!AppState.IsMonitoringActive)
|
||||||
{
|
{
|
||||||
AuctionMonitor.Start();
|
AuctionMonitor.Start();
|
||||||
|
|||||||
@@ -181,80 +181,10 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<div id="collapse-strategies" class="accordion-collapse collapse" aria-labelledby="heading-strategies" data-bs-parent="#settingsAccordion">
|
<div id="collapse-strategies" class="accordion-collapse collapse" aria-labelledby="heading-strategies" data-bs-parent="#settingsAccordion">
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
<h6 class="fw-bold mb-3"><i class="bi bi-speedometer2"></i> Timing & Latenza</h6>
|
<div class="alert alert-info border-0 shadow-sm mb-4">
|
||||||
<div class="row g-3 mb-4">
|
<i class="bi bi-info-circle me-2"></i>
|
||||||
<div class="col-12">
|
<strong>Nota:</strong> Le strategie decidono <strong>SE</strong> puntare, non <strong>QUANDO</strong>.
|
||||||
<div class="form-check form-switch">
|
Il timing è controllato solo dall'impostazione "Anticipo puntata" nelle Impostazioni Predefinite.
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<h6 class="fw-bold mb-3"><i class="bi bi-thermometer-half"></i> Rilevamento Competizione</h6>
|
<h6 class="fw-bold mb-3"><i class="bi bi-thermometer-half"></i> Rilevamento Competizione</h6>
|
||||||
@@ -287,6 +217,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
<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="row g-3 mb-4">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
@@ -308,6 +259,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<h6 class="fw-bold mb-3"><i class="bi bi-dice-5"></i> Puntata Probabilistica</h6>
|
<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="row g-3 mb-4">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
@@ -421,25 +373,113 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- LIMITI LOG -->
|
<!-- LOGGING GRANULARE -->
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="heading-logs">
|
<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">
|
<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>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
<div id="collapse-logs" class="accordion-collapse collapse" aria-labelledby="heading-logs" data-bs-parent="#settingsAccordion">
|
<div id="collapse-logs" class="accordion-collapse collapse" aria-labelledby="heading-logs" data-bs-parent="#settingsAccordion">
|
||||||
<div class="accordion-body">
|
<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="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>
|
<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" />
|
<input type="number" class="form-control" @bind="settings.MaxGlobalLogLines" />
|
||||||
</div>
|
</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>
|
<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" />
|
<input type="number" class="form-control" @bind="settings.MaxLogLinesPerAuction" />
|
||||||
</div>
|
</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>
|
<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" />
|
<input type="number" class="form-control" @bind="settings.MaxBidHistoryEntries" />
|
||||||
</div>
|
</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 ===
|
// === METODI GESTIONE ASTE ===
|
||||||
|
|
||||||
public void SetAuctions(List<AuctionInfo> auctions)
|
public void SetAuctions(List<AuctionInfo> auctions)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -98,7 +98,7 @@ namespace AutoBidder.Services
|
|||||||
if (!_auctions.Any(a => a.AuctionId == auction.AuctionId))
|
if (!_auctions.Any(a => a.AuctionId == auction.AuctionId))
|
||||||
{
|
{
|
||||||
_auctions.Add(auction);
|
_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})");
|
// OnLog?.Invoke($"[+] Asta aggiunta: {auction.Name} (ID: {auction.AuctionId})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,20 +111,20 @@ namespace AutoBidder.Services
|
|||||||
var auction = _auctions.FirstOrDefault(a => a.AuctionId == auctionId);
|
var auction = _auctions.FirstOrDefault(a => a.AuctionId == auctionId);
|
||||||
if (auction != null)
|
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)
|
if (!auction.IsActive && auction.LastState != null)
|
||||||
{
|
{
|
||||||
OnLog?.Invoke($"[STATS] Asta terminata rilevata: {auction.Name} - Salvataggio statistiche in corso...");
|
OnLog?.Invoke($"[STATS] Asta terminata rilevata: {auction.Name} - Salvataggio statistiche in corso...");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Determina se è stata vinta dall'utente
|
// Determina se è stata vinta dall'utente
|
||||||
var won = IsAuctionWonByUser(auction);
|
var won = IsAuctionWonByUser(auction);
|
||||||
|
|
||||||
OnLog?.Invoke($"[STATS] Asta {auction.Name} - Stato: {(won ? "VINTA" : "PERSA")}");
|
OnLog?.Invoke($"[STATS] Asta {auction.Name} - Stato: {(won ? "VINTA" : "PERSA")}");
|
||||||
|
|
||||||
// Emetti evento per salvare le statistiche
|
// 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);
|
OnAuctionCompleted?.Invoke(auction, auction.LastState, won);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -143,7 +143,7 @@ namespace AutoBidder.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determina se l'asta è stata vinta dall'utente corrente
|
/// Determina se l'asta è stata vinta dall'utente corrente
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool IsAuctionWonByUser(AuctionInfo auction)
|
private bool IsAuctionWonByUser(AuctionInfo auction)
|
||||||
{
|
{
|
||||||
@@ -154,7 +154,7 @@ namespace AutoBidder.Services
|
|||||||
|
|
||||||
if (string.IsNullOrEmpty(username)) return false;
|
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;
|
return auction.LastState.LastBidder?.Equals(username, StringComparison.OrdinalIgnoreCase) == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,26 +288,41 @@ namespace AutoBidder.Services
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay adattivo OTTIMIZZATO basato su timer più basso
|
// ?? Delay adattivo ULTRA-OTTIMIZZATO
|
||||||
var lowestTimer = activeAuctions
|
// Considera l'offset target più basso tra tutte le aste attive
|
||||||
.Select(a => GetLastTimer(a))
|
var settings = Utilities.SettingsManager.Load();
|
||||||
.Where(t => t > 0)
|
|
||||||
.DefaultIfEmpty(999)
|
|
||||||
.Min();
|
|
||||||
|
|
||||||
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
|
if (a.IsPaused || !a.LastDeadlineUpdateUtc.HasValue) continue;
|
||||||
< 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);
|
// 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)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@@ -355,7 +370,7 @@ namespace AutoBidder.Services
|
|||||||
// ?? Aggiorna latenza con storico
|
// ?? Aggiorna latenza con storico
|
||||||
auction.AddLatencyMeasurement(state.PollingLatencyMs);
|
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)
|
if (!auction.IsTrackedFromStart && auction.BidHistory.Count == 0)
|
||||||
{
|
{
|
||||||
auction.IsTrackedFromStart = true;
|
auction.IsTrackedFromStart = true;
|
||||||
@@ -368,7 +383,7 @@ namespace AutoBidder.Services
|
|||||||
MergeBidHistory(auction, state.RecentBidsHistory);
|
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
|
// Questo assicura che la puntata vincente sia sempre inclusa
|
||||||
if (!string.IsNullOrEmpty(state.LastBidder) && state.Price > 0)
|
if (!string.IsNullOrEmpty(state.LastBidder) && state.Price > 0)
|
||||||
{
|
{
|
||||||
@@ -391,7 +406,7 @@ namespace AutoBidder.Services
|
|||||||
var lastBidTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
var lastBidTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||||
var statePrice = (decimal)state.Price;
|
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 =>
|
var alreadyExists = auction.RecentBids.Any(b =>
|
||||||
Math.Abs(b.Price - statePrice) < 0.001m &&
|
Math.Abs(b.Price - statePrice) < 0.001m &&
|
||||||
b.Username.Equals(state.LastBidder, StringComparison.OrdinalIgnoreCase));
|
b.Username.Equals(state.LastBidder, StringComparison.OrdinalIgnoreCase));
|
||||||
@@ -441,7 +456,7 @@ namespace AutoBidder.Services
|
|||||||
|
|
||||||
if (state.Status == AuctionStatus.Running)
|
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
|
// Solo eventi importanti (bid, reset, errori) vengono loggati
|
||||||
}
|
}
|
||||||
else if (state.Status == AuctionStatus.Paused)
|
else if (state.Status == AuctionStatus.Paused)
|
||||||
@@ -457,11 +472,15 @@ namespace AutoBidder.Services
|
|||||||
OnAuctionUpdated?.Invoke(state);
|
OnAuctionUpdated?.Invoke(state);
|
||||||
UpdateAuctionHistory(auction, state);
|
UpdateAuctionHistory(auction, state);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ?? NUOVO: Calcola e aggiorna valore prodotto se disponibili dati
|
// ?? NUOVO: Calcola e aggiorna valore prodotto se disponibili dati
|
||||||
if (auction.BuyNowPrice.HasValue || auction.ShippingCost.HasValue)
|
if (auction.BuyNowPrice.HasValue || auction.ShippingCost.HasValue)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var settings = Utilities.SettingsManager.Load();
|
||||||
|
|
||||||
var productValue = Utilities.ProductValueCalculator.Calculate(
|
var productValue = Utilities.ProductValueCalculator.Calculate(
|
||||||
auction,
|
auction,
|
||||||
state.Price,
|
state.Price,
|
||||||
@@ -470,9 +489,9 @@ namespace AutoBidder.Services
|
|||||||
|
|
||||||
auction.CalculatedValue = productValue;
|
auction.CalculatedValue = productValue;
|
||||||
|
|
||||||
// Log valore solo se cambia significativamente o ogni 10 polling
|
// Log valore solo se abilitato nelle impostazioni
|
||||||
bool shouldLogValue = false;
|
bool shouldLogValue = false;
|
||||||
if (auction.PollingLatencyMs % 10 == 0) // Ogni ~10 poll
|
if (settings.LogValueCalculations && auction.PollingLatencyMs % 10 == 0) // Ogni ~10 poll
|
||||||
{
|
{
|
||||||
shouldLogValue = true;
|
shouldLogValue = true;
|
||||||
}
|
}
|
||||||
@@ -490,13 +509,10 @@ namespace AutoBidder.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NUOVA LOGICA: Punta solo se siamo vicini alla deadline E nessun altro ha appena puntato
|
// ?? NUOVA LOGICA: Controlla PRIMA il timing, POI le strategie
|
||||||
if (state.Status == AuctionStatus.Running && !auction.IsPaused && !auction.IsAttackInProgress)
|
if (state.Status == AuctionStatus.Running && !auction.IsPaused)
|
||||||
{
|
{
|
||||||
if (ShouldBid(auction, state))
|
await TryPlaceBid(auction, state, token);
|
||||||
{
|
|
||||||
await ExecuteBidStrategy(auction, state, token);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -507,117 +523,113 @@ namespace AutoBidder.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Strategia di puntata SEMPLIFICATA
|
/// Sistema di puntata SEMPLICE.
|
||||||
/// Le strategie restituiscono solo un booleano (posso puntare?).
|
/// Punta esattamente a `offset` ms dalla scadenza.
|
||||||
/// Il timing è sempre fisso: offset globale o override per asta.
|
/// Le strategie decidono SE puntare, non QUANDO.
|
||||||
/// </summary>
|
/// </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();
|
var settings = SettingsManager.Load();
|
||||||
|
|
||||||
// Calcola il tempo rimanente in millisecondi
|
// Offset: millisecondi prima della scadenza
|
||||||
double timerMs = state.Timer * 1000;
|
int offsetMs = auction.BidBeforeDeadlineMs > 0
|
||||||
|
|
||||||
// 🛑 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
|
|
||||||
? auction.BidBeforeDeadlineMs
|
? auction.BidBeforeDeadlineMs
|
||||||
: settings.DefaultBidBeforeDeadlineMs;
|
: settings.DefaultBidBeforeDeadlineMs;
|
||||||
|
|
||||||
// 🧠 VALUTAZIONE STRATEGIE (restituiscono solo bool)
|
// Timer dall'API (in secondi, convertito in ms)
|
||||||
var session = _apiClient.GetSession();
|
double timerMs = state.Timer * 1000;
|
||||||
var currentUsername = session?.Username ?? "";
|
|
||||||
|
|
||||||
// Verifica se le strategie permettono di puntare
|
// Skip se già vincitore o timer scaduto
|
||||||
var decision = _bidStrategy.ShouldPlaceBid(auction, state, settings, currentUsername);
|
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 (!decision.ShouldBid)
|
||||||
|
{
|
||||||
|
if (settings.LogStrategyDecisions)
|
||||||
{
|
{
|
||||||
auction.AddLog($"[STRATEGY] {decision.Reason}");
|
auction.AddLog($"[STRATEGY] {decision.Reason}");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🕐 TIMER-BASED SCHEDULING (offset fisso, niente calcoli dinamici)
|
// ?? PUNTA!
|
||||||
if (timerMs > fixedOffsetMs)
|
auction.BidScheduled = true;
|
||||||
{
|
|
||||||
// Timer ancora alto - Schedula puntata futura
|
|
||||||
double delayMs = timerMs - fixedOffsetMs;
|
|
||||||
|
|
||||||
// Non schedulare se già c'è un task attivo per questa asta
|
if (settings.LogBids)
|
||||||
if (auction.IsAttackInProgress)
|
|
||||||
{
|
{
|
||||||
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);
|
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>
|
/// <summary>
|
||||||
/// Esegue la puntata e registra metriche
|
/// Esegue la puntata e registra metriche
|
||||||
@@ -639,7 +651,7 @@ namespace AutoBidder.Services
|
|||||||
_bidStrategy.RecordTimerExpired(auction);
|
_bidStrategy.RecordTimerExpired(auction);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔥 FIX: Aggiorna contatore puntate
|
// ?? FIX: Aggiorna contatore puntate
|
||||||
if (result.Success)
|
if (result.Success)
|
||||||
{
|
{
|
||||||
if (result.RemainingBids.HasValue)
|
if (result.RemainingBids.HasValue)
|
||||||
@@ -681,7 +693,7 @@ namespace AutoBidder.Services
|
|||||||
Timer = state.Timer,
|
Timer = state.Timer,
|
||||||
LatencyMs = result.LatencyMs,
|
LatencyMs = result.LatencyMs,
|
||||||
Success = result.Success,
|
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)
|
catch (Exception ex)
|
||||||
@@ -695,40 +707,33 @@ namespace AutoBidder.Services
|
|||||||
{
|
{
|
||||||
var settings = Utilities.SettingsManager.Load();
|
var settings = Utilities.SettingsManager.Load();
|
||||||
|
|
||||||
// ?? CONTROLLO ANTI-AUTOBID BIDOO (PRIORITÀ MASSIMA)
|
if (settings.LogTiming)
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
// Timer ancora sopra la soglia - aspetta che le auto-puntate si attivino
|
auction.AddLog($"[DEBUG] === INIZIO CONTROLLI PUNTATA ===");
|
||||||
if (settings.LogAutoBidWaitSkips)
|
|
||||||
{
|
|
||||||
auction.AddLog($"[AUTOBID] Timer {state.Timer:F2}s > soglia {settings.WaitForAutoBidThresholdSeconds}s - Aspetto auto-puntate Bidoo");
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ?? CONTROLLO 0: Verifica convenienza (se dati disponibili)
|
// ?? CONTROLLO 0: Verifica convenienza (se abilitato e dati disponibili)
|
||||||
// IMPORTANTE: Applica solo se BuyNowPrice è valido (> 0)
|
if (settings.ValueCheckEnabled &&
|
||||||
// Se BuyNowPrice == 0, significa errore scraping - non bloccare le puntate
|
auction.BuyNowPrice.HasValue &&
|
||||||
if (auction.BuyNowPrice.HasValue &&
|
|
||||||
auction.BuyNowPrice.Value > 0 &&
|
auction.BuyNowPrice.Value > 0 &&
|
||||||
auction.CalculatedValue != null &&
|
auction.CalculatedValue != null &&
|
||||||
auction.CalculatedValue.Savings.HasValue &&
|
auction.CalculatedValue.Savings.HasValue &&
|
||||||
!auction.CalculatedValue.IsWorthIt)
|
!auction.CalculatedValue.IsWorthIt)
|
||||||
{
|
{
|
||||||
// Permetti comunque di puntare se il risparmio è ancora positivo (anche se piccolo)
|
// Usa la percentuale configurabile dall'utente
|
||||||
// Blocca solo se sta andando in perdita significativa (< -5%)
|
|
||||||
if (auction.CalculatedValue.SavingsPercentage.HasValue &&
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (settings.LogTiming && settings.ValueCheckEnabled)
|
||||||
|
{
|
||||||
|
auction.AddLog($"[DEBUG] ? Controllo convenienza OK");
|
||||||
|
}
|
||||||
|
|
||||||
// ?? CONTROLLO ANTI-COLLISIONE: Rileva aste troppo "affollate"
|
// ?? CONTROLLO ANTI-COLLISIONE: Rileva aste troppo "affollate"
|
||||||
// Se negli ultimi 10 secondi ci sono state 3+ puntate di utenti diversi, evita
|
// Se negli ultimi 10 secondi ci sono state 3+ puntate di utenti diversi, evita
|
||||||
var recentBidsThreshold = 10; // secondi
|
var recentBidsThreshold = 10; // secondi
|
||||||
@@ -746,9 +751,14 @@ namespace AutoBidder.Services
|
|||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
.Count();
|
.Count();
|
||||||
|
|
||||||
|
if (settings.LogTiming)
|
||||||
|
{
|
||||||
|
auction.AddLog($"[DEBUG] Bidder attivi ultimi {recentBidsThreshold}s: {activeBidders}/{maxActiveBidders}");
|
||||||
|
}
|
||||||
|
|
||||||
if (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 session = _apiClient.GetSession();
|
||||||
var lastBid = recentBids.OrderByDescending(b => b.Timestamp).FirstOrDefault();
|
var lastBid = recentBids.OrderByDescending(b => b.Timestamp).FirstOrDefault();
|
||||||
|
|
||||||
@@ -762,6 +772,11 @@ namespace AutoBidder.Services
|
|||||||
}
|
}
|
||||||
catch { /* Ignora errori nel controllo competizione */ }
|
catch { /* Ignora errori nel controllo competizione */ }
|
||||||
|
|
||||||
|
if (settings.LogTiming)
|
||||||
|
{
|
||||||
|
auction.AddLog($"[DEBUG] ? Controllo competizione OK");
|
||||||
|
}
|
||||||
|
|
||||||
// ?? CONTROLLO 1: Limite minimo puntate residue
|
// ?? CONTROLLO 1: Limite minimo puntate residue
|
||||||
if (settings.MinimumRemainingBids > 0)
|
if (settings.MinimumRemainingBids > 0)
|
||||||
{
|
{
|
||||||
@@ -771,27 +786,44 @@ namespace AutoBidder.Services
|
|||||||
auction.AddLog($"[LIMIT] Puntata bloccata: puntate residue ({session.RemainingBids}) <= limite ({settings.MinimumRemainingBids})");
|
auction.AddLog($"[LIMIT] Puntata bloccata: puntate residue ({session.RemainingBids}) <= limite ({settings.MinimumRemainingBids})");
|
||||||
return false;
|
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 (state.IsMyBid)
|
||||||
{
|
{
|
||||||
|
if (settings.LogTiming)
|
||||||
|
{
|
||||||
|
auction.AddLog($"[DEBUG] Sono già vincitore");
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ?? CONTROLLO 3: MinPrice/MaxPrice
|
// ?? CONTROLLO 3: MinPrice/MaxPrice
|
||||||
if (auction.MinPrice > 0 && state.Price < auction.MinPrice)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auction.MaxPrice > 0 && state.Price > auction.MaxPrice)
|
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;
|
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
|
// ?? CONTROLLO 4: MinResets/MaxResets
|
||||||
if (auction.MinResets > 0 && auction.ResetCount < auction.MinResets)
|
if (auction.MinResets > 0 && auction.ResetCount < auction.MinResets)
|
||||||
{
|
{
|
||||||
@@ -805,16 +837,34 @@ namespace AutoBidder.Services
|
|||||||
return false;
|
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)
|
// ?? CONTROLLO 6: Cooldown (evita puntate multiple ravvicinate)
|
||||||
if (auction.LastClickAt.HasValue)
|
if (auction.LastClickAt.HasValue)
|
||||||
{
|
{
|
||||||
var timeSinceLastClick = DateTime.UtcNow - auction.LastClickAt.Value;
|
var timeSinceLastClick = DateTime.UtcNow - auction.LastClickAt.Value;
|
||||||
if (timeSinceLastClick.TotalMilliseconds < 800)
|
if (timeSinceLastClick.TotalMilliseconds < 800)
|
||||||
{
|
{
|
||||||
|
if (settings.LogTiming)
|
||||||
|
{
|
||||||
|
auction.AddLog($"[DEBUG] Cooldown attivo ({timeSinceLastClick.TotalMilliseconds:F0}ms < 800ms)");
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (settings.LogTiming)
|
||||||
|
{
|
||||||
|
auction.AddLog($"[DEBUG] === TUTTI I CONTROLLI SUPERATI ===");
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -867,20 +917,8 @@ namespace AutoBidder.Services
|
|||||||
Notes = $"Puntata: EUR{state.Price:F2}"
|
Notes = $"Puntata: EUR{state.Price:F2}"
|
||||||
});
|
});
|
||||||
|
|
||||||
// Aggiorna statistiche bidder
|
// ? RIMOSSO: Non incrementare qui - è già gestito da UpdateBidderStatsFromRecentBids
|
||||||
if (!string.IsNullOrEmpty(state.LastBidder))
|
// L'incremento doppio causava conteggi gonfiati
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
OnResetCountChanged?.Invoke(auction.AuctionId);
|
OnResetCountChanged?.Invoke(auction.AuctionId);
|
||||||
}
|
}
|
||||||
@@ -894,8 +932,8 @@ namespace AutoBidder.Services
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unisce la storia puntate ricevuta dall'API con quella esistente,
|
/// Unisce la storia puntate ricevuta dall'API con quella esistente,
|
||||||
/// mantenendo le puntate più vecchie e aggiungendo solo le nuove.
|
/// mantenendo le puntate più vecchie e aggiungendo solo le nuove.
|
||||||
/// Le puntate sono ordinate con le più RECENTI in CIMA.
|
/// Le puntate sono ordinate con le più RECENTI in CIMA.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void MergeBidHistory(AuctionInfo auction, List<BidHistoryEntry> newBids)
|
private void MergeBidHistory(AuctionInfo auction, List<BidHistoryEntry> newBids)
|
||||||
{
|
{
|
||||||
@@ -908,7 +946,7 @@ namespace AutoBidder.Services
|
|||||||
// ?? FIX: Usa lock per thread-safety
|
// ?? FIX: Usa lock per thread-safety
|
||||||
lock (auction.RecentBids)
|
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)
|
if (auction.RecentBids.Count == 0)
|
||||||
{
|
{
|
||||||
auction.RecentBids = newBids.ToList();
|
auction.RecentBids = newBids.ToList();
|
||||||
@@ -953,7 +991,7 @@ namespace AutoBidder.Services
|
|||||||
.ThenByDescending(b => b.Price)
|
.ThenByDescending(b => b.Price)
|
||||||
.ToList();
|
.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)
|
if (maxEntries > 0 && auction.RecentBids.Count > maxEntries)
|
||||||
{
|
{
|
||||||
auction.RecentBids = auction.RecentBids
|
auction.RecentBids = auction.RecentBids
|
||||||
@@ -975,7 +1013,7 @@ namespace AutoBidder.Services
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Assicura che la puntata corrente (quella vincente) sia sempre presente nello storico.
|
/// 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.
|
/// 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>
|
/// </summary>
|
||||||
private void EnsureCurrentBidInHistory(AuctionInfo auction, AuctionState state)
|
private void EnsureCurrentBidInHistory(AuctionInfo auction, AuctionState state)
|
||||||
{
|
{
|
||||||
@@ -984,22 +1022,18 @@ namespace AutoBidder.Services
|
|||||||
var statePrice = (decimal)state.Price;
|
var statePrice = (decimal)state.Price;
|
||||||
var currentBidder = state.LastBidder;
|
var currentBidder = state.LastBidder;
|
||||||
|
|
||||||
// 🔥 VERIFICA: Controlla se è la stessa puntata che abbiamo già in cima
|
// ?? VERIFICA: Controlla se questa puntata è già presente nella lista
|
||||||
// Evitiamo di aggiungere continuamente la stessa puntata ad ogni polling
|
// Evitiamo duplicati controllando prezzo + utente in TUTTA la lista
|
||||||
if (auction.RecentBids.Count > 0)
|
var alreadyExists = auction.RecentBids.Any(b =>
|
||||||
{
|
Math.Abs(b.Price - statePrice) < 0.001m &&
|
||||||
var topBid = auction.RecentBids[0]; // Prima = più recente
|
b.Username.Equals(currentBidder, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
// Se la puntata in cima è identica (stesso prezzo + stesso utente), salta
|
if (alreadyExists)
|
||||||
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
|
// ?? NUOVA PUNTATA: Aggiungi solo se non esiste già
|
||||||
// Questo significa che c'è stata una nuova puntata che l'API non ha ancora segnalato
|
|
||||||
var lastBidTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
var lastBidTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||||
|
|
||||||
auction.RecentBids.Insert(0, new BidHistoryEntry
|
auction.RecentBids.Insert(0, new BidHistoryEntry
|
||||||
@@ -1010,52 +1044,45 @@ namespace AutoBidder.Services
|
|||||||
BidType = "Auto"
|
BidType = "Auto"
|
||||||
});
|
});
|
||||||
|
|
||||||
// Aggiorna anche le statistiche bidder
|
// ? RIMOSSO: Non incrementare BidderStats qui
|
||||||
if (!auction.BidderStats.ContainsKey(currentBidder))
|
// È gestito SOLO da UpdateBidderStatsFromRecentBids per evitare duplicazioni
|
||||||
{
|
// La puntata è stata aggiunta a RecentBids, sarà contata al prossimo aggiornamento
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch { /* Silenzioso */ }
|
catch { /* Silenzioso */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Aggiorna le statistiche dei bidder in modo CUMULATIVO.
|
/// Aggiorna le statistiche dei bidder basandosi SOLO su RecentBids.
|
||||||
/// BidCount è il totale dall'inizio del monitoraggio.
|
/// QUESTO È L'UNICO POSTO che aggiorna i conteggi.
|
||||||
/// RecentBidCount è il conteggio nella finestra corrente di RecentBids.
|
/// IMPORTANTE: Il conteggio è basato SOLO sulle puntate in RecentBids, non cumulativo.
|
||||||
/// I bidder NON vengono mai rimossi, il contatore è infinito.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void UpdateBidderStatsFromRecentBids(AuctionInfo auction)
|
private void UpdateBidderStatsFromRecentBids(AuctionInfo auction)
|
||||||
{
|
{
|
||||||
try
|
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
|
var bidsByUser = auction.RecentBids
|
||||||
.GroupBy(b => b.Username, StringComparer.OrdinalIgnoreCase)
|
.GroupBy(b => b.Username, StringComparer.OrdinalIgnoreCase)
|
||||||
.ToDictionary(
|
.ToDictionary(
|
||||||
g => g.Key,
|
g => g.Key,
|
||||||
g => new
|
g => new
|
||||||
{
|
{
|
||||||
RecentCount = g.Count(),
|
Count = g.Count(),
|
||||||
LastBidTime = DateTimeOffset.FromUnixTimeSeconds(g.Max(b => b.Timestamp)).DateTime
|
LastBidTime = DateTimeOffset.FromUnixTimeSeconds(g.Max(b => b.Timestamp)).DateTime
|
||||||
},
|
},
|
||||||
StringComparer.OrdinalIgnoreCase
|
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)
|
foreach (var kvp in bidsByUser)
|
||||||
{
|
{
|
||||||
var username = kvp.Key;
|
var username = kvp.Key;
|
||||||
@@ -1063,37 +1090,22 @@ namespace AutoBidder.Services
|
|||||||
|
|
||||||
if (!auction.BidderStats.ContainsKey(username))
|
if (!auction.BidderStats.ContainsKey(username))
|
||||||
{
|
{
|
||||||
// Nuovo bidder - inizializza con i conteggi attuali
|
|
||||||
auction.BidderStats[username] = new BidderInfo
|
auction.BidderStats[username] = new BidderInfo
|
||||||
{
|
{
|
||||||
Username = username,
|
Username = username,
|
||||||
BidCount = stats.RecentCount, // Primo conteggio
|
BidCount = stats.Count,
|
||||||
RecentBidCount = stats.RecentCount,
|
RecentBidCount = stats.Count,
|
||||||
LastBidTime = stats.LastBidTime
|
LastBidTime = stats.LastBidTime
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var existing = auction.BidderStats[username];
|
var existing = auction.BidderStats[username];
|
||||||
|
existing.BidCount = stats.Count;
|
||||||
// Calcola delta: quante nuove puntate rispetto all'ultimo check
|
existing.RecentBidCount = stats.Count;
|
||||||
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.LastBidTime = stats.LastBidTime;
|
existing.LastBidTime = stats.LastBidTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NON rimuovere i bidder che non sono più in RecentBids!
|
|
||||||
// Il conteggio totale è cumulativo e persistente.
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,84 +17,6 @@ namespace AutoBidder.Services
|
|||||||
private int _sessionTotalBids = 0;
|
private int _sessionTotalBids = 0;
|
||||||
private DateTime _sessionStartedAt = DateTime.UtcNow;
|
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>
|
/// <summary>
|
||||||
/// Aggiorna heat metric per un'asta
|
/// Aggiorna heat metric per un'asta
|
||||||
/// </summary>
|
/// </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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>
|
/// <summary>
|
||||||
/// Rileva pattern bot analizzando i delta timing degli ultimi bid
|
/// Rileva pattern bot analizzando i delta timing degli ultimi bid
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ namespace AutoBidder.Utilities
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calcola risparmio rispetto al prezzo "Compra Subito"
|
// Calcola risparmio rispetto al prezzo "Compra Subito"
|
||||||
if (auctionInfo.BuyNowPrice.HasValue)
|
if (auctionInfo.BuyNowPrice.HasValue && auctionInfo.BuyNowPrice.Value > 0)
|
||||||
{
|
{
|
||||||
var buyNowTotal = auctionInfo.BuyNowPrice.Value;
|
var buyNowTotal = auctionInfo.BuyNowPrice.Value;
|
||||||
if (auctionInfo.ShippingCost.HasValue)
|
if (auctionInfo.ShippingCost.HasValue)
|
||||||
@@ -50,12 +50,24 @@ namespace AutoBidder.Utilities
|
|||||||
}
|
}
|
||||||
|
|
||||||
value.Savings = buyNowTotal - value.TotalCostIfWin;
|
value.Savings = buyNowTotal - value.TotalCostIfWin;
|
||||||
|
|
||||||
|
// ?? FIX: Evita divisione per zero
|
||||||
|
if (buyNowTotal > 0)
|
||||||
|
{
|
||||||
value.SavingsPercentage = (value.Savings.Value / buyNowTotal) * 100.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;
|
value.IsWorthIt = value.Savings.Value > 0;
|
||||||
}
|
}
|
||||||
else
|
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;
|
value.IsWorthIt = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace AutoBidder.Utilities
|
|||||||
public class AppSettings
|
public class AppSettings
|
||||||
{
|
{
|
||||||
// NUOVE IMPOSTAZIONI PREDEFINITE PER LE ASTE
|
// 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 bool DefaultCheckAuctionOpenBeforeBid { get; set; } = false;
|
||||||
public double DefaultMinPrice { get; set; } = 0;
|
public double DefaultMinPrice { get; set; } = 0;
|
||||||
public double DefaultMaxPrice { get; set; } = 0;
|
public double DefaultMaxPrice { get; set; } = 0;
|
||||||
@@ -104,71 +104,64 @@ namespace AutoBidder.Utilities
|
|||||||
// STRATEGIE AVANZATE DI PUNTATA
|
// 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>
|
/// <summary>
|
||||||
/// Abilita compensazione adattiva della latenza.
|
/// Log quando viene piazzata una puntata [BID]
|
||||||
/// Misura latenza reale per ogni asta e adatta l'anticipo automaticamente.
|
|
||||||
/// Default: true
|
/// Default: true
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AdaptiveLatencyEnabled { get; set; } = true;
|
public bool LogBids { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Abilita jitter casuale sull'offset per evitare sincronizzazione con altri bot.
|
/// Log quando una strategia blocca la puntata [STRATEGY]
|
||||||
/// Aggiunge ±JitterRangeMs al timing di puntata.
|
|
||||||
/// Default: true
|
/// Default: true
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool JitterEnabled { get; set; } = true;
|
public bool LogStrategyDecisions { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Range massimo del jitter casuale in millisecondi (±X ms).
|
/// Log calcoli valore prodotto [VALUE]
|
||||||
/// Default: 50 (range -50ms a +50ms)
|
/// Default: false (attiva per debug)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int JitterRangeMs { get; set; } = 50;
|
public bool LogValueCalculations { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <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
|
/// Default: true
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool DynamicOffsetEnabled { get; set; } = true;
|
public bool LogErrors { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Offset minimo garantito in ms (non scende mai sotto questo valore).
|
/// Log stato asta (terminata, reset, ecc.) [STATUS]
|
||||||
/// 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.
|
|
||||||
/// Default: true
|
/// Default: true
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool WaitForAutoBidEnabled { get; set; } = true;
|
public bool LogAuctionStatus { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Soglia in secondi sotto la quale si può puntare.
|
/// Log profiling avversari [OPPONENT]
|
||||||
/// Bidoo attiva le auto-puntate a ~2 secondi, quindi aspettiamo che passino.
|
/// Default: false
|
||||||
/// Default: 1.8 (punta solo quando timer < 1.8s, dopo che le auto-puntate si sono attivate)
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double WaitForAutoBidThresholdSeconds { get; set; } = 1.8;
|
public bool LogOpponentProfiling { get; set; } = false;
|
||||||
|
|
||||||
/// <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;
|
|
||||||
|
|
||||||
// 🎯 STRATEGIE SEMPLIFICATE
|
// 🎯 STRATEGIE SEMPLIFICATE
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Entry Point: Usato SOLO per calcolare i limiti consigliati (70% del MaxPrice storico).
|
/// 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.
|
/// NON blocca le puntate! I limiti MinPrice/MaxPrice impostati dall'utente sono RIGIDI.
|
||||||
@@ -190,6 +183,26 @@ namespace AutoBidder.Utilities
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UserExhaustionEnabled { get; set; } = true;
|
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
|
// RILEVAMENTO COMPETIZIONE E HEAT METRIC
|
||||||
// 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥
|
// 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥
|
||||||
|
|||||||
Reference in New Issue
Block a user