P1c+P4: Module und Tests auf net8.0 - WinForms nur noch in PolyTrader.App

Variante B (Entscheidung Richard): Modul-UI entfernt statt in Zwischenprojekte
ausgelagert. Avalonia ist plattformuebergreifend, die neuen Ansichten kommen spaeter
direkt in die Modul-Projekte zurueck - kein Zwischenschritt, keine Wegwerfarbeit.

- 23 WinForms-Dateien aus den 4 Modulen entfernt (Spezifikation steht in
  docs/UI-SPEZIFIKATION-WinForms.md, Originalcode im Tag winforms-final).
- RegisterUi ist jetzt je Modul ein dokumentierter No-Op: View-ID, Titel, Gruppe,
  Order und der Tab-Aufbau stehen als XML-Doku drin, damit der Avalonia-Nachbau
  die stabilen IDs und die Struktur uebernimmt.
- Alle 4 Modulprojekte + Testprojekt: net8.0 statt net8.0-windows, UseWindowsForms raus.
- P4 vorgezogen (war durch den Testprojekt-Wechsel faellig): PDFsharp-MigraDoc-GDI
  -> PDFsharp-MigraDoc (Core-Build). Der Core-Build findet keine Systemschriften,
  daher neu Logic/PdfFontResolver.cs: durchsucht die Schriftverzeichnisse des OS nach
  Segoe UI/DejaVu/Liberation/Noto/Arial/FreeSans. Keine Schriftdateien im Repo noetig;
  fehlt auf Linux alles, kommt eine klare Meldung mit apt-Hinweis statt eines
  kryptischen Renderer-Fehlers.

Verifiziert: Core, alle 4 Module und das Testprojekt publishen fuer linux-x64, und
zwar ohne ein einziges Windows-spezifisches Paket in den deps.json. 442 Tests gruen
(inkl. PDF-Rendering) auf net8.0. Windows-App laeuft weiter mit den Core-Fenstern.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-08-06 17:32:12 +02:00
co-authored by Claude Opus 5
parent 0a26b8563d
commit 53546ceeeb
34 changed files with 243 additions and 5175 deletions
@@ -49,21 +49,22 @@ namespace PolyTrader.Modules.Accounting
services.AddHostedService(sp => sp.GetRequiredService<AccountingIngestService>()); services.AddHostedService(sp => sp.GetRequiredService<AccountingIngestService>());
} }
public void RegisterUi(IModuleUiHost host, System.IServiceProvider services) /// <summary>
/// Aktuell ohne Ansicht: Die WinForms-UI wurde mit der Linux-Portierung entfernt, die
/// Avalonia-Ansicht folgt (Stufe L3/L4). Die Fachlogik dieses Moduls laeuft davon
/// unabhaengig weiter die Shell zeigt schlicht kein Fenster fuer das Modul an.
///
/// <para><b>Beim Nachbau zu erhalten</b> (Spezifikation: docs/UI-SPEZIFIKATION-WinForms.md,
/// Originalcode: Git-Tag <c>winforms-final</c>):</para>
/// <list type="bullet">
/// <item>View-ID <c>accounting.main</c> (stabil Launcher-Button und Symbol haengen daran)</item>
/// <item>Titel <c>Accounting</c>, Gruppe <c>Accounting</c>, Order <c>400</c></item>
/// <item>Ein Fenster fuers ganze Modul: AccountingMainForm mit Tabs: Uebersicht+BWA / Ledger / Abruf+Status</item>
/// </list>
/// </summary>
public void RegisterUi(IModuleUiHost host, IServiceProvider services)
{ {
host.RegisterView(new ModuleView // Bewusst leer, bis die Avalonia-Ansicht steht (siehe Doku oben).
{
Id = "accounting.main",
Title = "Accounting",
Group = "Accounting",
Order = 400,
CreateView = () =>
{
var form = new Ui.AccountingMainForm();
form.Initialize(services);
return form;
}
});
} }
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask; public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -28,9 +28,15 @@ namespace PolyTrader.Modules.Accounting.Logic
decimal V(decimal usdc) => Math.Round(usdc * currencyFactor, 2, MidpointRounding.AwayFromZero); decimal V(decimal usdc) => Math.Round(usdc * currencyFactor, 2, MidpointRounding.AwayFromZero);
string M(decimal usdc) => V(usdc).ToString("N2") + " " + currencyCode; string M(decimal usdc) => V(usdc).ToString("N2") + " " + currencyCode;
// Schriftauflösung sicherstellen: der PDFsharp-Core-Build findet ohne Resolver keine
// Systemschriften (siehe PdfFontResolver). Idempotent, kostet nach dem ersten Mal nichts.
PdfFontResolver.EnsureRegistered();
var doc = new Document(); var doc = new Document();
doc.Info.Title = "Buchhalterische Abrechnung"; doc.Info.Title = "Buchhalterische Abrechnung";
var style = doc.Styles["Normal"]; var style = doc.Styles["Normal"];
// Wunschschrift; der Resolver weicht auf eine verfügbare aus, wenn es sie nicht gibt
// (auf Linux z.B. DejaVu Sans).
style.Font.Name = "Segoe UI"; style.Font.Name = "Segoe UI";
style.Font.Size = 9; style.Font.Size = 9;
@@ -0,0 +1,151 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using PdfSharp.Fonts;
namespace PolyTrader.Modules.Accounting.Logic
{
/// <summary>
/// Schriftauflösung für den PDF-Export. Nötig, seit der plattformneutrale PDFsharp-Core-Build
/// verwendet wird: der frühere <c>-GDI</c>-Build zog Systemschriften über
/// <c>System.Drawing.Common</c>, das seit .NET 7 Windows-only ist. Der Core-Build bringt keine
/// eigene Schriftsuche mit und braucht deshalb diesen Resolver.
///
/// <para>Der Resolver durchsucht die üblichen Schriftverzeichnisse des Betriebssystems nach einer
/// Kandidatenliste (Windows: Segoe UI/Arial, Linux: DejaVu/Liberation/Noto). Damit sind keine
/// Schriftdateien im Repository nötig und die Lizenzfrage der Schriften bleibt beim System.</para>
///
/// <para><b>Voraussetzung auf Linux:</b> mindestens eine der Kandidatenschriften muss installiert
/// sein. Auf Desktop-Distributionen ist das der Normalfall; auf schlanken Servern/Containern
/// genügt <c>apt install fonts-dejavu-core</c> (bzw. <c>fonts-liberation</c>). Fehlt jede Schrift,
/// wirft <see cref="EnsureRegistered"/> beim ersten Export eine Meldung mit genau diesem Hinweis
/// besser als eine kryptische Meldung aus dem Renderer-Inneren.</para>
/// </summary>
public sealed class PdfFontResolver : IFontResolver
{
/// <summary>Bevorzugte Schriftfamilien in Reihenfolge erste gefundene gewinnt.</summary>
private static readonly string[] PreferredFamilies =
{
"Segoe UI", "DejaVu Sans", "Liberation Sans", "Noto Sans", "Arial", "FreeSans"
};
/// <summary>Dateinamens-Kandidaten je Familie und Schnitt (klein geschrieben, ohne Endung).</summary>
private static readonly Dictionary<string, (string Regular, string Bold)> FileNames = new(StringComparer.OrdinalIgnoreCase)
{
["Segoe UI"] = ("segoeui", "segoeuib"),
["DejaVu Sans"] = ("DejaVuSans", "DejaVuSans-Bold"),
["Liberation Sans"] = ("LiberationSans-Regular", "LiberationSans-Bold"),
["Noto Sans"] = ("NotoSans-Regular", "NotoSans-Bold"),
["Arial"] = ("arial", "arialbd"),
["FreeSans"] = ("FreeSans", "FreeSansBold"),
};
private static readonly ConcurrentDictionary<string, byte[]> Cache = new();
private static readonly object RegisterLock = new();
private static bool _registered;
/// <summary>
/// Registriert den Resolver einmalig global. Muss vor dem ersten Rendern laufen; mehrfache
/// Aufrufe sind unschädlich (PDFsharp erlaubt nur eine Zuweisung pro Prozess).
/// </summary>
public static void EnsureRegistered()
{
if (_registered) return;
lock (RegisterLock)
{
if (_registered) return;
if (FindAnyAvailableFamily() == null)
{
throw new InvalidOperationException(
"Keine geeignete Schriftart gefunden. Der PDF-Export braucht mindestens eine von: " +
string.Join(", ", PreferredFamilies) + ". " +
"Auf schlanken Linux-Systemen nachinstallieren, z.B. 'apt install fonts-dejavu-core'.");
}
GlobalFontSettings.FontResolver = new PdfFontResolver();
_registered = true;
}
}
/// <summary>Erste verfügbare Familie aus <see cref="PreferredFamilies"/>, sonst <c>null</c>.</summary>
internal static string? FindAnyAvailableFamily() =>
PreferredFamilies.FirstOrDefault(f => LocateFile(f, bold: false) != null);
public FontResolverInfo? ResolveTypeface(string familyName, bool isBold, bool isItalic)
{
// Angeforderte Familie bevorzugen, sonst die erste verfügbare (z.B. "Segoe UI" auf Linux).
string? family = LocateFile(familyName, isBold) != null ? familyName : FindAnyAvailableFamily();
if (family == null) return null;
// Kursiv wird bewusst nicht aufgelöst: der Bericht nutzt es nicht, und ein fehlender
// Kursiv-Schnitt wäre sonst ein harter Fehler statt einer kleinen Abweichung.
return new FontResolverInfo($"{family}|{(isBold ? "b" : "r")}");
}
public byte[]? GetFont(string faceName)
{
return Cache.GetOrAdd(faceName, key =>
{
var parts = key.Split('|');
string family = parts[0];
bool bold = parts.Length > 1 && parts[1] == "b";
string? path = LocateFile(family, bold) ?? LocateFile(family, bold: false);
return path != null ? File.ReadAllBytes(path) : Array.Empty<byte>();
});
}
/// <summary>Sucht die Datei zu Familie + Schnitt in allen Schriftverzeichnissen des Systems.</summary>
private static string? LocateFile(string family, bool bold)
{
if (!FileNames.TryGetValue(family, out var names)) return null;
string stem = bold ? names.Bold : names.Regular;
foreach (string dir in FontDirectories())
{
if (!Directory.Exists(dir)) continue;
foreach (string ext in new[] { ".ttf", ".otf" })
{
try
{
// Rekursiv, weil Linux die Schriften in Unterordnern ablegt (truetype/dejavu/…).
var hit = Directory.EnumerateFiles(dir, stem + ext, SearchOption.AllDirectories)
.FirstOrDefault();
if (hit != null) return hit;
}
catch (UnauthorizedAccessException) { /* einzelne Ordner ohne Zugriff überspringen */ }
catch (IOException) { /* z.B. defekter Symlink */ }
}
}
return null;
}
/// <summary>Schriftverzeichnisse je Plattform.</summary>
private static IEnumerable<string> FontDirectories()
{
if (OperatingSystem.IsWindows())
{
yield return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Fonts");
yield break;
}
if (OperatingSystem.IsMacOS())
{
yield return "/System/Library/Fonts";
yield return "/Library/Fonts";
yield break;
}
// Linux/Unix
yield return "/usr/share/fonts";
yield return "/usr/local/share/fonts";
string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
if (!string.IsNullOrEmpty(home))
{
yield return Path.Combine(home, ".fonts");
yield return Path.Combine(home, ".local", "share", "fonts");
}
}
}
}
@@ -1,11 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\PolyTrader.Core\PolyTrader.Core.csproj" /> <ProjectReference Include="..\PolyTrader.Core\PolyTrader.Core.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="PDFsharp-MigraDoc-GDI" Version="6.2.4" /> <!-- Core-Build statt -GDI: der GDI-Build braucht System.Drawing.Common und ist damit
Windows-only. Der Core-Build findet dafuer keine Systemschriften von selbst -
siehe Logic/PdfFontResolver.cs. -->
<PackageReference Include="PDFsharp-MigraDoc" Version="6.2.4" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.3" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@@ -21,11 +24,12 @@
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework> <!-- Linux-Portierung: plattformneutral. Die WinForms-UI dieses Moduls (Ingest, BWA, Export)
wurde entfernt; die Avalonia-Ansicht kommt spaeter direkt hierher zurueck Avalonia ist
im Gegensatz zu WinForms plattformuebergreifend und braucht kein eigenes UI-Projekt. -->
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<!-- Modul trägt eigene WinForms-UI bei (Ledger/Status; BWA/Steuer/Export folgen A-2..A-4). -->
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup> </PropertyGroup>
</Project> </Project>
@@ -1,609 +0,0 @@
namespace PolyTrader.Modules.Accounting.Ui
{
partial class AccountingMainForm
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Vom Komponenten-Designer generierter Code
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(AccountingMainForm));
tabControlAcc = new TabControl();
tabUebersicht = new TabPage();
dgvMonthly = new DataGridView();
flpKpis = new FlowLayoutPanel();
lblKpiNet = new Label();
lblKpiClosing = new Label();
lblKpiDeposits = new Label();
lblKpiWithdrawals = new Label();
lblKpiFees = new Label();
lblKpiRewards = new Label();
lblKpiVolume = new Label();
lblKpiTrades = new Label();
pnlOverviewTop = new Panel();
btnExportPdf = new Button();
btnExportCsv = new Button();
btnCalc = new Button();
cbCurrency = new ComboBox();
lblWaehrung = new Label();
dtTo = new DateTimePicker();
lblBis = new Label();
dtFrom = new DateTimePicker();
lblVon = new Label();
cbOvAccount = new ComboBox();
lblOvKonto = new Label();
tabLedger = new TabPage();
dgvLedger = new DataGridView();
toolStripLedger = new ToolStrip();
lblLedgerKonto = new ToolStripLabel();
cbLedgerAccount = new ToolStripComboBox();
sepLedger = new ToolStripSeparator();
btnLedgerRefresh = new ToolStripButton();
tabStatus = new TabPage();
dgvRuns = new DataGridView();
toolStripStatus = new ToolStrip();
lblStatusKonto = new ToolStripLabel();
cbStatusAccount = new ToolStripComboBox();
sepStatus = new ToolStripSeparator();
btnIncremental = new ToolStripButton();
btnBackfill = new ToolStripButton();
sepStatus2 = new ToolStripSeparator();
btnStatusRefresh = new ToolStripButton();
lblAccStatus = new Label();
tabControlAcc.SuspendLayout();
tabUebersicht.SuspendLayout();
((System.ComponentModel.ISupportInitialize)dgvMonthly).BeginInit();
flpKpis.SuspendLayout();
pnlOverviewTop.SuspendLayout();
tabLedger.SuspendLayout();
((System.ComponentModel.ISupportInitialize)dgvLedger).BeginInit();
toolStripLedger.SuspendLayout();
tabStatus.SuspendLayout();
((System.ComponentModel.ISupportInitialize)dgvRuns).BeginInit();
toolStripStatus.SuspendLayout();
SuspendLayout();
//
// tabControlAcc
//
tabControlAcc.Controls.Add(tabUebersicht);
tabControlAcc.Controls.Add(tabLedger);
tabControlAcc.Controls.Add(tabStatus);
tabControlAcc.Dock = DockStyle.Fill;
tabControlAcc.Location = new Point(0, 0);
tabControlAcc.Margin = new Padding(4, 5, 4, 5);
tabControlAcc.Name = "tabControlAcc";
tabControlAcc.SelectedIndex = 0;
tabControlAcc.Size = new Size(1571, 1030);
tabControlAcc.TabIndex = 0;
//
// tabUebersicht
//
tabUebersicht.Controls.Add(dgvMonthly);
tabUebersicht.Controls.Add(flpKpis);
tabUebersicht.Controls.Add(pnlOverviewTop);
tabUebersicht.Location = new Point(4, 34);
tabUebersicht.Margin = new Padding(4, 5, 4, 5);
tabUebersicht.Name = "tabUebersicht";
tabUebersicht.Padding = new Padding(4, 5, 4, 5);
tabUebersicht.Size = new Size(1563, 992);
tabUebersicht.TabIndex = 0;
tabUebersicht.Text = "Übersicht / BWA";
tabUebersicht.UseVisualStyleBackColor = true;
//
// dgvMonthly
//
dgvMonthly.AllowUserToAddRows = false;
dgvMonthly.AllowUserToDeleteRows = false;
dgvMonthly.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dgvMonthly.Dock = DockStyle.Fill;
dgvMonthly.Location = new Point(4, 222);
dgvMonthly.Margin = new Padding(4, 5, 4, 5);
dgvMonthly.Name = "dgvMonthly";
dgvMonthly.ReadOnly = true;
dgvMonthly.RowHeadersVisible = false;
dgvMonthly.RowHeadersWidth = 62;
dgvMonthly.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dgvMonthly.Size = new Size(1555, 765);
dgvMonthly.TabIndex = 2;
//
// flpKpis
//
flpKpis.Controls.Add(lblKpiNet);
flpKpis.Controls.Add(lblKpiClosing);
flpKpis.Controls.Add(lblKpiDeposits);
flpKpis.Controls.Add(lblKpiWithdrawals);
flpKpis.Controls.Add(lblKpiFees);
flpKpis.Controls.Add(lblKpiRewards);
flpKpis.Controls.Add(lblKpiVolume);
flpKpis.Controls.Add(lblKpiTrades);
flpKpis.Dock = DockStyle.Top;
flpKpis.Location = new Point(4, 65);
flpKpis.Margin = new Padding(4, 5, 4, 5);
flpKpis.Name = "flpKpis";
flpKpis.Padding = new Padding(6, 7, 6, 7);
flpKpis.Size = new Size(1555, 157);
flpKpis.TabIndex = 1;
//
// lblKpiNet
//
lblKpiNet.BorderStyle = BorderStyle.FixedSingle;
lblKpiNet.Font = new Font("Segoe UI", 10F, FontStyle.Bold);
lblKpiNet.Location = new Point(12, 14);
lblKpiNet.Margin = new Padding(6, 7, 6, 7);
lblKpiNet.MinimumSize = new Size(285, 122);
lblKpiNet.Name = "lblKpiNet";
lblKpiNet.Padding = new Padding(11, 13, 11, 13);
lblKpiNet.Size = new Size(285, 122);
lblKpiNet.TabIndex = 0;
lblKpiNet.Text = "Netto-Handelsergebnis\n—";
lblKpiNet.TextAlign = ContentAlignment.MiddleLeft;
//
// lblKpiClosing
//
lblKpiClosing.BorderStyle = BorderStyle.FixedSingle;
lblKpiClosing.Font = new Font("Segoe UI", 10F, FontStyle.Bold);
lblKpiClosing.Location = new Point(309, 14);
lblKpiClosing.Margin = new Padding(6, 7, 6, 7);
lblKpiClosing.MinimumSize = new Size(256, 122);
lblKpiClosing.Name = "lblKpiClosing";
lblKpiClosing.Padding = new Padding(11, 13, 11, 13);
lblKpiClosing.Size = new Size(256, 122);
lblKpiClosing.TabIndex = 1;
lblKpiClosing.Text = "Endsaldo\n—";
lblKpiClosing.TextAlign = ContentAlignment.MiddleLeft;
//
// lblKpiDeposits
//
lblKpiDeposits.BorderStyle = BorderStyle.FixedSingle;
lblKpiDeposits.Font = new Font("Segoe UI", 10F, FontStyle.Bold);
lblKpiDeposits.Location = new Point(577, 14);
lblKpiDeposits.Margin = new Padding(6, 7, 6, 7);
lblKpiDeposits.MinimumSize = new Size(213, 122);
lblKpiDeposits.Name = "lblKpiDeposits";
lblKpiDeposits.Padding = new Padding(11, 13, 11, 13);
lblKpiDeposits.Size = new Size(213, 122);
lblKpiDeposits.TabIndex = 2;
lblKpiDeposits.Text = "Einzahlungen\n—";
lblKpiDeposits.TextAlign = ContentAlignment.MiddleLeft;
//
// lblKpiWithdrawals
//
lblKpiWithdrawals.BorderStyle = BorderStyle.FixedSingle;
lblKpiWithdrawals.Font = new Font("Segoe UI", 10F, FontStyle.Bold);
lblKpiWithdrawals.Location = new Point(802, 14);
lblKpiWithdrawals.Margin = new Padding(6, 7, 6, 7);
lblKpiWithdrawals.MinimumSize = new Size(213, 122);
lblKpiWithdrawals.Name = "lblKpiWithdrawals";
lblKpiWithdrawals.Padding = new Padding(11, 13, 11, 13);
lblKpiWithdrawals.Size = new Size(213, 122);
lblKpiWithdrawals.TabIndex = 3;
lblKpiWithdrawals.Text = "Auszahlungen\n—";
lblKpiWithdrawals.TextAlign = ContentAlignment.MiddleLeft;
//
// lblKpiFees
//
lblKpiFees.BorderStyle = BorderStyle.FixedSingle;
lblKpiFees.Font = new Font("Segoe UI", 10F, FontStyle.Bold);
lblKpiFees.Location = new Point(1027, 14);
lblKpiFees.Margin = new Padding(6, 7, 6, 7);
lblKpiFees.MinimumSize = new Size(199, 122);
lblKpiFees.Name = "lblKpiFees";
lblKpiFees.Padding = new Padding(11, 13, 11, 13);
lblKpiFees.Size = new Size(199, 122);
lblKpiFees.TabIndex = 4;
lblKpiFees.Text = "Fees\n—";
lblKpiFees.TextAlign = ContentAlignment.MiddleLeft;
//
// lblKpiRewards
//
lblKpiRewards.BorderStyle = BorderStyle.FixedSingle;
lblKpiRewards.Font = new Font("Segoe UI", 10F, FontStyle.Bold);
lblKpiRewards.Location = new Point(1238, 14);
lblKpiRewards.Margin = new Padding(6, 7, 6, 7);
lblKpiRewards.MinimumSize = new Size(199, 122);
lblKpiRewards.Name = "lblKpiRewards";
lblKpiRewards.Padding = new Padding(11, 13, 11, 13);
lblKpiRewards.Size = new Size(199, 122);
lblKpiRewards.TabIndex = 5;
lblKpiRewards.Text = "Rewards\n—";
lblKpiRewards.TextAlign = ContentAlignment.MiddleLeft;
//
// lblKpiVolume
//
lblKpiVolume.BorderStyle = BorderStyle.FixedSingle;
lblKpiVolume.Font = new Font("Segoe UI", 10F, FontStyle.Bold);
lblKpiVolume.Location = new Point(12, 150);
lblKpiVolume.Margin = new Padding(6, 7, 6, 7);
lblKpiVolume.MinimumSize = new Size(228, 122);
lblKpiVolume.Name = "lblKpiVolume";
lblKpiVolume.Padding = new Padding(11, 13, 11, 13);
lblKpiVolume.Size = new Size(228, 122);
lblKpiVolume.TabIndex = 6;
lblKpiVolume.Text = "Handelsvolumen\n—";
lblKpiVolume.TextAlign = ContentAlignment.MiddleLeft;
//
// lblKpiTrades
//
lblKpiTrades.BorderStyle = BorderStyle.FixedSingle;
lblKpiTrades.Font = new Font("Segoe UI", 10F, FontStyle.Bold);
lblKpiTrades.Location = new Point(252, 150);
lblKpiTrades.Margin = new Padding(6, 7, 6, 7);
lblKpiTrades.MinimumSize = new Size(171, 122);
lblKpiTrades.Name = "lblKpiTrades";
lblKpiTrades.Padding = new Padding(11, 13, 11, 13);
lblKpiTrades.Size = new Size(171, 122);
lblKpiTrades.TabIndex = 7;
lblKpiTrades.Text = "Trades\n—";
lblKpiTrades.TextAlign = ContentAlignment.MiddleLeft;
//
// pnlOverviewTop
//
pnlOverviewTop.Controls.Add(btnExportPdf);
pnlOverviewTop.Controls.Add(btnExportCsv);
pnlOverviewTop.Controls.Add(btnCalc);
pnlOverviewTop.Controls.Add(cbCurrency);
pnlOverviewTop.Controls.Add(lblWaehrung);
pnlOverviewTop.Controls.Add(dtTo);
pnlOverviewTop.Controls.Add(lblBis);
pnlOverviewTop.Controls.Add(dtFrom);
pnlOverviewTop.Controls.Add(lblVon);
pnlOverviewTop.Controls.Add(cbOvAccount);
pnlOverviewTop.Controls.Add(lblOvKonto);
pnlOverviewTop.Dock = DockStyle.Top;
pnlOverviewTop.Location = new Point(4, 5);
pnlOverviewTop.Margin = new Padding(4, 5, 4, 5);
pnlOverviewTop.Name = "pnlOverviewTop";
pnlOverviewTop.Size = new Size(1555, 60);
pnlOverviewTop.TabIndex = 0;
//
// btnExportPdf
//
btnExportPdf.Location = new Point(1334, 8);
btnExportPdf.Margin = new Padding(4, 5, 4, 5);
btnExportPdf.Name = "btnExportPdf";
btnExportPdf.Size = new Size(129, 43);
btnExportPdf.TabIndex = 10;
btnExportPdf.Text = "PDF-Export";
btnExportPdf.UseVisualStyleBackColor = true;
//
// btnExportCsv
//
btnExportCsv.Location = new Point(1197, 8);
btnExportCsv.Margin = new Padding(4, 5, 4, 5);
btnExportCsv.Name = "btnExportCsv";
btnExportCsv.Size = new Size(129, 43);
btnExportCsv.TabIndex = 9;
btnExportCsv.Text = "CSV-Export";
btnExportCsv.UseVisualStyleBackColor = true;
//
// btnCalc
//
btnCalc.Location = new Point(1046, 8);
btnCalc.Margin = new Padding(4, 5, 4, 5);
btnCalc.Name = "btnCalc";
btnCalc.Size = new Size(143, 43);
btnCalc.TabIndex = 8;
btnCalc.Text = "Berechnen";
btnCalc.UseVisualStyleBackColor = true;
//
// cbCurrency
//
cbCurrency.DropDownStyle = ComboBoxStyle.DropDownList;
cbCurrency.Items.AddRange(new object[] { "USDC", "USD", "EUR" });
cbCurrency.Location = new Point(921, 10);
cbCurrency.Margin = new Padding(4, 5, 4, 5);
cbCurrency.Name = "cbCurrency";
cbCurrency.Size = new Size(113, 33);
cbCurrency.TabIndex = 7;
//
// lblWaehrung
//
lblWaehrung.AutoSize = true;
lblWaehrung.Location = new Point(821, 15);
lblWaehrung.Margin = new Padding(4, 0, 4, 0);
lblWaehrung.Name = "lblWaehrung";
lblWaehrung.Size = new Size(88, 25);
lblWaehrung.TabIndex = 6;
lblWaehrung.Text = "Währung:";
//
// dtTo
//
dtTo.Format = DateTimePickerFormat.Short;
dtTo.Location = new Point(636, 10);
dtTo.Margin = new Padding(4, 5, 4, 5);
dtTo.Name = "dtTo";
dtTo.Size = new Size(155, 31);
dtTo.TabIndex = 5;
//
// lblBis
//
lblBis.AutoSize = true;
lblBis.Location = new Point(593, 15);
lblBis.Margin = new Padding(4, 0, 4, 0);
lblBis.Name = "lblBis";
lblBis.Size = new Size(38, 25);
lblBis.TabIndex = 4;
lblBis.Text = "Bis:";
//
// dtFrom
//
dtFrom.Format = DateTimePickerFormat.Short;
dtFrom.Location = new Point(429, 10);
dtFrom.Margin = new Padding(4, 5, 4, 5);
dtFrom.Name = "dtFrom";
dtFrom.Size = new Size(155, 31);
dtFrom.TabIndex = 3;
//
// lblVon
//
lblVon.AutoSize = true;
lblVon.Location = new Point(381, 15);
lblVon.Margin = new Padding(4, 0, 4, 0);
lblVon.Name = "lblVon";
lblVon.Size = new Size(47, 25);
lblVon.TabIndex = 2;
lblVon.Text = "Von:";
//
// cbOvAccount
//
cbOvAccount.DropDownStyle = ComboBoxStyle.DropDownList;
cbOvAccount.Location = new Point(79, 10);
cbOvAccount.Margin = new Padding(4, 5, 4, 5);
cbOvAccount.Name = "cbOvAccount";
cbOvAccount.Size = new Size(284, 33);
cbOvAccount.TabIndex = 1;
//
// lblOvKonto
//
lblOvKonto.AutoSize = true;
lblOvKonto.Location = new Point(9, 15);
lblOvKonto.Margin = new Padding(4, 0, 4, 0);
lblOvKonto.Name = "lblOvKonto";
lblOvKonto.Size = new Size(64, 25);
lblOvKonto.TabIndex = 0;
lblOvKonto.Text = "Konto:";
//
// tabLedger
//
tabLedger.Controls.Add(dgvLedger);
tabLedger.Controls.Add(toolStripLedger);
tabLedger.Location = new Point(4, 34);
tabLedger.Margin = new Padding(4, 5, 4, 5);
tabLedger.Name = "tabLedger";
tabLedger.Padding = new Padding(4, 5, 4, 5);
tabLedger.Size = new Size(1563, 992);
tabLedger.TabIndex = 1;
tabLedger.Text = "Ledger";
tabLedger.UseVisualStyleBackColor = true;
//
// dgvLedger
//
dgvLedger.AllowUserToAddRows = false;
dgvLedger.AllowUserToDeleteRows = false;
dgvLedger.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dgvLedger.Dock = DockStyle.Fill;
dgvLedger.Location = new Point(4, 39);
dgvLedger.Margin = new Padding(4, 5, 4, 5);
dgvLedger.Name = "dgvLedger";
dgvLedger.ReadOnly = true;
dgvLedger.RowHeadersVisible = false;
dgvLedger.RowHeadersWidth = 62;
dgvLedger.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dgvLedger.Size = new Size(1555, 948);
dgvLedger.TabIndex = 1;
//
// toolStripLedger
//
toolStripLedger.ImageScalingSize = new Size(24, 24);
toolStripLedger.Items.AddRange(new ToolStripItem[] { lblLedgerKonto, cbLedgerAccount, sepLedger, btnLedgerRefresh });
toolStripLedger.Location = new Point(4, 5);
toolStripLedger.Name = "toolStripLedger";
toolStripLedger.Padding = new Padding(0, 0, 3, 0);
toolStripLedger.Size = new Size(1555, 34);
toolStripLedger.TabIndex = 0;
//
// lblLedgerKonto
//
lblLedgerKonto.Name = "lblLedgerKonto";
lblLedgerKonto.Size = new Size(64, 29);
lblLedgerKonto.Text = "Konto:";
//
// cbLedgerAccount
//
cbLedgerAccount.DropDownStyle = ComboBoxStyle.DropDownList;
cbLedgerAccount.Name = "cbLedgerAccount";
cbLedgerAccount.Size = new Size(341, 34);
//
// sepLedger
//
sepLedger.Name = "sepLedger";
sepLedger.Size = new Size(6, 34);
//
// btnLedgerRefresh
//
btnLedgerRefresh.DisplayStyle = ToolStripItemDisplayStyle.Text;
btnLedgerRefresh.Name = "btnLedgerRefresh";
btnLedgerRefresh.Size = new Size(116, 29);
btnLedgerRefresh.Text = "Aktualisieren";
//
// tabStatus
//
tabStatus.Controls.Add(dgvRuns);
tabStatus.Controls.Add(toolStripStatus);
tabStatus.Location = new Point(4, 34);
tabStatus.Margin = new Padding(4, 5, 4, 5);
tabStatus.Name = "tabStatus";
tabStatus.Padding = new Padding(4, 5, 4, 5);
tabStatus.Size = new Size(1563, 992);
tabStatus.TabIndex = 2;
tabStatus.Text = "Abruf / Status";
tabStatus.UseVisualStyleBackColor = true;
//
// dgvRuns
//
dgvRuns.AllowUserToAddRows = false;
dgvRuns.AllowUserToDeleteRows = false;
dgvRuns.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dgvRuns.Dock = DockStyle.Fill;
dgvRuns.Location = new Point(4, 39);
dgvRuns.Margin = new Padding(4, 5, 4, 5);
dgvRuns.Name = "dgvRuns";
dgvRuns.ReadOnly = true;
dgvRuns.RowHeadersVisible = false;
dgvRuns.RowHeadersWidth = 62;
dgvRuns.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dgvRuns.Size = new Size(1555, 948);
dgvRuns.TabIndex = 1;
//
// toolStripStatus
//
toolStripStatus.ImageScalingSize = new Size(24, 24);
toolStripStatus.Items.AddRange(new ToolStripItem[] { lblStatusKonto, cbStatusAccount, sepStatus, btnIncremental, btnBackfill, sepStatus2, btnStatusRefresh });
toolStripStatus.Location = new Point(4, 5);
toolStripStatus.Name = "toolStripStatus";
toolStripStatus.Padding = new Padding(0, 0, 3, 0);
toolStripStatus.Size = new Size(1555, 34);
toolStripStatus.TabIndex = 0;
//
// lblStatusKonto
//
lblStatusKonto.Name = "lblStatusKonto";
lblStatusKonto.Size = new Size(64, 29);
lblStatusKonto.Text = "Konto:";
//
// cbStatusAccount
//
cbStatusAccount.DropDownStyle = ComboBoxStyle.DropDownList;
cbStatusAccount.Name = "cbStatusAccount";
cbStatusAccount.Size = new Size(341, 34);
//
// sepStatus
//
sepStatus.Name = "sepStatus";
sepStatus.Size = new Size(6, 34);
//
// btnIncremental
//
btnIncremental.DisplayStyle = ToolStripItemDisplayStyle.Text;
btnIncremental.Name = "btnIncremental";
btnIncremental.Size = new Size(134, 29);
btnIncremental.Text = "Inkrement jetzt";
btnIncremental.ToolTipText = "Nur neue Ereignisse seit dem letzten Stand abrufen (mit Lookback-Überlappung).";
//
// btnBackfill
//
btnBackfill.DisplayStyle = ToolStripItemDisplayStyle.Text;
btnBackfill.Name = "btnBackfill";
btnBackfill.Size = new Size(188, 29);
btnBackfill.Text = "Backfill (volle Historie)";
btnBackfill.ToolTipText = "Komplette Historie neu abrufen (idempotent bucht nichts doppelt).";
//
// sepStatus2
//
sepStatus2.Name = "sepStatus2";
sepStatus2.Size = new Size(6, 34);
//
// btnStatusRefresh
//
btnStatusRefresh.DisplayStyle = ToolStripItemDisplayStyle.Text;
btnStatusRefresh.Name = "btnStatusRefresh";
btnStatusRefresh.Size = new Size(116, 29);
btnStatusRefresh.Text = "Aktualisieren";
//
// lblAccStatus
//
lblAccStatus.Dock = DockStyle.Bottom;
lblAccStatus.Location = new Point(0, 1030);
lblAccStatus.Margin = new Padding(4, 0, 4, 0);
lblAccStatus.Name = "lblAccStatus";
lblAccStatus.Padding = new Padding(9, 3, 9, 3);
lblAccStatus.Size = new Size(1571, 37);
lblAccStatus.TabIndex = 1;
//
// AccountingMainForm
//
AutoScaleDimensions = new SizeF(10F, 25F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(1571, 1067);
Controls.Add(tabControlAcc);
Controls.Add(lblAccStatus);
Icon = (Icon)resources.GetObject("$this.Icon");
Margin = new Padding(4, 5, 4, 5);
Name = "AccountingMainForm";
StartPosition = FormStartPosition.CenterScreen;
Text = "Accounting";
tabControlAcc.ResumeLayout(false);
tabUebersicht.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)dgvMonthly).EndInit();
flpKpis.ResumeLayout(false);
pnlOverviewTop.ResumeLayout(false);
pnlOverviewTop.PerformLayout();
tabLedger.ResumeLayout(false);
tabLedger.PerformLayout();
((System.ComponentModel.ISupportInitialize)dgvLedger).EndInit();
toolStripLedger.ResumeLayout(false);
toolStripLedger.PerformLayout();
tabStatus.ResumeLayout(false);
tabStatus.PerformLayout();
((System.ComponentModel.ISupportInitialize)dgvRuns).EndInit();
toolStripStatus.ResumeLayout(false);
toolStripStatus.PerformLayout();
ResumeLayout(false);
}
#endregion
private System.Windows.Forms.TabControl tabControlAcc;
private System.Windows.Forms.TabPage tabUebersicht;
private System.Windows.Forms.Panel pnlOverviewTop;
private System.Windows.Forms.Label lblOvKonto;
private System.Windows.Forms.ComboBox cbOvAccount;
private System.Windows.Forms.Label lblVon;
private System.Windows.Forms.DateTimePicker dtFrom;
private System.Windows.Forms.Label lblBis;
private System.Windows.Forms.DateTimePicker dtTo;
private System.Windows.Forms.Label lblWaehrung;
private System.Windows.Forms.ComboBox cbCurrency;
private System.Windows.Forms.Button btnCalc;
private System.Windows.Forms.Button btnExportCsv;
private System.Windows.Forms.Button btnExportPdf;
private System.Windows.Forms.FlowLayoutPanel flpKpis;
private System.Windows.Forms.Label lblKpiNet;
private System.Windows.Forms.Label lblKpiClosing;
private System.Windows.Forms.Label lblKpiDeposits;
private System.Windows.Forms.Label lblKpiWithdrawals;
private System.Windows.Forms.Label lblKpiFees;
private System.Windows.Forms.Label lblKpiRewards;
private System.Windows.Forms.Label lblKpiVolume;
private System.Windows.Forms.Label lblKpiTrades;
private System.Windows.Forms.DataGridView dgvMonthly;
private System.Windows.Forms.TabPage tabLedger;
private System.Windows.Forms.ToolStrip toolStripLedger;
private System.Windows.Forms.ToolStripLabel lblLedgerKonto;
private System.Windows.Forms.ToolStripComboBox cbLedgerAccount;
private System.Windows.Forms.ToolStripSeparator sepLedger;
private System.Windows.Forms.ToolStripButton btnLedgerRefresh;
private System.Windows.Forms.DataGridView dgvLedger;
private System.Windows.Forms.TabPage tabStatus;
private System.Windows.Forms.ToolStrip toolStripStatus;
private System.Windows.Forms.ToolStripLabel lblStatusKonto;
private System.Windows.Forms.ToolStripComboBox cbStatusAccount;
private System.Windows.Forms.ToolStripSeparator sepStatus;
private System.Windows.Forms.ToolStripButton btnIncremental;
private System.Windows.Forms.ToolStripButton btnBackfill;
private System.Windows.Forms.ToolStripSeparator sepStatus2;
private System.Windows.Forms.ToolStripButton btnStatusRefresh;
private System.Windows.Forms.DataGridView dgvRuns;
private System.Windows.Forms.Label lblAccStatus;
}
}
@@ -1,322 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using Microsoft.Extensions.DependencyInjection;
using PolyTrader.Modules.Accounting.Logic;
using PolyTrader.Modules.Accounting.Persistence;
using PolyTrader.Modules.Accounting.Services;
using PolyTraderSharp;
namespace PolyTrader.Modules.Accounting.Ui
{
/// <summary>
/// Hauptfenster des Accounting-Moduls: Tab „Übersicht / BWA" (neutrale Periodenabrechnung + KPIs +
/// Monatsvergleich, Währung USDC/USD/EUR, CSV-Export), „Ledger" (Buchungssätze) und „Abruf / Status"
/// (Ingest-Läufe, manueller Backfill/Inkrement). Layout im Designer; Aggregation pur im
/// <see cref="AccountingEngine"/>. Die US-Steuerschicht (A-3) ist bewusst NICHT hier sie hängt an
/// den CPA-Antworten.
/// </summary>
public partial class AccountingMainForm : Form
{
private ILedgerRepository? _ledger;
private IIngestRunRepository? _runs;
private AccountingIngestService? _ingest;
private AccountingReportService? _report;
private TradingState? _state;
public AccountingMainForm()
{
InitializeComponent();
// Übersicht / BWA
btnCalc.Click += (_, _) => Recalculate();
btnExportCsv.Click += (_, _) => ExportCsv();
btnExportPdf.Click += (_, _) => ExportPdf();
cbOvAccount.SelectedIndexChanged += (_, _) => Recalculate();
cbCurrency.SelectedIndexChanged += (_, _) => Recalculate();
// Ledger + Status
btnLedgerRefresh.Click += (_, _) => LoadLedger();
cbLedgerAccount.SelectedIndexChanged += (_, _) => LoadLedger();
btnStatusRefresh.Click += (_, _) => LoadRuns();
btnIncremental.Click += async (_, _) => await RunIngestAsync(backfill: false);
btnBackfill.Click += async (_, _) => await RunIngestAsync(backfill: true);
}
public void Initialize(IServiceProvider services)
{
_ledger = services.GetRequiredService<ILedgerRepository>();
_runs = services.GetRequiredService<IIngestRunRepository>();
_ingest = services.GetRequiredService<AccountingIngestService>();
_report = services.GetRequiredService<AccountingReportService>();
_state = services.GetRequiredService<TradingState>();
// Standard-Zeitraum: laufender Monat.
var now = DateTime.Now;
dtFrom.Value = new DateTime(now.Year, now.Month, 1);
dtTo.Value = now;
if (cbCurrency.Items.Count > 0) cbCurrency.SelectedIndex = 0; // USDC
PopulateAccounts();
Recalculate();
LoadLedger();
LoadRuns();
}
// ---------------- Konten-Filter ----------------
private void PopulateAccounts()
{
var items = new List<AccountItem> { new(null, "Alle Live-Konten") };
if (_state != null)
items.AddRange(_state.Accounts.Values
.Where(a => !a.IsDemo && !string.IsNullOrWhiteSpace(a.WalletAddress))
.OrderBy(a => a.AccountId)
.Select(a => new AccountItem(a.AccountId, string.IsNullOrEmpty(a.Name) ? $"#{a.AccountId}" : $"{a.Name} (#{a.AccountId})")));
cbOvAccount.DisplayMember = nameof(AccountItem.Label);
cbOvAccount.DataSource = new List<AccountItem>(items);
foreach (var combo in new[] { cbLedgerAccount, cbStatusAccount })
{
combo.ComboBox.DisplayMember = nameof(AccountItem.Label);
combo.ComboBox.DataSource = new List<AccountItem>(items);
}
}
private static int? IdOf(object? item) => item is AccountItem it ? it.Id : null;
// ---------------- Übersicht / BWA ----------------
private void Recalculate()
{
if (_report == null) return;
try
{
DateTime from = dtFrom.Value.Date;
DateTime to = dtTo.Value.Date.AddDays(1).AddTicks(-1); // inklusive gewählter Bis-Tag
int? accId = IdOf(cbOvAccount.SelectedItem);
var statement = _report.BuildStatement(accId, from, to);
var monthly = _report.BuildMonthly(accId, from, to);
var cur = _report.ResolveCurrency(cbCurrency.SelectedItem?.ToString() ?? "USDC", to);
UpdateKpis(statement, cur);
dgvMonthly.DataSource = monthly.Select(m => new MonthlyRow(m, cur)).ToList();
lblAccStatus.Text = cur.Available
? $"Währung {cur.Code}: {cur.Note}"
: $"⚠️ {cur.Note} (Anzeige in USDC).";
}
catch (Exception ex)
{
lblAccStatus.Text = $"Abrechnung nicht möglich (acc_-Migration angewendet?): {ex.Message}";
}
}
private void UpdateKpis(PeriodStatement s, CurrencyContext cur)
{
CurrencyContext view = cur.Available ? cur : new CurrencyContext("USDC", 1m, true, "");
string unit = view.Code;
decimal V(decimal usdc) => AccountingReportService.Convert(usdc, view);
lblKpiNet.Text = $"Netto-Handelsergebnis\n{V(s.NetTradingResultUsdc):N2} {unit}";
lblKpiNet.ForeColor = s.NetTradingResultUsdc >= 0 ? System.Drawing.Color.ForestGreen : System.Drawing.Color.Firebrick;
lblKpiClosing.Text = $"Endsaldo\n{V(s.ClosingBalanceUsdc):N2} {unit}";
lblKpiDeposits.Text = $"Einzahlungen\n{V(s.Deposits):N2} {unit}";
lblKpiWithdrawals.Text = $"Auszahlungen\n{V(s.Withdrawals):N2} {unit}";
lblKpiFees.Text = $"Fees\n{V(s.Fees):N2} {unit}";
lblKpiRewards.Text = $"Rewards\n{V(s.Rewards):N2} {unit}";
lblKpiVolume.Text = $"Handelsvolumen\n{V(s.TradeVolume):N2} {unit}";
lblKpiTrades.Text = $"Trades\n{s.TradeCount}";
}
private void ExportCsv()
{
if (_ledger == null || _report == null) return;
DateTime from = dtFrom.Value.Date;
DateTime to = dtTo.Value.Date.AddDays(1).AddTicks(-1);
int? accId = IdOf(cbOvAccount.SelectedItem);
using var dlg = new SaveFileDialog
{
Filter = "CSV-Datei (*.csv)|*.csv",
FileName = $"accounting_{(accId?.ToString() ?? "alle")}_{from:yyyyMMdd}-{dtTo.Value:yyyyMMdd}.csv"
};
if (dlg.ShowDialog(this) != DialogResult.OK) return;
try
{
var statement = _report.BuildStatement(accId, from, to);
var entries = _ledger.Query(accId, from, to, 100000);
string csv = CsvExporter.Statement(statement) + Environment.NewLine + CsvExporter.Ledger(entries);
System.IO.File.WriteAllText(dlg.FileName, csv, new System.Text.UTF8Encoding(true));
lblAccStatus.Text = $"CSV exportiert: {dlg.FileName} ({entries.Count} Buchungen).";
}
catch (Exception ex)
{
lblAccStatus.Text = $"CSV-Export fehlgeschlagen: {ex.Message}";
}
}
private void ExportPdf()
{
if (_ledger == null || _report == null) return;
DateTime from = dtFrom.Value.Date;
DateTime to = dtTo.Value.Date.AddDays(1).AddTicks(-1);
int? accId = IdOf(cbOvAccount.SelectedItem);
using var dlg = new SaveFileDialog
{
Filter = "PDF-Datei (*.pdf)|*.pdf",
FileName = $"abrechnung_{(accId?.ToString() ?? "alle")}_{from:yyyyMMdd}-{dtTo.Value:yyyyMMdd}.pdf"
};
if (dlg.ShowDialog(this) != DialogResult.OK) return;
try
{
var statement = _report.BuildStatement(accId, from, to);
var monthly = _report.BuildMonthly(accId, from, to);
var entries = _ledger.Query(accId, from, to, 100000);
var cur = _report.ResolveCurrency(cbCurrency.SelectedItem?.ToString() ?? "USDC", to);
CurrencyContext view = cur.Available ? cur : new CurrencyContext("USDC", 1m, true, "Native Buchungswährung.");
byte[] pdf = PdfExporter.Render(statement, monthly, entries, view.Code, view.Factor, view.Note);
System.IO.File.WriteAllBytes(dlg.FileName, pdf);
lblAccStatus.Text = $"PDF exportiert: {dlg.FileName} ({entries.Count} Buchungen, {view.Code}).";
}
catch (Exception ex)
{
lblAccStatus.Text = $"PDF-Export fehlgeschlagen: {ex.Message}";
}
}
// ---------------- Ledger ----------------
private void LoadLedger()
{
if (_ledger == null) return;
try
{
var rows = _ledger.Query(IdOf(cbLedgerAccount.SelectedItem), null, null, 1000);
dgvLedger.DataSource = rows.Select(e => new LedgerRow(e)).ToList();
lblAccStatus.Text = rows.Count == 0
? "Noch keine Buchungen. (Ingest-Quellen sind offline bis zur Live-Anbindung im Zielland.)"
: $"{rows.Count} Buchungssätze angezeigt.";
}
catch (Exception ex)
{
lblAccStatus.Text = $"Ledger nicht lesbar (acc_-Migration angewendet?): {ex.Message}";
}
}
// ---------------- Abruf / Status ----------------
private void LoadRuns()
{
if (_runs == null) return;
try
{
dgvRuns.DataSource = _runs.GetRecent(IdOf(cbStatusAccount.SelectedItem), 100);
}
catch (Exception ex)
{
lblAccStatus.Text = $"Ingest-Läufe nicht lesbar: {ex.Message}";
}
}
private async System.Threading.Tasks.Task RunIngestAsync(bool backfill)
{
if (_ingest == null) return;
btnBackfill.Enabled = false;
btnIncremental.Enabled = false;
lblAccStatus.Text = backfill ? "Backfill läuft …" : "Inkrementeller Abruf läuft …";
try
{
await System.Threading.Tasks.Task.Run(() => _ingest.IngestAllAsync(backfill, CancellationToken.None));
lblAccStatus.Text = "Abruf abgeschlossen.";
LoadRuns();
LoadLedger();
Recalculate();
}
catch (Exception ex)
{
lblAccStatus.Text = $"Abruf-Fehler: {ex.Message}";
}
finally
{
btnBackfill.Enabled = true;
btnIncremental.Enabled = true;
}
}
private sealed record AccountItem(int? Id, string Label);
/// <summary>Anzeige-Zeile des Monatsvergleichs (Beträge in der gewählten Währung).</summary>
private sealed class MonthlyRow
{
public MonthlyRow(PeriodStatement m, CurrencyContext cur)
{
CurrencyContext view = cur.Available ? cur : new CurrencyContext("USDC", 1m, true, "");
decimal V(decimal usdc) => AccountingReportService.Convert(usdc, view);
Monat = m.From.ToString("yyyy-MM");
Anfangssaldo = V(m.OpeningBalanceUsdc);
Einzahlungen = V(m.Deposits);
Auszahlungen = V(m.Withdrawals);
Handelsvolumen = V(m.TradeVolume);
Rewards = V(m.Rewards);
Fees = V(m.Fees);
Handelsergebnis = V(m.NetTradingResultUsdc);
Endsaldo = V(m.ClosingBalanceUsdc);
Trades = m.TradeCount;
}
public string Monat { get; }
public decimal Anfangssaldo { get; }
public decimal Einzahlungen { get; }
public decimal Auszahlungen { get; }
public decimal Handelsvolumen { get; }
public decimal Rewards { get; }
public decimal Fees { get; }
public decimal Handelsergebnis { get; }
public decimal Endsaldo { get; }
public int Trades { get; }
}
/// <summary>Anzeige-Zeile fürs Ledger-Grid.</summary>
private sealed class LedgerRow
{
public LedgerRow(Models.LedgerEntry e)
{
Zeit = e.Timestamp;
Konto = e.AccountId;
Typ = e.EventType.ToString();
Markt = e.MarketSlug;
Outcome = e.Outcome;
Side = e.Side;
Size = e.Size;
Preis = e.PriceUsdc;
Brutto = e.GrossUsdc;
Fee = e.FeeUsdc;
Netto = e.NetUsdc;
TxHash = e.TxHash;
Quelle = e.Source;
}
public DateTime Zeit { get; }
public int Konto { get; }
public string Typ { get; }
public string Markt { get; }
public string Outcome { get; }
public string Side { get; }
public decimal Size { get; }
public decimal Preis { get; }
public decimal Brutto { get; }
public decimal Fee { get; }
public decimal Netto { get; }
public string TxHash { get; }
public string Quelle { get; }
}
}
}
@@ -1,202 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="toolStripLedger.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="toolStripStatus.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>206, 17</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
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
AABJRU5ErkJggg==
</value>
</data>
</root>
@@ -10,7 +10,6 @@ using PolyTrader.Core.Modularity;
using PolyTrader.Core.Streaming; using PolyTrader.Core.Streaming;
using PolyTrader.Modules.CopyTrading.Persistence; using PolyTrader.Modules.CopyTrading.Persistence;
using PolyTrader.Modules.CopyTrading.Persistence.Ef; using PolyTrader.Modules.CopyTrading.Persistence.Ef;
using PolyTrader.Modules.CopyTrading.Ui;
using PolyTraderSharp; using PolyTraderSharp;
using PolyTraderSharp.Models; using PolyTraderSharp.Models;
using PolyTraderSharp.Services; using PolyTraderSharp.Services;
@@ -81,24 +80,22 @@ namespace PolyTrader.Modules.CopyTrading
services.AddHostedService(sp => sp.GetRequiredService<SellLadderService>()); services.AddHostedService(sp => sp.GetRequiredService<SellLadderService>());
} }
/// <summary>
/// Aktuell ohne Ansicht: Die WinForms-UI wurde mit der Linux-Portierung entfernt, die
/// Avalonia-Ansicht folgt (Stufe L3/L4). Die Fachlogik dieses Moduls laeuft davon
/// unabhaengig weiter die Shell zeigt schlicht kein Fenster fuer das Modul an.
///
/// <para><b>Beim Nachbau zu erhalten</b> (Spezifikation: docs/UI-SPEZIFIKATION-WinForms.md,
/// Originalcode: Git-Tag <c>winforms-final</c>):</para>
/// <list type="bullet">
/// <item>View-ID <c>copytrading.main</c> (stabil Launcher-Button und Symbol haengen daran)</item>
/// <item>Titel <c>Copytrading</c>, Gruppe <c>CopyTrading</c>, Order <c>100</c></item>
/// <item>Ein Fenster fuers ganze Modul: CopyTradingMainForm mit Tabs: Master-Trader / Offene Trades / Geschlossene Trades / Account-Einstellungen</item>
/// </list>
/// </summary>
public void RegisterUi(IModuleUiHost host, IServiceProvider services) public void RegisterUi(IModuleUiHost host, IServiceProvider services)
{ {
// EIN Fenster fürs ganze Modul: die einzelnen Ansichten sind Tabs im // Bewusst leer, bis die Avalonia-Ansicht steht (siehe Doku oben).
// CopyTradingMainForm (Master-Trader / geschlossene Trades / Account-Einstellungen).
// So bleibt der Launcher schlank ein Button je Modul statt je View.
host.RegisterView(new ModuleView
{
Id = "copytrading.main",
Title = "Copytrading",
Group = "CopyTrading",
Order = 100,
CreateView = () =>
{
var form = new CopyTradingMainForm();
form.Initialize(services);
return form;
}
});
} }
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask; public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\PolyTrader.Core\PolyTrader.Core.csproj" /> <ProjectReference Include="..\PolyTrader.Core\PolyTrader.Core.csproj" />
@@ -20,11 +20,12 @@
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework> <!-- Linux-Portierung: plattformneutral. Die WinForms-UI dieses Moduls (Trading-Engine, SELL-Leiter, Master-Trader-Analytics)
wurde entfernt; die Avalonia-Ansicht kommt spaeter direkt hierher zurueck Avalonia ist
im Gegensatz zu WinForms plattformuebergreifend und braucht kein eigenes UI-Projekt. -->
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<!-- Modul trägt später eigene WinForms-UI-Tabs bei. -->
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup> </PropertyGroup>
</Project> </Project>
@@ -1,126 +0,0 @@
namespace PolyTrader.Modules.CopyTrading.Ui
{
partial class AccountSettingsView
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Vom Komponenten-Designer generierter Code
private void InitializeComponent()
{
this.toolbar = new System.Windows.Forms.ToolStrip();
this.tsAccountLabel = new System.Windows.Forms.ToolStripLabel();
this.tsAccounts = new System.Windows.Forms.ToolStripComboBox();
this.tsSep = new System.Windows.Forms.ToolStripSeparator();
this.tsSave = new System.Windows.Forms.ToolStripButton();
this.pgSettings = new System.Windows.Forms.PropertyGrid();
this.statusPanel = new System.Windows.Forms.Panel();
this.lblHint = new System.Windows.Forms.Label();
this.toolbar.SuspendLayout();
this.statusPanel.SuspendLayout();
this.SuspendLayout();
//
// toolbar
//
this.toolbar.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden;
this.toolbar.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.tsAccountLabel,
this.tsAccounts,
this.tsSep,
this.tsSave});
this.toolbar.Location = new System.Drawing.Point(0, 0);
this.toolbar.Name = "toolbar";
this.toolbar.Size = new System.Drawing.Size(560, 25);
this.toolbar.TabIndex = 0;
//
// tsAccountLabel
//
this.tsAccountLabel.Name = "tsAccountLabel";
this.tsAccountLabel.Size = new System.Drawing.Size(56, 22);
this.tsAccountLabel.Text = "Account:";
//
// tsAccounts
//
this.tsAccounts.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.tsAccounts.Name = "tsAccounts";
this.tsAccounts.Size = new System.Drawing.Size(320, 25);
//
// tsSep
//
this.tsSep.Name = "tsSep";
this.tsSep.Size = new System.Drawing.Size(6, 25);
//
// tsSave
//
this.tsSave.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.tsSave.Name = "tsSave";
this.tsSave.Size = new System.Drawing.Size(69, 22);
this.tsSave.Text = "Speichern";
//
// pgSettings
//
this.pgSettings.Dock = System.Windows.Forms.DockStyle.Fill;
this.pgSettings.Location = new System.Drawing.Point(0, 25);
this.pgSettings.Name = "pgSettings";
this.pgSettings.PropertySort = System.Windows.Forms.PropertySort.Categorized;
this.pgSettings.Size = new System.Drawing.Size(560, 549);
this.pgSettings.TabIndex = 1;
this.pgSettings.ToolbarVisible = false;
//
// statusPanel
//
this.statusPanel.Controls.Add(this.lblHint);
this.statusPanel.Dock = System.Windows.Forms.DockStyle.Bottom;
this.statusPanel.Location = new System.Drawing.Point(0, 574);
this.statusPanel.Name = "statusPanel";
this.statusPanel.Size = new System.Drawing.Size(560, 26);
this.statusPanel.TabIndex = 2;
//
// lblHint
//
this.lblHint.Dock = System.Windows.Forms.DockStyle.Fill;
this.lblHint.ForeColor = System.Drawing.Color.DimGray;
this.lblHint.Location = new System.Drawing.Point(0, 0);
this.lblHint.Name = "lblHint";
this.lblHint.Padding = new System.Windows.Forms.Padding(8, 0, 0, 0);
this.lblHint.Size = new System.Drawing.Size(560, 26);
this.lblHint.TabIndex = 0;
this.lblHint.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// AccountSettingsView
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.pgSettings);
this.Controls.Add(this.statusPanel);
this.Controls.Add(this.toolbar);
this.Name = "AccountSettingsView";
this.Size = new System.Drawing.Size(560, 600);
this.toolbar.ResumeLayout(false);
this.toolbar.PerformLayout();
this.statusPanel.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.ToolStrip toolbar;
private System.Windows.Forms.ToolStripLabel tsAccountLabel;
private System.Windows.Forms.ToolStripComboBox tsAccounts;
private System.Windows.Forms.ToolStripSeparator tsSep;
private System.Windows.Forms.ToolStripButton tsSave;
private System.Windows.Forms.PropertyGrid pgSettings;
private System.Windows.Forms.Panel statusPanel;
private System.Windows.Forms.Label lblHint;
}
}
@@ -1,77 +0,0 @@
using System;
using System.Linq;
using System.Windows.Forms;
using PolyTrader.Modules.CopyTrading.Persistence;
using PolyTraderSharp;
using PolyTraderSharp.Models;
namespace PolyTrader.Modules.CopyTrading.Ui
{
/// <summary>
/// Bearbeitet die copytrading-spezifischen Detail-Einstellungen je Account
/// (Investment-/Zeit-Limits, <see cref="CopyTradingAccountSettings"/>). Persistiert über
/// das Repo und aktualisiert den Hot-Path-State (<see cref="CopyTradingState.AccountSettings"/>).
/// Layout im Designer (AccountSettingsView.Designer.cs), Daten/Logik hier.
/// </summary>
public partial class AccountSettingsView : UserControl
{
private ICopyTradingAccountSettingsRepository? _repo;
private TradingState? _state;
private CopyTradingState? _copyState;
private CopyTradingAccountSettings? _current;
// Parameterloser Konstruktor für den WinForms-Designer.
public AccountSettingsView()
{
InitializeComponent();
tsAccounts.SelectedIndexChanged += (_, _) => LoadSelected();
tsSave.Click += (_, _) => Save();
}
/// <summary>Injiziert die Abhängigkeiten (nach der DI-Auflösung) und füllt die Auswahl.</summary>
public void Initialize(ICopyTradingAccountSettingsRepository repo, TradingState state, CopyTradingState copyState)
{
_repo = repo;
_state = state;
_copyState = copyState;
PopulateAccounts();
}
private void PopulateAccounts()
{
if (_state == null) return;
var items = _state.Accounts.Values
.OrderBy(a => a.AccountId)
.Select(a => new AccountItem(a.AccountId, string.IsNullOrEmpty(a.Name) ? $"#{a.AccountId}" : $"{a.Name} (#{a.AccountId}){(a.IsDemo ? " · Demo" : "")}"))
.ToList();
tsAccounts.ComboBox.DisplayMember = nameof(AccountItem.Label);
tsAccounts.ComboBox.ValueMember = nameof(AccountItem.Id);
tsAccounts.ComboBox.DataSource = items;
if (items.Count > 0) LoadSelected();
else lblHint.Text = "Keine Accounts vorhanden.";
}
private void LoadSelected()
{
if (_repo == null || tsAccounts.SelectedItem is not AccountItem item) return;
_current = _repo.Get(item.Id) ?? new CopyTradingAccountSettings { AccountId = item.Id };
pgSettings.SelectedObject = _current;
lblHint.Text = $"Einstellungen für Account #{item.Id}.";
}
private void Save()
{
if (_current == null || _repo == null || _copyState == null) return;
_repo.Upsert(_current);
_copyState.AccountSettings[_current.AccountId] = _current;
lblHint.Text = $"Gespeichert für Account #{_current.AccountId} um {DateTime.Now:HH:mm:ss}.";
}
private sealed record AccountItem(int Id, string Label);
}
}
@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
@@ -1,392 +0,0 @@
namespace PolyTrader.Modules.CopyTrading.Ui
{
partial class ClosedTradesView
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Vom Komponenten-Designer generierter Code
private void InitializeComponent()
{
this.toolbar = new System.Windows.Forms.ToolStrip();
this.tsRefresh = new System.Windows.Forms.ToolStripButton();
this.dgvTrades = new System.Windows.Forms.DataGridView();
this.colTradeId = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colAccount = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colTrader = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colMarket = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colOutcome = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colSide = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colEntry = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colExit = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colSize = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colPnl = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colPnlPct = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colOpenedAt = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colClosedAt = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.statusPanel = new System.Windows.Forms.Panel();
this.lblSummary = new System.Windows.Forms.Label();
this.pnlFilters = new System.Windows.Forms.Panel();
this.lblMarkt = new System.Windows.Forms.Label();
this.tbMarket = new System.Windows.Forms.TextBox();
this.lblMaster = new System.Windows.Forms.Label();
this.cbMaster = new System.Windows.Forms.ComboBox();
this.lblErgebnis = new System.Windows.Forms.Label();
this.cbResult = new System.Windows.Forms.ComboBox();
this.lblVon = new System.Windows.Forms.Label();
this.dtFrom = new System.Windows.Forms.DateTimePicker();
this.lblBis = new System.Windows.Forms.Label();
this.dtTo = new System.Windows.Forms.DateTimePicker();
this.btnReset = new System.Windows.Forms.Button();
this.toolbar.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dgvTrades)).BeginInit();
this.statusPanel.SuspendLayout();
this.pnlFilters.SuspendLayout();
this.SuspendLayout();
//
// toolbar
//
this.toolbar.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden;
this.toolbar.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.tsRefresh});
this.toolbar.Location = new System.Drawing.Point(0, 0);
this.toolbar.Name = "toolbar";
this.toolbar.Size = new System.Drawing.Size(1100, 25);
this.toolbar.TabIndex = 0;
//
// tsRefresh
//
this.tsRefresh.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.tsRefresh.Name = "tsRefresh";
this.tsRefresh.Size = new System.Drawing.Size(90, 22);
this.tsRefresh.Text = "Aktualisieren";
//
// dgvTrades
//
this.dgvTrades.AllowUserToAddRows = false;
this.dgvTrades.AllowUserToDeleteRows = false;
this.dgvTrades.AutoGenerateColumns = false;
this.dgvTrades.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dgvTrades.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.colTradeId,
this.colAccount,
this.colTrader,
this.colMarket,
this.colOutcome,
this.colSide,
this.colEntry,
this.colExit,
this.colSize,
this.colPnl,
this.colPnlPct,
this.colOpenedAt,
this.colClosedAt});
this.dgvTrades.Dock = System.Windows.Forms.DockStyle.Fill;
this.dgvTrades.Location = new System.Drawing.Point(0, 25);
this.dgvTrades.Name = "dgvTrades";
this.dgvTrades.ReadOnly = true;
this.dgvTrades.RowHeadersVisible = false;
this.dgvTrades.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
this.dgvTrades.Size = new System.Drawing.Size(1100, 549);
this.dgvTrades.TabIndex = 1;
//
// colTradeId
//
this.colTradeId.DataPropertyName = "TradeId";
this.colTradeId.HeaderText = "#";
this.colTradeId.Name = "colTradeId";
this.colTradeId.ReadOnly = true;
this.colTradeId.Width = 60;
//
// colAccount
//
this.colAccount.DataPropertyName = "AccountName";
this.colAccount.HeaderText = "Account";
this.colAccount.Name = "colAccount";
this.colAccount.ReadOnly = true;
this.colAccount.Width = 130;
//
// colTrader
//
this.colTrader.DataPropertyName = "SourceTraderName";
this.colTrader.HeaderText = "Master-Trader";
this.colTrader.Name = "colTrader";
this.colTrader.ReadOnly = true;
this.colTrader.Width = 130;
//
// colMarket
//
this.colMarket.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
this.colMarket.DataPropertyName = "MarketQuestion";
this.colMarket.HeaderText = "Markt";
this.colMarket.Name = "colMarket";
this.colMarket.ReadOnly = true;
//
// colOutcome
//
this.colOutcome.DataPropertyName = "Outcome";
this.colOutcome.HeaderText = "Outcome";
this.colOutcome.Name = "colOutcome";
this.colOutcome.ReadOnly = true;
this.colOutcome.Width = 80;
//
// colSide
//
this.colSide.DataPropertyName = "Side";
this.colSide.HeaderText = "Side";
this.colSide.Name = "colSide";
this.colSide.ReadOnly = true;
this.colSide.Width = 60;
//
// colEntry
//
this.colEntry.DataPropertyName = "EntryPrice";
this.colEntry.HeaderText = "Entry";
this.colEntry.Name = "colEntry";
this.colEntry.ReadOnly = true;
this.colEntry.Width = 70;
//
// colExit
//
this.colExit.DataPropertyName = "ExitPrice";
this.colExit.HeaderText = "Exit";
this.colExit.Name = "colExit";
this.colExit.ReadOnly = true;
this.colExit.Width = 70;
//
// colSize
//
this.colSize.DataPropertyName = "Size";
this.colSize.HeaderText = "Size";
this.colSize.Name = "colSize";
this.colSize.ReadOnly = true;
this.colSize.Width = 70;
//
// colPnl
//
this.colPnl.DataPropertyName = "RealizedPnl";
this.colPnl.HeaderText = "PnL";
this.colPnl.Name = "colPnl";
this.colPnl.ReadOnly = true;
this.colPnl.Width = 80;
//
// colPnlPct
//
this.colPnlPct.DataPropertyName = "PnlPercent";
this.colPnlPct.HeaderText = "PnL %";
this.colPnlPct.Name = "colPnlPct";
this.colPnlPct.ReadOnly = true;
this.colPnlPct.Width = 70;
//
// colOpenedAt
//
this.colOpenedAt.DataPropertyName = "OpenedAt";
this.colOpenedAt.HeaderText = "Eröffnet";
this.colOpenedAt.Name = "colOpenedAt";
this.colOpenedAt.ReadOnly = true;
this.colOpenedAt.Width = 130;
//
// colClosedAt
//
this.colClosedAt.DataPropertyName = "ClosedAt";
this.colClosedAt.HeaderText = "Geschlossen";
this.colClosedAt.Name = "colClosedAt";
this.colClosedAt.ReadOnly = true;
this.colClosedAt.Width = 130;
//
// statusPanel
//
this.statusPanel.Controls.Add(this.lblSummary);
this.statusPanel.Dock = System.Windows.Forms.DockStyle.Bottom;
this.statusPanel.Location = new System.Drawing.Point(0, 574);
this.statusPanel.Name = "statusPanel";
this.statusPanel.Size = new System.Drawing.Size(1100, 26);
this.statusPanel.TabIndex = 2;
//
// lblSummary
//
this.lblSummary.Dock = System.Windows.Forms.DockStyle.Fill;
this.lblSummary.Location = new System.Drawing.Point(0, 0);
this.lblSummary.Name = "lblSummary";
this.lblSummary.Padding = new System.Windows.Forms.Padding(8, 0, 0, 0);
this.lblSummary.Size = new System.Drawing.Size(1100, 26);
this.lblSummary.TabIndex = 0;
this.lblSummary.Text = "—";
this.lblSummary.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// pnlFilters
//
this.pnlFilters.Controls.Add(this.lblMarkt);
this.pnlFilters.Controls.Add(this.tbMarket);
this.pnlFilters.Controls.Add(this.lblMaster);
this.pnlFilters.Controls.Add(this.cbMaster);
this.pnlFilters.Controls.Add(this.lblErgebnis);
this.pnlFilters.Controls.Add(this.cbResult);
this.pnlFilters.Controls.Add(this.lblVon);
this.pnlFilters.Controls.Add(this.dtFrom);
this.pnlFilters.Controls.Add(this.lblBis);
this.pnlFilters.Controls.Add(this.dtTo);
this.pnlFilters.Controls.Add(this.btnReset);
this.pnlFilters.Dock = System.Windows.Forms.DockStyle.Top;
this.pnlFilters.Location = new System.Drawing.Point(0, 25);
this.pnlFilters.Name = "pnlFilters";
this.pnlFilters.Size = new System.Drawing.Size(1100, 34);
this.pnlFilters.TabIndex = 3;
//
// lblMarkt
//
this.lblMarkt.AutoSize = true;
this.lblMarkt.Location = new System.Drawing.Point(6, 9);
this.lblMarkt.Name = "lblMarkt";
this.lblMarkt.Size = new System.Drawing.Size(41, 15);
this.lblMarkt.Text = "Markt:";
//
// tbMarket
//
this.tbMarket.Location = new System.Drawing.Point(51, 6);
this.tbMarket.Name = "tbMarket";
this.tbMarket.PlaceholderText = "Suchtext …";
this.tbMarket.Size = new System.Drawing.Size(200, 23);
this.tbMarket.TabIndex = 0;
//
// lblMaster
//
this.lblMaster.AutoSize = true;
this.lblMaster.Location = new System.Drawing.Point(263, 9);
this.lblMaster.Name = "lblMaster";
this.lblMaster.Size = new System.Drawing.Size(48, 15);
this.lblMaster.Text = "Master:";
//
// cbMaster
//
this.cbMaster.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cbMaster.Location = new System.Drawing.Point(315, 6);
this.cbMaster.Name = "cbMaster";
this.cbMaster.Size = new System.Drawing.Size(170, 23);
this.cbMaster.TabIndex = 1;
//
// lblErgebnis
//
this.lblErgebnis.AutoSize = true;
this.lblErgebnis.Location = new System.Drawing.Point(497, 9);
this.lblErgebnis.Name = "lblErgebnis";
this.lblErgebnis.Size = new System.Drawing.Size(58, 15);
this.lblErgebnis.Text = "Ergebnis:";
//
// cbResult
//
this.cbResult.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cbResult.Items.AddRange(new object[] { "Alle", "Gewinner", "Verlierer" });
this.cbResult.Location = new System.Drawing.Point(559, 6);
this.cbResult.Name = "cbResult";
this.cbResult.Size = new System.Drawing.Size(110, 23);
this.cbResult.TabIndex = 2;
//
// lblVon
//
this.lblVon.AutoSize = true;
this.lblVon.Location = new System.Drawing.Point(681, 9);
this.lblVon.Name = "lblVon";
this.lblVon.Size = new System.Drawing.Size(31, 15);
this.lblVon.Text = "Von:";
//
// dtFrom
//
this.dtFrom.Checked = false;
this.dtFrom.Format = System.Windows.Forms.DateTimePickerFormat.Short;
this.dtFrom.Location = new System.Drawing.Point(714, 6);
this.dtFrom.Name = "dtFrom";
this.dtFrom.ShowCheckBox = true;
this.dtFrom.Size = new System.Drawing.Size(120, 23);
this.dtFrom.TabIndex = 3;
//
// lblBis
//
this.lblBis.AutoSize = true;
this.lblBis.Location = new System.Drawing.Point(840, 9);
this.lblBis.Name = "lblBis";
this.lblBis.Size = new System.Drawing.Size(26, 15);
this.lblBis.Text = "Bis:";
//
// dtTo
//
this.dtTo.Checked = false;
this.dtTo.Format = System.Windows.Forms.DateTimePickerFormat.Short;
this.dtTo.Location = new System.Drawing.Point(868, 6);
this.dtTo.Name = "dtTo";
this.dtTo.ShowCheckBox = true;
this.dtTo.Size = new System.Drawing.Size(120, 23);
this.dtTo.TabIndex = 4;
//
// btnReset
//
this.btnReset.Location = new System.Drawing.Point(994, 5);
this.btnReset.Name = "btnReset";
this.btnReset.Size = new System.Drawing.Size(90, 25);
this.btnReset.TabIndex = 5;
this.btnReset.Text = "Zurücksetzen";
this.btnReset.UseVisualStyleBackColor = true;
//
// ClosedTradesView
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.dgvTrades);
this.Controls.Add(this.statusPanel);
this.Controls.Add(this.pnlFilters);
this.Controls.Add(this.toolbar);
this.Name = "ClosedTradesView";
this.Size = new System.Drawing.Size(1100, 600);
this.toolbar.ResumeLayout(false);
this.toolbar.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.dgvTrades)).EndInit();
this.statusPanel.ResumeLayout(false);
this.pnlFilters.ResumeLayout(false);
this.pnlFilters.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.ToolStrip toolbar;
private System.Windows.Forms.ToolStripButton tsRefresh;
private System.Windows.Forms.DataGridView dgvTrades;
private System.Windows.Forms.DataGridViewTextBoxColumn colTradeId;
private System.Windows.Forms.DataGridViewTextBoxColumn colAccount;
private System.Windows.Forms.DataGridViewTextBoxColumn colTrader;
private System.Windows.Forms.DataGridViewTextBoxColumn colMarket;
private System.Windows.Forms.DataGridViewTextBoxColumn colOutcome;
private System.Windows.Forms.DataGridViewTextBoxColumn colSide;
private System.Windows.Forms.DataGridViewTextBoxColumn colEntry;
private System.Windows.Forms.DataGridViewTextBoxColumn colExit;
private System.Windows.Forms.DataGridViewTextBoxColumn colSize;
private System.Windows.Forms.DataGridViewTextBoxColumn colPnl;
private System.Windows.Forms.DataGridViewTextBoxColumn colPnlPct;
private System.Windows.Forms.DataGridViewTextBoxColumn colOpenedAt;
private System.Windows.Forms.DataGridViewTextBoxColumn colClosedAt;
private System.Windows.Forms.Panel statusPanel;
private System.Windows.Forms.Label lblSummary;
private System.Windows.Forms.Panel pnlFilters;
private System.Windows.Forms.Label lblMarkt;
private System.Windows.Forms.TextBox tbMarket;
private System.Windows.Forms.Label lblMaster;
private System.Windows.Forms.ComboBox cbMaster;
private System.Windows.Forms.Label lblErgebnis;
private System.Windows.Forms.ComboBox cbResult;
private System.Windows.Forms.Label lblVon;
private System.Windows.Forms.DateTimePicker dtFrom;
private System.Windows.Forms.Label lblBis;
private System.Windows.Forms.DateTimePicker dtTo;
private System.Windows.Forms.Button btnReset;
}
}
@@ -1,177 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;
using PolyTrader.Modules.CopyTrading.Logic;
using PolyTrader.Modules.CopyTrading.Persistence;
using PolyTraderSharp;
using PolyTraderSharp.Models;
namespace PolyTrader.Modules.CopyTrading.Ui
{
/// <summary>
/// Zeigt die geschlossenen Copytrades des Moduls (mod_copytrading_closed_trades) mit
/// aufgelösten Account-/Master-Trader-Namen und einer Kurzauswertung.
/// Layout im Designer (ClosedTradesView.Designer.cs), Daten/Logik hier.
/// </summary>
public partial class ClosedTradesView : UserControl
{
private ICopyTradeLogRepository? _tradeLog;
private TradingState? _state;
private CopyTradingState? _copyState;
private List<ClosedTradeRow> _allRows = new();
private bool _loading;
// Parameterloser Konstruktor für den WinForms-Designer.
public ClosedTradesView()
{
InitializeComponent();
colEntry.DefaultCellStyle.Format = "F3";
colExit.DefaultCellStyle.Format = "F3";
colSize.DefaultCellStyle.Format = "F2";
colPnl.DefaultCellStyle.Format = "F2";
colPnlPct.DefaultCellStyle.Format = "F1";
colOpenedAt.DefaultCellStyle.Format = "dd.MM.yyyy HH:mm";
colClosedAt.DefaultCellStyle.Format = "dd.MM.yyyy HH:mm";
tsRefresh.Click += (_, _) => LoadData();
// Zeilenfärbung nach PnL-% (grün/hellgrün/rot) nach jedem (Neu-)Binden.
dgvTrades.DataBindingComplete += (_, _) => ColorRows();
// Filter: Markt (Text), Master (Combo), Ergebnis (Win/Loss), Datum (Von/Bis, optional).
tbMarket.TextChanged += (_, _) => ApplyFilter();
cbMaster.SelectedIndexChanged += (_, _) => ApplyFilter();
cbResult.SelectedIndexChanged += (_, _) => ApplyFilter();
dtFrom.ValueChanged += (_, _) => ApplyFilter();
dtTo.ValueChanged += (_, _) => ApplyFilter();
btnReset.Click += (_, _) => ResetFilters();
if (cbResult.Items.Count > 0) cbResult.SelectedIndex = 0;
}
/// <summary>Injiziert die Abhängigkeiten (nach der DI-Auflösung) und lädt die Daten.</summary>
public void Initialize(ICopyTradeLogRepository tradeLog, TradingState state, CopyTradingState copyState)
{
_tradeLog = tradeLog;
_state = state;
_copyState = copyState;
LoadData();
}
private void LoadData()
{
if (_tradeLog == null) return;
_allRows = _tradeLog.Find(_ => true)
.OrderByDescending(t => t.ClosedAt)
.Select(t => new ClosedTradeRow
{
TradeId = t.TradeId,
AccountId = t.AccountId,
SourceTraderId = t.SourceTraderId,
IsDemo = t.IsDemo,
TokenId = t.TokenId,
MarketSlug = t.MarketSlug,
MarketQuestion = t.MarketQuestion,
Outcome = t.Outcome,
Side = t.Side,
EntryPrice = t.EntryPrice,
ExitPrice = t.ExitPrice,
Size = t.Size,
RealizedPnl = t.RealizedPnl,
PnlPercent = t.PnlPercent,
TotalFees = t.TotalFees,
OpenedAt = t.OpenedAt,
ClosedAt = t.ClosedAt,
ExitReason = t.ExitReason,
AccountName = ResolveAccount(t.AccountId, t.IsDemo),
SourceTraderName = ResolveTrader(t.SourceTraderId)
})
.ToList();
PopulateMasters();
ApplyFilter();
}
private void PopulateMasters()
{
_loading = true;
string prev = cbMaster.SelectedItem as string ?? "Alle";
cbMaster.Items.Clear();
cbMaster.Items.Add("Alle");
foreach (var m in _allRows.Select(r => r.SourceTraderName)
.Where(s => !string.IsNullOrEmpty(s)).Distinct().OrderBy(s => s))
cbMaster.Items.Add(m);
int idx = cbMaster.Items.IndexOf(prev);
cbMaster.SelectedIndex = idx >= 0 ? idx : 0;
_loading = false;
}
private void ApplyFilter()
{
if (_loading) return;
IEnumerable<ClosedTradeRow> q = _allRows;
string market = tbMarket.Text.Trim();
if (market.Length > 0)
q = q.Where(r => (r.MarketQuestion?.Contains(market, StringComparison.OrdinalIgnoreCase) ?? false)
|| (r.MarketSlug?.Contains(market, StringComparison.OrdinalIgnoreCase) ?? false));
if (cbMaster.SelectedItem is string master && master != "Alle")
q = q.Where(r => r.SourceTraderName == master);
string result = cbResult.SelectedItem as string ?? "Alle";
if (result == "Gewinner") q = q.Where(r => r.RealizedPnl > 0m);
else if (result == "Verlierer") q = q.Where(r => r.RealizedPnl < 0m);
if (dtFrom.Checked) { DateTime f = dtFrom.Value.Date; q = q.Where(r => r.ClosedAt >= f); }
if (dtTo.Checked) { DateTime t = dtTo.Value.Date.AddDays(1).AddTicks(-1); q = q.Where(r => r.ClosedAt <= t); }
var rows = q.ToList();
dgvTrades.DataSource = new BindingList<ClosedTradeRow>(rows);
decimal totalPnl = rows.Sum(x => x.RealizedPnl);
int wins = rows.Count(x => x.RealizedPnl > 0);
double winrate = rows.Count > 0 ? (double)wins / rows.Count * 100 : 0;
lblSummary.Text = $"{rows.Count} / {_allRows.Count} Trades | PnL: {totalPnl:F2} USDC | Winrate: {winrate:F1}%";
}
private void ResetFilters()
{
_loading = true;
tbMarket.Text = string.Empty;
if (cbMaster.Items.Count > 0) cbMaster.SelectedIndex = 0;
if (cbResult.Items.Count > 0) cbResult.SelectedIndex = 0;
dtFrom.Checked = false;
dtTo.Checked = false;
_loading = false;
ApplyFilter();
}
private void ColorRows()
{
foreach (DataGridViewRow row in dgvTrades.Rows)
if (row.DataBoundItem is ClosedTradeRow r)
row.DefaultCellStyle.BackColor = TradeRowPalette.ForPnlPercent(r.PnlPercent);
}
private string ResolveAccount(int accountId, bool isDemo)
{
string suffix = isDemo ? " (Demo)" : "";
if (_state != null && _state.Accounts.TryGetValue(accountId, out var acc) && !string.IsNullOrEmpty(acc.Name))
return acc.Name + suffix;
return $"#{accountId}{suffix}";
}
private string ResolveTrader(int traderId)
{
if (_copyState != null && _copyState.Traders.TryGetValue(traderId, out var t) && !string.IsNullOrEmpty(t.DisplayName))
return t.DisplayName;
return traderId > 0 ? $"#{traderId}" : "Unbekannt";
}
}
}
@@ -1,167 +0,0 @@
namespace PolyTrader.Modules.CopyTrading.Ui
{
partial class CopyTradingMainForm
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Vom Komponenten-Designer generierter Code
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(CopyTradingMainForm));
tabControl = new TabControl();
tabMasters = new TabPage();
masterTradersView = new MasterTradersView();
tabOpen = new TabPage();
openTradesView = new OpenTradesView();
tabClosed = new TabPage();
closedTradesView = new ClosedTradesView();
tabSettings = new TabPage();
accountSettingsView = new AccountSettingsView();
tabControl.SuspendLayout();
tabMasters.SuspendLayout();
tabOpen.SuspendLayout();
tabClosed.SuspendLayout();
tabSettings.SuspendLayout();
SuspendLayout();
//
// tabControl
//
tabControl.Controls.Add(tabMasters);
tabControl.Controls.Add(tabOpen);
tabControl.Controls.Add(tabClosed);
tabControl.Controls.Add(tabSettings);
tabControl.Dock = DockStyle.Fill;
tabControl.Location = new Point(0, 0);
tabControl.Margin = new Padding(4, 5, 4, 5);
tabControl.Name = "tabControl";
tabControl.SelectedIndex = 0;
tabControl.Size = new Size(1714, 1167);
tabControl.TabIndex = 0;
//
// tabMasters
//
tabMasters.Controls.Add(masterTradersView);
tabMasters.Location = new Point(4, 34);
tabMasters.Margin = new Padding(4, 5, 4, 5);
tabMasters.Name = "tabMasters";
tabMasters.Padding = new Padding(4, 5, 4, 5);
tabMasters.Size = new Size(1706, 1129);
tabMasters.TabIndex = 0;
tabMasters.Text = "Master-Trader";
tabMasters.UseVisualStyleBackColor = true;
//
// masterTradersView
//
masterTradersView.Dock = DockStyle.Fill;
masterTradersView.Location = new Point(4, 5);
masterTradersView.Margin = new Padding(6, 8, 6, 8);
masterTradersView.Name = "masterTradersView";
masterTradersView.Size = new Size(1698, 1119);
masterTradersView.TabIndex = 0;
//
// tabOpen
//
tabOpen.Controls.Add(openTradesView);
tabOpen.Location = new Point(4, 34);
tabOpen.Margin = new Padding(4, 5, 4, 5);
tabOpen.Name = "tabOpen";
tabOpen.Padding = new Padding(4, 5, 4, 5);
tabOpen.Size = new Size(1706, 1129);
tabOpen.TabIndex = 3;
tabOpen.Text = "Offene Trades";
tabOpen.UseVisualStyleBackColor = true;
//
// openTradesView
//
openTradesView.Dock = DockStyle.Fill;
openTradesView.Location = new Point(4, 5);
openTradesView.Margin = new Padding(6, 8, 6, 8);
openTradesView.Name = "openTradesView";
openTradesView.Size = new Size(1698, 1119);
openTradesView.TabIndex = 0;
//
// tabClosed
//
tabClosed.Controls.Add(closedTradesView);
tabClosed.Location = new Point(4, 34);
tabClosed.Margin = new Padding(4, 5, 4, 5);
tabClosed.Name = "tabClosed";
tabClosed.Padding = new Padding(4, 5, 4, 5);
tabClosed.Size = new Size(1706, 1129);
tabClosed.TabIndex = 1;
tabClosed.Text = "Geschlossene Trades";
tabClosed.UseVisualStyleBackColor = true;
//
// closedTradesView
//
closedTradesView.Dock = DockStyle.Fill;
closedTradesView.Location = new Point(4, 5);
closedTradesView.Margin = new Padding(6, 8, 6, 8);
closedTradesView.Name = "closedTradesView";
closedTradesView.Size = new Size(1698, 1119);
closedTradesView.TabIndex = 0;
//
// tabSettings
//
tabSettings.Controls.Add(accountSettingsView);
tabSettings.Location = new Point(4, 34);
tabSettings.Margin = new Padding(4, 5, 4, 5);
tabSettings.Name = "tabSettings";
tabSettings.Padding = new Padding(4, 5, 4, 5);
tabSettings.Size = new Size(1706, 1129);
tabSettings.TabIndex = 2;
tabSettings.Text = "Account-Einstellungen";
tabSettings.UseVisualStyleBackColor = true;
//
// accountSettingsView
//
accountSettingsView.Dock = DockStyle.Fill;
accountSettingsView.Location = new Point(4, 5);
accountSettingsView.Margin = new Padding(6, 8, 6, 8);
accountSettingsView.Name = "accountSettingsView";
accountSettingsView.Size = new Size(1698, 1119);
accountSettingsView.TabIndex = 0;
//
// CopyTradingMainForm
//
AutoScaleDimensions = new SizeF(10F, 25F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(1714, 1167);
Controls.Add(tabControl);
Icon = (Icon)resources.GetObject("$this.Icon");
Margin = new Padding(4, 5, 4, 5);
MinimumSize = new Size(1162, 763);
Name = "CopyTradingMainForm";
StartPosition = FormStartPosition.CenterScreen;
Text = "Copytrading";
tabControl.ResumeLayout(false);
tabMasters.ResumeLayout(false);
tabOpen.ResumeLayout(false);
tabClosed.ResumeLayout(false);
tabSettings.ResumeLayout(false);
ResumeLayout(false);
}
#endregion
private System.Windows.Forms.TabControl tabControl;
private System.Windows.Forms.TabPage tabMasters;
private System.Windows.Forms.TabPage tabOpen;
private System.Windows.Forms.TabPage tabClosed;
private System.Windows.Forms.TabPage tabSettings;
private MasterTradersView masterTradersView;
private OpenTradesView openTradesView;
private ClosedTradesView closedTradesView;
private AccountSettingsView accountSettingsView;
}
}
@@ -1,41 +0,0 @@
using System;
using System.Windows.Forms;
using Microsoft.Extensions.DependencyInjection;
using PolyTrader.Modules.CopyTrading.Persistence;
using PolyTraderSharp;
namespace PolyTrader.Modules.CopyTrading.Ui
{
/// <summary>
/// Das Hauptfenster des Copytrading-Moduls. Bündelt alle Modul-Ansichten in einem
/// TabControl (Master-Trader, geschlossene Trades, Account-Einstellungen), sodass der
/// Launcher nur EINEN "Copytrading"-Button braucht und nicht je View ein eigenes Fenster
/// öffnet. Layout im Designer (CopyTradingMainForm.Designer.cs).
/// </summary>
public partial class CopyTradingMainForm : Form
{
// Parameterloser Konstruktor für den WinForms-Designer.
public CopyTradingMainForm()
{
InitializeComponent();
}
/// <summary>Versorgt die einzelnen Tab-Ansichten mit ihren Abhängigkeiten (aus dem DI-Container).</summary>
public void Initialize(IServiceProvider services)
{
var state = services.GetRequiredService<TradingState>();
var copyState = services.GetRequiredService<CopyTradingState>();
masterTradersView.Initialize(
services.GetRequiredService<ITrackedTraderRepository>(), state, copyState);
openTradesView.Initialize(state, copyState);
closedTradesView.Initialize(
services.GetRequiredService<ICopyTradeLogRepository>(), state, copyState);
accountSettingsView.Initialize(
services.GetRequiredService<ICopyTradingAccountSettingsRepository>(), state, copyState);
}
}
}
@@ -1,176 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
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
YII=
</value>
</data>
</root>
@@ -1,348 +0,0 @@
namespace PolyTrader.Modules.CopyTrading.Ui
{
partial class MasterTradersView
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Vom Komponenten-Designer generierter Code
private void InitializeComponent()
{
this.toolbar = new System.Windows.Forms.ToolStrip();
this.tsNew = new System.Windows.Forms.ToolStripButton();
this.tsSave = new System.Windows.Forms.ToolStripButton();
this.tsDelete = new System.Windows.Forms.ToolStripButton();
this.tsSep = new System.Windows.Forms.ToolStripSeparator();
this.tsRefresh = new System.Windows.Forms.ToolStripButton();
this.grid = new System.Windows.Forms.DataGridView();
this.colId = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colWallet = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colName = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colCategory = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colActive = new System.Windows.Forms.DataGridViewCheckBoxColumn();
this.colTrades = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colWinrate = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colPnl = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colCopyPnl = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colCopyPf = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colCopyAvg = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colCopyCount = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.splitter = new System.Windows.Forms.Splitter();
this.rightPanel = new System.Windows.Forms.Panel();
this.pgDetail = new System.Windows.Forms.PropertyGrid();
this.grpAccounts = new System.Windows.Forms.GroupBox();
this.clbAccounts = new System.Windows.Forms.CheckedListBox();
this.statusPanel = new System.Windows.Forms.Panel();
this.lblHint = new System.Windows.Forms.Label();
this.toolbar.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.grid)).BeginInit();
this.rightPanel.SuspendLayout();
this.grpAccounts.SuspendLayout();
this.statusPanel.SuspendLayout();
this.SuspendLayout();
//
// toolbar
//
this.toolbar.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden;
this.toolbar.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.tsNew,
this.tsSave,
this.tsDelete,
this.tsSep,
this.tsRefresh});
this.toolbar.Location = new System.Drawing.Point(0, 0);
this.toolbar.Name = "toolbar";
this.toolbar.Size = new System.Drawing.Size(1180, 25);
this.toolbar.TabIndex = 0;
//
// tsNew
//
this.tsNew.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.tsNew.Name = "tsNew";
this.tsNew.Size = new System.Drawing.Size(35, 22);
this.tsNew.Text = "Neu";
//
// tsSave
//
this.tsSave.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.tsSave.Name = "tsSave";
this.tsSave.Size = new System.Drawing.Size(69, 22);
this.tsSave.Text = "Speichern";
//
// tsDelete
//
this.tsDelete.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.tsDelete.Name = "tsDelete";
this.tsDelete.Size = new System.Drawing.Size(57, 22);
this.tsDelete.Text = "Löschen";
//
// tsSep
//
this.tsSep.Name = "tsSep";
this.tsSep.Size = new System.Drawing.Size(6, 25);
//
// tsRefresh
//
this.tsRefresh.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.tsRefresh.Name = "tsRefresh";
this.tsRefresh.Size = new System.Drawing.Size(90, 22);
this.tsRefresh.Text = "Aktualisieren";
//
// grid
//
this.grid.AllowUserToAddRows = false;
this.grid.AllowUserToDeleteRows = false;
this.grid.AutoGenerateColumns = false;
this.grid.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
this.grid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.grid.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.colId,
this.colWallet,
this.colName,
this.colCategory,
this.colActive,
this.colTrades,
this.colWinrate,
this.colPnl,
this.colCopyPnl,
this.colCopyPf,
this.colCopyAvg,
this.colCopyCount});
this.grid.Dock = System.Windows.Forms.DockStyle.Fill;
this.grid.Location = new System.Drawing.Point(0, 25);
this.grid.MultiSelect = false;
this.grid.Name = "grid";
this.grid.ReadOnly = true;
this.grid.RowHeadersVisible = false;
this.grid.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
this.grid.Size = new System.Drawing.Size(715, 589);
this.grid.TabIndex = 4;
//
// colId
//
this.colId.DataPropertyName = "Id";
this.colId.HeaderText = "#";
this.colId.Name = "colId";
this.colId.ReadOnly = true;
this.colId.Width = 50;
//
// colWallet
//
this.colWallet.DataPropertyName = "WalletAddress";
this.colWallet.HeaderText = "Wallet";
this.colWallet.Name = "colWallet";
this.colWallet.ReadOnly = true;
this.colWallet.Width = 170;
//
// colName
//
this.colName.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
this.colName.DataPropertyName = "DisplayName";
this.colName.HeaderText = "Name";
this.colName.Name = "colName";
this.colName.ReadOnly = true;
//
// colCategory
//
this.colCategory.DataPropertyName = "Category";
this.colCategory.HeaderText = "Kategorie";
this.colCategory.Name = "colCategory";
this.colCategory.ReadOnly = true;
this.colCategory.Width = 110;
//
// colActive
//
this.colActive.DataPropertyName = "IsActive";
this.colActive.HeaderText = "Aktiv";
this.colActive.Name = "colActive";
this.colActive.ReadOnly = true;
this.colActive.Width = 50;
//
// colTrades
//
this.colTrades.DataPropertyName = "TotalTrades";
this.colTrades.HeaderText = "Trades (7T)";
this.colTrades.Name = "colTrades";
this.colTrades.ReadOnly = true;
this.colTrades.Width = 90;
//
// colWinrate
//
this.colWinrate.DataPropertyName = "Winrate30t";
this.colWinrate.HeaderText = "Winrate %";
this.colWinrate.Name = "colWinrate";
this.colWinrate.ReadOnly = true;
this.colWinrate.Width = 90;
//
// colPnl
//
this.colPnl.DataPropertyName = "TotalPnl";
this.colPnl.HeaderText = "PnL (7T)";
this.colPnl.Name = "colPnl";
this.colPnl.ReadOnly = true;
this.colPnl.Width = 100;
//
// colCopyPnl
//
this.colCopyPnl.DataPropertyName = "CopyPnl30d";
this.colCopyPnl.HeaderText = "Copy-PnL (30T)";
this.colCopyPnl.Name = "colCopyPnl";
this.colCopyPnl.ReadOnly = true;
this.colCopyPnl.Width = 110;
//
// colCopyPf
//
this.colCopyPf.DataPropertyName = "CopyProfitFactor";
this.colCopyPf.HeaderText = "Profit-Faktor";
this.colCopyPf.Name = "colCopyPf";
this.colCopyPf.ReadOnly = true;
this.colCopyPf.Width = 100;
//
// colCopyAvg
//
this.colCopyAvg.DataPropertyName = "CopyAvgPnlPerTrade";
this.colCopyAvg.HeaderText = "Ø PnL/Trade";
this.colCopyAvg.Name = "colCopyAvg";
this.colCopyAvg.ReadOnly = true;
this.colCopyAvg.Width = 100;
//
// colCopyCount
//
this.colCopyCount.DataPropertyName = "CopyTradeCount30d";
this.colCopyCount.HeaderText = "Copy-Trades";
this.colCopyCount.Name = "colCopyCount";
this.colCopyCount.ReadOnly = true;
this.colCopyCount.Width = 90;
//
// splitter
//
this.splitter.Dock = System.Windows.Forms.DockStyle.Right;
this.splitter.Location = new System.Drawing.Point(715, 25);
this.splitter.Name = "splitter";
this.splitter.Size = new System.Drawing.Size(5, 589);
this.splitter.TabIndex = 3;
this.splitter.TabStop = false;
//
// rightPanel
//
this.rightPanel.Controls.Add(this.pgDetail);
this.rightPanel.Controls.Add(this.grpAccounts);
this.rightPanel.Dock = System.Windows.Forms.DockStyle.Right;
this.rightPanel.Location = new System.Drawing.Point(720, 25);
this.rightPanel.Name = "rightPanel";
this.rightPanel.Padding = new System.Windows.Forms.Padding(6, 0, 0, 0);
this.rightPanel.Size = new System.Drawing.Size(460, 589);
this.rightPanel.TabIndex = 2;
//
// pgDetail
//
this.pgDetail.Dock = System.Windows.Forms.DockStyle.Fill;
this.pgDetail.Location = new System.Drawing.Point(6, 0);
this.pgDetail.Name = "pgDetail";
this.pgDetail.PropertySort = System.Windows.Forms.PropertySort.Categorized;
this.pgDetail.Size = new System.Drawing.Size(454, 399);
this.pgDetail.TabIndex = 0;
this.pgDetail.ToolbarVisible = false;
//
// grpAccounts
//
this.grpAccounts.Controls.Add(this.clbAccounts);
this.grpAccounts.Dock = System.Windows.Forms.DockStyle.Bottom;
this.grpAccounts.Location = new System.Drawing.Point(6, 399);
this.grpAccounts.Name = "grpAccounts";
this.grpAccounts.Padding = new System.Windows.Forms.Padding(8);
this.grpAccounts.Size = new System.Drawing.Size(454, 190);
this.grpAccounts.TabIndex = 1;
this.grpAccounts.TabStop = false;
this.grpAccounts.Text = "Zugewiesene Accounts (kopieren diesen Trader)";
//
// clbAccounts
//
this.clbAccounts.CheckOnClick = true;
this.clbAccounts.Dock = System.Windows.Forms.DockStyle.Fill;
this.clbAccounts.IntegralHeight = false;
this.clbAccounts.Location = new System.Drawing.Point(8, 24);
this.clbAccounts.Name = "clbAccounts";
this.clbAccounts.Size = new System.Drawing.Size(438, 158);
this.clbAccounts.TabIndex = 0;
//
// statusPanel
//
this.statusPanel.Controls.Add(this.lblHint);
this.statusPanel.Dock = System.Windows.Forms.DockStyle.Bottom;
this.statusPanel.Location = new System.Drawing.Point(0, 614);
this.statusPanel.Name = "statusPanel";
this.statusPanel.Size = new System.Drawing.Size(1180, 26);
this.statusPanel.TabIndex = 1;
//
// lblHint
//
this.lblHint.Dock = System.Windows.Forms.DockStyle.Fill;
this.lblHint.ForeColor = System.Drawing.Color.DimGray;
this.lblHint.Location = new System.Drawing.Point(0, 0);
this.lblHint.Name = "lblHint";
this.lblHint.Padding = new System.Windows.Forms.Padding(8, 0, 0, 0);
this.lblHint.Size = new System.Drawing.Size(1180, 26);
this.lblHint.TabIndex = 0;
this.lblHint.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// MasterTradersView
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.grid);
this.Controls.Add(this.splitter);
this.Controls.Add(this.rightPanel);
this.Controls.Add(this.statusPanel);
this.Controls.Add(this.toolbar);
this.Name = "MasterTradersView";
this.Size = new System.Drawing.Size(1180, 640);
this.toolbar.ResumeLayout(false);
this.toolbar.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.grid)).EndInit();
this.rightPanel.ResumeLayout(false);
this.grpAccounts.ResumeLayout(false);
this.statusPanel.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.ToolStrip toolbar;
private System.Windows.Forms.ToolStripButton tsNew;
private System.Windows.Forms.ToolStripButton tsSave;
private System.Windows.Forms.ToolStripButton tsDelete;
private System.Windows.Forms.ToolStripSeparator tsSep;
private System.Windows.Forms.ToolStripButton tsRefresh;
private System.Windows.Forms.DataGridView grid;
private System.Windows.Forms.DataGridViewTextBoxColumn colId;
private System.Windows.Forms.DataGridViewTextBoxColumn colWallet;
private System.Windows.Forms.DataGridViewTextBoxColumn colName;
private System.Windows.Forms.DataGridViewTextBoxColumn colCategory;
private System.Windows.Forms.DataGridViewCheckBoxColumn colActive;
private System.Windows.Forms.DataGridViewTextBoxColumn colTrades;
private System.Windows.Forms.DataGridViewTextBoxColumn colWinrate;
private System.Windows.Forms.DataGridViewTextBoxColumn colPnl;
private System.Windows.Forms.DataGridViewTextBoxColumn colCopyPnl;
private System.Windows.Forms.DataGridViewTextBoxColumn colCopyPf;
private System.Windows.Forms.DataGridViewTextBoxColumn colCopyAvg;
private System.Windows.Forms.DataGridViewTextBoxColumn colCopyCount;
private System.Windows.Forms.Splitter splitter;
private System.Windows.Forms.Panel rightPanel;
private System.Windows.Forms.PropertyGrid pgDetail;
private System.Windows.Forms.GroupBox grpAccounts;
private System.Windows.Forms.CheckedListBox clbAccounts;
private System.Windows.Forms.Panel statusPanel;
private System.Windows.Forms.Label lblHint;
}
}
@@ -1,152 +0,0 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;
using PolyTrader.Modules.CopyTrading.Persistence;
using PolyTraderSharp;
using PolyTraderSharp.Models;
namespace PolyTrader.Modules.CopyTrading.Ui
{
/// <summary>
/// Verwaltung der Master-Trader (mod_copytrading_traders): Liste, Detail-Editor
/// (PropertyGrid) und Account-Zuweisung (welche Accounts diesen Trader kopieren).
/// Persistiert über das Repo und hält den Hot-Path-State
/// (<see cref="CopyTradingState.Traders"/>) synchron.
/// Layout im Designer (MasterTradersView.Designer.cs), Daten/Logik hier.
/// </summary>
public partial class MasterTradersView : UserControl
{
private ITrackedTraderRepository? _repo;
private TradingState? _state;
private CopyTradingState? _copyState;
private BindingList<TrackedTrader> _binding = new();
private TrackedTrader? _current;
// Parameterloser Konstruktor für den WinForms-Designer.
public MasterTradersView()
{
InitializeComponent();
colWinrate.DefaultCellStyle.Format = "F1";
colPnl.DefaultCellStyle.Format = "F2";
colCopyPnl.DefaultCellStyle.Format = "F2";
colCopyPf.DefaultCellStyle.Format = "F2";
colCopyAvg.DefaultCellStyle.Format = "F2";
// Spaltenbreiten proportional (grid-weites Fill): keine überlaufenden Fixbreiten mehr,
// die Spalten teilen sich immer exakt die verfügbare Breite (Fix des „verbuggten" Grids).
colId.FillWeight = 40;
colWallet.FillWeight = 150;
colName.FillWeight = 200;
colCategory.FillWeight = 90;
colActive.FillWeight = 50;
colTrades.FillWeight = 85;
colWinrate.FillWeight = 90;
colPnl.FillWeight = 90;
colCopyPnl.FillWeight = 110;
colCopyPf.FillWeight = 95;
colCopyAvg.FillWeight = 95;
colCopyCount.FillWeight = 95;
tsNew.Click += (_, _) => AddNew();
tsSave.Click += (_, _) => SaveCurrent();
tsDelete.Click += (_, _) => DeleteCurrent();
tsRefresh.Click += (_, _) => LoadData();
grid.SelectionChanged += (_, _) => OnSelectionChanged();
}
/// <summary>Injiziert die Abhängigkeiten (nach der DI-Auflösung) und lädt die Trader.</summary>
public void Initialize(ITrackedTraderRepository repo, TradingState state, CopyTradingState copyState)
{
_repo = repo;
_state = state;
_copyState = copyState;
LoadData();
}
private void LoadData()
{
if (_repo == null) return;
var traders = _repo.GetAll().OrderBy(t => t.Id).ToList();
_binding = new BindingList<TrackedTrader>(traders);
grid.DataSource = _binding;
if (traders.Count > 0)
grid.CurrentCell = grid.Rows[0].Cells[0];
else
ClearDetail();
lblHint.Text = $"{traders.Count} Master-Trader geladen.";
}
private void OnSelectionChanged()
{
if (grid.CurrentRow?.DataBoundItem is TrackedTrader t)
BindDetail(t);
}
private void BindDetail(TrackedTrader trader)
{
_current = trader;
pgDetail.SelectedObject = trader;
clbAccounts.Items.Clear();
if (_state == null) return;
foreach (var acc in _state.Accounts.Values.OrderBy(a => a.AccountId))
{
string label = string.IsNullOrEmpty(acc.Name) ? $"#{acc.AccountId}" : $"{acc.Name} (#{acc.AccountId}){(acc.IsDemo ? " · Demo" : "")}";
int idx = clbAccounts.Items.Add(new AccountItem(acc.AccountId, label));
clbAccounts.SetItemChecked(idx, trader.AssignedAccountIds.Contains(acc.AccountId));
}
}
private void ClearDetail()
{
_current = null;
pgDetail.SelectedObject = null;
clbAccounts.Items.Clear();
}
private void AddNew()
{
int nextId = _binding.Count > 0 ? _binding.Max(t => t.Id) + 1 : 1;
var trader = new TrackedTrader { Id = nextId, DisplayName = $"Neuer Trader {nextId}" };
_binding.Add(trader);
grid.CurrentCell = grid.Rows[_binding.Count - 1].Cells[0];
lblHint.Text = $"Neuer Master-Trader #{nextId} Felder ausfüllen und Speichern.";
}
private void SaveCurrent()
{
if (_current == null || _repo == null || _copyState == null) { lblHint.Text = "Kein Trader ausgewählt."; return; }
_current.AssignedAccountIds = clbAccounts.CheckedItems.Cast<AccountItem>().Select(a => a.Id).ToHashSet();
_repo.Upsert(_current);
_copyState.Traders[_current.Id] = _current;
grid.Refresh();
lblHint.Text = $"Gespeichert: #{_current.Id} {_current.DisplayName} ({_current.AssignedAccountIds.Count} Account(s)) um {DateTime.Now:HH:mm:ss}.";
}
private void DeleteCurrent()
{
if (_current == null || _repo == null || _copyState == null) { lblHint.Text = "Kein Trader ausgewählt."; return; }
var id = _current.Id;
if (MessageBox.Show($"Master-Trader #{id} ({_current.DisplayName}) wirklich löschen?",
"Löschen bestätigen", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes)
return;
_repo.Delete(id);
_copyState.Traders.TryRemove(id, out _);
_binding.Remove(_current);
ClearDetail();
lblHint.Text = $"Master-Trader #{id} gelöscht.";
}
private sealed record AccountItem(int Id, string Label)
{
public override string ToString() => Label;
}
}
}
@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
@@ -1,241 +0,0 @@
namespace PolyTrader.Modules.CopyTrading.Ui
{
partial class OpenTradesView
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Vom Komponenten-Designer generierter Code
private void InitializeComponent()
{
this.toolbar = new System.Windows.Forms.ToolStrip();
this.tsRefresh = new System.Windows.Forms.ToolStripButton();
this.dgvOpen = new System.Windows.Forms.DataGridView();
this.colAccount = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colTrader = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colMarket = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colOutcome = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colSide = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colEntry = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colCurrent = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colSize = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colAmount = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colValue = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colUnrealized = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colUnrealizedPct = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colStatus = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.statusPanel = new System.Windows.Forms.Panel();
this.lblSummary = new System.Windows.Forms.Label();
this.toolbar.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dgvOpen)).BeginInit();
this.statusPanel.SuspendLayout();
this.SuspendLayout();
//
// toolbar
//
this.toolbar.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden;
this.toolbar.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.tsRefresh });
this.toolbar.Location = new System.Drawing.Point(0, 0);
this.toolbar.Name = "toolbar";
this.toolbar.Size = new System.Drawing.Size(1100, 25);
this.toolbar.TabIndex = 0;
//
// tsRefresh
//
this.tsRefresh.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.tsRefresh.Name = "tsRefresh";
this.tsRefresh.Size = new System.Drawing.Size(90, 22);
this.tsRefresh.Text = "Aktualisieren";
//
// dgvOpen
//
this.dgvOpen.AllowUserToAddRows = false;
this.dgvOpen.AllowUserToDeleteRows = false;
this.dgvOpen.AutoGenerateColumns = false;
this.dgvOpen.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
this.dgvOpen.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dgvOpen.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.colAccount, this.colTrader, this.colMarket, this.colOutcome, this.colSide,
this.colEntry, this.colCurrent, this.colSize, this.colAmount, this.colValue,
this.colUnrealized, this.colUnrealizedPct, this.colStatus});
this.dgvOpen.Dock = System.Windows.Forms.DockStyle.Fill;
this.dgvOpen.Location = new System.Drawing.Point(0, 25);
this.dgvOpen.Name = "dgvOpen";
this.dgvOpen.ReadOnly = true;
this.dgvOpen.RowHeadersVisible = false;
this.dgvOpen.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
this.dgvOpen.Size = new System.Drawing.Size(1100, 549);
this.dgvOpen.TabIndex = 1;
//
// colAccount
//
this.colAccount.DataPropertyName = "AccountName";
this.colAccount.HeaderText = "Account";
this.colAccount.Name = "colAccount";
this.colAccount.ReadOnly = true;
this.colAccount.FillWeight = 120;
//
// colTrader
//
this.colTrader.DataPropertyName = "SourceTraderName";
this.colTrader.HeaderText = "Master-Trader";
this.colTrader.Name = "colTrader";
this.colTrader.ReadOnly = true;
this.colTrader.FillWeight = 120;
//
// colMarket
//
this.colMarket.DataPropertyName = "MarketQuestion";
this.colMarket.HeaderText = "Markt";
this.colMarket.Name = "colMarket";
this.colMarket.ReadOnly = true;
this.colMarket.FillWeight = 240;
//
// colOutcome
//
this.colOutcome.DataPropertyName = "Outcome";
this.colOutcome.HeaderText = "Outcome";
this.colOutcome.Name = "colOutcome";
this.colOutcome.ReadOnly = true;
this.colOutcome.FillWeight = 80;
//
// colSide
//
this.colSide.DataPropertyName = "Side";
this.colSide.HeaderText = "Side";
this.colSide.Name = "colSide";
this.colSide.ReadOnly = true;
this.colSide.FillWeight = 55;
//
// colEntry
//
this.colEntry.DataPropertyName = "EntryPrice";
this.colEntry.HeaderText = "Entry";
this.colEntry.Name = "colEntry";
this.colEntry.ReadOnly = true;
this.colEntry.FillWeight = 65;
//
// colCurrent
//
this.colCurrent.DataPropertyName = "CurrentPrice";
this.colCurrent.HeaderText = "Aktuell";
this.colCurrent.Name = "colCurrent";
this.colCurrent.ReadOnly = true;
this.colCurrent.FillWeight = 65;
//
// colSize
//
this.colSize.DataPropertyName = "Size";
this.colSize.HeaderText = "Size";
this.colSize.Name = "colSize";
this.colSize.ReadOnly = true;
this.colSize.FillWeight = 70;
//
// colAmount
//
this.colAmount.DataPropertyName = "AmountUsd";
this.colAmount.HeaderText = "Einsatz";
this.colAmount.Name = "colAmount";
this.colAmount.ReadOnly = true;
this.colAmount.FillWeight = 80;
//
// colValue
//
this.colValue.DataPropertyName = "CurrentValueUsd";
this.colValue.HeaderText = "Wert";
this.colValue.Name = "colValue";
this.colValue.ReadOnly = true;
this.colValue.FillWeight = 80;
//
// colUnrealized
//
this.colUnrealized.DataPropertyName = "UnrealizedPnl";
this.colUnrealized.HeaderText = "Buchgewinn";
this.colUnrealized.Name = "colUnrealized";
this.colUnrealized.ReadOnly = true;
this.colUnrealized.FillWeight = 90;
//
// colUnrealizedPct
//
this.colUnrealizedPct.DataPropertyName = "UnrealizedPct";
this.colUnrealizedPct.HeaderText = "Buchgew. %";
this.colUnrealizedPct.Name = "colUnrealizedPct";
this.colUnrealizedPct.ReadOnly = true;
this.colUnrealizedPct.FillWeight = 80;
//
// colStatus
//
this.colStatus.DataPropertyName = "Status";
this.colStatus.HeaderText = "Status";
this.colStatus.Name = "colStatus";
this.colStatus.ReadOnly = true;
this.colStatus.FillWeight = 90;
//
// statusPanel
//
this.statusPanel.Controls.Add(this.lblSummary);
this.statusPanel.Dock = System.Windows.Forms.DockStyle.Bottom;
this.statusPanel.Location = new System.Drawing.Point(0, 574);
this.statusPanel.Name = "statusPanel";
this.statusPanel.Size = new System.Drawing.Size(1100, 26);
this.statusPanel.TabIndex = 2;
//
// lblSummary
//
this.lblSummary.Dock = System.Windows.Forms.DockStyle.Fill;
this.lblSummary.Location = new System.Drawing.Point(0, 0);
this.lblSummary.Name = "lblSummary";
this.lblSummary.Padding = new System.Windows.Forms.Padding(8, 0, 0, 0);
this.lblSummary.Size = new System.Drawing.Size(1100, 26);
this.lblSummary.TabIndex = 0;
this.lblSummary.Text = "—";
this.lblSummary.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// OpenTradesView
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.dgvOpen);
this.Controls.Add(this.statusPanel);
this.Controls.Add(this.toolbar);
this.Name = "OpenTradesView";
this.Size = new System.Drawing.Size(1100, 600);
this.toolbar.ResumeLayout(false);
this.toolbar.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.dgvOpen)).EndInit();
this.statusPanel.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.ToolStrip toolbar;
private System.Windows.Forms.ToolStripButton tsRefresh;
private System.Windows.Forms.DataGridView dgvOpen;
private System.Windows.Forms.DataGridViewTextBoxColumn colAccount;
private System.Windows.Forms.DataGridViewTextBoxColumn colTrader;
private System.Windows.Forms.DataGridViewTextBoxColumn colMarket;
private System.Windows.Forms.DataGridViewTextBoxColumn colOutcome;
private System.Windows.Forms.DataGridViewTextBoxColumn colSide;
private System.Windows.Forms.DataGridViewTextBoxColumn colEntry;
private System.Windows.Forms.DataGridViewTextBoxColumn colCurrent;
private System.Windows.Forms.DataGridViewTextBoxColumn colSize;
private System.Windows.Forms.DataGridViewTextBoxColumn colAmount;
private System.Windows.Forms.DataGridViewTextBoxColumn colValue;
private System.Windows.Forms.DataGridViewTextBoxColumn colUnrealized;
private System.Windows.Forms.DataGridViewTextBoxColumn colUnrealizedPct;
private System.Windows.Forms.DataGridViewTextBoxColumn colStatus;
private System.Windows.Forms.Panel statusPanel;
private System.Windows.Forms.Label lblSummary;
}
}
@@ -1,123 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;
using PolyTrader.Modules.CopyTrading.Logic;
using PolyTraderSharp;
using PolyTraderSharp.Models;
namespace PolyTrader.Modules.CopyTrading.Ui
{
/// <summary>
/// Listet ALLE aktuell offenen Positionen (Portfolios der Accounts) mit Buchgewinn/-verlust.
/// Quelle ist der Laufzeit-State (<see cref="TradingState.Accounts"/>), nicht die DB so ist die
/// Anzeige live. Zeilenfärbung nach Buchgewinn-% (<see cref="TradeRowColoring"/>).
/// Layout im Designer (OpenTradesView.Designer.cs).
/// </summary>
public partial class OpenTradesView : UserControl
{
private TradingState? _state;
private CopyTradingState? _copyState;
public OpenTradesView()
{
InitializeComponent();
colEntry.DefaultCellStyle.Format = "F3";
colCurrent.DefaultCellStyle.Format = "F3";
colSize.DefaultCellStyle.Format = "F2";
colAmount.DefaultCellStyle.Format = "F2";
colValue.DefaultCellStyle.Format = "F2";
colUnrealized.DefaultCellStyle.Format = "F2";
colUnrealizedPct.DefaultCellStyle.Format = "F1";
tsRefresh.Click += (_, _) => LoadData();
dgvOpen.DataBindingComplete += (_, _) => ColorRows();
}
public void Initialize(TradingState state, CopyTradingState copyState)
{
_state = state;
_copyState = copyState;
LoadData();
}
private void LoadData()
{
if (_state == null) return;
var rows = _state.Accounts.Values
.SelectMany(a => a.OpenPositions.Values.Select(p => (acc: a, pos: p)))
.OrderByDescending(x => x.pos.CurrentValueUsd)
.Select(x =>
{
decimal unrealized = x.pos.CurrentValueUsd - x.pos.AmountUsd;
decimal pct = x.pos.AmountUsd > 0m ? unrealized / x.pos.AmountUsd * 100m : 0m;
return new OpenTradeRow
{
AccountName = ResolveAccount(x.acc.AccountId, x.pos.IsDemo),
SourceTraderName = ResolveTrader(x.pos.SourceTraderId, x.pos.SourceTraderName),
MarketQuestion = x.pos.MarketQuestion,
Outcome = x.pos.Outcome,
Side = x.pos.Side,
EntryPrice = x.pos.EntryPrice,
CurrentPrice = x.pos.CurrentPrice,
Size = x.pos.Size,
AmountUsd = x.pos.AmountUsd,
CurrentValueUsd = x.pos.CurrentValueUsd,
UnrealizedPnl = unrealized,
UnrealizedPct = pct,
Status = x.pos.ExitPending ? "Exit läuft" : "offen"
};
})
.ToList();
dgvOpen.DataSource = new BindingList<OpenTradeRow>(rows);
decimal totalValue = rows.Sum(r => r.CurrentValueUsd);
decimal totalUnrealized = rows.Sum(r => r.UnrealizedPnl);
lblSummary.Text = $"{rows.Count} offene Positionen | Wert: {totalValue:F2} USDC | Buchgewinn: {totalUnrealized:F2} USDC";
}
private void ColorRows()
{
foreach (DataGridViewRow row in dgvOpen.Rows)
if (row.DataBoundItem is OpenTradeRow r)
row.DefaultCellStyle.BackColor = TradeRowPalette.ForPnlPercent(r.UnrealizedPct);
}
private string ResolveAccount(int accountId, bool isDemo)
{
string suffix = isDemo ? " (Demo)" : "";
if (_state != null && _state.Accounts.TryGetValue(accountId, out var acc) && !string.IsNullOrEmpty(acc.Name))
return acc.Name + suffix;
return $"#{accountId}{suffix}";
}
private string ResolveTrader(int traderId, string fallback)
{
if (_copyState != null && _copyState.Traders.TryGetValue(traderId, out var t) && !string.IsNullOrEmpty(t.DisplayName))
return t.DisplayName;
return !string.IsNullOrEmpty(fallback) ? fallback : (traderId > 0 ? $"#{traderId}" : "System");
}
/// <summary>Anzeige-Zeile für offene Positionen.</summary>
private sealed class OpenTradeRow
{
public string AccountName { get; set; } = string.Empty;
public string SourceTraderName { get; set; } = string.Empty;
public string MarketQuestion { get; set; } = string.Empty;
public string Outcome { get; set; } = string.Empty;
public string Side { get; set; } = string.Empty;
public decimal EntryPrice { get; set; }
public decimal CurrentPrice { get; set; }
public decimal Size { get; set; }
public decimal AmountUsd { get; set; }
public decimal CurrentValueUsd { get; set; }
public decimal UnrealizedPnl { get; set; }
public decimal UnrealizedPct { get; set; }
public string Status { get; set; } = string.Empty;
}
}
}
@@ -1,27 +0,0 @@
using System.Drawing;
using PolyTrader.Modules.CopyTrading.Logic;
namespace PolyTrader.Modules.CopyTrading.Ui
{
/// <summary>
/// WinForms-Farbpalette zu den <see cref="TradeRowTint"/>-Kategorien. Die Schwellenlogik liegt
/// (getestet) in <see cref="TradeRowColoring"/>; hier steht nur noch, wie die Kategorien aussehen.
/// Die Avalonia-UI bekommt später ihre eigene Palette zu denselben Kategorien.
/// </summary>
internal static class TradeRowPalette
{
public static readonly Color Loss = Color.FromArgb(245, 200, 200); // rot
public static readonly Color SmallWin = Color.FromArgb(212, 240, 212); // hellgrün
public static readonly Color BigWin = Color.FromArgb(140, 214, 140); // grün
public static Color For(TradeRowTint tint) => tint switch
{
TradeRowTint.Loss => Loss,
TradeRowTint.BigWin => BigWin,
_ => SmallWin
};
/// <summary>Bequemlichkeit für die Grids: PnL-Prozent direkt in die Zeilenfarbe.</summary>
public static Color ForPnlPercent(decimal pnlPercent) => For(TradeRowColoring.ForPnlPercent(pnlPercent));
}
}
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\PolyTrader.Core\PolyTrader.Core.csproj" /> <ProjectReference Include="..\PolyTrader.Core\PolyTrader.Core.csproj" />
@@ -20,11 +20,12 @@
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework> <!-- Linux-Portierung: plattformneutral. Die WinForms-UI dieses Moduls (Scanner und Ausfuehrung)
wurde entfernt; die Avalonia-Ansicht kommt spaeter direkt hierher zurueck Avalonia ist
im Gegensatz zu WinForms plattformuebergreifend und braucht kein eigenes UI-Projekt. -->
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<!-- Modul trägt eigene WinForms-UI-Tabs bei. -->
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup> </PropertyGroup>
</Project> </Project>
@@ -54,22 +54,22 @@ namespace PolyTrader.Modules.ResolutionFarming
// Live-Marktquelle, Live-Execution, On-Chain-Auto-Redeem und Kalibrierung folgen (Zielland). // Live-Marktquelle, Live-Execution, On-Chain-Auto-Redeem und Kalibrierung folgen (Zielland).
} }
public void RegisterUi(IModuleUiHost host, System.IServiceProvider services) /// <summary>
/// Aktuell ohne Ansicht: Die WinForms-UI wurde mit der Linux-Portierung entfernt, die
/// Avalonia-Ansicht folgt (Stufe L3/L4). Die Fachlogik dieses Moduls laeuft davon
/// unabhaengig weiter die Shell zeigt schlicht kein Fenster fuer das Modul an.
///
/// <para><b>Beim Nachbau zu erhalten</b> (Spezifikation: docs/UI-SPEZIFIKATION-WinForms.md,
/// Originalcode: Git-Tag <c>winforms-final</c>):</para>
/// <list type="bullet">
/// <item>View-ID <c>resolutionfarming.main</c> (stabil Launcher-Button und Symbol haengen daran)</item>
/// <item>Titel <c>ResolutionFarming</c>, Gruppe <c>ResolutionFarming</c>, Order <c>200</c></item>
/// <item>Ein Fenster fuers ganze Modul: ResolutionFarmingMainForm mit Tabs: Kandidaten / Positionen / Historie+Statistik / Settings</item>
/// </list>
/// </summary>
public void RegisterUi(IModuleUiHost host, IServiceProvider services)
{ {
// EIN Fenster fürs ganze Modul (Tabs: Kandidaten/Positionen/Historie/Settings). // Bewusst leer, bis die Avalonia-Ansicht steht (siehe Doku oben).
host.RegisterView(new ModuleView
{
Id = "resolutionfarming.main",
Title = "ResolutionFarming",
Group = "ResolutionFarming",
Order = 200,
CreateView = () =>
{
var form = new Ui.ResolutionFarmingMainForm();
form.Initialize(services);
return form;
}
});
} }
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask; public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
@@ -1,379 +0,0 @@
namespace PolyTrader.Modules.ResolutionFarming.Ui
{
partial class ResolutionFarmingMainForm
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Vom Komponenten-Designer generierter Code
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ResolutionFarmingMainForm));
tabControlRf = new TabControl();
tabKandidaten = new TabPage();
dgvCandidates = new DataGridView();
pnlCandTop = new Panel();
btnCandRefresh = new Button();
cbCandAccount = new ComboBox();
lblCandKonto = new Label();
tabPositionen = new TabPage();
dgvPositions = new DataGridView();
pnlPosTop = new Panel();
btnPosRefresh = new Button();
tabHistorie = new TabPage();
dgvHistory = new DataGridView();
lblHistSummary = new Label();
pnlHistTop = new Panel();
btnHistRefresh = new Button();
tabSettings = new TabPage();
pgSettings = new PropertyGrid();
pnlSetTop = new Panel();
btnSettingsSave = new Button();
cbSettingsAccount = new ComboBox();
lblSetKonto = new Label();
lblRfStatus = new Label();
tabControlRf.SuspendLayout();
tabKandidaten.SuspendLayout();
((System.ComponentModel.ISupportInitialize)dgvCandidates).BeginInit();
pnlCandTop.SuspendLayout();
tabPositionen.SuspendLayout();
((System.ComponentModel.ISupportInitialize)dgvPositions).BeginInit();
pnlPosTop.SuspendLayout();
tabHistorie.SuspendLayout();
((System.ComponentModel.ISupportInitialize)dgvHistory).BeginInit();
pnlHistTop.SuspendLayout();
tabSettings.SuspendLayout();
pnlSetTop.SuspendLayout();
SuspendLayout();
//
// tabControlRf
//
tabControlRf.Controls.Add(tabKandidaten);
tabControlRf.Controls.Add(tabPositionen);
tabControlRf.Controls.Add(tabHistorie);
tabControlRf.Controls.Add(tabSettings);
tabControlRf.Dock = DockStyle.Fill;
tabControlRf.Location = new Point(0, 0);
tabControlRf.Margin = new Padding(4, 5, 4, 5);
tabControlRf.Name = "tabControlRf";
tabControlRf.SelectedIndex = 0;
tabControlRf.Size = new Size(1429, 993);
tabControlRf.TabIndex = 0;
//
// tabKandidaten
//
tabKandidaten.Controls.Add(dgvCandidates);
tabKandidaten.Controls.Add(pnlCandTop);
tabKandidaten.Location = new Point(4, 34);
tabKandidaten.Margin = new Padding(4, 5, 4, 5);
tabKandidaten.Name = "tabKandidaten";
tabKandidaten.Padding = new Padding(4, 5, 4, 5);
tabKandidaten.Size = new Size(1421, 955);
tabKandidaten.TabIndex = 0;
tabKandidaten.Text = "Kandidaten";
tabKandidaten.UseVisualStyleBackColor = true;
//
// dgvCandidates
//
dgvCandidates.AllowUserToAddRows = false;
dgvCandidates.AllowUserToDeleteRows = false;
dgvCandidates.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dgvCandidates.Dock = DockStyle.Fill;
dgvCandidates.Location = new Point(4, 62);
dgvCandidates.Margin = new Padding(4, 5, 4, 5);
dgvCandidates.Name = "dgvCandidates";
dgvCandidates.ReadOnly = true;
dgvCandidates.RowHeadersVisible = false;
dgvCandidates.RowHeadersWidth = 62;
dgvCandidates.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dgvCandidates.Size = new Size(1413, 888);
dgvCandidates.TabIndex = 1;
//
// pnlCandTop
//
pnlCandTop.Controls.Add(btnCandRefresh);
pnlCandTop.Controls.Add(cbCandAccount);
pnlCandTop.Controls.Add(lblCandKonto);
pnlCandTop.Dock = DockStyle.Top;
pnlCandTop.Location = new Point(4, 5);
pnlCandTop.Margin = new Padding(4, 5, 4, 5);
pnlCandTop.Name = "pnlCandTop";
pnlCandTop.Size = new Size(1413, 57);
pnlCandTop.TabIndex = 0;
//
// btnCandRefresh
//
btnCandRefresh.Location = new Point(464, 7);
btnCandRefresh.Margin = new Padding(4, 5, 4, 5);
btnCandRefresh.Name = "btnCandRefresh";
btnCandRefresh.Size = new Size(157, 43);
btnCandRefresh.TabIndex = 2;
btnCandRefresh.Text = "Aktualisieren";
btnCandRefresh.UseVisualStyleBackColor = true;
//
// cbCandAccount
//
cbCandAccount.DropDownStyle = ComboBoxStyle.DropDownList;
cbCandAccount.Location = new Point(79, 8);
cbCandAccount.Margin = new Padding(4, 5, 4, 5);
cbCandAccount.Name = "cbCandAccount";
cbCandAccount.Size = new Size(370, 33);
cbCandAccount.TabIndex = 1;
//
// lblCandKonto
//
lblCandKonto.AutoSize = true;
lblCandKonto.Location = new Point(9, 15);
lblCandKonto.Margin = new Padding(4, 0, 4, 0);
lblCandKonto.Name = "lblCandKonto";
lblCandKonto.Size = new Size(64, 25);
lblCandKonto.TabIndex = 0;
lblCandKonto.Text = "Konto:";
//
// tabPositionen
//
tabPositionen.Controls.Add(dgvPositions);
tabPositionen.Controls.Add(pnlPosTop);
tabPositionen.Location = new Point(4, 34);
tabPositionen.Margin = new Padding(4, 5, 4, 5);
tabPositionen.Name = "tabPositionen";
tabPositionen.Padding = new Padding(4, 5, 4, 5);
tabPositionen.Size = new Size(1421, 955);
tabPositionen.TabIndex = 1;
tabPositionen.Text = "Positionen";
tabPositionen.UseVisualStyleBackColor = true;
//
// dgvPositions
//
dgvPositions.AllowUserToAddRows = false;
dgvPositions.AllowUserToDeleteRows = false;
dgvPositions.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dgvPositions.Dock = DockStyle.Fill;
dgvPositions.Location = new Point(4, 62);
dgvPositions.Margin = new Padding(4, 5, 4, 5);
dgvPositions.Name = "dgvPositions";
dgvPositions.ReadOnly = true;
dgvPositions.RowHeadersVisible = false;
dgvPositions.RowHeadersWidth = 62;
dgvPositions.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dgvPositions.Size = new Size(1413, 888);
dgvPositions.TabIndex = 1;
//
// pnlPosTop
//
pnlPosTop.Controls.Add(btnPosRefresh);
pnlPosTop.Dock = DockStyle.Top;
pnlPosTop.Location = new Point(4, 5);
pnlPosTop.Margin = new Padding(4, 5, 4, 5);
pnlPosTop.Name = "pnlPosTop";
pnlPosTop.Size = new Size(1413, 57);
pnlPosTop.TabIndex = 0;
//
// btnPosRefresh
//
btnPosRefresh.Location = new Point(9, 7);
btnPosRefresh.Margin = new Padding(4, 5, 4, 5);
btnPosRefresh.Name = "btnPosRefresh";
btnPosRefresh.Size = new Size(157, 43);
btnPosRefresh.TabIndex = 0;
btnPosRefresh.Text = "Aktualisieren";
btnPosRefresh.UseVisualStyleBackColor = true;
//
// tabHistorie
//
tabHistorie.Controls.Add(dgvHistory);
tabHistorie.Controls.Add(lblHistSummary);
tabHistorie.Controls.Add(pnlHistTop);
tabHistorie.Location = new Point(4, 34);
tabHistorie.Margin = new Padding(4, 5, 4, 5);
tabHistorie.Name = "tabHistorie";
tabHistorie.Padding = new Padding(4, 5, 4, 5);
tabHistorie.Size = new Size(1421, 955);
tabHistorie.TabIndex = 2;
tabHistorie.Text = "Historie / Statistik";
tabHistorie.UseVisualStyleBackColor = true;
//
// dgvHistory
//
dgvHistory.AllowUserToAddRows = false;
dgvHistory.AllowUserToDeleteRows = false;
dgvHistory.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dgvHistory.Dock = DockStyle.Fill;
dgvHistory.Location = new Point(4, 142);
dgvHistory.Margin = new Padding(4, 5, 4, 5);
dgvHistory.Name = "dgvHistory";
dgvHistory.ReadOnly = true;
dgvHistory.RowHeadersVisible = false;
dgvHistory.RowHeadersWidth = 62;
dgvHistory.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dgvHistory.Size = new Size(1413, 808);
dgvHistory.TabIndex = 2;
//
// lblHistSummary
//
lblHistSummary.Dock = DockStyle.Top;
lblHistSummary.Location = new Point(4, 62);
lblHistSummary.Margin = new Padding(4, 0, 4, 0);
lblHistSummary.Name = "lblHistSummary";
lblHistSummary.Padding = new Padding(9, 10, 9, 10);
lblHistSummary.Size = new Size(1413, 80);
lblHistSummary.TabIndex = 1;
//
// pnlHistTop
//
pnlHistTop.Controls.Add(btnHistRefresh);
pnlHistTop.Dock = DockStyle.Top;
pnlHistTop.Location = new Point(4, 5);
pnlHistTop.Margin = new Padding(4, 5, 4, 5);
pnlHistTop.Name = "pnlHistTop";
pnlHistTop.Size = new Size(1413, 57);
pnlHistTop.TabIndex = 0;
//
// btnHistRefresh
//
btnHistRefresh.Location = new Point(9, 7);
btnHistRefresh.Margin = new Padding(4, 5, 4, 5);
btnHistRefresh.Name = "btnHistRefresh";
btnHistRefresh.Size = new Size(157, 43);
btnHistRefresh.TabIndex = 0;
btnHistRefresh.Text = "Aktualisieren";
btnHistRefresh.UseVisualStyleBackColor = true;
//
// tabSettings
//
tabSettings.Controls.Add(pgSettings);
tabSettings.Controls.Add(pnlSetTop);
tabSettings.Location = new Point(4, 34);
tabSettings.Margin = new Padding(4, 5, 4, 5);
tabSettings.Name = "tabSettings";
tabSettings.Padding = new Padding(4, 5, 4, 5);
tabSettings.Size = new Size(1421, 955);
tabSettings.TabIndex = 3;
tabSettings.Text = "Settings";
tabSettings.UseVisualStyleBackColor = true;
//
// pgSettings
//
pgSettings.Dock = DockStyle.Fill;
pgSettings.Location = new Point(4, 62);
pgSettings.Margin = new Padding(4, 5, 4, 5);
pgSettings.Name = "pgSettings";
pgSettings.Size = new Size(1413, 888);
pgSettings.TabIndex = 1;
//
// pnlSetTop
//
pnlSetTop.Controls.Add(btnSettingsSave);
pnlSetTop.Controls.Add(cbSettingsAccount);
pnlSetTop.Controls.Add(lblSetKonto);
pnlSetTop.Dock = DockStyle.Top;
pnlSetTop.Location = new Point(4, 5);
pnlSetTop.Margin = new Padding(4, 5, 4, 5);
pnlSetTop.Name = "pnlSetTop";
pnlSetTop.Size = new Size(1413, 57);
pnlSetTop.TabIndex = 0;
//
// btnSettingsSave
//
btnSettingsSave.Location = new Point(464, 7);
btnSettingsSave.Margin = new Padding(4, 5, 4, 5);
btnSettingsSave.Name = "btnSettingsSave";
btnSettingsSave.Size = new Size(157, 43);
btnSettingsSave.TabIndex = 2;
btnSettingsSave.Text = "Speichern";
btnSettingsSave.UseVisualStyleBackColor = true;
//
// cbSettingsAccount
//
cbSettingsAccount.DropDownStyle = ComboBoxStyle.DropDownList;
cbSettingsAccount.Location = new Point(79, 8);
cbSettingsAccount.Margin = new Padding(4, 5, 4, 5);
cbSettingsAccount.Name = "cbSettingsAccount";
cbSettingsAccount.Size = new Size(370, 33);
cbSettingsAccount.TabIndex = 1;
//
// lblSetKonto
//
lblSetKonto.AutoSize = true;
lblSetKonto.Location = new Point(9, 15);
lblSetKonto.Margin = new Padding(4, 0, 4, 0);
lblSetKonto.Name = "lblSetKonto";
lblSetKonto.Size = new Size(64, 25);
lblSetKonto.TabIndex = 0;
lblSetKonto.Text = "Konto:";
//
// lblRfStatus
//
lblRfStatus.Dock = DockStyle.Bottom;
lblRfStatus.Location = new Point(0, 993);
lblRfStatus.Margin = new Padding(4, 0, 4, 0);
lblRfStatus.Name = "lblRfStatus";
lblRfStatus.Padding = new Padding(9, 3, 9, 3);
lblRfStatus.Size = new Size(1429, 37);
lblRfStatus.TabIndex = 1;
//
// ResolutionFarmingMainForm
//
AutoScaleDimensions = new SizeF(10F, 25F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(1429, 1030);
Controls.Add(tabControlRf);
Controls.Add(lblRfStatus);
Icon = (Icon)resources.GetObject("$this.Icon");
Margin = new Padding(4, 5, 4, 5);
Name = "ResolutionFarmingMainForm";
StartPosition = FormStartPosition.CenterScreen;
Text = "ResolutionFarming";
tabControlRf.ResumeLayout(false);
tabKandidaten.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)dgvCandidates).EndInit();
pnlCandTop.ResumeLayout(false);
pnlCandTop.PerformLayout();
tabPositionen.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)dgvPositions).EndInit();
pnlPosTop.ResumeLayout(false);
tabHistorie.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)dgvHistory).EndInit();
pnlHistTop.ResumeLayout(false);
tabSettings.ResumeLayout(false);
pnlSetTop.ResumeLayout(false);
pnlSetTop.PerformLayout();
ResumeLayout(false);
}
#endregion
private System.Windows.Forms.TabControl tabControlRf;
private System.Windows.Forms.TabPage tabKandidaten;
private System.Windows.Forms.Panel pnlCandTop;
private System.Windows.Forms.Label lblCandKonto;
private System.Windows.Forms.ComboBox cbCandAccount;
private System.Windows.Forms.Button btnCandRefresh;
private System.Windows.Forms.DataGridView dgvCandidates;
private System.Windows.Forms.TabPage tabPositionen;
private System.Windows.Forms.Panel pnlPosTop;
private System.Windows.Forms.Button btnPosRefresh;
private System.Windows.Forms.DataGridView dgvPositions;
private System.Windows.Forms.TabPage tabHistorie;
private System.Windows.Forms.Panel pnlHistTop;
private System.Windows.Forms.Button btnHistRefresh;
private System.Windows.Forms.Label lblHistSummary;
private System.Windows.Forms.DataGridView dgvHistory;
private System.Windows.Forms.TabPage tabSettings;
private System.Windows.Forms.Panel pnlSetTop;
private System.Windows.Forms.Label lblSetKonto;
private System.Windows.Forms.ComboBox cbSettingsAccount;
private System.Windows.Forms.Button btnSettingsSave;
private System.Windows.Forms.PropertyGrid pgSettings;
private System.Windows.Forms.Label lblRfStatus;
}
}
@@ -1,147 +0,0 @@
using System;
using System.Linq;
using System.Windows.Forms;
using Microsoft.Extensions.DependencyInjection;
using PolyTrader.Modules.ResolutionFarming.Models;
using PolyTrader.Modules.ResolutionFarming.Persistence;
using PolyTraderSharp;
namespace PolyTrader.Modules.ResolutionFarming.Ui
{
/// <summary>
/// Hauptfenster des ResolutionFarming-Moduls: Tabs Kandidaten, Positionen, Historie/Statistik,
/// Settings. Layout im Designer (ResolutionFarmingMainForm.Designer.cs), Verhalten/Daten hier.
/// DB-Zugriffe defensiv (Guarded), damit die UI auch ohne angewendete rf_-Migration bedienbar bleibt.
/// </summary>
public partial class ResolutionFarmingMainForm : Form
{
private IRfCandidateRepository? _candidates;
private IRfPositionRepository? _positions;
private IRfClosedTradeRepository? _closed;
private IRfSettingsRepository? _settingsRepo;
private TradingState? _state;
private RfSettings? _currentSettings;
public ResolutionFarmingMainForm()
{
InitializeComponent();
btnCandRefresh.Click += (_, _) => RefreshCandidates();
cbCandAccount.SelectedIndexChanged += (_, _) => RefreshCandidates();
btnPosRefresh.Click += (_, _) => RefreshPositions();
btnHistRefresh.Click += (_, _) => RefreshHistory();
btnSettingsSave.Click += (_, _) => SaveSettings();
cbSettingsAccount.SelectedIndexChanged += (_, _) => LoadSettings();
}
/// <summary>Injiziert Repos/State (nach DI-Auflösung) und lädt die Ansichten.</summary>
public void Initialize(IServiceProvider services)
{
_candidates = services.GetRequiredService<IRfCandidateRepository>();
_positions = services.GetRequiredService<IRfPositionRepository>();
_closed = services.GetRequiredService<IRfClosedTradeRepository>();
_settingsRepo = services.GetRequiredService<IRfSettingsRepository>();
_state = services.GetRequiredService<TradingState>();
PopulateAccounts();
RefreshCandidates();
RefreshPositions();
RefreshHistory();
}
// ---------------- Daten ----------------
private void PopulateAccounts()
{
if (_state == null) return;
var items = _state.Accounts.Values
.OrderBy(a => a.AccountId)
.Select(a => new AccountItem(a.AccountId, string.IsNullOrEmpty(a.Name) ? $"#{a.AccountId}" : $"{a.Name} (#{a.AccountId}){(a.IsDemo ? " · Demo" : "")}"))
.ToList();
foreach (var combo in new[] { cbCandAccount, cbSettingsAccount })
{
combo.DisplayMember = nameof(AccountItem.Label);
combo.ValueMember = nameof(AccountItem.Id);
combo.DataSource = items.ToList();
}
if (items.Count > 0) LoadSettings();
else SetStatus("Keine Accounts vorhanden.");
}
private int? SelectedAccountId(ComboBox combo) =>
combo.SelectedItem is AccountItem it ? it.Id : (int?)null;
private void RefreshCandidates()
{
if (_candidates == null || SelectedAccountId(cbCandAccount) is not int accId) return;
Guarded("Kandidaten", () => dgvCandidates.DataSource = _candidates.GetRecent(accId, 200));
}
private void RefreshPositions()
{
if (_positions == null) return;
Guarded("Positionen", () => dgvPositions.DataSource = _positions.GetAllOpen());
}
private void RefreshHistory()
{
if (_closed == null) return;
Guarded("Historie", () =>
{
var trades = _closed.Find(_ => true);
dgvHistory.DataSource = trades;
lblHistSummary.Text = BuildSummary(trades);
});
}
private static string BuildSummary(System.Collections.Generic.List<RfClosedTrade> trades)
{
if (trades.Count == 0) return "Noch keine abgeschlossenen Trades.";
int wins = trades.Count(t => t.RealizedPnl > 0m);
decimal pnl = trades.Sum(t => t.RealizedPnl);
decimal fees = trades.Sum(t => t.TotalFees);
double winrate = 100.0 * wins / trades.Count;
// Winrate je Preisband (Kalibrierung: realisierte Winrate sollte > Band-Mitte liegen).
var bands = trades
.GroupBy(t => $"{Math.Floor(t.EntryPrice * 20m) / 20m:F2}") // 5-¢-Bänder
.OrderBy(g => g.Key)
.Select(g => $"{g.Key}: {100.0 * g.Count(x => x.RealizedPnl > 0m) / g.Count():F0}% ({g.Count()})");
return $"Trades: {trades.Count} | Winrate: {winrate:F1}% | Netto-PnL: {pnl:F2} USDC | Fees: {fees:F2}\n" +
$"Winrate je Preisband: {string.Join(" | ", bands)}";
}
private void LoadSettings()
{
if (_settingsRepo == null || SelectedAccountId(cbSettingsAccount) is not int accId) return;
Guarded("Settings", () =>
{
_currentSettings = _settingsRepo.Get(accId) ?? new RfSettings { AccountId = accId };
pgSettings.SelectedObject = _currentSettings;
SetStatus($"Einstellungen für Konto #{accId}.");
});
}
private void SaveSettings()
{
if (_settingsRepo == null || _currentSettings == null) return;
Guarded("Speichern", () =>
{
_settingsRepo.Upsert(_currentSettings);
SetStatus($"Gespeichert für Konto #{_currentSettings.AccountId} um {DateTime.Now:HH:mm:ss}.");
});
}
private void Guarded(string what, Action action)
{
try { action(); }
catch (Exception ex) { SetStatus($"{what}: DB nicht bereit ({ex.Message}). Migration angewendet?"); }
}
private void SetStatus(string text) => lblRfStatus.Text = text;
private sealed record AccountItem(int Id, string Label);
}
}
@@ -1,208 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
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
RK5CYII=
</value>
</data>
</root>
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\PolyTrader.Core\PolyTrader.Core.csproj" /> <ProjectReference Include="..\PolyTrader.Core\PolyTrader.Core.csproj" />
@@ -25,11 +25,12 @@
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework> <!-- Linux-Portierung: plattformneutral. Die WinForms-UI dieses Moduls (KI-Analyse, Dossiers, MCP-Light)
wurde entfernt; die Avalonia-Ansicht kommt spaeter direkt hierher zurueck Avalonia ist
im Gegensatz zu WinForms plattformuebergreifend und braucht kein eigenes UI-Projekt. -->
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<!-- Modul trägt eigene WinForms-UI bei (Dossier-Browser, später Analyse-Chat). -->
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup> </PropertyGroup>
</Project> </Project>
@@ -54,21 +54,22 @@ namespace PolyTrader.Modules.Supervisor
services.AddHostedService<Mcp.McpLightServer>(); services.AddHostedService<Mcp.McpLightServer>();
} }
public void RegisterUi(IModuleUiHost host, System.IServiceProvider services) /// <summary>
/// Aktuell ohne Ansicht: Die WinForms-UI wurde mit der Linux-Portierung entfernt, die
/// Avalonia-Ansicht folgt (Stufe L3/L4). Die Fachlogik dieses Moduls laeuft davon
/// unabhaengig weiter die Shell zeigt schlicht kein Fenster fuer das Modul an.
///
/// <para><b>Beim Nachbau zu erhalten</b> (Spezifikation: docs/UI-SPEZIFIKATION-WinForms.md,
/// Originalcode: Git-Tag <c>winforms-final</c>):</para>
/// <list type="bullet">
/// <item>View-ID <c>supervisor.main</c> (stabil Launcher-Button und Symbol haengen daran)</item>
/// <item>Titel <c>Supervisor</c>, Gruppe <c>Supervisor</c>, Order <c>300</c></item>
/// <item>Ein Fenster fuers ganze Modul: SupervisorMainForm mit Tabs: Analyse (Chat) / Dossiers / Berichte / Counterfactual</item>
/// </list>
/// </summary>
public void RegisterUi(IModuleUiHost host, IServiceProvider services)
{ {
host.RegisterView(new ModuleView // Bewusst leer, bis die Avalonia-Ansicht steht (siehe Doku oben).
{
Id = "supervisor.main",
Title = "Supervisor",
Group = "Supervisor",
Order = 300,
CreateView = () =>
{
var form = new Ui.SupervisorMainForm();
form.Initialize(services);
return form;
}
});
} }
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask; public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
@@ -1,524 +0,0 @@
namespace PolyTrader.Modules.Supervisor.Ui
{
partial class SupervisorMainForm
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Vom Komponenten-Designer generierter Code
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SupervisorMainForm));
tabControlSup = new TabControl();
tabAnalyse = new TabPage();
rtbChat = new RichTextBox();
pnlChatInput = new Panel();
tbChatInput = new TextBox();
btnSend = new Button();
toolStripChat = new ToolStrip();
lblProfil = new ToolStripLabel();
cbProfile = new ToolStripComboBox();
lblModel = new ToolStripLabel();
tbModel = new ToolStripTextBox();
sepChat = new ToolStripSeparator();
btnClearChat = new ToolStripButton();
tabDossiers = new TabPage();
splitDossiers = new SplitContainer();
dgvSignals = new DataGridView();
tbDossier = new TextBox();
toolStripDossier = new ToolStrip();
btnDossierRefresh = new ToolStripButton();
sepDossier = new ToolStripSeparator();
lblSignal = new ToolStripLabel();
tbSignalId = new ToolStripTextBox();
btnDossierOpen = new ToolStripButton();
tabBerichte = new TabPage();
splitReports = new SplitContainer();
dgvReports = new DataGridView();
tbReport = new TextBox();
toolStripReports = new ToolStrip();
btnReportsRefresh = new ToolStripButton();
tabCounterfactual = new TabPage();
dgvCounterfactuals = new DataGridView();
toolStripCf = new ToolStrip();
btnCfRefresh = new ToolStripButton();
lblStatus = new Label();
tabControlSup.SuspendLayout();
tabAnalyse.SuspendLayout();
pnlChatInput.SuspendLayout();
toolStripChat.SuspendLayout();
tabDossiers.SuspendLayout();
((System.ComponentModel.ISupportInitialize)splitDossiers).BeginInit();
splitDossiers.Panel1.SuspendLayout();
splitDossiers.Panel2.SuspendLayout();
splitDossiers.SuspendLayout();
((System.ComponentModel.ISupportInitialize)dgvSignals).BeginInit();
toolStripDossier.SuspendLayout();
tabBerichte.SuspendLayout();
((System.ComponentModel.ISupportInitialize)splitReports).BeginInit();
splitReports.Panel1.SuspendLayout();
splitReports.Panel2.SuspendLayout();
splitReports.SuspendLayout();
((System.ComponentModel.ISupportInitialize)dgvReports).BeginInit();
toolStripReports.SuspendLayout();
tabCounterfactual.SuspendLayout();
((System.ComponentModel.ISupportInitialize)dgvCounterfactuals).BeginInit();
toolStripCf.SuspendLayout();
SuspendLayout();
//
// tabControlSup
//
tabControlSup.Controls.Add(tabAnalyse);
tabControlSup.Controls.Add(tabDossiers);
tabControlSup.Controls.Add(tabBerichte);
tabControlSup.Controls.Add(tabCounterfactual);
tabControlSup.Dock = DockStyle.Fill;
tabControlSup.Location = new Point(0, 0);
tabControlSup.Margin = new Padding(4, 5, 4, 5);
tabControlSup.Name = "tabControlSup";
tabControlSup.SelectedIndex = 0;
tabControlSup.Size = new Size(1786, 1126);
tabControlSup.TabIndex = 0;
//
// tabAnalyse
//
tabAnalyse.Controls.Add(rtbChat);
tabAnalyse.Controls.Add(pnlChatInput);
tabAnalyse.Controls.Add(toolStripChat);
tabAnalyse.Location = new Point(4, 34);
tabAnalyse.Margin = new Padding(4, 5, 4, 5);
tabAnalyse.Name = "tabAnalyse";
tabAnalyse.Padding = new Padding(4, 5, 4, 5);
tabAnalyse.Size = new Size(1778, 1088);
tabAnalyse.TabIndex = 0;
tabAnalyse.Text = "Analyse";
tabAnalyse.UseVisualStyleBackColor = true;
//
// rtbChat
//
rtbChat.BackColor = Color.White;
rtbChat.Dock = DockStyle.Fill;
rtbChat.Font = new Font("Segoe UI", 9.5F);
rtbChat.Location = new Point(4, 39);
rtbChat.Margin = new Padding(4, 5, 4, 5);
rtbChat.Name = "rtbChat";
rtbChat.ReadOnly = true;
rtbChat.Size = new Size(1770, 944);
rtbChat.TabIndex = 1;
rtbChat.Text = "";
//
// pnlChatInput
//
pnlChatInput.Controls.Add(tbChatInput);
pnlChatInput.Controls.Add(btnSend);
pnlChatInput.Dock = DockStyle.Bottom;
pnlChatInput.Location = new Point(4, 983);
pnlChatInput.Margin = new Padding(4, 5, 4, 5);
pnlChatInput.Name = "pnlChatInput";
pnlChatInput.Padding = new Padding(6, 7, 6, 7);
pnlChatInput.Size = new Size(1770, 100);
pnlChatInput.TabIndex = 2;
//
// tbChatInput
//
tbChatInput.Dock = DockStyle.Fill;
tbChatInput.Location = new Point(6, 7);
tbChatInput.Margin = new Padding(4, 5, 4, 5);
tbChatInput.Multiline = true;
tbChatInput.Name = "tbChatInput";
tbChatInput.PlaceholderText = "Analyse-Frage stellen … (Strg+Enter zum Senden)";
tbChatInput.Size = new Size(1601, 86);
tbChatInput.TabIndex = 0;
//
// btnSend
//
btnSend.Dock = DockStyle.Right;
btnSend.Location = new Point(1607, 7);
btnSend.Margin = new Padding(4, 5, 4, 5);
btnSend.Name = "btnSend";
btnSend.Size = new Size(157, 86);
btnSend.TabIndex = 1;
btnSend.Text = "Senden";
btnSend.UseVisualStyleBackColor = true;
//
// toolStripChat
//
toolStripChat.ImageScalingSize = new Size(24, 24);
toolStripChat.Items.AddRange(new ToolStripItem[] { lblProfil, cbProfile, lblModel, tbModel, sepChat, btnClearChat });
toolStripChat.Location = new Point(4, 5);
toolStripChat.Name = "toolStripChat";
toolStripChat.Padding = new Padding(0, 0, 3, 0);
toolStripChat.Size = new Size(1770, 34);
toolStripChat.TabIndex = 0;
//
// lblProfil
//
lblProfil.Name = "lblProfil";
lblProfil.Size = new Size(57, 29);
lblProfil.Text = "Profil:";
//
// cbProfile
//
cbProfile.DropDownStyle = ComboBoxStyle.DropDownList;
cbProfile.Name = "cbProfile";
cbProfile.Size = new Size(227, 34);
//
// lblModel
//
lblModel.Name = "lblModel";
lblModel.Size = new Size(71, 29);
lblModel.Text = "Modell:";
//
// tbModel
//
tbModel.AutoSize = false;
tbModel.Name = "tbModel";
tbModel.Size = new Size(313, 31);
tbModel.Text = "openrouter/auto";
//
// sepChat
//
sepChat.Name = "sepChat";
sepChat.Size = new Size(6, 34);
//
// btnClearChat
//
btnClearChat.DisplayStyle = ToolStripItemDisplayStyle.Text;
btnClearChat.Name = "btnClearChat";
btnClearChat.Size = new Size(122, 29);
btnClearChat.Text = "Verlauf leeren";
//
// tabDossiers
//
tabDossiers.Controls.Add(splitDossiers);
tabDossiers.Controls.Add(toolStripDossier);
tabDossiers.Location = new Point(4, 34);
tabDossiers.Margin = new Padding(4, 5, 4, 5);
tabDossiers.Name = "tabDossiers";
tabDossiers.Padding = new Padding(4, 5, 4, 5);
tabDossiers.Size = new Size(1778, 1088);
tabDossiers.TabIndex = 1;
tabDossiers.Text = "Dossiers";
tabDossiers.UseVisualStyleBackColor = true;
//
// splitDossiers
//
splitDossiers.Dock = DockStyle.Fill;
splitDossiers.Location = new Point(4, 39);
splitDossiers.Margin = new Padding(4, 5, 4, 5);
splitDossiers.Name = "splitDossiers";
//
// splitDossiers.Panel1
//
splitDossiers.Panel1.Controls.Add(dgvSignals);
//
// splitDossiers.Panel2
//
splitDossiers.Panel2.Controls.Add(tbDossier);
splitDossiers.Size = new Size(1770, 1044);
splitDossiers.SplitterDistance = 601;
splitDossiers.SplitterWidth = 6;
splitDossiers.TabIndex = 1;
//
// dgvSignals
//
dgvSignals.AllowUserToAddRows = false;
dgvSignals.AllowUserToDeleteRows = false;
dgvSignals.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dgvSignals.Dock = DockStyle.Fill;
dgvSignals.Location = new Point(0, 0);
dgvSignals.Margin = new Padding(4, 5, 4, 5);
dgvSignals.MultiSelect = false;
dgvSignals.Name = "dgvSignals";
dgvSignals.ReadOnly = true;
dgvSignals.RowHeadersVisible = false;
dgvSignals.RowHeadersWidth = 62;
dgvSignals.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dgvSignals.Size = new Size(601, 1044);
dgvSignals.TabIndex = 0;
//
// tbDossier
//
tbDossier.Dock = DockStyle.Fill;
tbDossier.Font = new Font("Consolas", 9.5F);
tbDossier.Location = new Point(0, 0);
tbDossier.Margin = new Padding(4, 5, 4, 5);
tbDossier.Multiline = true;
tbDossier.Name = "tbDossier";
tbDossier.ReadOnly = true;
tbDossier.ScrollBars = ScrollBars.Both;
tbDossier.Size = new Size(1163, 1044);
tbDossier.TabIndex = 0;
tbDossier.WordWrap = false;
//
// toolStripDossier
//
toolStripDossier.ImageScalingSize = new Size(24, 24);
toolStripDossier.Items.AddRange(new ToolStripItem[] { btnDossierRefresh, sepDossier, lblSignal, tbSignalId, btnDossierOpen });
toolStripDossier.Location = new Point(4, 5);
toolStripDossier.Name = "toolStripDossier";
toolStripDossier.Padding = new Padding(0, 0, 3, 0);
toolStripDossier.Size = new Size(1770, 34);
toolStripDossier.TabIndex = 0;
//
// btnDossierRefresh
//
btnDossierRefresh.DisplayStyle = ToolStripItemDisplayStyle.Text;
btnDossierRefresh.Name = "btnDossierRefresh";
btnDossierRefresh.Size = new Size(116, 29);
btnDossierRefresh.Text = "Aktualisieren";
//
// sepDossier
//
sepDossier.Name = "sepDossier";
sepDossier.Size = new Size(6, 34);
//
// lblSignal
//
lblSignal.Name = "lblSignal";
lblSignal.Size = new Size(80, 29);
lblSignal.Text = "SignalId:";
//
// tbSignalId
//
tbSignalId.AutoSize = false;
tbSignalId.Name = "tbSignalId";
tbSignalId.Size = new Size(313, 31);
//
// btnDossierOpen
//
btnDossierOpen.DisplayStyle = ToolStripItemDisplayStyle.Text;
btnDossierOpen.Name = "btnDossierOpen";
btnDossierOpen.Size = new Size(132, 29);
btnDossierOpen.Text = "Dossier öffnen";
//
// tabBerichte
//
tabBerichte.Controls.Add(splitReports);
tabBerichte.Controls.Add(toolStripReports);
tabBerichte.Location = new Point(4, 34);
tabBerichte.Margin = new Padding(4, 5, 4, 5);
tabBerichte.Name = "tabBerichte";
tabBerichte.Padding = new Padding(4, 5, 4, 5);
tabBerichte.Size = new Size(1778, 1089);
tabBerichte.TabIndex = 2;
tabBerichte.Text = "Berichte";
tabBerichte.UseVisualStyleBackColor = true;
//
// splitReports
//
splitReports.Dock = DockStyle.Fill;
splitReports.Location = new Point(4, 39);
splitReports.Margin = new Padding(4, 5, 4, 5);
splitReports.Name = "splitReports";
//
// splitReports.Panel1
//
splitReports.Panel1.Controls.Add(dgvReports);
//
// splitReports.Panel2
//
splitReports.Panel2.Controls.Add(tbReport);
splitReports.Size = new Size(1770, 1045);
splitReports.SplitterDistance = 744;
splitReports.SplitterWidth = 6;
splitReports.TabIndex = 1;
//
// dgvReports
//
dgvReports.AllowUserToAddRows = false;
dgvReports.AllowUserToDeleteRows = false;
dgvReports.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dgvReports.Dock = DockStyle.Fill;
dgvReports.Location = new Point(0, 0);
dgvReports.Margin = new Padding(4, 5, 4, 5);
dgvReports.MultiSelect = false;
dgvReports.Name = "dgvReports";
dgvReports.ReadOnly = true;
dgvReports.RowHeadersVisible = false;
dgvReports.RowHeadersWidth = 62;
dgvReports.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dgvReports.Size = new Size(744, 1045);
dgvReports.TabIndex = 0;
//
// tbReport
//
tbReport.Dock = DockStyle.Fill;
tbReport.Font = new Font("Segoe UI", 9.5F);
tbReport.Location = new Point(0, 0);
tbReport.Margin = new Padding(4, 5, 4, 5);
tbReport.Multiline = true;
tbReport.Name = "tbReport";
tbReport.ReadOnly = true;
tbReport.ScrollBars = ScrollBars.Both;
tbReport.Size = new Size(1020, 1045);
tbReport.TabIndex = 0;
//
// toolStripReports
//
toolStripReports.ImageScalingSize = new Size(24, 24);
toolStripReports.Items.AddRange(new ToolStripItem[] { btnReportsRefresh });
toolStripReports.Location = new Point(4, 5);
toolStripReports.Name = "toolStripReports";
toolStripReports.Padding = new Padding(0, 0, 3, 0);
toolStripReports.Size = new Size(1770, 34);
toolStripReports.TabIndex = 0;
//
// btnReportsRefresh
//
btnReportsRefresh.DisplayStyle = ToolStripItemDisplayStyle.Text;
btnReportsRefresh.Name = "btnReportsRefresh";
btnReportsRefresh.Size = new Size(116, 29);
btnReportsRefresh.Text = "Aktualisieren";
//
// tabCounterfactual
//
tabCounterfactual.Controls.Add(dgvCounterfactuals);
tabCounterfactual.Controls.Add(toolStripCf);
tabCounterfactual.Location = new Point(4, 34);
tabCounterfactual.Margin = new Padding(4, 5, 4, 5);
tabCounterfactual.Name = "tabCounterfactual";
tabCounterfactual.Padding = new Padding(4, 5, 4, 5);
tabCounterfactual.Size = new Size(1778, 1089);
tabCounterfactual.TabIndex = 3;
tabCounterfactual.Text = "Counterfactual";
tabCounterfactual.UseVisualStyleBackColor = true;
//
// dgvCounterfactuals
//
dgvCounterfactuals.AllowUserToAddRows = false;
dgvCounterfactuals.AllowUserToDeleteRows = false;
dgvCounterfactuals.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dgvCounterfactuals.Dock = DockStyle.Fill;
dgvCounterfactuals.Location = new Point(4, 39);
dgvCounterfactuals.Margin = new Padding(4, 5, 4, 5);
dgvCounterfactuals.MultiSelect = false;
dgvCounterfactuals.Name = "dgvCounterfactuals";
dgvCounterfactuals.ReadOnly = true;
dgvCounterfactuals.RowHeadersVisible = false;
dgvCounterfactuals.RowHeadersWidth = 62;
dgvCounterfactuals.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dgvCounterfactuals.Size = new Size(1770, 1045);
dgvCounterfactuals.TabIndex = 1;
//
// toolStripCf
//
toolStripCf.ImageScalingSize = new Size(24, 24);
toolStripCf.Items.AddRange(new ToolStripItem[] { btnCfRefresh });
toolStripCf.Location = new Point(4, 5);
toolStripCf.Name = "toolStripCf";
toolStripCf.Padding = new Padding(0, 0, 3, 0);
toolStripCf.Size = new Size(1770, 34);
toolStripCf.TabIndex = 0;
//
// btnCfRefresh
//
btnCfRefresh.DisplayStyle = ToolStripItemDisplayStyle.Text;
btnCfRefresh.Name = "btnCfRefresh";
btnCfRefresh.Size = new Size(116, 29);
btnCfRefresh.Text = "Aktualisieren";
//
// lblStatus
//
lblStatus.Dock = DockStyle.Bottom;
lblStatus.Location = new Point(0, 1126);
lblStatus.Margin = new Padding(4, 0, 4, 0);
lblStatus.Name = "lblStatus";
lblStatus.Padding = new Padding(9, 3, 9, 3);
lblStatus.Size = new Size(1786, 37);
lblStatus.TabIndex = 1;
//
// SupervisorMainForm
//
AutoScaleDimensions = new SizeF(10F, 25F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(1786, 1163);
Controls.Add(tabControlSup);
Controls.Add(lblStatus);
Icon = (Icon)resources.GetObject("$this.Icon");
Margin = new Padding(4, 5, 4, 5);
Name = "SupervisorMainForm";
StartPosition = FormStartPosition.CenterScreen;
Text = "Supervisor";
tabControlSup.ResumeLayout(false);
tabAnalyse.ResumeLayout(false);
tabAnalyse.PerformLayout();
pnlChatInput.ResumeLayout(false);
pnlChatInput.PerformLayout();
toolStripChat.ResumeLayout(false);
toolStripChat.PerformLayout();
tabDossiers.ResumeLayout(false);
tabDossiers.PerformLayout();
splitDossiers.Panel1.ResumeLayout(false);
splitDossiers.Panel2.ResumeLayout(false);
splitDossiers.Panel2.PerformLayout();
((System.ComponentModel.ISupportInitialize)splitDossiers).EndInit();
splitDossiers.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)dgvSignals).EndInit();
toolStripDossier.ResumeLayout(false);
toolStripDossier.PerformLayout();
tabBerichte.ResumeLayout(false);
tabBerichte.PerformLayout();
splitReports.Panel1.ResumeLayout(false);
splitReports.Panel2.ResumeLayout(false);
splitReports.Panel2.PerformLayout();
((System.ComponentModel.ISupportInitialize)splitReports).EndInit();
splitReports.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)dgvReports).EndInit();
toolStripReports.ResumeLayout(false);
toolStripReports.PerformLayout();
tabCounterfactual.ResumeLayout(false);
tabCounterfactual.PerformLayout();
((System.ComponentModel.ISupportInitialize)dgvCounterfactuals).EndInit();
toolStripCf.ResumeLayout(false);
toolStripCf.PerformLayout();
ResumeLayout(false);
}
#endregion
private System.Windows.Forms.TabControl tabControlSup;
private System.Windows.Forms.TabPage tabAnalyse;
private System.Windows.Forms.ToolStrip toolStripChat;
private System.Windows.Forms.ToolStripLabel lblProfil;
private System.Windows.Forms.ToolStripComboBox cbProfile;
private System.Windows.Forms.ToolStripLabel lblModel;
private System.Windows.Forms.ToolStripTextBox tbModel;
private System.Windows.Forms.ToolStripSeparator sepChat;
private System.Windows.Forms.ToolStripButton btnClearChat;
private System.Windows.Forms.RichTextBox rtbChat;
private System.Windows.Forms.Panel pnlChatInput;
private System.Windows.Forms.TextBox tbChatInput;
private System.Windows.Forms.Button btnSend;
private System.Windows.Forms.TabPage tabDossiers;
private System.Windows.Forms.ToolStrip toolStripDossier;
private System.Windows.Forms.ToolStripButton btnDossierRefresh;
private System.Windows.Forms.ToolStripSeparator sepDossier;
private System.Windows.Forms.ToolStripLabel lblSignal;
private System.Windows.Forms.ToolStripTextBox tbSignalId;
private System.Windows.Forms.ToolStripButton btnDossierOpen;
private System.Windows.Forms.SplitContainer splitDossiers;
private System.Windows.Forms.DataGridView dgvSignals;
private System.Windows.Forms.TextBox tbDossier;
private System.Windows.Forms.TabPage tabBerichte;
private System.Windows.Forms.ToolStrip toolStripReports;
private System.Windows.Forms.ToolStripButton btnReportsRefresh;
private System.Windows.Forms.SplitContainer splitReports;
private System.Windows.Forms.DataGridView dgvReports;
private System.Windows.Forms.TextBox tbReport;
private System.Windows.Forms.TabPage tabCounterfactual;
private System.Windows.Forms.ToolStrip toolStripCf;
private System.Windows.Forms.ToolStripButton btnCfRefresh;
private System.Windows.Forms.DataGridView dgvCounterfactuals;
private System.Windows.Forms.Label lblStatus;
}
}
@@ -1,205 +0,0 @@
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Microsoft.Extensions.DependencyInjection;
using PolyTrader.Core.Analytics;
using PolyTrader.Modules.Supervisor.Agent;
using PolyTrader.Modules.Supervisor.Persistence;
using PolyTrader.Modules.Supervisor.Services;
namespace PolyTrader.Modules.Supervisor.Ui
{
/// <summary>
/// Hauptfenster des Supervisor-Moduls: Tab „Analyse" (Chat mit Profil-/Modellwahl, Tool-Aufrufe
/// transparent), Tab „Dossiers" (Signal-Browser), Tab „Berichte" (gespeicherte Analysen).
/// Layout im Designer (SupervisorMainForm.Designer.cs), Verhalten/Daten hier.
/// </summary>
public partial class SupervisorMainForm : Form
{
private DossierService? _dossiers;
private SupervisorAgent? _agent;
private ISupervisorReportRepository? _reports;
private ISupervisorCounterfactualRepository? _counterfactuals;
public SupervisorMainForm()
{
InitializeComponent();
foreach (var profile in SupervisorProfiles.All)
cbProfile.Items.Add(profile);
cbProfile.SelectedIndex = 0;
btnSend.Click += async (_, _) => await SendQuestionAsync();
tbChatInput.KeyDown += async (_, e) =>
{
if (e.Control && e.KeyCode == Keys.Enter) { e.SuppressKeyPress = true; await SendQuestionAsync(); }
};
btnClearChat.Click += (_, _) => rtbChat.Clear();
btnDossierRefresh.Click += (_, _) => LoadSignals();
btnDossierOpen.Click += (_, _) => OpenDossier(tbSignalId.Text.Trim());
dgvSignals.SelectionChanged += (_, _) => OpenSelectedSignal();
btnReportsRefresh.Click += (_, _) => LoadReports();
dgvReports.SelectionChanged += (_, _) => ShowSelectedReport();
btnCfRefresh.Click += (_, _) => LoadCounterfactuals();
}
public void Initialize(IServiceProvider services)
{
_dossiers = services.GetRequiredService<DossierService>();
_agent = services.GetRequiredService<SupervisorAgent>();
_reports = services.GetRequiredService<ISupervisorReportRepository>();
_counterfactuals = services.GetRequiredService<ISupervisorCounterfactualRepository>();
LoadSignals();
LoadReports();
LoadCounterfactuals();
AppendChat("System", "Supervisor bereit (read-only). API-Key: POLYTRADER_OPENROUTER_KEY oder Datei openrouter.key. " +
"MCP-Light für externe Clients: POLYTRADER_MCP_PORT setzen.", System.Drawing.Color.Gray);
}
// ===== Analyse-Chat =====
private async System.Threading.Tasks.Task SendQuestionAsync()
{
if (_agent == null) return;
string question = tbChatInput.Text.Trim();
if (question.Length == 0) return;
var profile = cbProfile.SelectedItem as SupervisorProfile ?? SupervisorProfiles.Allgemein;
tbChatInput.Text = "";
btnSend.Enabled = false;
AppendChat($"Du ({profile.Name})", question, System.Drawing.Color.DarkBlue);
lblStatus.Text = "Analyse läuft …";
var progress = new Progress<string>(msg => AppendChat("Tool", msg, System.Drawing.Color.DarkGoldenrod));
try
{
string model = tbModel.Text;
var result = await System.Threading.Tasks.Task.Run(() =>
_agent.AskAsync(question, model, progress, profile));
AppendChat("Supervisor", result.Answer, System.Drawing.Color.Black);
lblStatus.Text = $"Fertig. {result.ToolInvocations.Count} Tool-Aufruf(e), ~{result.PromptTokens + result.CompletionTokens} Tokens.";
SaveReport(profile, model, question, result);
}
catch (Exception ex)
{
AppendChat("Fehler", ex.Message, System.Drawing.Color.Firebrick);
lblStatus.Text = "Fehler bei der Analyse.";
}
finally
{
btnSend.Enabled = true;
}
}
private void SaveReport(SupervisorProfile profile, string model, string question, AgentResult result)
{
try
{
var calls = new List<object>();
foreach (var (tool, args, _) in result.ToolInvocations)
calls.Add(new { tool, args });
_reports?.Insert(new SupervisorReport
{
Profile = profile.Name,
Model = model,
Question = question,
Answer = result.Answer,
ToolCallsJson = System.Text.Json.JsonSerializer.Serialize(calls),
ToolCallCount = result.ToolInvocations.Count,
PromptTokens = result.PromptTokens,
CompletionTokens = result.CompletionTokens
});
LoadReports();
}
catch { /* Bericht-Ablage ist Beiwerk Analyse-Ergebnis steht im Chat */ }
}
private void AppendChat(string who, string text, System.Drawing.Color color)
{
if (InvokeRequired) { BeginInvoke(() => AppendChat(who, text, color)); return; }
rtbChat.SelectionStart = rtbChat.TextLength;
rtbChat.SelectionColor = color;
rtbChat.AppendText($"[{DateTime.Now:HH:mm:ss}] {who}: {text}{Environment.NewLine}{Environment.NewLine}");
rtbChat.ScrollToCaret();
}
// ===== Dossier-Browser =====
private void LoadSignals()
{
if (_dossiers == null) return;
try
{
List<SignalSummary> signals = _dossiers.RecentSignals(200);
dgvSignals.DataSource = signals;
lblStatus.Text = signals.Count == 0
? "Noch keine Journal-Einträge (Entscheidungen entstehen, sobald Signale verarbeitet werden)."
: $"{signals.Count} Signale.";
}
catch (Exception ex)
{
lblStatus.Text = $"Journal nicht lesbar: {ex.Message}";
}
}
private void OpenSelectedSignal()
{
if (dgvSignals.CurrentRow?.DataBoundItem is SignalSummary s)
OpenDossier(s.SignalId);
}
private void OpenDossier(string signalId)
{
if (_dossiers == null || string.IsNullOrWhiteSpace(signalId)) return;
try
{
var dossier = _dossiers.BuildForSignal(signalId);
tbDossier.Text = DossierBuilder.ToMarkdown(dossier).Replace("\n", Environment.NewLine);
tbSignalId.Text = signalId;
}
catch (Exception ex)
{
tbDossier.Text = $"Dossier konnte nicht geladen werden: {ex.Message}";
}
}
// ===== Berichte =====
private void LoadReports()
{
if (_reports == null) return;
try
{
dgvReports.DataSource = _reports.GetRecent(100);
}
catch (Exception ex)
{
lblStatus.Text = $"Berichte nicht lesbar (sup_-Migration angewendet?): {ex.Message}";
}
}
// ===== Counterfactuals =====
private void LoadCounterfactuals()
{
if (_counterfactuals == null) return;
try
{
dgvCounterfactuals.DataSource = _counterfactuals.GetRecent(200);
}
catch (Exception ex)
{
lblStatus.Text = $"Counterfactuals nicht lesbar (Migration angewendet?): {ex.Message}";
}
}
private void ShowSelectedReport()
{
if (dgvReports.CurrentRow?.DataBoundItem is SupervisorReport r)
tbReport.Text =
$"[{r.CreatedAt:dd.MM.yyyy HH:mm}] Profil {r.Profile} · Modell {r.Model} · {r.ToolCallCount} Tool-Aufrufe{Environment.NewLine}{Environment.NewLine}" +
$"FRAGE:{Environment.NewLine}{r.Question}{Environment.NewLine}{Environment.NewLine}" +
$"ANTWORT:{Environment.NewLine}{r.Answer.Replace("\n", Environment.NewLine)}";
}
}
}
@@ -1,210 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="toolStripChat.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="toolStripDossier.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>188, 17</value>
</metadata>
<metadata name="toolStripReports.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>382, 17</value>
</metadata>
<metadata name="toolStripCf.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>578, 17</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
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
</value>
</data>
</root>
@@ -1,12 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<!-- Core/Module sind net8.0-windows (WinForms); damit deren Assemblies sauber laden. -->
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>