@
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> @
This commit is contained in:
+132
-56
@@ -4,107 +4,183 @@ using IBKRTrader.Core.Database;
|
||||
using IBKRTrader.Core.Database.Migrations;
|
||||
using IBKRTrader.Core.IBKR;
|
||||
using IBKRTrader.Core.Logging;
|
||||
using IBKRTrader.Core.Modules;
|
||||
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()
|
||||
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();
|
||||
|
||||
// ── DI-Container aufbauen ────────────────────────────────────────────
|
||||
var services = new ServiceCollection();
|
||||
var modules = new List<IModule> { new CongressTradingModule() };
|
||||
|
||||
// Core: Settings (zuerst laden)
|
||||
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);
|
||||
|
||||
// Core: Logging
|
||||
services.AddSingleton<LoggingService>();
|
||||
|
||||
// Core: Database
|
||||
services.AddSingleton<DatabaseService>();
|
||||
services.AddSingleton<CoreMigrations>();
|
||||
services.AddSingleton<IBKRMigrations>();
|
||||
|
||||
// Core: IBKR Services
|
||||
services.AddSingleton<IBKRGatewayService>();
|
||||
services.AddSingleton<IBKRMarketDataRepository>();
|
||||
|
||||
// Core: Other Services
|
||||
services.AddSingleton<BudgetService>();
|
||||
services.AddSingleton<TradeHistoryService>();
|
||||
services.AddSingleton<AIModelService>();
|
||||
|
||||
// Core: Trading-Kern
|
||||
// Trading-Kern
|
||||
services.AddSingleton<IRiskService, RiskService>();
|
||||
services.AddSingleton<IPortfolioService, PortfolioService>();
|
||||
services.AddSingleton<IExecutionService, ExecutionService>();
|
||||
// Sicherer Standard-Broker: handelt nicht, bis der echte IBKR-Adapter
|
||||
// angebunden und gegen den Paper-Gateway verifiziert ist.
|
||||
// Sicherer Standard-Broker: handelt nicht, bis der echte IBKR-Adapter verifiziert ist.
|
||||
services.AddSingleton<IBrokerClient, NullBrokerClient>();
|
||||
|
||||
// Core: Built-In Worker
|
||||
// Core-Worker/Services
|
||||
services.AddSingleton<BackupWorker>();
|
||||
services.AddSingleton<WebserverService>();
|
||||
services.AddSingleton<WebApiService>();
|
||||
services.AddSingleton<IBKRInstrumentSyncWorker>();
|
||||
services.AddSingleton<IBKRPriceHistoryWorker>();
|
||||
|
||||
// ── Module registrieren ──────────────────────────────────────────────
|
||||
// Der Core kennt konkrete Module nicht: Module werden hier instanziiert,
|
||||
// hängen ihre Services über IModule.RegisterServices ein und werden über
|
||||
// die ModuleRegistry zugänglich gemacht.
|
||||
IModule[] modules =
|
||||
[
|
||||
new CongressTradingModule(),
|
||||
];
|
||||
|
||||
foreach (var module in modules)
|
||||
module.RegisterServices(services);
|
||||
|
||||
var moduleRegistry = new ModuleRegistry(modules);
|
||||
services.AddSingleton(moduleRegistry);
|
||||
|
||||
// ── IWorker-Sammlung: Core + Module ──────────────────────────────────
|
||||
services.AddSingleton<IEnumerable<IWorker>>(sp => new IWorker[]
|
||||
{
|
||||
// Core-Worker
|
||||
sp.GetRequiredService<BackupWorker>(),
|
||||
sp.GetRequiredService<WebserverService>(),
|
||||
sp.GetRequiredService<WebApiService>(),
|
||||
// IBKR Market Data Worker
|
||||
sp.GetRequiredService<IBKRInstrumentSyncWorker>(),
|
||||
sp.GetRequiredService<IBKRPriceHistoryWorker>(),
|
||||
}
|
||||
// Modul-Worker dynamisch anhängen
|
||||
.Concat(moduleRegistry.Modules.SelectMany(m => m.GetWorkers(sp)))
|
||||
.ToArray());
|
||||
// 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>();
|
||||
|
||||
// UI: Launcher + Fenster-Verwaltung
|
||||
services.AddSingleton<WindowManager>();
|
||||
services.AddSingleton<LauncherForm>();
|
||||
|
||||
var provider = services.BuildServiceProvider();
|
||||
|
||||
// WebApiService bekommt Engine-Referenz (zirkuläre Abhängigkeit auflösen)
|
||||
var webApi = provider.GetRequiredService<WebApiService>();
|
||||
var engine = provider.GetRequiredService<WorkerEngine>();
|
||||
webApi.SetEngine(engine);
|
||||
|
||||
Application.Run(provider.GetRequiredService<LauncherForm>());
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user