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:
co-authored by
Claude Opus 4.8
parent
b7b2a141e3
commit
c7668170a4
+352
@@ -0,0 +1,352 @@
|
||||
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()
|
||||
{
|
||||
this.tabControlRf = new System.Windows.Forms.TabControl();
|
||||
this.tabKandidaten = new System.Windows.Forms.TabPage();
|
||||
this.dgvCandidates = new System.Windows.Forms.DataGridView();
|
||||
this.pnlCandTop = new System.Windows.Forms.Panel();
|
||||
this.lblCandKonto = new System.Windows.Forms.Label();
|
||||
this.cbCandAccount = new System.Windows.Forms.ComboBox();
|
||||
this.btnCandRefresh = new System.Windows.Forms.Button();
|
||||
this.tabPositionen = new System.Windows.Forms.TabPage();
|
||||
this.dgvPositions = new System.Windows.Forms.DataGridView();
|
||||
this.pnlPosTop = new System.Windows.Forms.Panel();
|
||||
this.btnPosRefresh = new System.Windows.Forms.Button();
|
||||
this.tabHistorie = new System.Windows.Forms.TabPage();
|
||||
this.dgvHistory = new System.Windows.Forms.DataGridView();
|
||||
this.lblHistSummary = new System.Windows.Forms.Label();
|
||||
this.pnlHistTop = new System.Windows.Forms.Panel();
|
||||
this.btnHistRefresh = new System.Windows.Forms.Button();
|
||||
this.tabSettings = new System.Windows.Forms.TabPage();
|
||||
this.pgSettings = new System.Windows.Forms.PropertyGrid();
|
||||
this.pnlSetTop = new System.Windows.Forms.Panel();
|
||||
this.lblSetKonto = new System.Windows.Forms.Label();
|
||||
this.cbSettingsAccount = new System.Windows.Forms.ComboBox();
|
||||
this.btnSettingsSave = new System.Windows.Forms.Button();
|
||||
this.lblRfStatus = new System.Windows.Forms.Label();
|
||||
this.tabControlRf.SuspendLayout();
|
||||
this.tabKandidaten.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dgvCandidates)).BeginInit();
|
||||
this.pnlCandTop.SuspendLayout();
|
||||
this.tabPositionen.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dgvPositions)).BeginInit();
|
||||
this.pnlPosTop.SuspendLayout();
|
||||
this.tabHistorie.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dgvHistory)).BeginInit();
|
||||
this.pnlHistTop.SuspendLayout();
|
||||
this.tabSettings.SuspendLayout();
|
||||
this.pnlSetTop.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// tabControlRf
|
||||
//
|
||||
this.tabControlRf.Controls.Add(this.tabKandidaten);
|
||||
this.tabControlRf.Controls.Add(this.tabPositionen);
|
||||
this.tabControlRf.Controls.Add(this.tabHistorie);
|
||||
this.tabControlRf.Controls.Add(this.tabSettings);
|
||||
this.tabControlRf.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tabControlRf.Location = new System.Drawing.Point(0, 0);
|
||||
this.tabControlRf.Name = "tabControlRf";
|
||||
this.tabControlRf.SelectedIndex = 0;
|
||||
this.tabControlRf.Size = new System.Drawing.Size(1000, 596);
|
||||
this.tabControlRf.TabIndex = 0;
|
||||
//
|
||||
// tabKandidaten
|
||||
//
|
||||
this.tabKandidaten.Controls.Add(this.dgvCandidates);
|
||||
this.tabKandidaten.Controls.Add(this.pnlCandTop);
|
||||
this.tabKandidaten.Location = new System.Drawing.Point(4, 24);
|
||||
this.tabKandidaten.Name = "tabKandidaten";
|
||||
this.tabKandidaten.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tabKandidaten.Size = new System.Drawing.Size(992, 568);
|
||||
this.tabKandidaten.TabIndex = 0;
|
||||
this.tabKandidaten.Text = "Kandidaten";
|
||||
this.tabKandidaten.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// dgvCandidates
|
||||
//
|
||||
this.dgvCandidates.AllowUserToAddRows = false;
|
||||
this.dgvCandidates.AllowUserToDeleteRows = false;
|
||||
this.dgvCandidates.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this.dgvCandidates.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.dgvCandidates.Location = new System.Drawing.Point(3, 37);
|
||||
this.dgvCandidates.Name = "dgvCandidates";
|
||||
this.dgvCandidates.ReadOnly = true;
|
||||
this.dgvCandidates.RowHeadersVisible = false;
|
||||
this.dgvCandidates.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
|
||||
this.dgvCandidates.Size = new System.Drawing.Size(986, 528);
|
||||
this.dgvCandidates.TabIndex = 1;
|
||||
//
|
||||
// pnlCandTop
|
||||
//
|
||||
this.pnlCandTop.Controls.Add(this.btnCandRefresh);
|
||||
this.pnlCandTop.Controls.Add(this.cbCandAccount);
|
||||
this.pnlCandTop.Controls.Add(this.lblCandKonto);
|
||||
this.pnlCandTop.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this.pnlCandTop.Location = new System.Drawing.Point(3, 3);
|
||||
this.pnlCandTop.Name = "pnlCandTop";
|
||||
this.pnlCandTop.Size = new System.Drawing.Size(986, 34);
|
||||
this.pnlCandTop.TabIndex = 0;
|
||||
//
|
||||
// lblCandKonto
|
||||
//
|
||||
this.lblCandKonto.AutoSize = true;
|
||||
this.lblCandKonto.Location = new System.Drawing.Point(6, 9);
|
||||
this.lblCandKonto.Name = "lblCandKonto";
|
||||
this.lblCandKonto.Size = new System.Drawing.Size(43, 15);
|
||||
this.lblCandKonto.TabIndex = 0;
|
||||
this.lblCandKonto.Text = "Konto:";
|
||||
//
|
||||
// cbCandAccount
|
||||
//
|
||||
this.cbCandAccount.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.cbCandAccount.Location = new System.Drawing.Point(55, 5);
|
||||
this.cbCandAccount.Name = "cbCandAccount";
|
||||
this.cbCandAccount.Size = new System.Drawing.Size(260, 23);
|
||||
this.cbCandAccount.TabIndex = 1;
|
||||
//
|
||||
// btnCandRefresh
|
||||
//
|
||||
this.btnCandRefresh.Location = new System.Drawing.Point(325, 4);
|
||||
this.btnCandRefresh.Name = "btnCandRefresh";
|
||||
this.btnCandRefresh.Size = new System.Drawing.Size(110, 26);
|
||||
this.btnCandRefresh.TabIndex = 2;
|
||||
this.btnCandRefresh.Text = "Aktualisieren";
|
||||
this.btnCandRefresh.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// tabPositionen
|
||||
//
|
||||
this.tabPositionen.Controls.Add(this.dgvPositions);
|
||||
this.tabPositionen.Controls.Add(this.pnlPosTop);
|
||||
this.tabPositionen.Location = new System.Drawing.Point(4, 24);
|
||||
this.tabPositionen.Name = "tabPositionen";
|
||||
this.tabPositionen.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tabPositionen.Size = new System.Drawing.Size(992, 568);
|
||||
this.tabPositionen.TabIndex = 1;
|
||||
this.tabPositionen.Text = "Positionen";
|
||||
this.tabPositionen.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// dgvPositions
|
||||
//
|
||||
this.dgvPositions.AllowUserToAddRows = false;
|
||||
this.dgvPositions.AllowUserToDeleteRows = false;
|
||||
this.dgvPositions.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this.dgvPositions.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.dgvPositions.Location = new System.Drawing.Point(3, 37);
|
||||
this.dgvPositions.Name = "dgvPositions";
|
||||
this.dgvPositions.ReadOnly = true;
|
||||
this.dgvPositions.RowHeadersVisible = false;
|
||||
this.dgvPositions.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
|
||||
this.dgvPositions.Size = new System.Drawing.Size(986, 528);
|
||||
this.dgvPositions.TabIndex = 1;
|
||||
//
|
||||
// pnlPosTop
|
||||
//
|
||||
this.pnlPosTop.Controls.Add(this.btnPosRefresh);
|
||||
this.pnlPosTop.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this.pnlPosTop.Location = new System.Drawing.Point(3, 3);
|
||||
this.pnlPosTop.Name = "pnlPosTop";
|
||||
this.pnlPosTop.Size = new System.Drawing.Size(986, 34);
|
||||
this.pnlPosTop.TabIndex = 0;
|
||||
//
|
||||
// btnPosRefresh
|
||||
//
|
||||
this.btnPosRefresh.Location = new System.Drawing.Point(6, 4);
|
||||
this.btnPosRefresh.Name = "btnPosRefresh";
|
||||
this.btnPosRefresh.Size = new System.Drawing.Size(110, 26);
|
||||
this.btnPosRefresh.TabIndex = 0;
|
||||
this.btnPosRefresh.Text = "Aktualisieren";
|
||||
this.btnPosRefresh.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// tabHistorie
|
||||
//
|
||||
this.tabHistorie.Controls.Add(this.dgvHistory);
|
||||
this.tabHistorie.Controls.Add(this.lblHistSummary);
|
||||
this.tabHistorie.Controls.Add(this.pnlHistTop);
|
||||
this.tabHistorie.Location = new System.Drawing.Point(4, 24);
|
||||
this.tabHistorie.Name = "tabHistorie";
|
||||
this.tabHistorie.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tabHistorie.Size = new System.Drawing.Size(992, 568);
|
||||
this.tabHistorie.TabIndex = 2;
|
||||
this.tabHistorie.Text = "Historie / Statistik";
|
||||
this.tabHistorie.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// dgvHistory
|
||||
//
|
||||
this.dgvHistory.AllowUserToAddRows = false;
|
||||
this.dgvHistory.AllowUserToDeleteRows = false;
|
||||
this.dgvHistory.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this.dgvHistory.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.dgvHistory.Location = new System.Drawing.Point(3, 85);
|
||||
this.dgvHistory.Name = "dgvHistory";
|
||||
this.dgvHistory.ReadOnly = true;
|
||||
this.dgvHistory.RowHeadersVisible = false;
|
||||
this.dgvHistory.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
|
||||
this.dgvHistory.Size = new System.Drawing.Size(986, 480);
|
||||
this.dgvHistory.TabIndex = 2;
|
||||
//
|
||||
// lblHistSummary
|
||||
//
|
||||
this.lblHistSummary.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this.lblHistSummary.Location = new System.Drawing.Point(3, 37);
|
||||
this.lblHistSummary.Name = "lblHistSummary";
|
||||
this.lblHistSummary.Padding = new System.Windows.Forms.Padding(6);
|
||||
this.lblHistSummary.Size = new System.Drawing.Size(986, 48);
|
||||
this.lblHistSummary.TabIndex = 1;
|
||||
this.lblHistSummary.Text = "";
|
||||
//
|
||||
// pnlHistTop
|
||||
//
|
||||
this.pnlHistTop.Controls.Add(this.btnHistRefresh);
|
||||
this.pnlHistTop.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this.pnlHistTop.Location = new System.Drawing.Point(3, 3);
|
||||
this.pnlHistTop.Name = "pnlHistTop";
|
||||
this.pnlHistTop.Size = new System.Drawing.Size(986, 34);
|
||||
this.pnlHistTop.TabIndex = 0;
|
||||
//
|
||||
// btnHistRefresh
|
||||
//
|
||||
this.btnHistRefresh.Location = new System.Drawing.Point(6, 4);
|
||||
this.btnHistRefresh.Name = "btnHistRefresh";
|
||||
this.btnHistRefresh.Size = new System.Drawing.Size(110, 26);
|
||||
this.btnHistRefresh.TabIndex = 0;
|
||||
this.btnHistRefresh.Text = "Aktualisieren";
|
||||
this.btnHistRefresh.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// tabSettings
|
||||
//
|
||||
this.tabSettings.Controls.Add(this.pgSettings);
|
||||
this.tabSettings.Controls.Add(this.pnlSetTop);
|
||||
this.tabSettings.Location = new System.Drawing.Point(4, 24);
|
||||
this.tabSettings.Name = "tabSettings";
|
||||
this.tabSettings.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tabSettings.Size = new System.Drawing.Size(992, 568);
|
||||
this.tabSettings.TabIndex = 3;
|
||||
this.tabSettings.Text = "Settings";
|
||||
this.tabSettings.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// pgSettings
|
||||
//
|
||||
this.pgSettings.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.pgSettings.Location = new System.Drawing.Point(3, 37);
|
||||
this.pgSettings.Name = "pgSettings";
|
||||
this.pgSettings.Size = new System.Drawing.Size(986, 528);
|
||||
this.pgSettings.TabIndex = 1;
|
||||
//
|
||||
// pnlSetTop
|
||||
//
|
||||
this.pnlSetTop.Controls.Add(this.btnSettingsSave);
|
||||
this.pnlSetTop.Controls.Add(this.cbSettingsAccount);
|
||||
this.pnlSetTop.Controls.Add(this.lblSetKonto);
|
||||
this.pnlSetTop.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this.pnlSetTop.Location = new System.Drawing.Point(3, 3);
|
||||
this.pnlSetTop.Name = "pnlSetTop";
|
||||
this.pnlSetTop.Size = new System.Drawing.Size(986, 34);
|
||||
this.pnlSetTop.TabIndex = 0;
|
||||
//
|
||||
// lblSetKonto
|
||||
//
|
||||
this.lblSetKonto.AutoSize = true;
|
||||
this.lblSetKonto.Location = new System.Drawing.Point(6, 9);
|
||||
this.lblSetKonto.Name = "lblSetKonto";
|
||||
this.lblSetKonto.Size = new System.Drawing.Size(43, 15);
|
||||
this.lblSetKonto.TabIndex = 0;
|
||||
this.lblSetKonto.Text = "Konto:";
|
||||
//
|
||||
// cbSettingsAccount
|
||||
//
|
||||
this.cbSettingsAccount.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.cbSettingsAccount.Location = new System.Drawing.Point(55, 5);
|
||||
this.cbSettingsAccount.Name = "cbSettingsAccount";
|
||||
this.cbSettingsAccount.Size = new System.Drawing.Size(260, 23);
|
||||
this.cbSettingsAccount.TabIndex = 1;
|
||||
//
|
||||
// btnSettingsSave
|
||||
//
|
||||
this.btnSettingsSave.Location = new System.Drawing.Point(325, 4);
|
||||
this.btnSettingsSave.Name = "btnSettingsSave";
|
||||
this.btnSettingsSave.Size = new System.Drawing.Size(110, 26);
|
||||
this.btnSettingsSave.TabIndex = 2;
|
||||
this.btnSettingsSave.Text = "Speichern";
|
||||
this.btnSettingsSave.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// lblRfStatus
|
||||
//
|
||||
this.lblRfStatus.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.lblRfStatus.Location = new System.Drawing.Point(0, 596);
|
||||
this.lblRfStatus.Name = "lblRfStatus";
|
||||
this.lblRfStatus.Padding = new System.Windows.Forms.Padding(6, 2, 6, 2);
|
||||
this.lblRfStatus.Size = new System.Drawing.Size(1000, 22);
|
||||
this.lblRfStatus.TabIndex = 1;
|
||||
this.lblRfStatus.Text = "";
|
||||
//
|
||||
// ResolutionFarmingMainForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(1000, 618);
|
||||
this.Controls.Add(this.tabControlRf);
|
||||
this.Controls.Add(this.lblRfStatus);
|
||||
this.Name = "ResolutionFarmingMainForm";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.Text = "ResolutionFarming";
|
||||
this.tabControlRf.ResumeLayout(false);
|
||||
this.tabKandidaten.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)(this.dgvCandidates)).EndInit();
|
||||
this.pnlCandTop.ResumeLayout(false);
|
||||
this.pnlCandTop.PerformLayout();
|
||||
this.tabPositionen.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)(this.dgvPositions)).EndInit();
|
||||
this.pnlPosTop.ResumeLayout(false);
|
||||
this.tabHistorie.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)(this.dgvHistory)).EndInit();
|
||||
this.pnlHistTop.ResumeLayout(false);
|
||||
this.tabSettings.ResumeLayout(false);
|
||||
this.pnlSetTop.ResumeLayout(false);
|
||||
this.pnlSetTop.PerformLayout();
|
||||
this.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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ namespace PolyTrader.Modules.Supervisor.Agent
|
||||
IDecisionJournal journal,
|
||||
IOrderEventLog orderEvents,
|
||||
ITradeLogRepository tradeLog,
|
||||
DossierService dossiers)
|
||||
DossierService dossiers,
|
||||
PolyTrader.Modules.Supervisor.Persistence.ISupervisorCounterfactualRepository? counterfactuals = null)
|
||||
{
|
||||
var reg = new SupervisorToolRegistry();
|
||||
string logsDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
|
||||
@@ -197,6 +198,25 @@ namespace PolyTrader.Modules.Supervisor.Agent
|
||||
"""{"type":"object","properties":{}}""",
|
||||
_ => ArchitectureContext.Load()));
|
||||
|
||||
if (counterfactuals != null)
|
||||
{
|
||||
reg.Register(new SupervisorTool(
|
||||
"query_counterfactuals",
|
||||
"Was wäre aus ABGELEHNTEN BUY-Signalen geworden? Liefert nach Marktauflösung ausgewertete Rejects " +
|
||||
"(Reason, Signalpreis, Gewinner ja/nein, hypothetischer PnL je Share) — zeigt, ob Risk-Limits Gewinne oder Verluste verhindert haben.",
|
||||
"""{"type":"object","properties":{"limit":{"type":"integer"}}}""",
|
||||
args =>
|
||||
{
|
||||
int limit = Math.Clamp(SupervisorToolRegistry.GetInt(args, "limit") ?? 200, 1, 500);
|
||||
var rows = counterfactuals.GetRecent(limit).Select(c => new
|
||||
{
|
||||
c.SignalId, c.CheckedAt, c.ModuleName, c.AccountId, market = c.MarketQuestion,
|
||||
reason = c.Reason, price = c.SignalPrice, c.IsWinner, pnlPerShare = c.HypotheticalPnlPerShare
|
||||
});
|
||||
return JsonSerializer.Serialize(rows, JsonOpts);
|
||||
}));
|
||||
}
|
||||
|
||||
return reg;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using PolyTrader.Core.Persistence;
|
||||
using PolyTraderSharp.Models;
|
||||
using PolyTraderSharp.Services;
|
||||
|
||||
namespace PolyTrader.Modules.Supervisor.Counterfactual
|
||||
{
|
||||
/// <summary>
|
||||
/// Auflösungsstatus-Quelle für Counterfactuals. Interface, damit der Job ohne echte
|
||||
/// Polymarket-API testbar ist; die Live-Implementierung wickelt den PolymarketApiService.
|
||||
/// </summary>
|
||||
public interface ICounterfactualResolutionSource
|
||||
{
|
||||
Task<(bool isResolved, bool isWinner)> CheckAsync(string marketSlug, string tokenId, CancellationToken ct);
|
||||
}
|
||||
|
||||
/// <summary>Live-Quelle: dünner Adapter um <see cref="PolymarketApiService.CheckMarketResolutionAsync"/> (read-only).</summary>
|
||||
public sealed class ApiCounterfactualResolutionSource : ICounterfactualResolutionSource
|
||||
{
|
||||
private readonly PolymarketApiService _api;
|
||||
public ApiCounterfactualResolutionSource(PolymarketApiService api) => _api = api;
|
||||
public Task<(bool isResolved, bool isWinner)> CheckAsync(string marketSlug, string tokenId, CancellationToken ct)
|
||||
=> _api.CheckMarketResolutionAsync(marketSlug, tokenId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counterfactual-Job (S-3): nimmt ABGELEHNTE BUY-Entscheidungen aus dem Journal, deren Markt
|
||||
/// inzwischen abgelaufen ist, prüft die Auflösung und speichert den hypothetischen Ausgang
|
||||
/// (sup_counterfactuals; ein Ergebnis je Entscheidung). Läuft alle 6 h, API-schonend gedrosselt.
|
||||
/// </summary>
|
||||
public sealed class CounterfactualJob : BackgroundService
|
||||
{
|
||||
private static readonly TimeSpan Interval = TimeSpan.FromHours(6);
|
||||
private const int BatchLimit = 100; // API-Last je Lauf begrenzen
|
||||
|
||||
private readonly IDecisionJournal _journal;
|
||||
private readonly Persistence.ISupervisorCounterfactualRepository _repo;
|
||||
private readonly ICounterfactualResolutionSource _resolution;
|
||||
private readonly TerminalLogger _logger;
|
||||
|
||||
public CounterfactualJob(IDecisionJournal journal, Persistence.ISupervisorCounterfactualRepository repo,
|
||||
ICounterfactualResolutionSource resolution, TerminalLogger logger)
|
||||
{
|
||||
_journal = journal;
|
||||
_repo = repo;
|
||||
_resolution = resolution;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(2), stoppingToken); // nach den Trading-Services
|
||||
_logger.Info("Counterfactual-Job gestartet (abgelehnte Signale vs. Marktausgang).");
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
int added = await CheckOnceAsync(stoppingToken);
|
||||
if (added > 0)
|
||||
_logger.Info($"🔮 [Counterfactual] {added} abgelehnte Signale nach Auflösung ausgewertet.");
|
||||
}
|
||||
catch (OperationCanceledException) { break; }
|
||||
catch (Exception ex) { _logger.Error($"Counterfactual-Job Fehler: {ex.Message}"); }
|
||||
|
||||
await Task.Delay(Interval, stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Testbarer Kern: einen Prüf-Durchlauf ausführen. Liefert die Anzahl neuer Ergebnisse.</summary>
|
||||
internal async Task<int> CheckOnceAsync(CancellationToken ct)
|
||||
{
|
||||
DateTime now = DateTime.UtcNow;
|
||||
var candidates = _journal.Query(d =>
|
||||
d.Decision == TradeDecision.Rejected &&
|
||||
d.Side == "BUY" &&
|
||||
d.MarketEndDate != null && d.MarketEndDate < now &&
|
||||
d.MarketSlug != "" && d.TokenId != "", BatchLimit * 3)
|
||||
.ToList();
|
||||
if (candidates.Count == 0) return 0;
|
||||
|
||||
var known = _repo.ExistingDecisionIds(candidates.Select(c => c.Id));
|
||||
int added = 0;
|
||||
|
||||
foreach (var d in candidates.Where(c => !known.Contains(c.Id)).Take(BatchLimit))
|
||||
{
|
||||
if (ct.IsCancellationRequested) break;
|
||||
|
||||
(bool isResolved, bool isWinner) result;
|
||||
try { result = await _resolution.CheckAsync(d.MarketSlug, d.TokenId, ct); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"[Counterfactual] Resolution-Check fehlgeschlagen ({d.MarketSlug}): {ex.Message}");
|
||||
continue;
|
||||
}
|
||||
if (!result.isResolved) continue; // noch nicht aufgelöst → späterer Lauf
|
||||
|
||||
_repo.Insert(new CounterfactualRecord
|
||||
{
|
||||
DecisionId = d.Id,
|
||||
SignalId = d.SignalId,
|
||||
ModuleName = d.ModuleName,
|
||||
AccountId = d.AccountId,
|
||||
TokenId = d.TokenId,
|
||||
MarketSlug = d.MarketSlug,
|
||||
MarketQuestion = d.MarketQuestion,
|
||||
Reason = d.Reason.ToString(),
|
||||
SignalPrice = d.SignalPrice,
|
||||
IsWinner = result.isWinner,
|
||||
HypotheticalPnlPerShare = CounterfactualMath.PnlPerShare(d.SignalPrice, result.isWinner)
|
||||
});
|
||||
added++;
|
||||
}
|
||||
return added;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
|
||||
namespace PolyTrader.Modules.Supervisor.Counterfactual
|
||||
{
|
||||
/// <summary>
|
||||
/// Counterfactual-Ergebnis (Tabelle sup_counterfactuals): Was wäre aus einem ABGELEHNTEN
|
||||
/// BUY-Signal geworden, wenn wir gekauft hätten? Wird nach Marktauflösung berechnet —
|
||||
/// die halbe Strategie-Kalibrierung: verhindern unsere Risk-Limits Verluste oder Gewinne?
|
||||
/// </summary>
|
||||
public class CounterfactualRecord
|
||||
{
|
||||
public long Id { get; set; } // DB-Autoincrement
|
||||
public DateTime CheckedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>Journal-Eintrag, auf den sich dieses Ergebnis bezieht (ein Ergebnis je Entscheidung).</summary>
|
||||
public long DecisionId { get; set; }
|
||||
public string SignalId { get; set; } = string.Empty;
|
||||
|
||||
public string ModuleName { get; set; } = string.Empty;
|
||||
public int AccountId { get; set; }
|
||||
public string TokenId { get; set; } = string.Empty;
|
||||
public string MarketSlug { get; set; } = string.Empty;
|
||||
public string MarketQuestion { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Ablehnungsgrund (ReasonCode als String) — die Auswertedimension.</summary>
|
||||
public string Reason { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Signalpreis zum Ablehnungszeitpunkt (Einstieg, den wir NICHT genommen haben).</summary>
|
||||
public decimal SignalPrice { get; set; }
|
||||
|
||||
public bool IsWinner { get; set; }
|
||||
|
||||
/// <summary>Hypothetischer PnL je Share: Gewinner (1 − Preis), Verlierer (−Preis). Ohne Fees.</summary>
|
||||
public decimal HypotheticalPnlPerShare { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>Reine Rechenlogik (getestet): hypothetischer PnL je Share eines nicht genommenen BUYs.</summary>
|
||||
public static class CounterfactualMath
|
||||
{
|
||||
public static decimal PnlPerShare(decimal signalPrice, bool isWinner) =>
|
||||
isWinner ? 1m - signalPrice : -signalPrice;
|
||||
}
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using PolyTrader.Modules.Supervisor.Persistence;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PolyTrader.Modules.Supervisor.Persistence.Migrations
|
||||
{
|
||||
[DbContext(typeof(SupervisorDbContext))]
|
||||
[Migration("20260718091331_AddCounterfactuals")]
|
||||
partial class AddCounterfactuals
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.13")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("PolyTrader.Modules.Supervisor.Counterfactual.CounterfactualRecord", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<int>("AccountId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CheckedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<long>("DecisionId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<decimal>("HypotheticalPnlPerShare")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<bool>("IsWinner")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("MarketQuestion")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("varchar(1000)");
|
||||
|
||||
b.Property<string>("MarketSlug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("varchar(300)");
|
||||
|
||||
b.Property<string>("ModuleName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("varchar(64)");
|
||||
|
||||
b.Property<string>("Reason")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)");
|
||||
|
||||
b.Property<string>("SignalId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(40)
|
||||
.HasColumnType("varchar(40)");
|
||||
|
||||
b.Property<decimal>("SignalPrice")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<string>("TokenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("varchar(120)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CheckedAt");
|
||||
|
||||
b.HasIndex("DecisionId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Reason");
|
||||
|
||||
b.ToTable("sup_counterfactuals", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PolyTrader.Modules.Supervisor.Persistence.SupervisorReport", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("Answer")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("CompletionTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Model")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("varchar(120)");
|
||||
|
||||
b.Property<string>("Profile")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)");
|
||||
|
||||
b.Property<int>("PromptTokens")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Question")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4000)
|
||||
.HasColumnType("varchar(4000)");
|
||||
|
||||
b.Property<int>("ToolCallCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ToolCallsJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.ToTable("sup_reports", (string)null);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PolyTrader.Modules.Supervisor.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddCounterfactuals : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "sup_counterfactuals",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
CheckedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
DecisionId = table.Column<long>(type: "bigint", nullable: false),
|
||||
SignalId = table.Column<string>(type: "varchar(40)", maxLength: 40, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
ModuleName = table.Column<string>(type: "varchar(64)", maxLength: 64, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
AccountId = table.Column<int>(type: "int", nullable: false),
|
||||
TokenId = table.Column<string>(type: "varchar(120)", maxLength: 120, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
MarketSlug = table.Column<string>(type: "varchar(300)", maxLength: 300, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
MarketQuestion = table.Column<string>(type: "varchar(1000)", maxLength: 1000, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Reason = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
SignalPrice = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||
IsWinner = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
HypotheticalPnlPerShare = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_sup_counterfactuals", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_sup_counterfactuals_CheckedAt",
|
||||
table: "sup_counterfactuals",
|
||||
column: "CheckedAt");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_sup_counterfactuals_DecisionId",
|
||||
table: "sup_counterfactuals",
|
||||
column: "DecisionId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_sup_counterfactuals_Reason",
|
||||
table: "sup_counterfactuals",
|
||||
column: "Reason");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "sup_counterfactuals");
|
||||
}
|
||||
}
|
||||
}
|
||||
+70
@@ -22,6 +22,76 @@ namespace PolyTrader.Modules.Supervisor.Persistence.Migrations
|
||||
|
||||
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("PolyTrader.Modules.Supervisor.Counterfactual.CounterfactualRecord", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<int>("AccountId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("CheckedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<long>("DecisionId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<decimal>("HypotheticalPnlPerShare")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<bool>("IsWinner")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("MarketQuestion")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("varchar(1000)");
|
||||
|
||||
b.Property<string>("MarketSlug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("varchar(300)");
|
||||
|
||||
b.Property<string>("ModuleName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("varchar(64)");
|
||||
|
||||
b.Property<string>("Reason")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)");
|
||||
|
||||
b.Property<string>("SignalId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(40)
|
||||
.HasColumnType("varchar(40)");
|
||||
|
||||
b.Property<decimal>("SignalPrice")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<string>("TokenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("varchar(120)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CheckedAt");
|
||||
|
||||
b.HasIndex("DecisionId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Reason");
|
||||
|
||||
b.ToTable("sup_counterfactuals", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PolyTrader.Modules.Supervisor.Persistence.SupervisorReport", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace PolyTrader.Modules.Supervisor.Persistence
|
||||
public SupervisorDbContext(DbContextOptions<SupervisorDbContext> options) : base(options) { }
|
||||
|
||||
public DbSet<SupervisorReport> Reports => Set<SupervisorReport>();
|
||||
public DbSet<Counterfactual.CounterfactualRecord> Counterfactuals => Set<Counterfactual.CounterfactualRecord>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder b)
|
||||
{
|
||||
@@ -28,6 +29,24 @@ namespace PolyTrader.Modules.Supervisor.Persistence
|
||||
e.Property(x => x.ToolCallsJson).HasColumnType("text");
|
||||
e.HasIndex(x => x.CreatedAt);
|
||||
});
|
||||
|
||||
b.Entity<Counterfactual.CounterfactualRecord>(e =>
|
||||
{
|
||||
e.ToTable("sup_counterfactuals");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).ValueGeneratedOnAdd();
|
||||
e.Property(x => x.SignalId).HasMaxLength(40);
|
||||
e.Property(x => x.ModuleName).HasMaxLength(64);
|
||||
e.Property(x => x.TokenId).HasMaxLength(120);
|
||||
e.Property(x => x.MarketSlug).HasMaxLength(300);
|
||||
e.Property(x => x.MarketQuestion).HasMaxLength(1000);
|
||||
e.Property(x => x.Reason).HasMaxLength(50);
|
||||
e.Property(x => x.SignalPrice).HasPrecision(18, 6);
|
||||
e.Property(x => x.HypotheticalPnlPerShare).HasPrecision(18, 6);
|
||||
e.HasIndex(x => x.DecisionId).IsUnique(); // ein Ergebnis je Entscheidung
|
||||
e.HasIndex(x => x.CheckedAt);
|
||||
e.HasIndex(x => x.Reason);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +73,50 @@ namespace PolyTrader.Modules.Supervisor.Persistence
|
||||
List<SupervisorReport> GetRecent(int limit);
|
||||
}
|
||||
|
||||
/// <summary>Counterfactual-Ablage. Write fehlertolerant.</summary>
|
||||
public interface ISupervisorCounterfactualRepository
|
||||
{
|
||||
HashSet<long> ExistingDecisionIds(IEnumerable<long> decisionIds);
|
||||
void Insert(Counterfactual.CounterfactualRecord record);
|
||||
List<Counterfactual.CounterfactualRecord> GetRecent(int limit);
|
||||
}
|
||||
|
||||
public class EfSupervisorCounterfactualRepository : ISupervisorCounterfactualRepository
|
||||
{
|
||||
private readonly IDbContextFactory<SupervisorDbContext> _factory;
|
||||
public EfSupervisorCounterfactualRepository(IDbContextFactory<SupervisorDbContext> factory) => _factory = factory;
|
||||
|
||||
public HashSet<long> ExistingDecisionIds(IEnumerable<long> decisionIds)
|
||||
{
|
||||
var ids = decisionIds.ToList();
|
||||
using var ctx = _factory.CreateDbContext();
|
||||
return ctx.Counterfactuals.AsNoTracking()
|
||||
.Where(c => ids.Contains(c.DecisionId))
|
||||
.Select(c => c.DecisionId)
|
||||
.ToHashSet();
|
||||
}
|
||||
|
||||
public void Insert(Counterfactual.CounterfactualRecord record)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var ctx = _factory.CreateDbContext();
|
||||
ctx.Counterfactuals.Add(record);
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[Counterfactuals] Write fehlgeschlagen (ignoriert): {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public List<Counterfactual.CounterfactualRecord> GetRecent(int limit)
|
||||
{
|
||||
using var ctx = _factory.CreateDbContext();
|
||||
return ctx.Counterfactuals.AsNoTracking().OrderByDescending(c => c.CheckedAt).Take(limit).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public class EfSupervisorReportRepository : ISupervisorReportRepository
|
||||
{
|
||||
private readonly IDbContextFactory<SupervisorDbContext> _factory;
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using PolyTrader.Modules.Supervisor.Agent;
|
||||
using PolyTrader.Modules.Supervisor.Persistence;
|
||||
using PolyTraderSharp.Services;
|
||||
|
||||
namespace PolyTrader.Modules.Supervisor.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Täglicher Supervisor-Bericht (S-3): lässt den Agenten einmal am Tag eine Kurz-Analyse der
|
||||
/// letzten 24 h erstellen (KPIs, Auffälligkeiten, Fehler) und sendet sie via Threema; zusätzlich
|
||||
/// als sup_report gespeichert. OPT-IN: läuft nur, wenn POLYTRADER_SUPERVISOR_DAILY die Ziel-Stunde
|
||||
/// (0–23, lokale Zeit) enthält — und braucht den OpenRouter-Key (sonst still übersprungen).
|
||||
/// </summary>
|
||||
public sealed class DailyReportService : BackgroundService
|
||||
{
|
||||
public const string DailyPrompt =
|
||||
"Erstelle den täglichen Supervisor-Kurzbericht für die letzten 24 Stunden: " +
|
||||
"1) KPIs gesamt und je Modul (get_kpis, sinceDays=1), 2) auffällige Fehler/Warnings " +
|
||||
"(read_logs, level=Error für heute), 3) bemerkenswerte Reject-Häufungen (query_decisions, " +
|
||||
"sinceHours=24, decision=Rejected). Maximal ~1200 Zeichen, deutsch, stichpunktartig, " +
|
||||
"mit klarem Fazit (läuft alles normal / Handlungsbedarf X).";
|
||||
|
||||
private readonly SupervisorAgent _agent;
|
||||
private readonly ISupervisorReportRepository _reports;
|
||||
private readonly Func<string, Task> _sendAsync; // Threema-Versand (injizierbar für Tests)
|
||||
private readonly TerminalLogger _logger;
|
||||
|
||||
public DailyReportService(SupervisorAgent agent, ISupervisorReportRepository reports,
|
||||
ThreemaService threema, TerminalLogger logger)
|
||||
: this(agent, reports, async msg => await threema.SendMessageAsync(msg), logger) { }
|
||||
|
||||
internal DailyReportService(SupervisorAgent agent, ISupervisorReportRepository reports,
|
||||
Func<string, Task> sendAsync, TerminalLogger logger)
|
||||
{
|
||||
_agent = agent;
|
||||
_reports = reports;
|
||||
_sendAsync = sendAsync;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
string? hourRaw = Environment.GetEnvironmentVariable("POLYTRADER_SUPERVISOR_DAILY");
|
||||
if (string.IsNullOrWhiteSpace(hourRaw))
|
||||
{
|
||||
_logger.Info("Supervisor-Tagesbericht: deaktiviert (POLYTRADER_SUPERVISOR_DAILY nicht gesetzt; Wert = Stunde 0–23).");
|
||||
return;
|
||||
}
|
||||
if (!int.TryParse(hourRaw, out int hour) || hour is < 0 or > 23)
|
||||
{
|
||||
_logger.Warning($"Supervisor-Tagesbericht: ungültige Stunde '{hourRaw}' – deaktiviert.");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Info($"Supervisor-Tagesbericht aktiv: täglich um {hour:D2}:00 (lokal).");
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
var next = now.Date.AddHours(hour);
|
||||
if (next <= now) next = next.AddDays(1);
|
||||
try { await Task.Delay(next - now, stoppingToken); }
|
||||
catch (OperationCanceledException) { break; }
|
||||
|
||||
try { await RunOnceAsync(stoppingToken); }
|
||||
catch (Exception ex) { _logger.Error($"Supervisor-Tagesbericht fehlgeschlagen: {ex.Message}"); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Testbarer Kern: erstellt den Bericht, sendet ihn und legt ihn ab.</summary>
|
||||
internal async Task RunOnceAsync(CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(OpenRouterClient.DefaultApiKeyProvider()))
|
||||
{
|
||||
_logger.Info("Supervisor-Tagesbericht: übersprungen (kein OpenRouter-Key).");
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _agent.AskAsync(DailyPrompt, profile: SupervisorProfiles.Allgemein, ct: ct);
|
||||
|
||||
_reports.Insert(new SupervisorReport
|
||||
{
|
||||
Profile = "Tagesbericht",
|
||||
Model = SupervisorAgent.DefaultModel,
|
||||
Question = DailyPrompt,
|
||||
Answer = result.Answer,
|
||||
ToolCallCount = result.ToolInvocations.Count,
|
||||
PromptTokens = result.PromptTokens,
|
||||
CompletionTokens = result.CompletionTokens
|
||||
});
|
||||
|
||||
try { await _sendAsync($"📋 Supervisor-Tagesbericht\n\n{result.Answer}"); }
|
||||
catch (Exception ex) { _logger.Error($"Tagesbericht-Versand fehlgeschlagen: {ex.Message}"); }
|
||||
|
||||
_logger.Info("📋 Supervisor-Tagesbericht erstellt und versendet.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,8 @@ namespace PolyTrader.Modules.Supervisor
|
||||
sp.GetRequiredService<PolyTrader.Core.Persistence.IDecisionJournal>(),
|
||||
sp.GetRequiredService<PolyTrader.Core.Persistence.IOrderEventLog>(),
|
||||
sp.GetRequiredService<PolyTrader.Core.Persistence.ITradeLogRepository>(),
|
||||
sp.GetRequiredService<DossierService>()));
|
||||
sp.GetRequiredService<DossierService>(),
|
||||
sp.GetRequiredService<Persistence.ISupervisorCounterfactualRepository>()));
|
||||
services.AddSingleton<Agent.IChatCompletionClient>(_ =>
|
||||
new Agent.OpenRouterClient(new System.Net.Http.HttpClient { Timeout = TimeSpan.FromMinutes(3) }));
|
||||
services.AddSingleton<Agent.SupervisorAgent>();
|
||||
@@ -40,6 +41,13 @@ namespace PolyTrader.Modules.Supervisor
|
||||
services.AddDbContextFactory<Persistence.SupervisorDbContext>(o =>
|
||||
o.UseMySql(conn, PolyTrader.Core.Configuration.DatabaseServerVersion.Value));
|
||||
services.AddSingleton<Persistence.ISupervisorReportRepository, Persistence.EfSupervisorReportRepository>();
|
||||
services.AddSingleton<Persistence.ISupervisorCounterfactualRepository, Persistence.EfSupervisorCounterfactualRepository>();
|
||||
|
||||
// S-3: Counterfactual-Job (abgelehnte BUYs vs. Marktausgang) + täglicher Threema-Bericht
|
||||
// (OPT-IN via POLYTRADER_SUPERVISOR_DAILY = Stunde 0–23).
|
||||
services.AddSingleton<Counterfactual.ICounterfactualResolutionSource, Counterfactual.ApiCounterfactualResolutionSource>();
|
||||
services.AddHostedService<Counterfactual.CounterfactualJob>();
|
||||
services.AddHostedService<Services.DailyReportService>();
|
||||
|
||||
// S-4: MCP-Light – exponiert die read-only Tool-Registry für externe KI-Clients
|
||||
// (z. B. Claude Code). OPT-IN via POLYTRADER_MCP_PORT, bindet nur 127.0.0.1.
|
||||
|
||||
@@ -46,6 +46,10 @@ namespace PolyTrader.Modules.Supervisor.Ui
|
||||
this.tbReport = new System.Windows.Forms.TextBox();
|
||||
this.toolStripReports = new System.Windows.Forms.ToolStrip();
|
||||
this.btnReportsRefresh = new System.Windows.Forms.ToolStripButton();
|
||||
this.tabCounterfactual = new System.Windows.Forms.TabPage();
|
||||
this.dgvCounterfactuals = new System.Windows.Forms.DataGridView();
|
||||
this.toolStripCf = new System.Windows.Forms.ToolStrip();
|
||||
this.btnCfRefresh = new System.Windows.Forms.ToolStripButton();
|
||||
this.lblStatus = new System.Windows.Forms.Label();
|
||||
this.tabControlSup.SuspendLayout();
|
||||
this.tabAnalyse.SuspendLayout();
|
||||
@@ -65,6 +69,9 @@ namespace PolyTrader.Modules.Supervisor.Ui
|
||||
this.splitReports.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dgvReports)).BeginInit();
|
||||
this.toolStripReports.SuspendLayout();
|
||||
this.tabCounterfactual.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dgvCounterfactuals)).BeginInit();
|
||||
this.toolStripCf.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// tabControlSup
|
||||
@@ -72,6 +79,7 @@ namespace PolyTrader.Modules.Supervisor.Ui
|
||||
this.tabControlSup.Controls.Add(this.tabAnalyse);
|
||||
this.tabControlSup.Controls.Add(this.tabDossiers);
|
||||
this.tabControlSup.Controls.Add(this.tabBerichte);
|
||||
this.tabControlSup.Controls.Add(this.tabCounterfactual);
|
||||
this.tabControlSup.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tabControlSup.Location = new System.Drawing.Point(0, 0);
|
||||
this.tabControlSup.Name = "tabControlSup";
|
||||
@@ -329,6 +337,48 @@ namespace PolyTrader.Modules.Supervisor.Ui
|
||||
this.btnReportsRefresh.Name = "btnReportsRefresh";
|
||||
this.btnReportsRefresh.Text = "Aktualisieren";
|
||||
//
|
||||
// tabCounterfactual
|
||||
//
|
||||
this.tabCounterfactual.Controls.Add(this.dgvCounterfactuals);
|
||||
this.tabCounterfactual.Controls.Add(this.toolStripCf);
|
||||
this.tabCounterfactual.Location = new System.Drawing.Point(4, 24);
|
||||
this.tabCounterfactual.Name = "tabCounterfactual";
|
||||
this.tabCounterfactual.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tabCounterfactual.Size = new System.Drawing.Size(1242, 648);
|
||||
this.tabCounterfactual.TabIndex = 3;
|
||||
this.tabCounterfactual.Text = "Counterfactual";
|
||||
this.tabCounterfactual.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// dgvCounterfactuals
|
||||
//
|
||||
this.dgvCounterfactuals.AllowUserToAddRows = false;
|
||||
this.dgvCounterfactuals.AllowUserToDeleteRows = false;
|
||||
this.dgvCounterfactuals.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this.dgvCounterfactuals.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.dgvCounterfactuals.Location = new System.Drawing.Point(3, 28);
|
||||
this.dgvCounterfactuals.MultiSelect = false;
|
||||
this.dgvCounterfactuals.Name = "dgvCounterfactuals";
|
||||
this.dgvCounterfactuals.ReadOnly = true;
|
||||
this.dgvCounterfactuals.RowHeadersVisible = false;
|
||||
this.dgvCounterfactuals.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
|
||||
this.dgvCounterfactuals.Size = new System.Drawing.Size(1236, 617);
|
||||
this.dgvCounterfactuals.TabIndex = 1;
|
||||
//
|
||||
// toolStripCf
|
||||
//
|
||||
this.toolStripCf.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.btnCfRefresh});
|
||||
this.toolStripCf.Location = new System.Drawing.Point(3, 3);
|
||||
this.toolStripCf.Name = "toolStripCf";
|
||||
this.toolStripCf.Size = new System.Drawing.Size(1236, 25);
|
||||
this.toolStripCf.TabIndex = 0;
|
||||
//
|
||||
// btnCfRefresh
|
||||
//
|
||||
this.btnCfRefresh.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
|
||||
this.btnCfRefresh.Name = "btnCfRefresh";
|
||||
this.btnCfRefresh.Text = "Aktualisieren";
|
||||
//
|
||||
// lblStatus
|
||||
//
|
||||
this.lblStatus.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
@@ -373,6 +423,11 @@ namespace PolyTrader.Modules.Supervisor.Ui
|
||||
((System.ComponentModel.ISupportInitialize)(this.dgvReports)).EndInit();
|
||||
this.toolStripReports.ResumeLayout(false);
|
||||
this.toolStripReports.PerformLayout();
|
||||
this.tabCounterfactual.ResumeLayout(false);
|
||||
this.tabCounterfactual.PerformLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dgvCounterfactuals)).EndInit();
|
||||
this.toolStripCf.ResumeLayout(false);
|
||||
this.toolStripCf.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
}
|
||||
|
||||
@@ -407,6 +462,10 @@ namespace PolyTrader.Modules.Supervisor.Ui
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace PolyTrader.Modules.Supervisor.Ui
|
||||
private DossierService? _dossiers;
|
||||
private SupervisorAgent? _agent;
|
||||
private ISupervisorReportRepository? _reports;
|
||||
private ISupervisorCounterfactualRepository? _counterfactuals;
|
||||
|
||||
public SupervisorMainForm()
|
||||
{
|
||||
@@ -39,6 +40,7 @@ namespace PolyTrader.Modules.Supervisor.Ui
|
||||
dgvSignals.SelectionChanged += (_, _) => OpenSelectedSignal();
|
||||
btnReportsRefresh.Click += (_, _) => LoadReports();
|
||||
dgvReports.SelectionChanged += (_, _) => ShowSelectedReport();
|
||||
btnCfRefresh.Click += (_, _) => LoadCounterfactuals();
|
||||
}
|
||||
|
||||
public void Initialize(IServiceProvider services)
|
||||
@@ -46,8 +48,10 @@ namespace PolyTrader.Modules.Supervisor.Ui
|
||||
_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);
|
||||
}
|
||||
@@ -174,6 +178,21 @@ namespace PolyTrader.Modules.Supervisor.Ui
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 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)
|
||||
|
||||
Reference in New Issue
Block a user