Refactoring e nuove funzionalità per AutoBidder v4.0
* Aggiornamento alla versione 4.0.0 * Refactoring architetturale: introdotte partial classes e UserControls modulari per migliorare manutenibilità e leggibilità. * Aggiunti nuovi UserControls: `AuctionMonitorControl`, `BrowserControl`, `SettingsControl`, `StatisticsControl`. * Introdotto supporto per WebView2 per il browser integrato. * Migliorata gestione delle aste: aggiunta/rimozione tramite URL o ID, configurazione predefinita. * Nuove funzionalità di esportazione: supporto CSV, JSON, XML con opzioni configurabili. * Logging avanzato: codifica colore per severità e auto-scroll. * Tema scuro moderno e miglioramenti UI/UX: sidebar di navigazione, griglie virtualizzate, icone emoji. * Persistenza dati: salvataggio automatico di aste e impostazioni in file JSON. * Documentazione aggiornata: `README.md`, `CHANGELOG.md` e nuovi file di supporto. * Miglioramenti alla sicurezza: cookie di sessione salvati in modo sicuro con DPAPI. * Preparazione per future estensioni: placeholder per funzionalità avanzate e struttura modulare.
This commit is contained in:
232
Mimante/Models/ProductInsights.cs
Normal file
232
Mimante/Models/ProductInsights.cs
Normal file
@@ -0,0 +1,232 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace AutoBidder.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Insight statistici avanzati per un prodotto specifico
|
||||
/// Aiuta a decidere quando iniziare l'asta per massimizzare probabilità di vittoria
|
||||
/// </summary>
|
||||
public class ProductInsights
|
||||
{
|
||||
public string ProductKey { get; set; } = string.Empty;
|
||||
public string ProductName { get; set; } = string.Empty;
|
||||
|
||||
// Statistiche di base
|
||||
public int TotalAuctions { get; set; }
|
||||
public double AverageFinalPrice { get; set; }
|
||||
public double AverageBidsUsed { get; set; }
|
||||
|
||||
// Timing ottimale
|
||||
public double OptimalStartTimeSeconds { get; set; } // Secondi prima della fine per iniziare
|
||||
public double OptimalStartPrice { get; set; } // Prezzo ideale per iniziare a puntare
|
||||
|
||||
// Distribuzione temporale
|
||||
public Dictionary<int, int> HourlyDistribution { get; set; } = new(); // Ora del giorno -> conteggio aste
|
||||
public Dictionary<string, int> DayOfWeekDistribution { get; set; } = new(); // Giorno -> conteggio
|
||||
|
||||
// Analisi competitors
|
||||
public double AverageCompetitors { get; set; }
|
||||
public double CompetitionIntensity { get; set; } // 0-1, quanto è competitivo
|
||||
|
||||
// Prezzi
|
||||
public double MinFinalPrice { get; set; }
|
||||
public double MaxFinalPrice { get; set; }
|
||||
public double MedianFinalPrice { get; set; }
|
||||
public double PriceStandardDeviation { get; set; }
|
||||
|
||||
// Puntate
|
||||
public int MinBidsUsed { get; set; }
|
||||
public int MaxBidsUsed { get; set; }
|
||||
public int MedianBidsUsed { get; set; }
|
||||
|
||||
// Confidence score (0-100)
|
||||
public int ConfidenceScore { get; set; }
|
||||
|
||||
// Raccomandazioni
|
||||
public string RecommendedStrategy { get; set; } = string.Empty;
|
||||
public double RecommendedMaxPrice { get; set; }
|
||||
public int RecommendedMaxBids { get; set; }
|
||||
public double RecommendedStartTimer { get; set; } // Quando iniziare a puntare (timer in secondi)
|
||||
|
||||
// Timestamp
|
||||
public DateTime LastUpdated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Calcola insights avanzati da una lista di aste chiuse
|
||||
/// </summary>
|
||||
public static ProductInsights Calculate(List<ClosedAuctionRecord> auctions, string productKey, string productName)
|
||||
{
|
||||
if (auctions == null || auctions.Count == 0)
|
||||
{
|
||||
return new ProductInsights
|
||||
{
|
||||
ProductKey = productKey,
|
||||
ProductName = productName,
|
||||
ConfidenceScore = 0,
|
||||
RecommendedStrategy = "Dati insufficienti per raccomandazioni"
|
||||
};
|
||||
}
|
||||
|
||||
var insights = new ProductInsights
|
||||
{
|
||||
ProductKey = productKey,
|
||||
ProductName = productName,
|
||||
TotalAuctions = auctions.Count,
|
||||
LastUpdated = DateTime.UtcNow
|
||||
};
|
||||
|
||||
// Filtra aste con dati validi
|
||||
var validPrices = auctions.Where(a => a.FinalPrice.HasValue).Select(a => a.FinalPrice!.Value).ToList();
|
||||
var validBids = auctions.Where(a => a.BidsUsed.HasValue).Select(a => a.BidsUsed!.Value).ToList();
|
||||
|
||||
if (validPrices.Any())
|
||||
{
|
||||
insights.AverageFinalPrice = validPrices.Average();
|
||||
insights.MinFinalPrice = validPrices.Min();
|
||||
insights.MaxFinalPrice = validPrices.Max();
|
||||
insights.MedianFinalPrice = CalculateMedian(validPrices);
|
||||
insights.PriceStandardDeviation = CalculateStandardDeviation(validPrices);
|
||||
}
|
||||
|
||||
if (validBids.Any())
|
||||
{
|
||||
insights.AverageBidsUsed = validBids.Average();
|
||||
insights.MinBidsUsed = validBids.Min();
|
||||
insights.MaxBidsUsed = validBids.Max();
|
||||
insights.MedianBidsUsed = (int)CalculateMedian(validBids.Select(b => (double)b).ToList());
|
||||
}
|
||||
|
||||
// Calcola timing ottimale (stima basata su numero medio di puntate)
|
||||
// Assumendo ~3 secondi per puntata in media
|
||||
insights.OptimalStartTimeSeconds = insights.AverageBidsUsed * 3.0;
|
||||
|
||||
// Prezzo ottimale per iniziare: mediana - 1 deviazione standard
|
||||
insights.OptimalStartPrice = Math.Max(0, insights.MedianFinalPrice - insights.PriceStandardDeviation);
|
||||
|
||||
// Analisi distribuzione temporale
|
||||
insights.HourlyDistribution = auctions
|
||||
// ClosedAuctionRecord has ScrapedAt (DateTime)
|
||||
.GroupBy(a => a.ScrapedAt.Hour)
|
||||
.ToDictionary(g => g.Key, g => g.Count());
|
||||
|
||||
insights.DayOfWeekDistribution = auctions
|
||||
.GroupBy(a => a.ScrapedAt.DayOfWeek.ToString())
|
||||
.ToDictionary(g => g.Key, g => g.Count());
|
||||
|
||||
// Calcola intensità competizione (basata su varianza puntate)
|
||||
if (validBids.Any() && insights.AverageBidsUsed > 0)
|
||||
{
|
||||
var bidVariance = CalculateStandardDeviation(validBids.Select(b => (double)b).ToList());
|
||||
insights.CompetitionIntensity = Math.Min(1.0, bidVariance / insights.AverageBidsUsed);
|
||||
}
|
||||
|
||||
// Confidence score basato su numero di campioni e consistenza dati
|
||||
insights.ConfidenceScore = CalculateConfidenceScore(auctions.Count, insights.PriceStandardDeviation, insights.AverageFinalPrice);
|
||||
|
||||
// Raccomandazioni intelligenti
|
||||
insights.GenerateRecommendations();
|
||||
|
||||
return insights;
|
||||
}
|
||||
|
||||
private void GenerateRecommendations()
|
||||
{
|
||||
// Raccomandazione prezzo massimo: mediana + 0.5 * deviazione standard
|
||||
RecommendedMaxPrice = MedianFinalPrice + (PriceStandardDeviation * 0.5);
|
||||
|
||||
// Raccomandazione puntate massime: mediana + 30%
|
||||
RecommendedMaxBids = (int)Math.Ceiling(MedianBidsUsed * 1.3);
|
||||
|
||||
// Timer raccomandato per iniziare: basato su analisi timing
|
||||
// Se alta competizione, inizia prima
|
||||
if (CompetitionIntensity > 0.7)
|
||||
{
|
||||
RecommendedStartTimer = Math.Max(30, OptimalStartTimeSeconds * 1.5);
|
||||
RecommendedStrategy = "Competizione Alta: Inizia presto e monitora costantemente";
|
||||
}
|
||||
else if (CompetitionIntensity > 0.4)
|
||||
{
|
||||
RecommendedStartTimer = OptimalStartTimeSeconds;
|
||||
RecommendedStrategy = "Competizione Media: Inizia quando timer raggiunge ~" + OptimalStartTimeSeconds.ToString("F0") + "s";
|
||||
}
|
||||
else
|
||||
{
|
||||
RecommendedStartTimer = Math.Min(OptimalStartTimeSeconds, 60);
|
||||
RecommendedStrategy = "Competizione Bassa: Puoi iniziare più tardi per risparmiare puntate";
|
||||
}
|
||||
|
||||
// Aggiusta per confidence basso
|
||||
if (ConfidenceScore < 50)
|
||||
{
|
||||
RecommendedStrategy = "?? Dati insufficienti - " + RecommendedStrategy;
|
||||
}
|
||||
}
|
||||
|
||||
private static double CalculateMedian(List<double> values)
|
||||
{
|
||||
if (values.Count == 0) return 0;
|
||||
var sorted = values.OrderBy(v => v).ToList();
|
||||
int mid = sorted.Count / 2;
|
||||
return sorted.Count % 2 == 0
|
||||
? (sorted[mid - 1] + sorted[mid]) / 2.0
|
||||
: sorted[mid];
|
||||
}
|
||||
|
||||
private static double CalculateStandardDeviation(List<double> values)
|
||||
{
|
||||
if (values.Count < 2) return 0;
|
||||
var avg = values.Average();
|
||||
var sumSquares = values.Sum(v => Math.Pow(v - avg, 2));
|
||||
return Math.Sqrt(sumSquares / (values.Count - 1));
|
||||
}
|
||||
|
||||
private static int CalculateConfidenceScore(int sampleSize, double stdDev, double average)
|
||||
{
|
||||
// Confidence basato su:
|
||||
// 1. Numero campioni (più campioni = più confidence)
|
||||
// 2. Consistenza dati (bassa varianza = più confidence)
|
||||
|
||||
int sampleScore = Math.Min(70, sampleSize * 7); // Max 70 punti per campioni
|
||||
|
||||
int consistencyScore = 30;
|
||||
if (average > 0)
|
||||
{
|
||||
double coefficientOfVariation = stdDev / average;
|
||||
if (coefficientOfVariation < 0.1) consistencyScore = 30;
|
||||
else if (coefficientOfVariation < 0.3) consistencyScore = 20;
|
||||
else if (coefficientOfVariation < 0.5) consistencyScore = 10;
|
||||
else consistencyScore = 0;
|
||||
}
|
||||
|
||||
return Math.Min(100, sampleScore + consistencyScore);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determina se è il momento giusto per iniziare l'asta
|
||||
/// </summary>
|
||||
public bool ShouldStartNow(double currentTimer, double currentPrice)
|
||||
{
|
||||
// Se confidence è troppo basso, lascia decidere all'utente
|
||||
if (ConfidenceScore < 30)
|
||||
return false;
|
||||
|
||||
// Non iniziare se il prezzo è già troppo alto
|
||||
if (currentPrice > RecommendedMaxPrice)
|
||||
return false;
|
||||
|
||||
// Inizia se il timer è nel range ottimale
|
||||
if (currentTimer <= RecommendedStartTimer && currentTimer > 5)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{ProductName} - Aste: {TotalAuctions}, Prezzo medio: €{AverageFinalPrice:F2}, " +
|
||||
$"Puntate medie: {AverageBidsUsed:F1}, Confidence: {ConfidenceScore}%";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user