f512a08772
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.
85 lines
4.0 KiB
C#
85 lines
4.0 KiB
C#
using Marathon.Application.Abstractions;
|
|
using Marathon.Domain.ValueObjects;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace Marathon.UI.Services;
|
|
|
|
/// <summary>
|
|
/// Repository-backed implementation of <see cref="IDashboardSummaryService"/>.
|
|
/// Composes server-side counts (no full-table materialisation) with the top few
|
|
/// anomaly signals and the worker-toggle state. Scoped — captures the per-circuit
|
|
/// repository scope like the other browsing services.
|
|
/// </summary>
|
|
public sealed class DashboardSummaryService : IDashboardSummaryService
|
|
{
|
|
private const int LatestSignalCount = 5;
|
|
|
|
private readonly IEventRepository _events;
|
|
private readonly ISnapshotRepository _snapshots;
|
|
private readonly IAnomalyRepository _anomalies;
|
|
private readonly IAnomalyBrowsingService _anomalyBrowsing;
|
|
private readonly IPaperTradingService _paperTrading;
|
|
private readonly IOptionsMonitor<WorkerOptions> _workers;
|
|
|
|
public DashboardSummaryService(
|
|
IEventRepository events,
|
|
ISnapshotRepository snapshots,
|
|
IAnomalyRepository anomalies,
|
|
IAnomalyBrowsingService anomalyBrowsing,
|
|
IPaperTradingService paperTrading,
|
|
IOptionsMonitor<WorkerOptions> workers)
|
|
{
|
|
_events = events ?? throw new ArgumentNullException(nameof(events));
|
|
_snapshots = snapshots ?? throw new ArgumentNullException(nameof(snapshots));
|
|
_anomalies = anomalies ?? throw new ArgumentNullException(nameof(anomalies));
|
|
_anomalyBrowsing = anomalyBrowsing ?? throw new ArgumentNullException(nameof(anomalyBrowsing));
|
|
_paperTrading = paperTrading ?? throw new ArgumentNullException(nameof(paperTrading));
|
|
_workers = workers ?? throw new ArgumentNullException(nameof(workers));
|
|
}
|
|
|
|
public async Task<DashboardSummary> GetAsync(CancellationToken ct)
|
|
{
|
|
var todayStart = new DateTimeOffset(MoscowTime.Now.Date, MoscowTime.Offset);
|
|
|
|
var eventsTracked = await _events.CountAsync(ct).ConfigureAwait(false);
|
|
var snapshotsToday = await _snapshots.CountSinceAsync(todayStart, ct).ConfigureAwait(false);
|
|
// DateTimeOffset.MinValue lower bound counts every row (year dominates the
|
|
// lexical comparison regardless of offset).
|
|
var anomaliesTotal = await _anomalies.CountSinceAsync(DateTimeOffset.MinValue, ct).ConfigureAwait(false);
|
|
var anomaliesToday = await _anomalies.CountSinceAsync(todayStart, ct).ConfigureAwait(false);
|
|
var sports = await _events.ListDistinctSportCodesAsync(ct).ConfigureAwait(false);
|
|
|
|
var latest = anomaliesTotal == 0
|
|
? (IReadOnlyList<AnomalyListItem>)Array.Empty<AnomalyListItem>()
|
|
: (await _anomalyBrowsing.ListAsync(new AnomalyFilter(), ct).ConfigureAwait(false))
|
|
.Take(LatestSignalCount)
|
|
.ToList();
|
|
|
|
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(
|
|
EventsTracked: eventsTracked,
|
|
SnapshotsToday: snapshotsToday,
|
|
AnomaliesTotal: anomaliesTotal,
|
|
AnomaliesToday: anomaliesToday,
|
|
SportsCovered: sports.Count,
|
|
LatestSignals: latest,
|
|
ScheduleStatus: StageStatus(w.UpcomingPollerEnabled, eventsTracked > 0),
|
|
SnapshotStatus: StageStatus(w.LivePollerEnabled, snapshotsToday > 0),
|
|
DetectorStatus: StageStatus(w.AnomalyDetectionEnabled, anomaliesTotal > 0),
|
|
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
|
|
// not yet producing → warn (waiting), enabled and producing → ok.
|
|
private static string StageStatus(bool enabled, bool producing) =>
|
|
!enabled ? "idle" : producing ? "ok" : "warn";
|
|
}
|