using IBKRTrader.Core.AI; using IBKRTrader.Core.Budget; using IBKRTrader.Core.Configuration; using IBKRTrader.Core.Database; using IBKRTrader.Core.Database.Migrations; using IBKRTrader.Core.DependencyInjection; using IBKRTrader.Core.IBKR; using IBKRTrader.Core.Logging; using IBKRTrader.Core.Modularity; using IBKRTrader.Core.Settings; using IBKRTrader.Core.Trading; using IBKRTrader.Core.Workers; using IBKRTrader.Core.Workers.BuiltIn; using IBKRTrader.Modules.CongressTrading; using IBKRTrader.UI; using IBKRTrader.UI.Views; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace IBKRTrader; internal static class Program { public static IHost? AppHost { get; private set; } [STAThread] static void Main(string[] args) { // Headless-Smoke-Test der UI (konstruiert jede View + Launcher, ohne Message-Loop). if (args.Length > 0 && string.Equals(args[0], "--smoke-ui", StringComparison.OrdinalIgnoreCase)) { Environment.ExitCode = RunSmokeUi(); return; } // Diagnose: gibt die MySQL/MariaDB-Serverversion aus (für das EF-ServerVersion-Pinning). if (args.Length > 0 && string.Equals(args[0], "--db-version", StringComparison.OrdinalIgnoreCase)) { RunDbVersion(); return; } ApplicationConfiguration.Initialize(); var modules = new List { new CongressTradingModule() }; AppHost = Host.CreateDefaultBuilder() .UseContentRoot(AppContext.BaseDirectory) .ConfigureAppConfiguration((_, config) => config.AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: false)) .ConfigureServices((context, services) => { RegisterCoreServices(services, context.Configuration); foreach (var module in modules) { services.AddSingleton(module); module.RegisterServices(services, context.Configuration); } services.AddSingleton(); services.AddSingleton(sp => sp.GetRequiredService()); services.AddSingleton(); }) .Build(); // Zirkuläre Abhängigkeit auflösen: WebApiService braucht die Engine-Referenz. AppHost.Services.GetRequiredService() .SetEngine(AppHost.Services.GetRequiredService()); // Views registrieren (Core + Module), dann Launcher starten. var uiHost = AppHost.Services.GetRequiredService(); RegisterCoreViews(uiHost, AppHost.Services); foreach (var module in modules) module.RegisterUi(uiHost, AppHost.Services); AssignViewIcons(uiHost); Application.Run(AppHost.Services.GetRequiredService()); AppHost.StopAsync().GetAwaiter().GetResult(); } /// Registriert alle Core-Services im DI-Container. private static void RegisterCoreServices(IServiceCollection services, IConfiguration configuration) { // EF-Core-Persistenz (Connection aus appsettings.Local.json). services.AddCorePersistence(new DatabaseOptions { MySqlConnectionString = configuration["Database:MySqlConnectionString"] ?? string.Empty }); // Settings zuerst laden (eine Quelle, als Singleton weitergereicht). var settingsService = new SettingsService(); settingsService.Load(); services.AddSingleton(settingsService); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); // Trading-Kern services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); // Sicherer Standard-Broker: handelt nicht, bis der echte IBKR-Adapter verifiziert ist. services.AddSingleton(); // Core-Worker/Services services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); // Als IWorker registrieren → die WorkerEngine erhält alle über IEnumerable. services.AddSingleton(sp => sp.GetRequiredService()); services.AddSingleton(sp => sp.GetRequiredService()); services.AddSingleton(sp => sp.GetRequiredService()); services.AddSingleton(sp => sp.GetRequiredService()); services.AddSingleton(sp => sp.GetRequiredService()); services.AddSingleton(); } /// /// Weist den registrierten Views ihr Button-/Menü-Icon aus den App-Ressourcen zu (über die stabile /// View-ID). Icons stammen aus PolytraderSharp; nicht passende können später ausgetauscht werden. /// Bereits gesetzte Icons bleiben erhalten. /// private static void AssignViewIcons(IModuleUiHost uiHost) { var map = new Dictionary { ["core.workers"] = Properties.Resources.system_time, ["core.logs"] = Properties.Resources.error_log, ["core.settings"] = Properties.Resources.setting_tools, ["congresstrading.main"] = Properties.Resources.cross_reference, }; foreach (var view in uiHost.Views) if (view.Icon is null && map.TryGetValue(view.Id, out var img)) view.Icon = img; } /// Registriert die Core-Views (Logs, Settings, Workers) bei der Shell. private static void RegisterCoreViews(IModuleUiHost uiHost, IServiceProvider sp) { uiHost.RegisterView(new ModuleView { Id = "core.workers", Title = "Workers / Services", Group = "Core", Order = 10, CreateForm = () => new WorkersView(sp.GetRequiredService()) }); uiHost.RegisterView(new ModuleView { Id = "core.logs", Title = "Logs", Group = "Core", Order = 20, CreateForm = () => new LogsView(sp.GetRequiredService()) }); uiHost.RegisterView(new ModuleView { Id = "core.settings", Title = "Settings", Group = "Core", Order = 30, CreateForm = () => new SettingsView(sp.GetRequiredService()) }); } /// Diagnose: öffnet die DB (aus settings.json) und gibt die Serverversion aus. Kein UI. private static void RunDbVersion() { var settings = new SettingsService(); settings.Load(); var conn = settings.Settings.Database.BuildConnectionString(); try { using var c = new MySqlConnector.MySqlConnection(conn); c.Open(); Console.WriteLine($"ServerVersion: {c.ServerVersion}"); } catch (Exception ex) { Console.WriteLine($"FEHLER: {ex.GetType().Name}: {ex.Message}"); } } /// /// Headless-Smoke-Test: baut den Host, registriert Views + Modul-UI und konstruiert jede /// registrierte View sowie den Launcher – ohne Message-Loop. Gibt die Fehleranzahl zurück. /// private static int RunSmokeUi() { ApplicationConfiguration.Initialize(); var modules = new List { new CongressTradingModule() }; using var host = Host.CreateDefaultBuilder() .UseContentRoot(AppContext.BaseDirectory) .ConfigureAppConfiguration((_, config) => config.AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: false)) .ConfigureServices((context, services) => { RegisterCoreServices(services, context.Configuration); foreach (var module in modules) { services.AddSingleton(module); module.RegisterServices(services, context.Configuration); } services.AddSingleton(); services.AddSingleton(s => s.GetRequiredService()); services.AddSingleton(); }) .Build(); host.Services.GetRequiredService() .SetEngine(host.Services.GetRequiredService()); var uiHost = host.Services.GetRequiredService(); RegisterCoreViews(uiHost, host.Services); foreach (var module in modules) module.RegisterUi(uiHost, host.Services); var failures = 0; Console.WriteLine("=== Smoke-UI: View-Konstruktion ==="); foreach (var view in uiHost.Views) { try { using var form = view.CreateForm(); Console.WriteLine($"[OK] {view.Id} ({view.Title})"); } catch (Exception ex) { failures++; Console.WriteLine($"[FEHLER] {view.Id}: {ex.GetType().Name}: {ex.Message}"); } } try { using var launcher = host.Services.GetRequiredService(); Console.WriteLine("[OK] LauncherForm konstruiert"); } catch (Exception ex) { failures++; Console.WriteLine($"[FEHLER] LauncherForm: {ex.GetType().Name}: {ex.Message}"); } Console.WriteLine(failures == 0 ? "=== Smoke-UI OK ===" : $"=== Smoke-UI: {failures} Fehler ==="); return failures; } }