From c88eac5f8a8f74577b80306ea6fa46395ce0cac4 Mon Sep 17 00:00:00 2001 From: bergm Date: Wed, 1 Jul 2026 18:38:47 +0200 Subject: [PATCH] Phase 4.5: Startup-Reihenfolge-Fix (State-Hydration vor Trading-Services) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Neuer StartupHydrationService (IHostedService), als ERSTER HostedService registriert: hydriert Accounts (+ Demo-Positionen via Repos) und Trader (trackers-Collection) in den TradingState, bevor die Trading-Services ihre ExecuteAsync starten. - frm_main.LoadDatabaseAndState() + beide Aufrufe entfernt (verhinderte doppelte, überschreibende Hydration nach Service-Start). - Behebt den Race, bei dem Services gegen leeren State anliefen. - Build 0 Fehler. Co-Authored-By: Claude Opus 4.8 --- Program.cs | 3 ++ frm_main.cs | 29 ++--------- services/StartupHydrationService.cs | 76 +++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 25 deletions(-) create mode 100644 services/StartupHydrationService.cs diff --git a/Program.cs b/Program.cs index a7a886d..0c93f17 100644 --- a/Program.cs +++ b/Program.cs @@ -72,6 +72,9 @@ internal static class Program services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + // MUSS als erster HostedService laufen: hydriert den State, bevor die + // Trading-Services (unten) gegen einen leeren State anlaufen. + services.AddHostedService(); services.AddHostedService((IServiceProvider sp) => sp.GetRequiredService()); services.AddHostedService(); services.AddHostedService(); diff --git a/frm_main.cs b/frm_main.cs index 407798d..c6bfdeb 100644 --- a/frm_main.cs +++ b/frm_main.cs @@ -342,7 +342,6 @@ namespace PolyTraderSharp dgv_jobs.DataSource = _jobManager.Jobs; this.FormClosing += Frm_main_FormClosing; - LoadDatabaseAndState(); // Setup Terminal Filter cb_terminalLogLevel.Items.Clear(); @@ -503,8 +502,7 @@ namespace PolyTraderSharp btn_vpnConnect.Click += async (s, ev) => await _vpnService.ConnectAsync(); btn_vpndisconnect.Click += async (s, ev) => await _vpnService.DisconnectAsync(); - // Default Inactive overriding snapshot - LoadDatabaseAndState(); + // Default Inactive overriding snapshot (State-Hydration erfolgt im StartupHydrationService) _tradingState.LiveTradingMode = TradingMode.Inactive; _tradingState.DemoTradingMode = TradingMode.Inactive; UpdateToggleButtons(); @@ -964,28 +962,9 @@ namespace PolyTraderSharp FormatTradeGrid(dgv_closedTrades); } - private void LoadDatabaseAndState() - { - var accountsCol = _db.GetCollection("accounts"); - var tradersCol = _db.GetCollection("trackers"); - - var allAccounts = accountsCol.LiteFindAll().ToList(); - foreach (var acc in allAccounts) - { - if (acc.IsDemo) - { - var demoPosCol = _db.GetCollection($"demo_positions_{acc.AccountId}"); - foreach (var pos in demoPosCol.LiteFindAll()) - { - acc.OpenPositions.TryAdd(pos.TokenId, pos); - } - } - _tradingState.Accounts[acc.AccountId] = acc; - } - - foreach (var trd in tradersCol.LiteFindAll()) - _tradingState.Traders[trd.Id] = trd; - } + // Hydration des TradingState erfolgt jetzt zentral im StartupHydrationService + // (läuft vor den Trading-Services). Die frühere LoadDatabaseAndState()-Methode + // wurde entfernt, um doppelte/überschreibende Hydration zu vermeiden. private void SetupTraderManagementUI() { diff --git a/services/StartupHydrationService.cs b/services/StartupHydrationService.cs new file mode 100644 index 0000000..5e6c2ba --- /dev/null +++ b/services/StartupHydrationService.cs @@ -0,0 +1,76 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using MongoDB.Driver; +using PolyTrader.Core.Persistence; +using PolyTraderSharp.Models; + +namespace PolyTraderSharp.Services +{ + /// + /// Hydriert den TradingState (Accounts, Demo-Positionen, Trader) EINMALIG beim App-Start. + /// Registriert als erster IHostedService, damit die Hydration abgeschlossen ist, bevor die + /// Trading-BackgroundServices (TraderMonitor, CopyTradingEngine, ...) ihre ExecuteAsync + /// starten. Behebt den Startup-Race, bei dem Services gegen einen leeren State anliefen + /// (früher lag diese Logik in frm_main.LoadDatabaseAndState und lief erst NACH AppHost.Start()). + /// + public class StartupHydrationService : IHostedService + { + private readonly TradingState _state; + private readonly IAccountRepository _accountRepo; + private readonly IPositionRepository _positionRepo; + private readonly IMongoDatabase _db; + private readonly TerminalLogger _logger; + + public StartupHydrationService( + TradingState state, + IAccountRepository accountRepo, + IPositionRepository positionRepo, + IMongoDatabase db, + TerminalLogger logger) + { + _state = state; + _accountRepo = accountRepo; + _positionRepo = positionRepo; + _db = db; + _logger = logger; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + try + { + foreach (var acc in _accountRepo.GetAll()) + { + if (acc.IsDemo) + { + foreach (var pos in _positionRepo.GetDemo(acc.AccountId)) + { + acc.OpenPositions.TryAdd(pos.TokenId, pos); + } + } + _state.Accounts[acc.AccountId] = acc; + } + + // Trader (TrackedTrader) sind aktuell noch modulnah; bis zum Trader-Repo in + // Phase 5 direkt aus der "trackers"-Collection geladen. + var tradersCol = _db.GetCollection("trackers"); + foreach (var trd in tradersCol.Find(_ => true).ToList()) + { + _state.Traders[trd.Id] = trd; + } + + _logger.Info($"Startup-Hydration abgeschlossen: {_state.Accounts.Count} Accounts, {_state.Traders.Count} Trader geladen."); + } + catch (System.Exception ex) + { + _logger.Error($"Startup-Hydration fehlgeschlagen: {ex.Message}"); + } + + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } +}