Neues Modul PolyTrader.Modules.Supervisor (IPolyTraderModule, Name=Supervisor, DbPrefix=sup_, nur Core-Referenz, strikt read-only): - DossierBuilder (Core/Analytics, pur+getestet): TradeDossier aus Entscheidungen + Order-Events + Trades + Log-Zeilen, chronologisch, mit Markdown-Rendering (Tabellen, Pipe-Escaping). - DossierService (Modul): beschafft Journal/Events/Trade-Log per SignalId + JSONL-Zeilen per CID (nur Tagesdateien im Ereignis-Zeitfenster +-1 Tag); RecentSignals-Uebersicht (Journal gruppiert). - SupervisorMainForm: Dossier-Browser - links juengste Signale, rechts Markdown-Dossier; SignalId-Suche; Analyse-Chat (OpenRouter) folgt in S-2. In Launcher/Smoke registriert. Journal-Nachverdrahtung (S-0-Vervollstaendigung): - TraderMonitor: Profit-Target erzeugt eigene SignalId -> Leiter + Journal (ProfitTargetTriggered); Stale-Cleanup-Cancels als OrderEvents (StaleCleanupCancel). - StartupOrderReconciliation: K2-Cancels als OrderEvents (StartupReconcileCancel). - RF: Demo-Einstiege (DemoFilled, eigene SignalId) + Resolution-Closes (SystemResolutionClose) im Journal - damit sind ALLE Module im Entscheidungsjournal vertreten. Tests: +3 DossierBuilder; 4 Service-Builder auf neue Ctors. Build 0 Fehler, 344 Tests gruen, --smoke-ui: [OK] supervisor.main (alle 5 Views gruen). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
106 lines
4.3 KiB
C#
106 lines
4.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Windows.Forms;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using PolyTrader.Core.Analytics;
|
|
using PolyTrader.Modules.Supervisor.Services;
|
|
|
|
namespace PolyTrader.Modules.Supervisor.Ui
|
|
{
|
|
/// <summary>
|
|
/// Hauptfenster des Supervisor-Moduls (S-1): Dossier-Browser — links die jüngsten Signale
|
|
/// (aus dem Entscheidungsjournal), rechts das komplette Dossier (Markdown). Der Analyse-Chat
|
|
/// (OpenRouter-Agent) folgt in S-2 als weiterer Tab. Code-only konstruiert (Muster RF-Modul);
|
|
/// DB-Zugriffe defensiv, damit die UI auch bei leerer/nicht erreichbarer DB bedienbar bleibt.
|
|
/// </summary>
|
|
public sealed class SupervisorMainForm : Form
|
|
{
|
|
private DossierService? _dossiers;
|
|
|
|
private readonly ToolStrip _toolStrip = new();
|
|
private readonly ToolStripButton _btnRefresh = new() { Text = "Aktualisieren", DisplayStyle = ToolStripItemDisplayStyle.Text };
|
|
private readonly ToolStripLabel _lblSearch = new() { Text = "SignalId:" };
|
|
private readonly ToolStripTextBox _tbSignalId = new() { AutoSize = false, Width = 220 };
|
|
private readonly ToolStripButton _btnOpen = new() { Text = "Dossier öffnen", DisplayStyle = ToolStripItemDisplayStyle.Text };
|
|
|
|
private readonly SplitContainer _split = new() { Dock = DockStyle.Fill, SplitterDistance = 420 };
|
|
private readonly DataGridView _grid = new()
|
|
{
|
|
Dock = DockStyle.Fill, ReadOnly = true, AllowUserToAddRows = false, AllowUserToDeleteRows = false,
|
|
AutoGenerateColumns = true, SelectionMode = DataGridViewSelectionMode.FullRowSelect,
|
|
RowHeadersVisible = false, MultiSelect = false
|
|
};
|
|
private readonly TextBox _dossierText = new()
|
|
{
|
|
Dock = DockStyle.Fill, Multiline = true, ReadOnly = true, ScrollBars = ScrollBars.Both,
|
|
Font = new System.Drawing.Font("Consolas", 9.5f), WordWrap = false
|
|
};
|
|
private readonly Label _status = new() { Dock = DockStyle.Bottom, Height = 22, Padding = new Padding(6, 2, 6, 2), Text = "" };
|
|
|
|
public SupervisorMainForm()
|
|
{
|
|
Text = "Supervisor";
|
|
Width = 1250;
|
|
Height = 700;
|
|
StartPosition = FormStartPosition.CenterScreen;
|
|
|
|
_toolStrip.Items.AddRange(new ToolStripItem[] { _btnRefresh, new ToolStripSeparator(), _lblSearch, _tbSignalId, _btnOpen });
|
|
_split.Panel1.Controls.Add(_grid);
|
|
_split.Panel2.Controls.Add(_dossierText);
|
|
|
|
Controls.Add(_split);
|
|
Controls.Add(_toolStrip);
|
|
Controls.Add(_status);
|
|
_toolStrip.Dock = DockStyle.Top;
|
|
|
|
_btnRefresh.Click += (_, _) => LoadSignals();
|
|
_btnOpen.Click += (_, _) => OpenDossier(_tbSignalId.Text.Trim());
|
|
_grid.SelectionChanged += (_, _) => OpenSelected();
|
|
}
|
|
|
|
public void Initialize(IServiceProvider services)
|
|
{
|
|
_dossiers = services.GetRequiredService<DossierService>();
|
|
LoadSignals();
|
|
}
|
|
|
|
private void LoadSignals()
|
|
{
|
|
if (_dossiers == null) return;
|
|
try
|
|
{
|
|
List<SignalSummary> signals = _dossiers.RecentSignals(200);
|
|
_grid.DataSource = signals;
|
|
_status.Text = signals.Count == 0
|
|
? "Noch keine Journal-Einträge (Entscheidungen entstehen, sobald Signale verarbeitet werden)."
|
|
: $"{signals.Count} Signale.";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_status.Text = $"Journal nicht lesbar: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
private void OpenSelected()
|
|
{
|
|
if (_grid.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);
|
|
_dossierText.Text = DossierBuilder.ToMarkdown(dossier).Replace("\n", Environment.NewLine);
|
|
_tbSignalId.Text = signalId;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_dossierText.Text = $"Dossier konnte nicht geladen werden: {ex.Message}";
|
|
}
|
|
}
|
|
}
|
|
}
|