R2: Generic Host + Modul-Vertrag + Shell-UI (PolytraderSharp-Konzept) - Core/Modularity: neuer IModule (Name, DbPrefix, RegisterServices(services,config), RegisterUi(host,sp), StartAsync/StopAsync, GetActivationBlocker) + ModuleView + IModuleUiHost + WindowMenu. Alte IModule/ModuleRegistry/WindowManager/ModuleFormBase entfernt. - Program.cs: Host.CreateDefaultBuilder + IConfiguration (appsettings.json/.Local.json); Core-Services registriert, Module via RegisterServices, Views via RegisterUi. - UI: ShellUiHost (Einzelinstanz-Fenster + Fenster-Menue), LauncherForm als Shell (Buttons je View), Core-Views Logs/Settings/Workers als eigene Fenster. - CongressTrading auf neuen Vertrag; Worker als IWorker registriert. - --smoke-ui Headless-Test (konstruiert jede View + Launcher). - appsettings.Local.json gitignored. - Tests angepasst -> 30/30 gruen; Build + Smoke-UI + App-Start verifiziert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> @
187 lines
7.8 KiB
C#
187 lines
7.8 KiB
C#
using IBKRTrader.Core.AI;
|
||
using IBKRTrader.Core.Budget;
|
||
using IBKRTrader.Core.Database;
|
||
using IBKRTrader.Core.Database.Migrations;
|
||
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;
|
||
}
|
||
|
||
ApplicationConfiguration.Initialize();
|
||
|
||
var modules = new List<IModule> { new CongressTradingModule() };
|
||
|
||
AppHost = Host.CreateDefaultBuilder()
|
||
.UseContentRoot(AppContext.BaseDirectory)
|
||
.ConfigureAppConfiguration((_, config) =>
|
||
config.AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: false))
|
||
.ConfigureServices((context, services) =>
|
||
{
|
||
RegisterCoreServices(services);
|
||
foreach (var module in modules)
|
||
{
|
||
services.AddSingleton(module);
|
||
module.RegisterServices(services, context.Configuration);
|
||
}
|
||
services.AddSingleton<ShellUiHost>();
|
||
services.AddSingleton<IModuleUiHost>(sp => sp.GetRequiredService<ShellUiHost>());
|
||
services.AddSingleton<LauncherForm>();
|
||
})
|
||
.Build();
|
||
|
||
// Zirkuläre Abhängigkeit auflösen: WebApiService braucht die Engine-Referenz.
|
||
AppHost.Services.GetRequiredService<WebApiService>()
|
||
.SetEngine(AppHost.Services.GetRequiredService<WorkerEngine>());
|
||
|
||
// Views registrieren (Core + Module), dann Launcher starten.
|
||
var uiHost = AppHost.Services.GetRequiredService<ShellUiHost>();
|
||
RegisterCoreViews(uiHost, AppHost.Services);
|
||
foreach (var module in modules)
|
||
module.RegisterUi(uiHost, AppHost.Services);
|
||
|
||
Application.Run(AppHost.Services.GetRequiredService<LauncherForm>());
|
||
AppHost.StopAsync().GetAwaiter().GetResult();
|
||
}
|
||
|
||
/// <summary>Registriert alle Core-Services im DI-Container.</summary>
|
||
private static void RegisterCoreServices(IServiceCollection services)
|
||
{
|
||
// Settings zuerst laden (eine Quelle, als Singleton weitergereicht).
|
||
var settingsService = new SettingsService();
|
||
settingsService.Load();
|
||
services.AddSingleton(settingsService);
|
||
|
||
services.AddSingleton<LoggingService>();
|
||
|
||
services.AddSingleton<DatabaseService>();
|
||
services.AddSingleton<CoreMigrations>();
|
||
services.AddSingleton<IBKRMigrations>();
|
||
|
||
services.AddSingleton<IBKRGatewayService>();
|
||
services.AddSingleton<IBKRMarketDataRepository>();
|
||
|
||
services.AddSingleton<BudgetService>();
|
||
services.AddSingleton<TradeHistoryService>();
|
||
services.AddSingleton<AIModelService>();
|
||
|
||
// Trading-Kern
|
||
services.AddSingleton<IRiskService, RiskService>();
|
||
services.AddSingleton<IPortfolioService, PortfolioService>();
|
||
services.AddSingleton<IExecutionService, ExecutionService>();
|
||
// Sicherer Standard-Broker: handelt nicht, bis der echte IBKR-Adapter verifiziert ist.
|
||
services.AddSingleton<IBrokerClient, NullBrokerClient>();
|
||
|
||
// Core-Worker/Services
|
||
services.AddSingleton<BackupWorker>();
|
||
services.AddSingleton<WebserverService>();
|
||
services.AddSingleton<WebApiService>();
|
||
services.AddSingleton<IBKRInstrumentSyncWorker>();
|
||
services.AddSingleton<IBKRPriceHistoryWorker>();
|
||
|
||
// Als IWorker registrieren → die WorkerEngine erhält alle über IEnumerable<IWorker>.
|
||
services.AddSingleton<IWorker>(sp => sp.GetRequiredService<BackupWorker>());
|
||
services.AddSingleton<IWorker>(sp => sp.GetRequiredService<WebserverService>());
|
||
services.AddSingleton<IWorker>(sp => sp.GetRequiredService<WebApiService>());
|
||
services.AddSingleton<IWorker>(sp => sp.GetRequiredService<IBKRInstrumentSyncWorker>());
|
||
services.AddSingleton<IWorker>(sp => sp.GetRequiredService<IBKRPriceHistoryWorker>());
|
||
|
||
services.AddSingleton<WorkerEngine>();
|
||
}
|
||
|
||
/// <summary>Registriert die Core-Views (Logs, Settings, Workers) bei der Shell.</summary>
|
||
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<WorkerEngine>())
|
||
});
|
||
uiHost.RegisterView(new ModuleView
|
||
{
|
||
Id = "core.logs", Title = "Logs", Group = "Core", Order = 20,
|
||
CreateForm = () => new LogsView(sp.GetRequiredService<LoggingService>())
|
||
});
|
||
uiHost.RegisterView(new ModuleView
|
||
{
|
||
Id = "core.settings", Title = "Settings", Group = "Core", Order = 30,
|
||
CreateForm = () => new SettingsView(sp.GetRequiredService<SettingsService>())
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 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.
|
||
/// </summary>
|
||
private static int RunSmokeUi()
|
||
{
|
||
ApplicationConfiguration.Initialize();
|
||
|
||
var modules = new List<IModule> { 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);
|
||
foreach (var module in modules)
|
||
{
|
||
services.AddSingleton(module);
|
||
module.RegisterServices(services, context.Configuration);
|
||
}
|
||
services.AddSingleton<ShellUiHost>();
|
||
services.AddSingleton<IModuleUiHost>(s => s.GetRequiredService<ShellUiHost>());
|
||
services.AddSingleton<LauncherForm>();
|
||
})
|
||
.Build();
|
||
|
||
host.Services.GetRequiredService<WebApiService>()
|
||
.SetEngine(host.Services.GetRequiredService<WorkerEngine>());
|
||
|
||
var uiHost = host.Services.GetRequiredService<ShellUiHost>();
|
||
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<LauncherForm>(); 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;
|
||
}
|
||
}
|