Das WebGUI konnte bisher Dashboard, Konten, Jobs, Logs und Einstellungen.
Die vier Strategie- und Analysemodule waren nur in der Avalonia-Oberflaeche
erreichbar - wer den Server aus der Ferne bediente, sah sie nicht.
Fuenf neue Endpunktgruppen holen sie herein:
/api/analytics KPIs, Equity-Kurve, PnL je Modul und Tag,
durchsuchbare Trade-Historie mit Seitenlauf
/api/modules/copytrading Master-Trader (CRUD), offene und
geschlossene Trades, Risikolimits je Konto
/api/modules/resolutionfarming Scanner-Kandidaten, Positionen, Historie,
Farming-Parameter je Konto
/api/modules/supervisor Profile, Agentenfragen, Berichtsarchiv,
Signal-Dossiers, Counterfactuals
/api/modules/accounting BWA-Periodenabrechnung, Ledger-Journal,
PDF- und CSV-Export, Ingest-Laeufe
Dazu im Bestand:
- Dashboard bekommt /overview-widgets: Modul-KPIs (heute/7T/30T), auffaellige
Trades der letzten 24 Stunden, Warnungen und Fehler des Tages, den letzten
Supervisor-Bericht.
- Die Kontenliste zeigt 3-Tage-PnL, Winrate, Gesamtergebnis und die
beteiligten Module statt nur Salden.
- Jobs lassen sich ueber /toggle und /state ein- und ausschalten, nicht nur
ausloesen.
Die Oberflaeche bleibt ohne Fremdbibliotheken: alle Diagramme sind Inline-SVG,
kein CDN, kein npm. Alle neuen Pfade liegen unter /api/ und damit hinter der
bestehenden Token-Pruefung.
Zwei Fehler nebenbei behoben:
- AccountingReportService.ResolveCurrency lief in eine NullReferenceException,
wenn der FX-Dienst nicht gesetzt war. Betrifft jeden EUR-Aufruf ohne
geladene EZB-Kurse.
- Ein Master-Trader, dessen Name ein Apostroph enthaelt, hatte einen toten
Loeschen-Knopf: escapeHtml laesst ' unberuehrt, und im onclick-Attribut
beendete das Zeichen das JavaScript-Literal vorzeitig. Laengerer Text mit
Anfuehrungszeichen konnte darueber eigenen Code einschleusen. Neu setzt
jsArg() den Wert als JSON-Literal ein; geprueft mit
O'Brien "quote" <b>bold</b> \ backslash.
McpLightServerTests weicht jetzt aus, wenn HttpListener mangels URL-ACL nicht
binden darf, statt den Lauf scheitern zu lassen.
Geprueft auf der Linux-Test-VM (Ubuntu 26.04, .NET 10): Build ohne Fehler,
533 von 533 Tests bestanden, alle 11 Ansichten und 15 Unteransichten geladen,
alle Endpunkte mit 200 beantwortet, Schreibwege bis in die Datenbank
nachverfolgt, PDF- und CSV-Export gueltig, SSE-Logstrom live.
Offene Befunde aus der Abnahme liegen als #532 (Kopfleiste laeuft unter
1623 px ueber), #533 (EUR-PDF ohne EZB-Kurse liefert Nullbetraege) und #534
(Einstellungen melden Erfolg trotz gescheitertem Speichern) im Bugtracker.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
100 lines
3.4 KiB
C#
100 lines
3.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using PolyTrader.Core.Analytics;
|
|
using PolyTrader.Web.Endpoints;
|
|
using PolyTraderSharp;
|
|
using PolyTraderSharp.Models;
|
|
using PolyTraderSharp.Services;
|
|
using Xunit;
|
|
|
|
namespace PolyTrader.Tests.WebUi
|
|
{
|
|
public class DashboardAndJobsEndpointsTests
|
|
{
|
|
[Fact]
|
|
public void JobStatusRow_ToggleIsEnabled_WorksProperly()
|
|
{
|
|
var job = new JobStatusRow
|
|
{
|
|
JobName = "TestJob",
|
|
IsEnabled = true
|
|
};
|
|
|
|
job.IsEnabled = !job.IsEnabled;
|
|
Assert.False(job.IsEnabled);
|
|
|
|
job.IsEnabled = !job.IsEnabled;
|
|
Assert.True(job.IsEnabled);
|
|
}
|
|
|
|
[Fact]
|
|
public void AccountOverviewRow_CalculatesPerformanceCorrectly()
|
|
{
|
|
var trades = new List<TradeRecord>
|
|
{
|
|
new TradeRecord
|
|
{
|
|
AccountId = 1,
|
|
ModuleName = "CopyTrading",
|
|
MarketQuestion = "Test Market 1",
|
|
RealizedPnl = 50m,
|
|
ClosedAt = DateTime.UtcNow.AddDays(-1)
|
|
},
|
|
new TradeRecord
|
|
{
|
|
AccountId = 1,
|
|
ModuleName = "CopyTrading",
|
|
MarketQuestion = "Test Market 2",
|
|
RealizedPnl = -20m,
|
|
ClosedAt = DateTime.UtcNow.AddDays(-2)
|
|
},
|
|
new TradeRecord
|
|
{
|
|
AccountId = 1,
|
|
ModuleName = "ResolutionFarming",
|
|
MarketQuestion = "Old Market",
|
|
RealizedPnl = 100m,
|
|
ClosedAt = DateTime.UtcNow.AddDays(-10)
|
|
}
|
|
};
|
|
|
|
var since3d = DateTime.UtcNow.AddDays(-3);
|
|
var (pnl3d, win3d, count3d) = TradeAnalytics.WindowSummary(trades.Where(t => t.ClosedAt >= since3d));
|
|
|
|
Assert.Equal(30m, pnl3d);
|
|
Assert.Equal(50m, win3d); // 1 out of 2 trades won
|
|
Assert.Equal(2, count3d);
|
|
|
|
decimal overallPnl = trades.Sum(t => t.RealizedPnl);
|
|
Assert.Equal(130m, overallPnl);
|
|
|
|
string modules = trades
|
|
.Select(t => t.ModuleName)
|
|
.Where(m => !string.IsNullOrEmpty(m))
|
|
.Distinct().OrderBy(m => m)
|
|
.Aggregate((a, b) => a + ", " + b);
|
|
|
|
Assert.Equal("CopyTrading, ResolutionFarming", modules);
|
|
}
|
|
|
|
[Fact]
|
|
public void TradeAnalytics_ComputeKpis_HandlesModuleSet()
|
|
{
|
|
var trades = new List<TradeRecord>
|
|
{
|
|
new TradeRecord { ModuleName = "CopyTrading", RealizedPnl = 25m, ClosedAt = DateTime.UtcNow.AddDays(-2) },
|
|
new TradeRecord { ModuleName = "CopyTrading", RealizedPnl = -10m, ClosedAt = DateTime.UtcNow.AddDays(-1) },
|
|
new TradeRecord { ModuleName = "CopyTrading", RealizedPnl = 15m, ClosedAt = DateTime.UtcNow.Date }
|
|
};
|
|
|
|
var kpis = TradeAnalytics.ComputeKpis(trades);
|
|
Assert.Equal(3, kpis.TradeCount);
|
|
Assert.Equal(30m, kpis.NetPnl);
|
|
Assert.True(kpis.WinRatePct > 60m);
|
|
Assert.Equal(40m / 10m, kpis.ProfitFactor); // gross win 40 / gross loss 10 = 4.0
|
|
}
|
|
}
|
|
}
|