R3-Vorbereitung: --db-version-Diagnose + EF-Strategie festgelegt - Program: --db-version gibt die DB-Serverversion aus (fuer EF-ServerVersion-Pin) - Bestaetigt: MariaDB 11.8.6 -> Pin wie PolytraderSharp (MariaDbServerVersion 11.8.6) - docs/ARCHITECTURE.md: R3-Strategie fixiert (EF-Migrationen extern, Verbindung aus appsettings.Local.json) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> @
232 lines
9.6 KiB
C#
232 lines
9.6 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;
|
||
}
|
||
|
||
// 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<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);
|
||
AssignViewIcons(uiHost);
|
||
|
||
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>
|
||
/// 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.
|
||
/// </summary>
|
||
private static void AssignViewIcons(IModuleUiHost uiHost)
|
||
{
|
||
var map = new Dictionary<string, Image>
|
||
{
|
||
["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;
|
||
}
|
||
|
||
/// <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>Diagnose: öffnet die DB (aus settings.json) und gibt die Serverversion aus. Kein UI.</summary>
|
||
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}");
|
||
}
|
||
}
|
||
|
||
/// <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;
|
||
}
|
||
}
|