Le strategie a numero fisso (calore, ritiro morbido, anti-bot, avversari aggressivi, puntata probabilistica, velocità del prezzo, esaurimento, concorrenza) e le impostazioni morte (cadenze di polling, finestra critica, database, log avanzati, suggerimenti sull'anticipo, schede dettagliate) non ci sono più: rigiocate sui dossier non fermavano una puntata sbagliata senza fermarne anche di giuste, e i loro numeri andavano tarati a mano. Restano i paletti dell'utente (tetti, budget, fascia oraria) e il duello. Al loro posto due componenti che imparano dalla sessione in corso, dentro i paletti: - CompetitionRegime per asta (Calmo / Sfogo / Sondaggio) guidato dal valore atteso appreso: tre negativi di fila e si lascia sfogare gli altri, si rientra con una puntata di prova dopo abbastanza cicli buoni, e se viene coperta subito la pazienza raddoppia. È il "capire da soli quando tornare a puntare". - LatencyModel per l'anticipo: margine + p99 della latenza misurata adesso, tenuto fra LeadMinMs e LeadMaxMs; una puntata tardiva alza il margine subito, venti in tempo lo abbassano piano. Un anticipo scritto a mano su un'asta vince sempre (BidLeadIsManual). Archiviazione: l'archivio mensile aste-AAAA-MM.jsonl (95 MB) duplicava il riepilogo che ogni dossier ha già in coda. AuctionDetailStore ora legge testa e coda dei dossier, con cache: 6349 file in 6 s la prima volta. Pulsanti per svuotare le esportazioni e per azzerare le statistiche voce per voce (lo storico passa dalla copia di sicurezza). Scheda Apprendimento: sezione "Autonomia sul momento" con il modello di latenza e il regime di ogni asta seguita. Ml/LEGGIMI.md descrive l'algoritmo per intero. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
361 lines
15 KiB
C#
361 lines
15 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media;
|
|
using AutoBidder.Utilities;
|
|
using AutoBidder.ViewModels;
|
|
|
|
namespace AutoBidder
|
|
{
|
|
/// <summary>
|
|
/// UI update methods and selected auction details
|
|
/// </summary>
|
|
public partial class MainWindow
|
|
{
|
|
/// <summary>Firma dell'ultimo log disegnato: asta, righe e ultima voce.</summary>
|
|
private (string Id, int Count, DateTime Last, int Repeat) _lastLogSignature;
|
|
|
|
/// <summary>Firma dell'ultima griglia puntatori disegnata.</summary>
|
|
private (string Id, int Count, int TotalBids) _lastBiddersSignature;
|
|
|
|
private void UpdateAuctionLog(AuctionViewModel auction)
|
|
{
|
|
try
|
|
{
|
|
var auctionInfo = auction.AuctionInfo;
|
|
var log = auctionInfo.AuctionLog;
|
|
if (log == null) return;
|
|
|
|
// Ricostruire il documento significa creare un Paragraph e un Run per ogni
|
|
// riga, fino a duecento, a ogni risposta del server. Se il log non è
|
|
// cambiato non c'è nulla da ridisegnare.
|
|
var last = log.Count > 0 ? log[^1] : null;
|
|
var signature = (auction.AuctionId, log.Count,
|
|
last?.Timestamp ?? DateTime.MinValue, last?.RepeatCount ?? 0);
|
|
|
|
if (signature == _lastLogSignature) return;
|
|
_lastLogSignature = signature;
|
|
|
|
var logBox = SelectedAuctionLog;
|
|
var doc = logBox.Document;
|
|
doc.Blocks.Clear();
|
|
|
|
foreach (var entry in log)
|
|
{
|
|
// Color coding based on structured log level
|
|
Brush color = entry.Level switch
|
|
{
|
|
Models.AuctionLogLevel.Error => new SolidColorBrush(Color.FromRgb(232, 17, 35)), // Red
|
|
Models.AuctionLogLevel.Warning => new SolidColorBrush(Color.FromRgb(255, 183, 0)), // Yellow/Orange
|
|
Models.AuctionLogLevel.Success => new SolidColorBrush(Color.FromRgb(0, 216, 0)), // Green
|
|
Models.AuctionLogLevel.Bid => new SolidColorBrush(Color.FromRgb(0, 216, 0)), // Green
|
|
Models.AuctionLogLevel.Strategy => new SolidColorBrush(Color.FromRgb(200, 160, 255)),// Purple
|
|
Models.AuctionLogLevel.Timing => new SolidColorBrush(Color.FromRgb(150, 150, 150)), // Gray
|
|
Models.AuctionLogLevel.Debug => new SolidColorBrush(Color.FromRgb(120, 120, 120)), // Dark gray
|
|
_ => new SolidColorBrush(Color.FromRgb(100, 180, 255)) // Light Blue
|
|
};
|
|
|
|
var repeatSuffix = entry.RepeatCount > 1 ? $" (x{entry.RepeatCount})" : "";
|
|
var line = $"[{entry.TimeDisplay}] [{entry.LevelLabel}] {entry.Message}{repeatSuffix}";
|
|
|
|
var p = new System.Windows.Documents.Paragraph { Margin = new Thickness(0, 2, 0, 2) };
|
|
var r = new System.Windows.Documents.Run(line) { Foreground = color };
|
|
p.Inlines.Add(r);
|
|
doc.Blocks.Add(p);
|
|
}
|
|
|
|
// Auto-scroll if near bottom
|
|
var viewer = logBox;
|
|
var vpos = viewer.VerticalOffset;
|
|
var vmax = viewer.ExtentHeight - viewer.ViewportHeight;
|
|
if (vmax - vpos < 40)
|
|
{
|
|
viewer.ScrollToEnd();
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
private void RefreshBiddersGrid(AuctionViewModel auction)
|
|
{
|
|
try
|
|
{
|
|
// Copia sotto lucchetto: il motore riscrive il dizionario dal proprio thread.
|
|
var bidders = auction.AuctionInfo.SnapshotBidderStats();
|
|
|
|
// La quota si calcola qui: il singolo puntatore non conosce il totale dell'asta.
|
|
var totalBids = bidders.Sum(b => b.BidCount);
|
|
foreach (var bidder in bidders)
|
|
{
|
|
bidder.SharePercent = totalBids > 0 ? bidder.BidCount * 100.0 / totalBids : 0;
|
|
}
|
|
|
|
// Riassegnare ItemsSource ricostruisce l'intera griglia e la fa lampeggiare:
|
|
// si rifà solo quando i numeri sono davvero cambiati.
|
|
var signature = (auction.AuctionId, bidders.Count, totalBids);
|
|
if (signature != _lastBiddersSignature)
|
|
{
|
|
_lastBiddersSignature = signature;
|
|
|
|
SelectedAuctionBiddersGrid.ItemsSource = null;
|
|
SelectedAuctionBiddersGrid.ItemsSource = bidders;
|
|
SelectedAuctionBiddersCount.Text = bidders.Count == 0
|
|
? "Nessun dato sui puntatori."
|
|
: $"{bidders.Count} puntatori · {totalBids} puntate osservate";
|
|
}
|
|
|
|
// ?? NUOVO: Aggiorna il contatore della storia puntate con limite configurato
|
|
var settings = SettingsManager.Load();
|
|
var maxEntries = settings?.MaxBidHistoryEntries ?? 20;
|
|
var historyCount = auction.BidHistoryEntries?.Count ?? 0;
|
|
|
|
var bidHistoryCountTextBlock = AuctionMonitor.FindName("BidHistoryCount") as TextBlock;
|
|
if (bidHistoryCountTextBlock != null)
|
|
{
|
|
// Mostra "Ultime 20 puntate" se il limite � attivo
|
|
if (maxEntries > 0)
|
|
{
|
|
bidHistoryCountTextBlock.Text = $"Ultime {maxEntries} puntate";
|
|
}
|
|
else
|
|
{
|
|
bidHistoryCountTextBlock.Text = $"Ultime puntate: {historyCount}";
|
|
}
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
private void UpdateAuctionSettingsDisplay(AuctionViewModel auction)
|
|
{
|
|
try
|
|
{
|
|
// Blocca temporaneamente i TextChanged per evitare loop di aggiornamento
|
|
_isUpdatingSelection = true;
|
|
|
|
SelectedAuctionName.Text = auction.Name;
|
|
SelectedBidBeforeDeadlineMs.Text = auction.AuctionInfo.BidBeforeDeadlineMs.ToString();
|
|
SelectedMinPrice.Text = auction.MinPrice.ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
|
|
SelectedMaxPrice.Text = auction.MaxPrice.ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
|
|
SelectedMaxClicks.Text = auction.MaxClicks.ToString();
|
|
AuctionMonitor.SelectedMaxSpend.Text = auction.AuctionInfo.MaxTotalSpendEuro
|
|
.ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
|
|
AuctionMonitor.SelectedStopAtBreakEven.IsChecked = auction.AuctionInfo.StopAtBreakEven;
|
|
|
|
var url = auction.AuctionInfo.OriginalUrl;
|
|
if (string.IsNullOrEmpty(url))
|
|
url = $"https://it.bidoo.com/auction.php?a=asta_{auction.AuctionId}";
|
|
SelectedAuctionUrl.Text = url;
|
|
|
|
ResetSettingsButton.IsEnabled = true;
|
|
ClearBiddersButton.IsEnabled = true;
|
|
ClearLogButton.IsEnabled = true;
|
|
|
|
UpdateAuctionLog(auction);
|
|
RefreshBiddersGrid(auction);
|
|
|
|
_isUpdatingSelection = false;
|
|
}
|
|
catch
|
|
{
|
|
_isUpdatingSelection = false;
|
|
}
|
|
}
|
|
|
|
private void UpdateTotalCount()
|
|
{
|
|
MonitorateTitle.Text = $"Aste monitorate: {_auctionViewModels.Count}";
|
|
}
|
|
|
|
private void UpdateGlobalControlButtons()
|
|
{
|
|
try
|
|
{
|
|
var hasAuctions = _auctionViewModels.Count > 0;
|
|
|
|
if (!hasAuctions)
|
|
{
|
|
// Nessuna asta: tutti disabilitati
|
|
StartButton.IsEnabled = false;
|
|
StartButton.Opacity = 0.4;
|
|
PauseAllButton.IsEnabled = false;
|
|
PauseAllButton.Opacity = 0.4;
|
|
StopButton.IsEnabled = false;
|
|
StopButton.Opacity = 0.4;
|
|
return;
|
|
}
|
|
|
|
// Conta quante aste possono eseguire ogni azione
|
|
int canStartCount = _auctionViewModels.Count(a => a.CanStart);
|
|
int canPauseCount = _auctionViewModels.Count(a => a.CanPause);
|
|
int canStopCount = _auctionViewModels.Count(a => a.CanStop);
|
|
|
|
// AVVIA TUTTI: abilitato se ALMENO UNA asta pu� essere avviata
|
|
// Scuro se NESSUNA asta pu� essere avviata (tutte gi� avviate)
|
|
StartButton.IsEnabled = canStartCount > 0;
|
|
StartButton.Opacity = canStartCount > 0 ? 1.0 : 0.4;
|
|
|
|
// OSSERVA TUTTE: abilitato se ALMENO UNA asta non e' gia' in sola osservazione
|
|
PauseAllButton.IsEnabled = canPauseCount > 0;
|
|
PauseAllButton.Opacity = canPauseCount > 0 ? 1.0 : 0.4;
|
|
|
|
// FERMA TUTTI: abilitato se ALMENO UNA asta pu� essere fermata
|
|
// Scuro se NESSUNA asta pu� essere fermata (tutte gi� ferme)
|
|
StopButton.IsEnabled = canStopCount > 0;
|
|
StopButton.Opacity = canStopCount > 0 ? 1.0 : 0.4;
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
private void MultiAuctionsGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
if (MultiAuctionsGrid.SelectedItem is AuctionViewModel selected)
|
|
{
|
|
_selectedAuction = selected;
|
|
UpdateSelectedAuctionDetails(selected);
|
|
}
|
|
}
|
|
|
|
private void GridOpenAuction_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
if (sender is FrameworkElement element && element.DataContext is AuctionViewModel vm)
|
|
{
|
|
var url = vm.AuctionInfo.OriginalUrl;
|
|
if (string.IsNullOrEmpty(url))
|
|
url = $"https://it.bidoo.com/auction.php?a=asta_{vm.AuctionId}";
|
|
|
|
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
|
{
|
|
FileName = url,
|
|
UseShellExecute = true
|
|
});
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"[ERRORE] Apertura asta: {ex.Message}", LogLevel.Error);
|
|
}
|
|
}
|
|
|
|
private void ExportSelectedAuction_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
if (sender is FrameworkElement element && element.DataContext is AuctionViewModel vm)
|
|
{
|
|
MessageBox.Show(this, $"Esportazione singola asta non ancora implementata.\nUsa 'Esporta Aste' dalla toolbar.", "Info", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"[ERRORE] Esportazione asta: {ex.Message}", LogLevel.Error);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resetta le impostazioni dell'asta selezionata ai valori predefiniti
|
|
/// </summary>
|
|
private void ResetSettingsButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
if (_selectedAuction == null)
|
|
{
|
|
MessageBox.Show("Seleziona un'asta dalla griglia", "Nessuna Selezione", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
return;
|
|
}
|
|
|
|
var settings = SettingsManager.Load();
|
|
|
|
// Resetta ai valori predefiniti dalle impostazioni
|
|
_selectedAuction.AuctionInfo.BidBeforeDeadlineMs = settings.DefaultBidBeforeDeadlineMs;
|
|
_selectedAuction.AuctionInfo.BidLeadIsManual = false; // torna all'anticipo adattivo
|
|
_selectedAuction.MinPrice = settings.DefaultMinPrice;
|
|
_selectedAuction.MaxPrice = settings.DefaultMaxPrice;
|
|
_selectedAuction.MaxClicks = settings.DefaultMaxClicks;
|
|
|
|
// Aggiorna UI
|
|
UpdateAuctionSettingsDisplay(_selectedAuction);
|
|
|
|
// Salva
|
|
SaveAuctions();
|
|
|
|
Log($"[RESET] Impostazioni ripristinate ai valori predefiniti per: {_selectedAuction.Name}", LogLevel.Info);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"[ERRORE] Reset impostazioni: {ex.Message}", LogLevel.Error);
|
|
MessageBox.Show($"Errore durante il reset: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pulisce la lista degli utenti che hanno puntato sull'asta selezionata
|
|
/// </summary>
|
|
private void ClearBiddersButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
if (_selectedAuction == null)
|
|
{
|
|
MessageBox.Show("Seleziona un'asta dalla griglia", "Nessuna Selezione", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
return;
|
|
}
|
|
|
|
var result = MessageBox.Show(
|
|
$"Pulire la lista degli utenti per questa asta?\n\n{_selectedAuction.Name}\n\nLa lista degli utenti che hanno puntato verr� svuotata.",
|
|
"Conferma Pulizia",
|
|
MessageBoxButton.YesNo,
|
|
MessageBoxImage.Question);
|
|
|
|
if (result != MessageBoxResult.Yes)
|
|
return;
|
|
|
|
// Pulisci la lista bidders
|
|
_selectedAuction.AuctionInfo.BidderStats.Clear();
|
|
|
|
// Aggiorna UI
|
|
RefreshBiddersGrid(_selectedAuction);
|
|
|
|
Log($"[CLEAR] Lista utenti pulita per: {_selectedAuction.Name}", LogLevel.Info);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"[ERRORE] Pulizia lista utenti: {ex.Message}", LogLevel.Error);
|
|
MessageBox.Show($"Errore durante la pulizia: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pulisce il log dell'asta selezionata
|
|
/// </summary>
|
|
private void ClearLogButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
if (_selectedAuction == null)
|
|
{
|
|
MessageBox.Show("Seleziona un'asta dalla griglia", "Nessuna Selezione", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
return;
|
|
}
|
|
|
|
// Pulisci il log dell'asta
|
|
_selectedAuction.AuctionInfo.AuctionLog.Clear();
|
|
|
|
// Aggiorna UI
|
|
UpdateAuctionLog(_selectedAuction);
|
|
|
|
Log($"[CLEAR] Log pulito per: {_selectedAuction.Name}", LogLevel.Info);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log($"[ERRORE] Pulizia log asta: {ex.Message}", LogLevel.Error);
|
|
MessageBox.Show($"Errore durante la pulizia: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
}
|
|
}
|
|
}
|
|
}
|