UI Slice 4: Launcher-Live-Widgets + VS-Re-Serialisierungs-Regressionen behoben
Slice 4 - Launcher-Live-Ueberblick (LauncherWidgetsPanel, isoliertes UserControl, rechts angedockt, 30s-Refresh; minimaler Eingriff ins von Richard bearbeitete Launcher-Designer): - Modul-PnL/Winrate-Kacheln (je Modul + Gesamt: Heute/7T/30T, gruen/rot) via TradeAnalytics. - Supervisor-KI-Kurzfassung (letzter sup_report). - Warnungen & Fehler (heutige JSONL-Logs, Error/Warning). - Auffaellige Trades (24h, nach |PnL| sortiert). Regressionen aus VS-Re-Serialisierung behoben (VS liess hand-erstellte DataGridView-Spalten fallen -> col* null -> NRE beim Oeffnen): - DashboardView (dgvTrades): Spalten-Instanziierung + AutoGenerateColumns=false + Columns.AddRange + Spalten-Konfig wiederhergestellt. - JobsView (dgvJobs): dito (nur Button-Spalte hatte ueberlebt). - Smoke-UI dauerhaft um JobsView/TerminalView/SettingsView erweitert -> faengt diese Regressionsklasse kuenftig ab. Enthaelt ausserdem Richards zwischenzeitliche UI-Arbeit (Launcher-Icons cross_reference/emotion_batman/ file_start_workflow, Designer-Re-Serialisierungen, .ico-Sammlung, Modul-Form-.resx). Persoenliche Notizdatei bewusst NICHT committet. Build 0 Fehler, 396 Tests gruen, --smoke-ui alle 9 Views/Forms gruen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
936144c318
commit
3c8ea3534e
@@ -0,0 +1,205 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using PolyTrader.Core.Analytics;
|
||||
using PolyTrader.Core.Persistence;
|
||||
using PolyTrader.Modules.Supervisor.Persistence;
|
||||
using PolyTraderSharp.Models;
|
||||
using PolyTraderSharp.Services;
|
||||
|
||||
namespace PolyTraderSharp.Ui
|
||||
{
|
||||
/// <summary>
|
||||
/// Live-Überblick-Widgets für den Launcher (Slice 4): Modul-PnL/Winrate-Kacheln, Supervisor-KI-
|
||||
/// Kurzfassung, Warnungen/Fehler aus den Logs und auffällige Trades. Isoliert als UserControl,
|
||||
/// damit der Launcher-Designer schlank bleibt. Alle Datenzugriffe defensiv (Widgets dürfen den
|
||||
/// Launcher nie brechen). Layout im Designer (LauncherWidgetsPanel.Designer.cs).
|
||||
/// </summary>
|
||||
public partial class LauncherWidgetsPanel : UserControl
|
||||
{
|
||||
private ITradeLogRepository? _tradeLog;
|
||||
private ISupervisorReportRepository? _reports;
|
||||
private readonly string _logsDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
|
||||
|
||||
public LauncherWidgetsPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
colNotPnl.DefaultCellStyle.Format = "F2";
|
||||
colNotPnlPct.DefaultCellStyle.Format = "F1";
|
||||
}
|
||||
|
||||
public void Initialize(IServiceProvider services)
|
||||
{
|
||||
_tradeLog = services.GetService<ITradeLogRepository>();
|
||||
_reports = services.GetService<ISupervisorReportRepository>();
|
||||
RefreshData();
|
||||
}
|
||||
|
||||
/// <summary>Aktualisiert alle Widgets. Wird vom Launcher periodisch aufgerufen.</summary>
|
||||
public void RefreshData()
|
||||
{
|
||||
if (IsDisposed) return;
|
||||
UpdateModuleKpis();
|
||||
UpdateAlerts();
|
||||
UpdateNotable();
|
||||
UpdateSupervisor();
|
||||
}
|
||||
|
||||
// ----- Modul-PnL/Winrate-Kacheln -----
|
||||
|
||||
private void UpdateModuleKpis()
|
||||
{
|
||||
flpKpis.SuspendLayout();
|
||||
flpKpis.Controls.Clear();
|
||||
try
|
||||
{
|
||||
var since = DateTime.UtcNow.AddDays(-30);
|
||||
List<TradeRecord> all = _tradeLog?.Find(t => t.ClosedAt >= since) ?? new List<TradeRecord>();
|
||||
|
||||
DateTime today = DateTime.UtcNow.Date;
|
||||
DateTime week = DateTime.UtcNow.AddDays(-7);
|
||||
|
||||
foreach (var module in all.Select(t => t.ModuleName).Where(m => !string.IsNullOrEmpty(m)).Distinct().OrderBy(m => m))
|
||||
flpKpis.Controls.Add(BuildTile(module, all.Where(t => t.ModuleName == module).ToList(), today, week));
|
||||
|
||||
flpKpis.Controls.Add(BuildTile("Gesamt", all, today, week));
|
||||
|
||||
if (flpKpis.Controls.Count == 1) // nur "Gesamt", keine Trades
|
||||
flpKpis.Controls.Add(new Label { AutoSize = true, Margin = new Padding(6), ForeColor = Color.Gray, Text = "Noch keine abgeschlossenen Trades." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
flpKpis.Controls.Add(new Label { AutoSize = true, ForeColor = Color.Firebrick, Text = $"KPIs n/v: {ex.Message}" });
|
||||
}
|
||||
flpKpis.ResumeLayout();
|
||||
}
|
||||
|
||||
private static Label BuildTile(string module, List<TradeRecord> moduleTrades, DateTime today, DateTime week)
|
||||
{
|
||||
decimal todayPnl = moduleTrades.Where(t => t.ClosedAt >= today).Sum(t => t.RealizedPnl);
|
||||
var k7 = TradeAnalytics.ComputeKpis(moduleTrades.Where(t => t.ClosedAt >= week));
|
||||
var k30 = TradeAnalytics.ComputeKpis(moduleTrades);
|
||||
|
||||
var tile = new Label
|
||||
{
|
||||
AutoSize = false,
|
||||
Width = 252,
|
||||
Height = 92,
|
||||
BorderStyle = BorderStyle.FixedSingle,
|
||||
Margin = new Padding(3),
|
||||
Padding = new Padding(7),
|
||||
TextAlign = ContentAlignment.TopLeft,
|
||||
Font = new Font("Segoe UI", 9F),
|
||||
Text = $"{module}\n" +
|
||||
$"Heute: {todayPnl:+0.00;-0.00} USDC\n" +
|
||||
$"7T: {k7.NetPnl:+0.00;-0.00} · {k7.WinRatePct:0}% · {k7.TradeCount} Tr.\n" +
|
||||
$"30T: {k30.NetPnl:+0.00;-0.00} · {k30.WinRatePct:0}%"
|
||||
};
|
||||
tile.ForeColor = k30.NetPnl >= 0 ? Color.ForestGreen : Color.Firebrick;
|
||||
return tile;
|
||||
}
|
||||
|
||||
// ----- Warnungen & Fehler (heute, JSONL) -----
|
||||
|
||||
private void UpdateAlerts()
|
||||
{
|
||||
try
|
||||
{
|
||||
string path = Path.Combine(_logsDir, $"{DateTime.Now:yyyy-MM-dd}.jsonl");
|
||||
var rows = new List<AlertRow>();
|
||||
if (File.Exists(path))
|
||||
{
|
||||
foreach (var line in File.ReadLines(path))
|
||||
{
|
||||
var p = LogJson.ParseLine(line);
|
||||
if (p == null) continue;
|
||||
if (p.Level != "Error" && p.Level != "Warning") continue;
|
||||
rows.Add(new AlertRow { Time = ShortTime(p.Time), Level = p.Level, Message = OneLine(p.Message) });
|
||||
}
|
||||
}
|
||||
rows.Reverse(); // neueste zuerst
|
||||
dgvAlerts.DataSource = rows.Take(200).ToList();
|
||||
grpAlerts.Text = $"Warnungen & Fehler (heute): {rows.Count}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
grpAlerts.Text = $"Warnungen & Fehler – n/v ({ex.Message})";
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Auffällige Trades (24h) -----
|
||||
|
||||
private void UpdateNotable()
|
||||
{
|
||||
try
|
||||
{
|
||||
var since = DateTime.UtcNow.AddDays(-1);
|
||||
var rows = (_tradeLog?.Find(t => t.ClosedAt >= since) ?? new List<TradeRecord>())
|
||||
.OrderByDescending(t => Math.Abs(t.RealizedPnl))
|
||||
.Take(30)
|
||||
.Select(t => new NotableRow
|
||||
{
|
||||
Module = t.ModuleName,
|
||||
Market = t.MarketQuestion,
|
||||
Pnl = t.RealizedPnl,
|
||||
PnlPct = t.PnlPercent
|
||||
})
|
||||
.ToList();
|
||||
dgvNotable.DataSource = rows;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
grpNotable.Text = $"Auffällige Trades – n/v ({ex.Message})";
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Supervisor-KI-Kurzfassung -----
|
||||
|
||||
private void UpdateSupervisor()
|
||||
{
|
||||
if (_reports == null)
|
||||
{
|
||||
rtbSupervisor.Text = "Supervisor-Modul nicht verfügbar.";
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
var last = _reports.GetRecent(1).FirstOrDefault();
|
||||
rtbSupervisor.Text = last == null
|
||||
? "Noch kein Supervisor-Bericht. (OpenRouter-Key setzen und im Supervisor-Fenster eine Analyse starten.)"
|
||||
: $"[{last.CreatedAt:dd.MM. HH:mm}] {last.Profile} · {last.Model}\n\n{last.Answer}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
rtbSupervisor.Text = $"Supervisor-Berichte n/v: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Helfer -----
|
||||
|
||||
private static string ShortTime(string iso) =>
|
||||
DateTime.TryParse(iso, out var dt) ? dt.ToString("HH:mm:ss") : iso;
|
||||
|
||||
private static string OneLine(string s) =>
|
||||
(s ?? string.Empty).Replace("\r", " ").Replace("\n", " ").Trim();
|
||||
|
||||
private sealed class AlertRow
|
||||
{
|
||||
public string Time { get; set; } = string.Empty;
|
||||
public string Level { get; set; } = string.Empty;
|
||||
public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
private sealed class NotableRow
|
||||
{
|
||||
public string Module { get; set; } = string.Empty;
|
||||
public string Market { get; set; } = string.Empty;
|
||||
public decimal Pnl { get; set; }
|
||||
public decimal PnlPct { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user