diff --git a/src/PolyTrader.App.Avalonia/App.axaml.cs b/src/PolyTrader.App.Avalonia/App.axaml.cs index a3fd30b..3274a6d 100644 --- a/src/PolyTrader.App.Avalonia/App.axaml.cs +++ b/src/PolyTrader.App.Avalonia/App.axaml.cs @@ -33,6 +33,7 @@ namespace PolyTrader.App.Avalonia CoreViews.Register(uiHost, Services); foreach (var module in Services.GetServices()) module.RegisterUi(uiHost, Services); + ModuleViews.Register(uiHost, Services); ViewIcons.AssignDefaults(uiHost); var launcher = new LauncherWindow(uiHost, Services); diff --git a/src/PolyTrader.App.Avalonia/Program.cs b/src/PolyTrader.App.Avalonia/Program.cs index e8da7b8..a865fde 100644 --- a/src/PolyTrader.App.Avalonia/Program.cs +++ b/src/PolyTrader.App.Avalonia/Program.cs @@ -175,6 +175,7 @@ namespace PolyTrader.App.Avalonia CoreViews.Register(uiHost, host.Services); foreach (var module in host.Services.GetServices()) module.RegisterUi(uiHost, host.Services); + ModuleViews.Register(uiHost, host.Services); ViewIcons.AssignDefaults(uiHost); int failures = 0; diff --git a/src/PolyTrader.App.Avalonia/Shell/ModuleViews.cs b/src/PolyTrader.App.Avalonia/Shell/ModuleViews.cs new file mode 100644 index 0000000..b318e76 --- /dev/null +++ b/src/PolyTrader.App.Avalonia/Shell/ModuleViews.cs @@ -0,0 +1,91 @@ +using System; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using PolyTrader.Core.Modularity; + +namespace PolyTrader.App.Avalonia.Shell +{ + /// + /// Registriert die Fenster der Module. Liegt bewusst in der App und nicht in den Modulen – + /// Begründung in Views/Modules/README.md: die Modulprojekte bleiben dadurch frei von + /// Avalonia, was für den kopflosen Linux-Betrieb (--headless) den Ausschlag gibt. + /// + /// Registriert wird nur, was auch geladen ist: ist ein Modul über + /// DisabledModules abgeschaltet, fehlen seine Dienste im Container – dann entfällt auch + /// sein Fenster, und der Launcher zeigt es gar nicht erst an. + /// + public static class ModuleViews + { + public static void Register(IModuleUiHost host, IServiceProvider services) + { + RegisterIfLoaded(services, "CopyTrading", () => host.RegisterView(new ModuleView + { + Id = "copytrading.main", + Title = "Copytrading", + Group = "CopyTrading", + Order = 100, + CreateView = () => new Views.Modules.CopyTradingWindow( + host, + services.GetRequiredService(), + services.GetRequiredService(), + services.GetRequiredService(), + services.GetRequiredService(), + services.GetRequiredService()) + })); + + RegisterIfLoaded(services, "ResolutionFarming", () => host.RegisterView(new ModuleView + { + Id = "resolutionfarming.main", + Title = "ResolutionFarming", + Group = "ResolutionFarming", + Order = 200, + CreateView = () => new Views.Modules.ResolutionFarmingWindow( + host, + services.GetRequiredService(), + services.GetRequiredService(), + services.GetRequiredService(), + services.GetRequiredService(), + services.GetRequiredService()) + })); + + RegisterIfLoaded(services, "Supervisor", () => host.RegisterView(new ModuleView + { + Id = "supervisor.main", + Title = "Supervisor", + Group = "Supervisor", + Order = 300, + CreateView = () => new Views.Modules.SupervisorWindow( + host, + services.GetRequiredService(), + services.GetRequiredService(), + services.GetRequiredService(), + services.GetRequiredService(), + services.GetRequiredService()) + })); + + RegisterIfLoaded(services, "Accounting", () => host.RegisterView(new ModuleView + { + Id = "accounting.main", + Title = "Accounting", + Group = "Accounting", + Order = 400, + CreateView = () => new Views.Modules.AccountingWindow( + host, + services.GetRequiredService(), + services.GetRequiredService(), + services.GetRequiredService(), + services.GetRequiredService(), + services.GetRequiredService(), + services.GetRequiredService()) + })); + } + + /// Registriert die Ansicht nur, wenn das Modul in dieser Sitzung geladen ist. + private static void RegisterIfLoaded(IServiceProvider services, string moduleName, Action register) + { + bool loaded = services.GetServices() + .Any(m => string.Equals(m.Name, moduleName, StringComparison.OrdinalIgnoreCase)); + if (loaded) register(); + } + } +} diff --git a/src/PolyTrader.App.Avalonia/ViewModels/CopyTradingRows.cs b/src/PolyTrader.App.Avalonia/ViewModels/CopyTradingRows.cs new file mode 100644 index 0000000..304ff22 --- /dev/null +++ b/src/PolyTrader.App.Avalonia/ViewModels/CopyTradingRows.cs @@ -0,0 +1,78 @@ +using System; +using Avalonia.Media; +using PolyTrader.Modules.CopyTrading.Logic; + +namespace PolyTrader.App.Avalonia.ViewModels +{ + /// + /// Avalonia-Farbpalette zu den -Kategorien. Die Schwellenlogik liegt + /// (getestet) in ; hier steht nur, wie die Kategorien aussehen. + /// Werte unverändert aus der WinForms-Oberfläche übernommen. + /// + public static class TradeRowPalette + { + public static readonly IBrush Loss = new SolidColorBrush(Color.FromRgb(245, 200, 200)); // rot + public static readonly IBrush SmallWin = new SolidColorBrush(Color.FromRgb(212, 240, 212)); // hellgrün + public static readonly IBrush BigWin = new SolidColorBrush(Color.FromRgb(140, 214, 140)); // grün + + public static IBrush ForPnlPercent(decimal pnlPercent) => TradeRowColoring.ForPnlPercent(pnlPercent) switch + { + TradeRowTint.Loss => Loss, + TradeRowTint.BigWin => BigWin, + _ => SmallWin + }; + } + + /// Zeile des Grids „Offene Trades" (aufgeloester Kontoname + Zeilenfarbe). + public sealed class CopyOpenTradeRow + { + public string Account { get; init; } = string.Empty; + public string Trader { get; init; } = string.Empty; + public string Market { get; init; } = string.Empty; + public string Outcome { get; init; } = string.Empty; + public string Side { get; init; } = string.Empty; + public decimal Entry { get; init; } + public decimal Current { get; init; } + public decimal Size { get; init; } + public decimal Amount { get; init; } + public decimal Value { get; init; } + public decimal Unrealized { get; init; } + public decimal UnrealizedPct { get; init; } + public string Status { get; init; } = string.Empty; + + /// Zeilenhintergrund nach Buchgewinn – wie zuvor die DataBindingComplete-Färbung. + public IBrush RowBrush => TradeRowPalette.ForPnlPercent(UnrealizedPct); + } + + /// + /// Zeile des Grids „Geschlossene Trades". Bewusst eigener Typ und nicht das Modell + /// PolyTraderSharp.Models.ClosedTradeRow: hier stehen der aufgeloeste Master-Trader-Name + /// und die Zeilenfarbe, die es dort nicht gibt. + /// + public sealed class CopyClosedTradeRow + { + public int TradeId { get; init; } + public string Account { get; init; } = string.Empty; + public string Trader { get; init; } = string.Empty; + public string Market { get; init; } = string.Empty; + public string Outcome { get; init; } = string.Empty; + public string Side { get; init; } = string.Empty; + public decimal Entry { get; init; } + public decimal Exit { get; init; } + public decimal Size { get; init; } + public decimal Pnl { get; init; } + public decimal PnlPercent { get; init; } + public DateTime OpenedAt { get; init; } + public DateTime ClosedAt { get; init; } + + public IBrush RowBrush => TradeRowPalette.ForPnlPercent(PnlPercent); + } + + /// Eintrag der Account-Zuweisung eines Master-Traders (Mehrfachauswahl). + public sealed class AccountAssignment + { + public int AccountId { get; init; } + public string Label { get; init; } = string.Empty; + public bool IsAssigned { get; set; } + } +} diff --git a/src/PolyTrader.App.Avalonia/ViewModels/DashboardRows.cs b/src/PolyTrader.App.Avalonia/ViewModels/DashboardRows.cs index a267fa8..07ba8a8 100644 --- a/src/PolyTrader.App.Avalonia/ViewModels/DashboardRows.cs +++ b/src/PolyTrader.App.Avalonia/ViewModels/DashboardRows.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace PolyTrader.App.Avalonia.ViewModels { @@ -33,4 +33,20 @@ namespace PolyTrader.App.Avalonia.ViewModels /// public bool CanToggle { get; init; } = true; } + + /// + /// Monatszeile der BWA-Übersicht. Die Werte sind bereits in die gewählte Währung umgerechnet + /// und als Text formatiert – die Umrechnung passiert einmal beim Aufbau, nicht je Bindung. + /// + public sealed class MonthlyRow + { + public string Month { get; init; } = string.Empty; + public string Net { get; init; } = string.Empty; + public string Deposits { get; init; } = string.Empty; + public string Withdrawals { get; init; } = string.Empty; + public string Fees { get; init; } = string.Empty; + public string Volume { get; init; } = string.Empty; + public int Trades { get; init; } + public string Closing { get; init; } = string.Empty; + } } diff --git a/src/PolyTrader.App.Avalonia/ViewModels/LogRows.cs b/src/PolyTrader.App.Avalonia/ViewModels/LogRows.cs index 9d36cee..078d3e5 100644 --- a/src/PolyTrader.App.Avalonia/ViewModels/LogRows.cs +++ b/src/PolyTrader.App.Avalonia/ViewModels/LogRows.cs @@ -1,4 +1,4 @@ -using Avalonia.Media; +using Avalonia.Media; namespace PolyTrader.App.Avalonia.ViewModels { @@ -20,4 +20,12 @@ namespace PolyTrader.App.Avalonia.ViewModels public string Cid { get; init; } = string.Empty; public string Message { get; init; } = string.Empty; } + + /// Ein Eintrag im Supervisor-Analyseverlauf (Frage oder Antwort). + public sealed class ChatEntry + { + public string Header { get; init; } = string.Empty; + public string Text { get; init; } = string.Empty; + public IBrush HeaderBrush { get; init; } = Brushes.SteelBlue; + } } diff --git a/src/PolyTrader.App.Avalonia/Views/Modules/AccountingWindow.axaml b/src/PolyTrader.App.Avalonia/Views/Modules/AccountingWindow.axaml new file mode 100644 index 0000000..a9bf0f7 --- /dev/null +++ b/src/PolyTrader.App.Avalonia/Views/Modules/AccountingWindow.axaml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + +