feat(home): surface forward-test P&L on the dashboard
Adds a forward-test (paper-trading) net-P&L tile to the Home dashboard, shown only
once the worker has opened or settled any paper bet. Reuses the existing
IPaperTradingService aggregation (settled-only net + open count) so there is one
definition of the figure, and makes the H4 forward-test result visible from the
landing page instead of only its own route.
- DashboardSummary gains Paper{OpenCount,SettledCount,NetProfit,RoiPercent}
(default-valued, so existing constructions are unaffected) + HasPaperTrades;
DashboardSummaryService injects IPaperTradingService; Home tile + en/ru resx.
This commit is contained in:
@@ -24,6 +24,10 @@
|
|||||||
Delta="@AnomaliesDelta"
|
Delta="@AnomaliesDelta"
|
||||||
Anomaly="true" />
|
Anomaly="true" />
|
||||||
<StatCard Label="@L["Home.Stat.Coverage"]" Value="@_summary.SportsCovered.ToString()" />
|
<StatCard Label="@L["Home.Stat.Coverage"]" Value="@_summary.SportsCovered.ToString()" />
|
||||||
|
@if (_summary.HasPaperTrades)
|
||||||
|
{
|
||||||
|
<StatCard Label="@L["Home.Stat.ForwardPnl"]" Value="@PaperNet" Delta="@PaperDelta" />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="m-grid--asym m-rise m-rise-3" style="margin-top: var(--m-space-6);">
|
<div class="m-grid--asym m-rise m-rise-3" style="margin-top: var(--m-space-6);">
|
||||||
@@ -114,6 +118,22 @@
|
|||||||
? string.Format(CultureInfo.CurrentCulture, L["Home.Stat.NewToday"], _summary.AnomaliesToday)
|
? string.Format(CultureInfo.CurrentCulture, L["Home.Stat.NewToday"], _summary.AnomaliesToday)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
// Forward-test headline: settled-only net P&L, with open-bet count as the delta line.
|
||||||
|
private string PaperNet
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_summary.PaperSettledCount == 0) return "—";
|
||||||
|
var v = _summary.PaperNetProfit;
|
||||||
|
var sign = v > 0m ? "+" : (v < 0m ? "-" : "");
|
||||||
|
return sign + Math.Abs(v).ToString("0.00", CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? PaperDelta => _summary.PaperOpenCount > 0
|
||||||
|
? string.Format(CultureInfo.CurrentCulture, L["Home.Stat.ForwardOpen"], _summary.PaperOpenCount)
|
||||||
|
: null;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -74,6 +74,8 @@
|
|||||||
<data name="Home.Stat.Snapshots"><value>Snapshots today</value></data>
|
<data name="Home.Stat.Snapshots"><value>Snapshots today</value></data>
|
||||||
<data name="Home.Stat.Anomalies"><value>Anomalies flagged</value></data>
|
<data name="Home.Stat.Anomalies"><value>Anomalies flagged</value></data>
|
||||||
<data name="Home.Stat.Coverage"><value>Sports covered</value></data>
|
<data name="Home.Stat.Coverage"><value>Sports covered</value></data>
|
||||||
|
<data name="Home.Stat.ForwardPnl"><value>Forward-test P&L</value></data>
|
||||||
|
<data name="Home.Stat.ForwardOpen"><value>{0} open</value></data>
|
||||||
<data name="Home.Section.Latest"><value>Latest signals</value></data>
|
<data name="Home.Section.Latest"><value>Latest signals</value></data>
|
||||||
<data name="Home.Section.Pipeline"><value>Capture pipeline</value></data>
|
<data name="Home.Section.Pipeline"><value>Capture pipeline</value></data>
|
||||||
<data name="Home.Pipeline.Step1"><value>Schedule capture (`/su`)</value></data>
|
<data name="Home.Pipeline.Step1"><value>Schedule capture (`/su`)</value></data>
|
||||||
|
|||||||
@@ -77,6 +77,8 @@
|
|||||||
<data name="Home.Stat.Snapshots"><value>Снимков сегодня</value></data>
|
<data name="Home.Stat.Snapshots"><value>Снимков сегодня</value></data>
|
||||||
<data name="Home.Stat.Anomalies"><value>Аномалий найдено</value></data>
|
<data name="Home.Stat.Anomalies"><value>Аномалий найдено</value></data>
|
||||||
<data name="Home.Stat.Coverage"><value>Видов спорта</value></data>
|
<data name="Home.Stat.Coverage"><value>Видов спорта</value></data>
|
||||||
|
<data name="Home.Stat.ForwardPnl"><value>P&L форвард-теста</value></data>
|
||||||
|
<data name="Home.Stat.ForwardOpen"><value>{0} открытых</value></data>
|
||||||
<data name="Home.Section.Latest"><value>Свежий поток</value></data>
|
<data name="Home.Section.Latest"><value>Свежий поток</value></data>
|
||||||
<data name="Home.Section.Pipeline"><value>Конвейер сбора</value></data>
|
<data name="Home.Section.Pipeline"><value>Конвейер сбора</value></data>
|
||||||
<data name="Home.Pipeline.Step1"><value>Сбор расписания (`/su`)</value></data>
|
<data name="Home.Pipeline.Step1"><value>Сбор расписания (`/su`)</value></data>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public sealed class DashboardSummaryService : IDashboardSummaryService
|
|||||||
private readonly ISnapshotRepository _snapshots;
|
private readonly ISnapshotRepository _snapshots;
|
||||||
private readonly IAnomalyRepository _anomalies;
|
private readonly IAnomalyRepository _anomalies;
|
||||||
private readonly IAnomalyBrowsingService _anomalyBrowsing;
|
private readonly IAnomalyBrowsingService _anomalyBrowsing;
|
||||||
|
private readonly IPaperTradingService _paperTrading;
|
||||||
private readonly IOptionsMonitor<WorkerOptions> _workers;
|
private readonly IOptionsMonitor<WorkerOptions> _workers;
|
||||||
|
|
||||||
public DashboardSummaryService(
|
public DashboardSummaryService(
|
||||||
@@ -25,12 +26,14 @@ public sealed class DashboardSummaryService : IDashboardSummaryService
|
|||||||
ISnapshotRepository snapshots,
|
ISnapshotRepository snapshots,
|
||||||
IAnomalyRepository anomalies,
|
IAnomalyRepository anomalies,
|
||||||
IAnomalyBrowsingService anomalyBrowsing,
|
IAnomalyBrowsingService anomalyBrowsing,
|
||||||
|
IPaperTradingService paperTrading,
|
||||||
IOptionsMonitor<WorkerOptions> workers)
|
IOptionsMonitor<WorkerOptions> workers)
|
||||||
{
|
{
|
||||||
_events = events ?? throw new ArgumentNullException(nameof(events));
|
_events = events ?? throw new ArgumentNullException(nameof(events));
|
||||||
_snapshots = snapshots ?? throw new ArgumentNullException(nameof(snapshots));
|
_snapshots = snapshots ?? throw new ArgumentNullException(nameof(snapshots));
|
||||||
_anomalies = anomalies ?? throw new ArgumentNullException(nameof(anomalies));
|
_anomalies = anomalies ?? throw new ArgumentNullException(nameof(anomalies));
|
||||||
_anomalyBrowsing = anomalyBrowsing ?? throw new ArgumentNullException(nameof(anomalyBrowsing));
|
_anomalyBrowsing = anomalyBrowsing ?? throw new ArgumentNullException(nameof(anomalyBrowsing));
|
||||||
|
_paperTrading = paperTrading ?? throw new ArgumentNullException(nameof(paperTrading));
|
||||||
_workers = workers ?? throw new ArgumentNullException(nameof(workers));
|
_workers = workers ?? throw new ArgumentNullException(nameof(workers));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +57,9 @@ public sealed class DashboardSummaryService : IDashboardSummaryService
|
|||||||
|
|
||||||
var w = _workers.CurrentValue;
|
var w = _workers.CurrentValue;
|
||||||
|
|
||||||
|
// Reuse the forward-test aggregation (settled-only P&L) for the headline tile.
|
||||||
|
var paper = await _paperTrading.GetAsync(ct).ConfigureAwait(false);
|
||||||
|
|
||||||
return new DashboardSummary(
|
return new DashboardSummary(
|
||||||
EventsTracked: eventsTracked,
|
EventsTracked: eventsTracked,
|
||||||
SnapshotsToday: snapshotsToday,
|
SnapshotsToday: snapshotsToday,
|
||||||
@@ -64,7 +70,11 @@ public sealed class DashboardSummaryService : IDashboardSummaryService
|
|||||||
ScheduleStatus: StageStatus(w.UpcomingPollerEnabled, eventsTracked > 0),
|
ScheduleStatus: StageStatus(w.UpcomingPollerEnabled, eventsTracked > 0),
|
||||||
SnapshotStatus: StageStatus(w.LivePollerEnabled, snapshotsToday > 0),
|
SnapshotStatus: StageStatus(w.LivePollerEnabled, snapshotsToday > 0),
|
||||||
DetectorStatus: StageStatus(w.AnomalyDetectionEnabled, anomaliesTotal > 0),
|
DetectorStatus: StageStatus(w.AnomalyDetectionEnabled, anomaliesTotal > 0),
|
||||||
ExportStatus: eventsTracked > 0 ? "ok" : "idle");
|
ExportStatus: eventsTracked > 0 ? "ok" : "idle",
|
||||||
|
PaperOpenCount: paper.OpenCount,
|
||||||
|
PaperSettledCount: paper.SettledCount,
|
||||||
|
PaperNetProfit: paper.NetProfit,
|
||||||
|
PaperRoiPercent: paper.RoiPercent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maps a worker stage to the PipelineStep token: disabled → idle, enabled but
|
// Maps a worker stage to the PipelineStep token: disabled → idle, enabled but
|
||||||
|
|||||||
@@ -25,8 +25,15 @@ public sealed record DashboardSummary(
|
|||||||
string ScheduleStatus,
|
string ScheduleStatus,
|
||||||
string SnapshotStatus,
|
string SnapshotStatus,
|
||||||
string DetectorStatus,
|
string DetectorStatus,
|
||||||
string ExportStatus)
|
string ExportStatus,
|
||||||
|
int PaperOpenCount = 0,
|
||||||
|
int PaperSettledCount = 0,
|
||||||
|
decimal PaperNetProfit = 0m,
|
||||||
|
decimal? PaperRoiPercent = null)
|
||||||
{
|
{
|
||||||
|
/// <summary>True once the forward-test worker has opened or settled any paper bet.</summary>
|
||||||
|
public bool HasPaperTrades => PaperOpenCount > 0 || PaperSettledCount > 0;
|
||||||
|
|
||||||
/// <summary>True once anything has been captured — gates the welcome/empty state.</summary>
|
/// <summary>True once anything has been captured — gates the welcome/empty state.</summary>
|
||||||
public bool HasAnyData => EventsTracked > 0 || AnomaliesTotal > 0;
|
public bool HasAnyData => EventsTracked > 0 || AnomaliesTotal > 0;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user