UI-U3: server-side min-winrate/min-copyability filters

Add optional minWinRate/minCopyability params to GetTradersAsync so the
leaderboard min-value filters see the full pool instead of being applied
client-side to only the first page of results. Covered by a new
AnalyticsService test (three traders, both filters).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
This commit is contained in:
Richard
2026-07-23 15:56:58 +02:00
parent 02c5a4d6f4
commit d8c204995a
4 changed files with 60 additions and 4 deletions
@@ -101,6 +101,51 @@ public class AnalyticsServiceTests
Assert.Equal(87.5m, deepDive.EntryQuality);
}
[Fact]
public async Task GetTradersAsync_MinFilters_ExcludeBelowThreshold()
{
// Arrange
using var db = CreateDbContext();
var traderRepo = new TraderRepository(db);
var tradeRepo = new TradeRepository(db, NullLogger<TradeRepository>.Instance);
var discoveryMock = new Mock<IDiscoveryService>();
var pnlEngineMock = new Mock<IPositionPnLEngine>();
var alertRepoMock = new Mock<IAlertRepository>();
var watchlistRepoMock = new Mock<IWatchlistRepository>();
watchlistRepoMock.Setup(w => w.GetAllAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<WatchlistEntry>());
var marketRepo = new MarketRepository(db);
var providers = new List<IPlatformProvider>();
var svc = new AnalyticsService(
traderRepo, tradeRepo, alertRepoMock.Object, watchlistRepoMock.Object,
marketRepo, discoveryMock.Object, providers, pnlEngineMock.Object,
NullLogger<AnalyticsService>.Instance);
// Three traders: WinRate 40 / 60 / 80, Copyability 30 / 55 / 90.
db.Traders.Add(new Trader { Id = 1, PlatformUserId = "0xA", DisplayName = "Low", WinRate = 40m,
Analytics = new TraderAnalytics { TraderId = 1, CopytradingCopyabilityScore = 30m } });
db.Traders.Add(new Trader { Id = 2, PlatformUserId = "0xB", DisplayName = "Mid", WinRate = 60m,
Analytics = new TraderAnalytics { TraderId = 2, CopytradingCopyabilityScore = 55m } });
db.Traders.Add(new Trader { Id = 3, PlatformUserId = "0xC", DisplayName = "High", WinRate = 80m,
Analytics = new TraderAnalytics { TraderId = 3, CopytradingCopyabilityScore = 90m } });
await db.SaveChangesAsync();
// Act — minWinRate 55 keeps Mid + High.
var byWinRate = await svc.GetTradersAsync(minWinRate: 55m);
// minCopyability 60 keeps only High.
var byCopyability = await svc.GetTradersAsync(minCopyability: 60m);
// No filter keeps all three.
var unfiltered = await svc.GetTradersAsync();
// Assert
Assert.Equal(2, byWinRate.Count);
Assert.DoesNotContain(byWinRate, t => t.DisplayName == "Low");
Assert.Single(byCopyability);
Assert.Equal("High", byCopyability[0].DisplayName);
Assert.Equal(3, unfiltered.Count);
}
private class MockDiscoveryService : IDiscoveryService
{
public Task<int> ImportTraderAsync(PlatformType platform, string platformUserId, string displayName, bool isAutoDiscovered = true, CancellationToken ct = default)