Implement B4: Add Predictalytics.Application.Tests project with unit tests for PositionPnLEngine and AnalyticsService

This commit is contained in:
Richard
2026-07-03 11:22:43 +02:00
parent f8c8230d99
commit 7a44914d9d
7 changed files with 459 additions and 1 deletions
@@ -0,0 +1,104 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using Predictalytics.Application.Interfaces;
using Predictalytics.Application.Services;
using Predictalytics.Domain.Entities;
using Predictalytics.Domain.Enums;
using Predictalytics.Domain.Interfaces;
using Predictalytics.Infrastructure.Data;
using Predictalytics.Infrastructure.Data.Repositories;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace Predictalytics.Application.Tests.Services;
public class AnalyticsServiceTests
{
private AppDbContext CreateDbContext()
{
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
return new AppDbContext(options);
}
[Fact]
public async Task GetTraderDeepDiveAsync_CalculatesCorrectMetrics()
{
// Arrange
using var db = CreateDbContext();
var traderRepo = new TraderRepository(db);
var tradeRepo = new TradeRepository(db);
var marketRepo = new MarketRepository(db);
var discoveryMock = new MockDiscoveryService();
var providers = new List<IPlatformProvider>();
var analyticsService = new AnalyticsService(
traderRepo,
tradeRepo,
null!, // alertRepo (not used in GetTraderDeepDiveAsync)
null!, // watchlistRepo (not used in GetTraderDeepDiveAsync)
marketRepo,
discoveryMock,
providers,
NullLogger<AnalyticsService>.Instance
);
var trader = new Trader { Id = 1, PlatformUserId = "0x1", DisplayName = "Trader 1" };
db.Traders.Add(trader);
var market = new Market { Id = 10, PlatformMarketId = "pm1", Question = "Q?" };
var outcome = new MarketOutcome { Id = 100, MarketId = 10, Label = "Yes", TokenId = "t100", CurrentPrice = 0.50m };
market.Outcomes.Add(outcome);
db.Markets.Add(market);
var baseTime = DateTime.UtcNow.AddDays(-5);
db.Trades.Add(new Trade
{
Id = 501, TraderId = 1, DbMarketId = 10, MarketOutcomeId = 100,
Side = TradeSide.Buy, Price = 0.40m, Size = 100m, Amount = 40m,
ExecutedAt = baseTime
});
db.Trades.Add(new Trade
{
Id = 502, TraderId = 1, DbMarketId = 10, MarketOutcomeId = 100,
Side = TradeSide.Sell, Price = 0.60m, Size = 100m, Amount = 60m,
ExecutedAt = baseTime.AddHours(24) // 24 hours holding duration
});
db.MarketOutcomePriceSnapshots.Add(new MarketOutcomePriceSnapshot
{
Id = 1, MarketOutcomeId = 100, Price = 0.50m, Timestamp = baseTime.AddHours(2)
});
db.MarketOutcomePriceSnapshots.Add(new MarketOutcomePriceSnapshot
{
Id = 2, MarketOutcomeId = 100, Price = 0.60m, Timestamp = baseTime.AddHours(12)
});
await db.SaveChangesAsync();
// Act
var deepDive = await analyticsService.GetTraderDeepDiveAsync(1);
// Assert
Assert.NotNull(deepDive);
Assert.Equal(24.0, (double)deepDive.AvgHoldDurationHours, 2);
// Entry Quality: Buy at 0.40, subsequent prices are 0.50 and 0.60 (avg 0.55).
// Entry Quality = 50 + ((0.55 - 0.40) / 0.40) * 100 = 50 + 0.375 * 100 = 87.5
Assert.Equal(87.5m, deepDive.EntryQuality);
}
private class MockDiscoveryService : IDiscoveryService
{
public Task<int> ImportTraderAsync(PlatformType platform, string platformUserId, string displayName, CancellationToken ct) => Task.FromResult(0);
public Task ScanTopHoldersAsync(CancellationToken ct) => Task.CompletedTask;
public Task<IReadOnlyList<DiscoveredTrader>> RunDiscoveryAsync(PlatformType platform, CancellationToken ct) => Task.FromResult<IReadOnlyList<DiscoveredTrader>>(new List<DiscoveredTrader>());
}
}