Compare commits
3
Commits
v4.16.0
...
4e835e220a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e835e220a | ||
|
|
480e423124 | ||
|
|
48096c7fc7 |
@@ -97,6 +97,12 @@ namespace AutoBidder
|
|||||||
{
|
{
|
||||||
LoadStatistics();
|
LoadStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ultimo passo, e non prima: lo storico è già scritto, il dossier
|
||||||
|
// chiuso, le statistiche del prodotto aggiornate. Togliere l'asta
|
||||||
|
// dall'elenco a questo punto non perde niente — tutto ciò che aveva
|
||||||
|
// da dire è già altrove.
|
||||||
|
RimuoviSeConclusaEDaTogliere(auction, won, settings);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -105,6 +111,56 @@ namespace AutoBidder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Toglie dall'elenco l'asta appena conclusa, se la regola lo consente.
|
||||||
|
///
|
||||||
|
/// <para>Con l'aggiunta automatica accesa le aste concluse si accumulano a centinaia
|
||||||
|
/// e seppelliscono quelle vive, che è il contrario di quello che serve guardando un
|
||||||
|
/// cruscotto. Restano però quelle su cui si è speso: sono le uniche che vale la pena
|
||||||
|
/// riguardare, e i soldi erano veri. Vedi <see cref="FinishedAuctionCleanup"/>.</para>
|
||||||
|
///
|
||||||
|
/// <para>Il motivo finisce sempre nel registro, anche quando l'asta resta: senza,
|
||||||
|
/// un'asta che sparisce o che non sparisce sarebbero entrambe inspiegabili.</para>
|
||||||
|
/// </summary>
|
||||||
|
private void RimuoviSeConclusaEDaTogliere(AuctionInfo auction, bool won, AppSettings settings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var verdict = FinishedAuctionCleanup.Decide(auction, won, new FinishedAuctionCleanup.Options
|
||||||
|
{
|
||||||
|
Enabled = settings.AutoRemoveFinished,
|
||||||
|
KeepWithMyBids = settings.AutoRemoveKeepWithMyBids,
|
||||||
|
KeepWon = settings.AutoRemoveKeepWon,
|
||||||
|
KeepUnclear = settings.AutoRemoveKeepUnclear
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!verdict.Remove)
|
||||||
|
{
|
||||||
|
if (settings.AutoRemoveFinished)
|
||||||
|
Log($"[ELENCO] '{auction.Name}' resta in elenco: {verdict.Reason}", LogLevel.Info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stesso giro della rimozione a mano: prima il motore, poi la griglia,
|
||||||
|
// poi il salvataggio. Toglierla solo dalla griglia lascerebbe il runner
|
||||||
|
// a interrogare un'asta che non esiste piu' per chi guarda.
|
||||||
|
_auctionMonitor.RemoveAuction(auction.AuctionId);
|
||||||
|
|
||||||
|
var vm = _auctionViewModels.FirstOrDefault(x => x.AuctionId == auction.AuctionId);
|
||||||
|
if (vm != null) _auctionViewModels.Remove(vm);
|
||||||
|
|
||||||
|
SaveAuctions();
|
||||||
|
UpdateTotalCount();
|
||||||
|
UpdateGlobalControlButtons();
|
||||||
|
|
||||||
|
Log($"[ELENCO] '{auction.Name}' tolta dall'elenco: {verdict.Reason}", LogLevel.Info);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[ELENCO ERROR] {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Quando l'asta è finita davvero, in ordine di attendibilità: l'istante fissato dal
|
/// Quando l'asta è finita davvero, in ordine di attendibilità: l'istante fissato dal
|
||||||
/// monitor quando ha visto la conclusione dal vivo; la scadenza dichiarata dal
|
/// monitor quando ha visto la conclusione dal vivo; la scadenza dichiarata dal
|
||||||
|
|||||||
@@ -31,7 +31,37 @@ namespace AutoBidder.Engine.Backtest
|
|||||||
int LeadMs,
|
int LeadMs,
|
||||||
AppSettings Settings,
|
AppSettings Settings,
|
||||||
string Username = "",
|
string Username = "",
|
||||||
double BidCostEuro = 0.20);
|
double BidCostEuro = 0.20)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Le impostazioni come le vede la rigiocata.
|
||||||
|
///
|
||||||
|
/// <para>Spegne la sospensione a fascia oraria, che guarda l'orologio di parete:
|
||||||
|
/// una rigiocata ripercorre aste già concluse, e l'ora in cui viene lanciata non
|
||||||
|
/// c'entra nulla con l'ora in cui quelle aste correvano. Lasciarla accesa
|
||||||
|
/// renderebbe il risultato dipendente da quando si preme il pulsante — di notte
|
||||||
|
/// il motore rifiuterebbe ogni puntata e la rigiocata direbbe zero, che è
|
||||||
|
/// esattamente il tipo di risultato che sembra vero e non lo è.</para>
|
||||||
|
///
|
||||||
|
/// <para>Vale anche per la suite di test, che gira a qualunque ora.</para>
|
||||||
|
/// </summary>
|
||||||
|
public AppSettings EffectiveSettings { get; } = Clona(Settings);
|
||||||
|
|
||||||
|
private static AppSettings Clona(AppSettings origine)
|
||||||
|
{
|
||||||
|
var copia = origine ?? new AppSettings();
|
||||||
|
|
||||||
|
// Copia superficiale via serializzazione: le impostazioni sono un sacco di
|
||||||
|
// proprietà semplici, e mutare quella dell'utente sarebbe un effetto
|
||||||
|
// collaterale che nessuno si aspetta da una rigiocata.
|
||||||
|
var json = System.Text.Json.JsonSerializer.Serialize(copia);
|
||||||
|
var clone = System.Text.Json.JsonSerializer.Deserialize<AppSettings>(json)
|
||||||
|
?? new AppSettings();
|
||||||
|
|
||||||
|
clone.QuietHoursEnabled = false;
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Esito su una singola asta.</summary>
|
/// <summary>Esito su una singola asta.</summary>
|
||||||
public sealed class Result
|
public sealed class Result
|
||||||
@@ -139,7 +169,7 @@ namespace AutoBidder.Engine.Backtest
|
|||||||
ShippingCost: null,
|
ShippingCost: null,
|
||||||
MaxBids: auction.MaxClicks,
|
MaxBids: auction.MaxClicks,
|
||||||
MaxTotalSpendEuro: auction.MaxTotalSpendEuro,
|
MaxTotalSpendEuro: auction.MaxTotalSpendEuro,
|
||||||
MinSavingsPercentage: options.Settings.MinSavingsPercentage,
|
MinSavingsPercentage: options.EffectiveSettings.MinSavingsPercentage,
|
||||||
StopAtBreakEven: auction.StopAtBreakEven));
|
StopAtBreakEven: auction.StopAtBreakEven));
|
||||||
|
|
||||||
if (!budget.CanBid)
|
if (!budget.CanBid)
|
||||||
@@ -151,8 +181,8 @@ namespace AutoBidder.Engine.Backtest
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
strategy.UpdateHeatMetric(auction, options.Settings, options.Username);
|
strategy.UpdateHeatMetric(auction, options.EffectiveSettings, options.Username);
|
||||||
var decision = strategy.ShouldPlaceBid(auction, state, options.Settings, options.Username);
|
var decision = strategy.ShouldPlaceBid(auction, state, options.EffectiveSettings, options.Username);
|
||||||
|
|
||||||
if (!decision.ShouldBid)
|
if (!decision.ShouldBid)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
Ottimo! Ora altre modifiche:
|
||||||
|
- Aggiungi l'opzione di default già attiva che mi permette di cancellare dall'elenco un'asta terminata. Aggiungere altre opzioni per non eliminare quelle su cui ho puntato ma che sono terminate in modo da verificarle a mano
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
using AutoBidder.Utilities;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AutoBidder.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rimozione automatica delle aste concluse dall'elenco del monitor.
|
||||||
|
///
|
||||||
|
/// <para>La proprieta' da difendere e' asimmetrica, e va nel verso della prudenza: togliere
|
||||||
|
/// un'asta solo osservata non costa niente, perche' lo storico la racconta meglio
|
||||||
|
/// dell'elenco; togliere un'asta su cui si e' speso significa perdere l'occasione di
|
||||||
|
/// controllare dove sono finiti dei soldi veri. Nel dubbio si trattiene.</para>
|
||||||
|
/// </summary>
|
||||||
|
public class FinishedAuctionCleanupTests
|
||||||
|
{
|
||||||
|
private static FinishedAuctionCleanup.Verdict Decidi(
|
||||||
|
bool conclusa = true, bool vinta = false, int puntate = 0, bool fineVista = true,
|
||||||
|
FinishedAuctionCleanup.Options? opzioni = null) =>
|
||||||
|
FinishedAuctionCleanup.Decide(conclusa, vinta, puntate, fineVista,
|
||||||
|
opzioni ?? new FinishedAuctionCleanup.Options());
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Un_asta_persa_senza_mie_puntate_si_toglie()
|
||||||
|
{
|
||||||
|
Assert.True(Decidi().Remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Un_asta_ancora_in_corso_non_si_tocca()
|
||||||
|
{
|
||||||
|
var v = Decidi(conclusa: false);
|
||||||
|
|
||||||
|
Assert.False(v.Remove);
|
||||||
|
Assert.Contains("non conclusa", v.Reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Le_aste_su_cui_ho_puntato_restano()
|
||||||
|
{
|
||||||
|
// Sono quelle costate qualcosa, ed e' l'unico elenco in cui si vedono accanto ai
|
||||||
|
// loro dati: farle sparire da sole significa perdere il controllo a mano.
|
||||||
|
var v = Decidi(puntate: 12);
|
||||||
|
|
||||||
|
Assert.False(v.Remove);
|
||||||
|
Assert.Contains("12 puntate", v.Reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Le_vinte_restano_perche_vanno_confermate_su_Bidoo()
|
||||||
|
{
|
||||||
|
var v = Decidi(vinta: true);
|
||||||
|
|
||||||
|
Assert.False(v.Remove);
|
||||||
|
Assert.Contains("conferma", v.Reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Se_la_fine_non_e_stata_vista_l_asta_resta()
|
||||||
|
{
|
||||||
|
var v = Decidi(fineVista: false);
|
||||||
|
|
||||||
|
Assert.False(v.Remove);
|
||||||
|
Assert.Contains("fine non", v.Reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Una_vinta_su_cui_ho_puntato_resta_per_il_motivo_piu_importante()
|
||||||
|
{
|
||||||
|
// Due ragioni per trattenerla: quella che finisce nel registro deve essere la
|
||||||
|
// vittoria, non le puntate, perche' e' quella che richiede un'azione.
|
||||||
|
var v = Decidi(vinta: true, puntate: 30);
|
||||||
|
|
||||||
|
Assert.False(v.Remove);
|
||||||
|
Assert.Contains("vinta", v.Reason.ToLowerInvariant());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Spegnendo_la_rimozione_non_si_toglie_piu_niente()
|
||||||
|
{
|
||||||
|
var v = Decidi(opzioni: new FinishedAuctionCleanup.Options { Enabled = false });
|
||||||
|
|
||||||
|
Assert.False(v.Remove);
|
||||||
|
Assert.Contains("spenta", v.Reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Ogni_eccezione_si_puo_spegnere_da_sola()
|
||||||
|
{
|
||||||
|
var solo = new FinishedAuctionCleanup.Options
|
||||||
|
{
|
||||||
|
KeepWithMyBids = false,
|
||||||
|
KeepWon = false,
|
||||||
|
KeepUnclear = false
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.True(Decidi(puntate: 40, opzioni: solo).Remove);
|
||||||
|
Assert.True(Decidi(vinta: true, opzioni: solo).Remove);
|
||||||
|
Assert.True(Decidi(fineVista: false, opzioni: solo).Remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void I_predefiniti_tolgono_le_osservate_e_trattengono_il_resto()
|
||||||
|
{
|
||||||
|
var o = new FinishedAuctionCleanup.Options();
|
||||||
|
|
||||||
|
Assert.True(o.Enabled);
|
||||||
|
Assert.True(o.KeepWithMyBids);
|
||||||
|
Assert.True(o.KeepWon);
|
||||||
|
Assert.True(o.KeepUnclear);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
using AutoBidder.Utilities;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AutoBidder.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Migrazione delle impostazioni salvate da una versione precedente.
|
||||||
|
///
|
||||||
|
/// <para>La proprieta' da difendere: un predefinito nuovo <b>non arriva</b> a chi ha gia' un
|
||||||
|
/// file salvato, perche' il valore su disco vince. Va bene per le scelte dell'utente, non
|
||||||
|
/// per due valori che erano conseguenze di misure sbagliate — il tetto di 40 richieste al
|
||||||
|
/// secondo e l'anticipo di 1000 ms erano stati tarati quando il tetto stesso accodava le
|
||||||
|
/// chiamate e gonfiava il ping. Senza migrazione l'aggiornamento sarebbe arrivato con le
|
||||||
|
/// correzioni gia' annullate dal file dell'utente.</para>
|
||||||
|
///
|
||||||
|
/// <para>L'altra proprieta', altrettanto importante: chi quei valori li aveva gia' cambiati
|
||||||
|
/// di suo se li tiene. Quella e' una scelta, e non spetta a un aggiornamento ribaltarla.</para>
|
||||||
|
/// </summary>
|
||||||
|
public class SettingsMigrationTests
|
||||||
|
{
|
||||||
|
private static AppSettings Vecchie() => new()
|
||||||
|
{
|
||||||
|
SettingsSchemaVersion = 0,
|
||||||
|
MaxRequestsPerSecond = 40,
|
||||||
|
DefaultBidBeforeDeadlineMs = 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void I_valori_vecchi_vengono_portati_avanti()
|
||||||
|
{
|
||||||
|
var s = Vecchie();
|
||||||
|
|
||||||
|
Assert.True(SettingsManager.Migra(s));
|
||||||
|
|
||||||
|
Assert.Equal(0, s.MaxRequestsPerSecond); // nessun tetto
|
||||||
|
Assert.Equal(500, s.DefaultBidBeforeDeadlineMs);
|
||||||
|
Assert.Equal(SettingsManager.SchemaCorrente, s.SettingsSchemaVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Una_scelta_dell_utente_non_viene_ribaltata()
|
||||||
|
{
|
||||||
|
var s = new AppSettings
|
||||||
|
{
|
||||||
|
SettingsSchemaVersion = 0,
|
||||||
|
MaxRequestsPerSecond = 15, // scelto apposta, non il vecchio predefinito
|
||||||
|
DefaultBidBeforeDeadlineMs = 2000 // idem
|
||||||
|
};
|
||||||
|
|
||||||
|
SettingsManager.Migra(s);
|
||||||
|
|
||||||
|
Assert.Equal(15, s.MaxRequestsPerSecond);
|
||||||
|
Assert.Equal(2000, s.DefaultBidBeforeDeadlineMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Gira_una_volta_sola()
|
||||||
|
{
|
||||||
|
var s = Vecchie();
|
||||||
|
|
||||||
|
Assert.True(SettingsManager.Migra(s));
|
||||||
|
Assert.False(SettingsManager.Migra(s));
|
||||||
|
|
||||||
|
// E se poi l'utente rimette 40 di suo, non glielo si toglie al giro dopo.
|
||||||
|
s.MaxRequestsPerSecond = 40;
|
||||||
|
Assert.False(SettingsManager.Migra(s));
|
||||||
|
Assert.Equal(40, s.MaxRequestsPerSecond);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Impostazioni_nuove_di_zecca_sono_gia_allo_schema_corrente()
|
||||||
|
{
|
||||||
|
// Un file che non esiste ancora non e' un file vecchio: nasce gia' aggiornato,
|
||||||
|
// e non deve far scattare nessuna migrazione.
|
||||||
|
var s = new AppSettings { SettingsSchemaVersion = SettingsManager.SchemaCorrente };
|
||||||
|
|
||||||
|
Assert.False(SettingsManager.Migra(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void I_predefiniti_di_fabbrica_sono_quelli_misurati_oggi()
|
||||||
|
{
|
||||||
|
var s = new AppSettings();
|
||||||
|
|
||||||
|
Assert.Equal(0, s.MaxRequestsPerSecond);
|
||||||
|
Assert.Equal(500, s.DefaultBidBeforeDeadlineMs);
|
||||||
|
Assert.True(s.QuietHoursEnabled);
|
||||||
|
Assert.True(s.AutoBidDuelWithdrawEnabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
using AutoBidder.Models;
|
||||||
|
|
||||||
|
namespace AutoBidder.Utilities
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Decide se un'asta conclusa può sparire da sola dall'elenco del monitor.
|
||||||
|
///
|
||||||
|
/// <para><b>Perché serve.</b> Le aste concluse restano nel monitor finché non le si
|
||||||
|
/// toglie a mano, e con l'aggiunta automatica accesa se ne accumulano centinaia: quelle
|
||||||
|
/// vive finiscono sepolte sotto quelle finite, che è il contrario di quello che serve
|
||||||
|
/// guardando un cruscotto.</para>
|
||||||
|
///
|
||||||
|
/// <para><b>Perché non basta togliere tutto.</b> Un'asta su cui si è puntato è l'unica
|
||||||
|
/// che vale la pena riguardare: quanto è costata, se il ritiro dal duello è scattato al
|
||||||
|
/// momento giusto, se il prezzo finale sta dove ci si aspettava. Farla sparire da sola
|
||||||
|
/// significa perdere l'occasione di controllarla, e i soldi erano veri. Le altre — quelle
|
||||||
|
/// solo osservate — non hanno niente da dire che lo storico non dica meglio.</para>
|
||||||
|
///
|
||||||
|
/// <para>Da qui la forma della regola: si toglie di serie, si trattiene ciò su cui c'è
|
||||||
|
/// stato un esborso. Classe pura: nessuno stato, nessun orologio, nessun disco.</para>
|
||||||
|
/// </summary>
|
||||||
|
public static class FinishedAuctionCleanup
|
||||||
|
{
|
||||||
|
/// <summary>Cosa fare delle aste concluse, e cosa risparmiare.</summary>
|
||||||
|
public sealed class Options
|
||||||
|
{
|
||||||
|
/// <summary>Togliere da sole le aste concluse. Acceso di serie.</summary>
|
||||||
|
public bool Enabled { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trattenere le aste su cui si è puntato, per poterle controllare a mano.
|
||||||
|
/// Acceso di serie: sono quelle costate qualcosa.
|
||||||
|
/// </summary>
|
||||||
|
public bool KeepWithMyBids { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trattenere le aste vinte. Acceso di serie: c'è da confermare l'acquisto su
|
||||||
|
/// Bidoo, e una vittoria sparita dall'elenco è il modo più semplice di
|
||||||
|
/// dimenticarsene.
|
||||||
|
/// </summary>
|
||||||
|
public bool KeepWon { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trattenere le aste finite con un errore o senza esito leggibile: sono
|
||||||
|
/// esattamente quelle da guardare. Acceso di serie.
|
||||||
|
/// </summary>
|
||||||
|
public bool KeepUnclear { get; set; } = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Esito della decisione, col motivo per il registro.</summary>
|
||||||
|
public readonly record struct Verdict(bool Remove, string Reason);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Quest'asta conclusa si può togliere dall'elenco?
|
||||||
|
///
|
||||||
|
/// <para>L'ordine dei controlli è quello che conta: prima le ragioni per
|
||||||
|
/// <b>trattenere</b>, poi la rimozione. Un'asta vinta su cui si è anche puntato deve
|
||||||
|
/// restare per il primo motivo che si incontra, non per l'ultimo, così il messaggio
|
||||||
|
/// nel registro dice la cosa più importante.</para>
|
||||||
|
/// </summary>
|
||||||
|
public static Verdict Decide(bool conclusa, bool vinta, int puntateMie, bool fineVista, Options options)
|
||||||
|
{
|
||||||
|
options ??= new Options();
|
||||||
|
|
||||||
|
if (!options.Enabled)
|
||||||
|
return new Verdict(false, "rimozione automatica spenta");
|
||||||
|
|
||||||
|
if (!conclusa)
|
||||||
|
return new Verdict(false, "asta non conclusa");
|
||||||
|
|
||||||
|
if (options.KeepWon && vinta)
|
||||||
|
return new Verdict(false, "vinta: resta in elenco per la conferma su Bidoo");
|
||||||
|
|
||||||
|
if (options.KeepWithMyBids && puntateMie > 0)
|
||||||
|
return new Verdict(false,
|
||||||
|
$"trattenuta: ci hai speso {puntateMie} puntate, controllala a mano");
|
||||||
|
|
||||||
|
if (options.KeepUnclear && !fineVista)
|
||||||
|
return new Verdict(false, "trattenuta: la fine non è stata vista, esito da controllare");
|
||||||
|
|
||||||
|
return new Verdict(true, "conclusa senza puntate mie: tolta dall'elenco");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Comodità per il motore: legge dall'asta i quattro numeri che servono.
|
||||||
|
///
|
||||||
|
/// <para>Le puntate si prendono dal massimo fra il contatore dell'asta e quello di
|
||||||
|
/// sessione: uno dei due può essere a zero a seconda di come l'asta è entrata nel
|
||||||
|
/// monitor, e trattenere per eccesso è l'errore giusto da fare.</para>
|
||||||
|
/// </summary>
|
||||||
|
public static Verdict Decide(AuctionInfo auction, bool vinta, Options options)
|
||||||
|
{
|
||||||
|
if (auction == null) return new Verdict(false, "nessuna asta");
|
||||||
|
|
||||||
|
var perAsta = auction.BidsUsedOnThisAuction ?? 0;
|
||||||
|
var puntate = perAsta > auction.SessionBidCount ? perAsta : auction.SessionBidCount;
|
||||||
|
|
||||||
|
return Decide(
|
||||||
|
conclusa: !string.IsNullOrWhiteSpace(auction.ConcludedOutcome),
|
||||||
|
vinta: vinta,
|
||||||
|
puntateMie: puntate,
|
||||||
|
fineVista: auction.ObservedToEnd,
|
||||||
|
options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -565,6 +565,33 @@ namespace AutoBidder.Utilities
|
|||||||
// Vedi BiddingHours per i numeri: alle 0 e alle 9 la stessa asta costa quasi il
|
// Vedi BiddingHours per i numeri: alle 0 e alle 9 la stessa asta costa quasi il
|
||||||
// doppio che fra le 10 e le 13.
|
// doppio che fra le 10 e le 13.
|
||||||
|
|
||||||
|
// ── Rimozione automatica delle aste concluse ─────────────────────
|
||||||
|
// Vedi FinishedAuctionCleanup: si toglie di serie, si trattiene cio' su cui c'e'
|
||||||
|
// stato un esborso.
|
||||||
|
|
||||||
|
/// <summary>Togliere da sole dall'elenco le aste concluse. Acceso di serie.</summary>
|
||||||
|
public bool AutoRemoveFinished { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>Trattenere le aste su cui hai puntato, per controllarle a mano.</summary>
|
||||||
|
public bool AutoRemoveKeepWithMyBids { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>Trattenere le aste vinte: c'è da confermare l'acquisto su Bidoo.</summary>
|
||||||
|
public bool AutoRemoveKeepWon { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>Trattenere le aste di cui non si è vista la fine: esito da controllare.</summary>
|
||||||
|
public bool AutoRemoveKeepUnclear { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Versione dello schema delle impostazioni, per le migrazioni una tantum.
|
||||||
|
///
|
||||||
|
/// <para>Un file scritto da una versione precedente non ha questo campo e vale
|
||||||
|
/// quindi 0. Serve perché un predefinito nuovo <b>non arriva</b> a chi ha già un
|
||||||
|
/// file salvato: il valore su disco vince sempre, ed è giusto che sia così, tranne
|
||||||
|
/// quando il vecchio valore veniva da una misura che si è poi rivelata sbagliata.
|
||||||
|
/// Vedi <see cref="Migra"/>.</para>
|
||||||
|
/// </summary>
|
||||||
|
public int SettingsSchemaVersion { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sospendere le puntate nella fascia indicata. Acceso di serie.
|
/// Sospendere le puntate nella fascia indicata. Acceso di serie.
|
||||||
/// L'asta resta comunque Attiva e riprende da sola: vedi <see cref="BiddingHours"/>.
|
/// L'asta resta comunque Attiva e riprende da sola: vedi <see cref="BiddingHours"/>.
|
||||||
@@ -833,6 +860,36 @@ namespace AutoBidder.Utilities
|
|||||||
|
|
||||||
private const int CACHE_TTL_MS = 2000;
|
private const int CACHE_TTL_MS = 2000;
|
||||||
|
|
||||||
|
/// <summary>Schema corrente. Alzarlo fa girare <see cref="Migra"/> una volta sola.</summary>
|
||||||
|
public const int SchemaCorrente = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Porta avanti un file scritto da una versione precedente. Restituisce true se
|
||||||
|
/// qualcosa è cambiato, così chi chiama sa che va risalvato.
|
||||||
|
///
|
||||||
|
/// <para><b>Perché serve.</b> Il valore su disco vince sui predefiniti, ed è
|
||||||
|
/// giusto: sono scelte dell'utente. Ma due di quei valori non erano scelte, erano
|
||||||
|
/// conseguenze di misure sbagliate — il tetto di 40 richieste al secondo e
|
||||||
|
/// l'anticipo di 1000 ms erano stati tarati quando il tetto stesso accodava le
|
||||||
|
/// chiamate e gonfiava il ping a 444 ms. Senza migrazione, chi aggiorna si ritrova
|
||||||
|
/// le correzioni di oggi già annullate dal proprio file, e conclude che non
|
||||||
|
/// funzionano.</para>
|
||||||
|
///
|
||||||
|
/// <para>Si tocca solo chi ha ancora esattamente il vecchio valore. Chi lo aveva
|
||||||
|
/// già cambiato di suo se lo tiene: quella è una scelta, e non spetta a un
|
||||||
|
/// aggiornamento ribaltarla.</para>
|
||||||
|
/// </summary>
|
||||||
|
public static bool Migra(AppSettings s)
|
||||||
|
{
|
||||||
|
if (s == null || s.SettingsSchemaVersion >= SchemaCorrente) return false;
|
||||||
|
|
||||||
|
if (Math.Abs(s.MaxRequestsPerSecond - 40) < 0.001) s.MaxRequestsPerSecond = 0;
|
||||||
|
if (s.DefaultBidBeforeDeadlineMs == 1000) s.DefaultBidBeforeDeadlineMs = 500;
|
||||||
|
|
||||||
|
s.SettingsSchemaVersion = SchemaCorrente;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public static AppSettings Load()
|
public static AppSettings Load()
|
||||||
{
|
{
|
||||||
// Percorso comune: nessun lucchetto, nessuna allocazione.
|
// Percorso comune: nessun lucchetto, nessuna allocazione.
|
||||||
@@ -848,11 +905,20 @@ namespace AutoBidder.Utilities
|
|||||||
return cached;
|
return cached;
|
||||||
|
|
||||||
AppSettings loaded;
|
AppSettings loaded;
|
||||||
|
var daRisalvare = false;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
loaded = File.Exists(_file)
|
var esiste = File.Exists(_file);
|
||||||
|
|
||||||
|
loaded = esiste
|
||||||
? JsonSerializer.Deserialize<AppSettings>(File.ReadAllText(_file)) ?? new AppSettings()
|
? JsonSerializer.Deserialize<AppSettings>(File.ReadAllText(_file)) ?? new AppSettings()
|
||||||
: new AppSettings();
|
: new AppSettings();
|
||||||
|
|
||||||
|
// Un file nuovo nasce già allo schema corrente: la migrazione riguarda
|
||||||
|
// solo chi arriva da una versione precedente.
|
||||||
|
if (!esiste) loaded.SettingsSchemaVersion = SchemaCorrente;
|
||||||
|
else daRisalvare = Migra(loaded);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -860,6 +926,15 @@ namespace AutoBidder.Utilities
|
|||||||
loaded = _cached ?? new AppSettings();
|
loaded = _cached ?? new AppSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scritto qui e non dentro Save: Load è sul percorso caldo e non deve
|
||||||
|
// toccare il disco, ma una migrazione capita una volta sola nella vita
|
||||||
|
// del file, e lasciarla non salvata la farebbe ripetere a ogni avvio.
|
||||||
|
if (daRisalvare)
|
||||||
|
{
|
||||||
|
try { File.WriteAllText(_file, JsonSerializer.Serialize(loaded, new JsonSerializerOptions { WriteIndented = true })); }
|
||||||
|
catch { /* si riproverà al prossimo avvio */ }
|
||||||
|
}
|
||||||
|
|
||||||
_cached = loaded;
|
_cached = loaded;
|
||||||
Interlocked.Exchange(ref _cacheExpiryTicks, DateTime.UtcNow.AddMilliseconds(CACHE_TTL_MS).Ticks);
|
Interlocked.Exchange(ref _cacheExpiryTicks, DateTime.UtcNow.AddMilliseconds(CACHE_TTL_MS).Ticks);
|
||||||
return loaded;
|
return loaded;
|
||||||
|
|||||||
@@ -433,9 +433,22 @@
|
|||||||
<Target Name="Pubblica" DependsOnTargets="DeterminaVersione">
|
<Target Name="Pubblica" DependsOnTargets="DeterminaVersione">
|
||||||
<Message Importance="High" Text="== Pubblicazione dell'eseguibile ($(V)) ==" />
|
<Message Importance="High" Text="== Pubblicazione dell'eseguibile ($(V)) ==" />
|
||||||
|
|
||||||
|
<!-- ── Perché gli intermedi vanno altrove ──────────────────────────────
|
||||||
|
La sola opzione -o sposta il risultato ma lascia gli INTERMEDI nella obj/
|
||||||
|
del progetto, che è la stessa che usa la compilazione di debug lanciata da
|
||||||
|
VS Code. La compilazione WPF genera un progetto temporaneo `_wpftmp.csproj`
|
||||||
|
a ogni giro, e quando ci trova dentro lo stato di un'altra configurazione
|
||||||
|
smette di produrre le classi parziali dello XAML: si ottiene una raffica di
|
||||||
|
CS2001 «file di origine MainWindow.g.cs non trovato», a giri alterni, senza
|
||||||
|
che il codice sia cambiato.
|
||||||
|
|
||||||
|
Era già il rimedio adottato per i test; qui era rimasto indietro, e bastava
|
||||||
|
pubblicare mentre si compilava in VS Code per rompere la compilazione di
|
||||||
|
debug. L'opzione artifacts-path sposta gli intermedi, la -o continua a
|
||||||
|
decidere dove finisce l'eseguibile: le due convivono. -->
|
||||||
<Exec WorkingDirectory="$(Radice)"
|
<Exec WorkingDirectory="$(Radice)"
|
||||||
EnvironmentVariables="$(AmbientePulito)"
|
EnvironmentVariables="$(AmbientePulito)"
|
||||||
Command="dotnet publish "$(Csproj)" -c Release -r win-x64 --nologo -v q --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:EnableCompressionInSingleFile=true -p:PublishReadyToRun=true -p:PublishTrimmed=false -p:Version=$(V) -p:AssemblyVersion=$(V).0 -p:FileVersion=$(V).0 -p:InformationalVersion=$(V) -o "$(CartellaPubblicazione)"" />
|
Command="dotnet publish "$(Csproj)" -c Release -r win-x64 --nologo -v q --artifacts-path "$(CartellaProve)" --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:EnableCompressionInSingleFile=true -p:PublishReadyToRun=true -p:PublishTrimmed=false -p:Version=$(V) -p:AssemblyVersion=$(V).0 -p:FileVersion=$(V).0 -p:InformationalVersion=$(V) -o "$(CartellaPubblicazione)"" />
|
||||||
|
|
||||||
<Error Condition="!Exists('$(CartellaPubblicazione)\AutoBidder.exe')"
|
<Error Condition="!Exists('$(CartellaPubblicazione)\AutoBidder.exe')"
|
||||||
Text="Pubblicazione fallita: AutoBidder.exe non trovato." />
|
Text="Pubblicazione fallita: AutoBidder.exe non trovato." />
|
||||||
|
|||||||
Reference in New Issue
Block a user