Files
IBKRTrader/src/IBKRTrader.App/Views/Modules/CongressTradingWindow.axaml.cs
T
RichardandClaude Opus 5 c176b05ea1
Build & Test / build (ubuntu-latest) (push) Waiting to run
Build & Test / build (windows-latest) (push) Waiting to run
L6: IBKRTrader.App.Avalonia -> IBKRTrader.App; letzte WinForms-Spuren raus
Das Suffix ".Avalonia" gab es nur, weil daneben ein WinForms-IBKRTrader.App
stand. Das ist seit L5 weg, also faellt auch das Suffix. Git erkennt alle
Dateien als Umbenennung; Assembly, Wurzel-Namensraum und die
avares://-Ressourcen-URI sind mitgezogen.

Nebeneffekt der Umbenennung: die global::Avalonia-Qualifizierungen entfallen.
Sie waren noetig, weil der Namensraum IBKRTrader.App.Avalonia das
Avalonia-Paket verdeckt hat - ein Ueberbleibsel genau der Namensgebung, die
jetzt weg ist.

Inhaltlich falsch gewordene Aussagen berichtigt - das waren die eigentlichen
Ueberbleibsel, nicht die Kommentare:
- .agents/rules/grundregeln.md schrieb weiterhin "C# .NET 10 WinForms",
  RichTextBox-Logging, LauncherForm und PropertyGrid vor. Das ist die Regel,
  nach der kuenftig gearbeitet wird - sie haette die Portierung Stueck fuer
  Stueck rueckgaengig gemacht. Jetzt: Avalonia, keine Plattform-Suffixe, die
  11er-Pinnung mit Begruendung, dazu die beiden Regeln, die uns in L1b am
  meisten gekostet haben (UTC persistieren + AppTimeZone statt DateTime.Now;
  jede Formatierung mit ausdruecklichem IFormatProvider).
- Core: LogEntry ("wird in RichTextBox geschrieben"), IWorker/WorkerEngine/
  WorkerInfo ("DataGridView-Zeile"/"-Binding"), ModuleView ("die
  WinForms-Shell castet auf Form").
- Doku: ARCHITECTURE (Modul-Ui-Ordner, "designbare Forms mit Initialize"),
  KONZEPT-Modul-Accounting ("UI (WinForms, ein Fenster mit Tabs)").

BEWUSST STEHEN GEBLIEBEN sind die Kommentare, die WinForms nur als
Begruendung nennen - warum LoggingService ein Ereignis hat statt einer
RichTextBox, warum ModuleView Func<object> liefert, warum es benannte
Record-Zeilentypen gibt, warum die Einstellungsmaske aus Attributen entsteht.
Das ist die Herleitung des heutigen Entwurfs; ohne sie sieht spaeter jede
dieser Stellen nach Umstaendlichkeit ohne Grund aus.

KONZEPT-Linux-Portierung.md bekommt einen Statusvermerk: umgesetzt, die
Pfadangaben im Fundstellenverzeichnis beziehen sich auf den alten Aufbau.
Zwei Abweichungen von der Schaetzung sind dort festgehalten - der geringere
Aufwand dank der PolytraderSharp-Vorlage, und dass die dort empfohlene
InvariantGlobalization ein Fehler gewesen waere.

Verifiziert: Build 0 Fehler/0 Warnungen, 193 Tests gruen, Smoke-UI
konstruiert alle 7 Ansichten + Launcher + Dialog, Daemon-Prueflauf OK,
publish -r linux-x64 fuer beide Einstiegspunkte fehlerfrei.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 21:16:48 +02:00

110 lines
4.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using IBKRTrader.App.Shell;
using IBKRTrader.App.ViewModels;
using IBKRTrader.Core.Logging;
using IBKRTrader.Core.Modularity;
using IBKRTrader.Core.Time;
using IBKRTrader.Core.Trading;
using IBKRTrader.Core.Workers;
using IBKRTrader.Modules.CongressTrading;
using IBKRTrader.Modules.CongressTrading.Database;
namespace IBKRTrader.App.Views.Modules;
/// <summary>
/// Fenster des CongressTrading-Moduls: DB-Kennzahlen, manueller Scrape-Auslöser und die offenen
/// Positionen des Moduls (aus dem Core-Portfolio).
///
/// <para>DB-Zugriffe laufen NUR beim Anzeigen und auf Nutzerinteraktion nie im Konstruktor,
/// damit die Konstruktionsprüfung auch ohne Datenbank durchläuft.</para>
/// </summary>
public partial class CongressTradingWindow : Window
{
private const string ScrapeWorkerName = "CT-ScrapeWorker";
private readonly CongressRepository _repo;
private readonly WorkerEngine _engine;
private readonly IPortfolioService _portfolio;
private readonly LoggingService _logger;
public CongressTradingWindow(IModuleUiHost uiHost,
CongressRepository repo,
WorkerEngine engine,
IPortfolioService portfolio,
LoggingService logger)
{
_repo = repo;
_engine = engine;
_portfolio = portfolio;
_logger = logger;
InitializeComponent();
WindowMenu.Wire(this.FindControl<Menu>("WindowMenuBar")!, uiHost, "congresstrading.main");
this.FindControl<Button>("RefreshButton")!.Click += async (_, _) => await RefreshAsync();
this.FindControl<Button>("ScrapeButton")!.Click += async (_, _) => await TriggerScrapeAsync();
Opened += async (_, _) => await RefreshAsync();
}
private void InitializeComponent() => AvaloniaXamlLoader.Load(this);
private async Task RefreshAsync()
{
try
{
var trades = await _repo.GetTradeCountAsync();
var members = await _repo.GetMemberCountAsync();
ShowKpis(("Trades in DB", trades.ToString("N0")),
("Mitglieder in DB", members.ToString("N0")));
var positions = await _portfolio.GetPositionsAsync(CongressTradingModule.LogTag);
this.FindControl<DataGrid>("PositionsGrid")!.ItemsSource = positions
.Select(p => new PositionRow(p.Symbol, p.Quantity, p.AvgPrice, p.Notional))
.ToList();
SetStatus($"Aktualisiert: {AppTimeZone.Now:HH:mm:ss}");
}
catch (Exception ex)
{
ShowKpis(("Trades in DB", "n/v"), ("Mitglieder in DB", "n/v"));
SetStatus($"DB nicht erreichbar: {ex.Message}");
_logger.Warn(CongressTradingModule.LogTag, $"Kennzahlen konnten nicht geladen werden: {ex.Message}");
}
}
private async Task TriggerScrapeAsync()
{
try
{
SetStatus("Scrape angestoßen …");
await _engine.TriggerWorkerAsync(ScrapeWorkerName);
_logger.Info(CongressTradingModule.LogTag, "Scrape-Worker manuell ausgelöst (aus Modul-Fenster).");
SetStatus("Scrape ausgelöst.");
}
catch (Exception ex)
{
SetStatus($"Scrape fehlgeschlagen: {ex.Message}");
_logger.Error(CongressTradingModule.LogTag, "Manueller Scrape-Trigger fehlgeschlagen.", ex);
}
}
private void ShowKpis(params (string Caption, string Value)[] kpis)
{
var panel = this.FindControl<WrapPanel>("KpiPanel")!;
panel.Children.Clear();
foreach (var (caption, value) in kpis)
{
var stack = new StackPanel();
stack.Children.Add(new TextBlock { Text = caption, Classes = { "caption" } });
stack.Children.Add(new TextBlock { Text = value, Classes = { "value" } });
panel.Children.Add(new Border { Classes = { "kpi" }, Child = stack });
}
}
private void SetStatus(string text) =>
this.FindControl<TextBlock>("StatusText")!.Text = text;
}