Supervisor S-3-Rest + RF-Designer-Konvertierung: Counterfactual, Tagesbericht, UI

RF-UI designerfaehig (Richards Vorgabe, letzter code-only-Altbestand):
- ResolutionFarmingMainForm auf partial + .Designer.cs umgestellt (4 Tabs Kandidaten/
  Positionen/Historie/Settings, alle Controls im Designer; Verhalten unveraendert).

S-3 Counterfactual ('Was waere aus abgelehnten BUYs geworden?'):
- CounterfactualJob (alle 6h, API-gedrosselt): nimmt Rejected-BUY-Entscheidungen mit
  abgelaufenem MarketEndDate aus dem Journal, prueft die Marktaufloesung
  (ICounterfactualResolutionSource; live = Adapter um CheckMarketResolutionAsync) und
  speichert IsWinner + hypothetischen PnL/Share (CounterfactualMath, pur) nach
  sup_counterfactuals (unique je DecisionId -> idempotent). Migration generiert+angewendet.
- Agent-Tool query_counterfactuals + UI-Tab 'Counterfactual' (via Designer).

S-3 Threema-Tagesbericht:
- DailyReportService: taeglich zur konfigurierten Stunde (OPT-IN via
  POLYTRADER_SUPERVISOR_DAILY=0-23) laesst der Agent einen 24h-Kurzbericht erstellen
  (KPIs je Modul, Fehler, Reject-Haeufungen), sendet via Threema und legt ihn als
  sup_report ab. Ohne OpenRouter-Key: stiller Skip mit Log.

Tests: +7 (CounterfactualMath, Job aufgeloest/unaufgeloest/idempotent, Tagesbericht
mit/ohne Key). Build 0 Fehler, 367 Tests gruen, --smoke-ui alle 5 Views gruen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-07-18 11:14:45 +02:00
co-authored by Claude Opus 4.8
parent b7b2a141e3
commit c7668170a4
14 changed files with 1252 additions and 95 deletions
@@ -9,11 +9,11 @@ using PolyTraderSharp;
namespace PolyTrader.Modules.ResolutionFarming.Ui
{
/// <summary>
/// Hauptfenster des ResolutionFarming-Moduls: ein TabControl mit Kandidaten, Positionen,
/// Historie/Statistik und Settings. Bewusst code-only konstruiert (kein Designer/.resx). DB-Zugriffe
/// sind defensiv (try/catch) die UI bleibt bedienbar, auch bevor die rf_-Migration angewendet ist.
/// 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 sealed class ResolutionFarmingMainForm : Form
public partial class ResolutionFarmingMainForm : Form
{
private IRfCandidateRepository? _candidates;
private IRfPositionRepository? _positions;
@@ -21,24 +21,18 @@ namespace PolyTrader.Modules.ResolutionFarming.Ui
private IRfSettingsRepository? _settingsRepo;
private TradingState? _state;
private readonly ComboBox _candAccount = new() { DropDownStyle = ComboBoxStyle.DropDownList, Width = 260 };
private readonly ComboBox _settingsAccount = new() { DropDownStyle = ComboBoxStyle.DropDownList, Width = 260 };
private readonly DataGridView _candGrid = NewGrid();
private readonly DataGridView _posGrid = NewGrid();
private readonly DataGridView _histGrid = NewGrid();
private readonly PropertyGrid _settingsGrid = new() { Dock = DockStyle.Fill };
private readonly Label _histSummary = new() { Dock = DockStyle.Top, Height = 48, Padding = new Padding(6), Text = "" };
private readonly Label _status = new() { Dock = DockStyle.Bottom, Height = 22, Padding = new Padding(6, 2, 6, 2), Text = "" };
private RfSettings? _currentSettings;
public ResolutionFarmingMainForm()
{
Text = "ResolutionFarming";
Width = 1000;
Height = 640;
StartPosition = FormStartPosition.CenterScreen;
BuildUi();
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>
@@ -56,72 +50,6 @@ namespace PolyTrader.Modules.ResolutionFarming.Ui
RefreshHistory();
}
// ---------------- UI-Aufbau ----------------
private static DataGridView NewGrid() => new()
{
Dock = DockStyle.Fill,
ReadOnly = true,
AllowUserToAddRows = false,
AllowUserToDeleteRows = false,
AutoGenerateColumns = true,
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.DisplayedCells,
SelectionMode = DataGridViewSelectionMode.FullRowSelect,
RowHeadersVisible = false
};
private void BuildUi()
{
var tabs = new TabControl { Dock = DockStyle.Fill };
// --- Kandidaten ---
var candTab = new TabPage("Kandidaten");
var candTop = new FlowLayoutPanel { Dock = DockStyle.Top, Height = 34, Padding = new Padding(4) };
candTop.Controls.Add(new Label { Text = "Konto:", AutoSize = true, Padding = new Padding(0, 6, 4, 0) });
candTop.Controls.Add(_candAccount);
var candRefresh = new Button { Text = "Aktualisieren" };
candRefresh.Click += (_, _) => RefreshCandidates();
candTop.Controls.Add(candRefresh);
candTab.Controls.Add(_candGrid);
candTab.Controls.Add(candTop);
_candAccount.SelectedIndexChanged += (_, _) => RefreshCandidates();
// --- Positionen ---
var posTab = new TabPage("Positionen");
var posTop = new FlowLayoutPanel { Dock = DockStyle.Top, Height = 34, Padding = new Padding(4) };
var posRefresh = new Button { Text = "Aktualisieren" };
posRefresh.Click += (_, _) => RefreshPositions();
posTop.Controls.Add(posRefresh);
posTab.Controls.Add(_posGrid);
posTab.Controls.Add(posTop);
// --- Historie / Statistik ---
var histTab = new TabPage("Historie / Statistik");
var histTop = new FlowLayoutPanel { Dock = DockStyle.Top, Height = 34, Padding = new Padding(4) };
var histRefresh = new Button { Text = "Aktualisieren" };
histRefresh.Click += (_, _) => RefreshHistory();
histTop.Controls.Add(histRefresh);
histTab.Controls.Add(_histGrid);
histTab.Controls.Add(_histSummary);
histTab.Controls.Add(histTop);
// --- Settings ---
var setTab = new TabPage("Settings");
var setTop = new FlowLayoutPanel { Dock = DockStyle.Top, Height = 34, Padding = new Padding(4) };
setTop.Controls.Add(new Label { Text = "Konto:", AutoSize = true, Padding = new Padding(0, 6, 4, 0) });
setTop.Controls.Add(_settingsAccount);
var setSave = new Button { Text = "Speichern" };
setSave.Click += (_, _) => SaveSettings();
setTop.Controls.Add(setSave);
setTab.Controls.Add(_settingsGrid);
setTab.Controls.Add(setTop);
_settingsAccount.SelectedIndexChanged += (_, _) => LoadSettings();
tabs.TabPages.AddRange(new[] { candTab, posTab, histTab, setTab });
Controls.Add(tabs);
Controls.Add(_status);
}
// ---------------- Daten ----------------
private void PopulateAccounts()
@@ -132,7 +60,7 @@ namespace PolyTrader.Modules.ResolutionFarming.Ui
.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[] { _candAccount, _settingsAccount })
foreach (var combo in new[] { cbCandAccount, cbSettingsAccount })
{
combo.DisplayMember = nameof(AccountItem.Label);
combo.ValueMember = nameof(AccountItem.Id);
@@ -148,14 +76,14 @@ namespace PolyTrader.Modules.ResolutionFarming.Ui
private void RefreshCandidates()
{
if (_candidates == null || SelectedAccountId(_candAccount) is not int accId) return;
Guarded("Kandidaten", () => _candGrid.DataSource = _candidates.GetRecent(accId, 200));
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", () => _posGrid.DataSource = _positions.GetAllOpen());
Guarded("Positionen", () => dgvPositions.DataSource = _positions.GetAllOpen());
}
private void RefreshHistory()
@@ -164,8 +92,8 @@ namespace PolyTrader.Modules.ResolutionFarming.Ui
Guarded("Historie", () =>
{
var trades = _closed.Find(_ => true);
_histGrid.DataSource = trades;
_histSummary.Text = BuildSummary(trades);
dgvHistory.DataSource = trades;
lblHistSummary.Text = BuildSummary(trades);
});
}
@@ -187,11 +115,11 @@ namespace PolyTrader.Modules.ResolutionFarming.Ui
private void LoadSettings()
{
if (_settingsRepo == null || SelectedAccountId(_settingsAccount) is not int accId) return;
if (_settingsRepo == null || SelectedAccountId(cbSettingsAccount) is not int accId) return;
Guarded("Settings", () =>
{
_currentSettings = _settingsRepo.Get(accId) ?? new RfSettings { AccountId = accId };
_settingsGrid.SelectedObject = _currentSettings;
pgSettings.SelectedObject = _currentSettings;
SetStatus($"Einstellungen für Konto #{accId}.");
});
}
@@ -212,7 +140,7 @@ namespace PolyTrader.Modules.ResolutionFarming.Ui
catch (Exception ex) { SetStatus($"{what}: DB nicht bereit ({ex.Message}). Migration angewendet?"); }
}
private void SetStatus(string text) => _status.Text = text;
private void SetStatus(string text) => lblRfStatus.Text = text;
private sealed record AccountItem(int Id, string Label);
}