117 lines
4.3 KiB
C#
117 lines
4.3 KiB
C#
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 Moq;
|
|
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, Microsoft.Extensions.Logging.Abstractions.NullLogger<TradeRepository>.Instance);
|
|
var watchlistRepo = new WatchlistRepository(db);
|
|
|
|
var discoveryMock = new Mock<IDiscoveryService>();
|
|
var pnlEngineMock = new Mock<IPositionPnLEngine>();
|
|
var alertRepoMock = new Mock<IAlertRepository>();
|
|
var watchlistRepoMock = new Mock<IWatchlistRepository>();
|
|
var marketRepo = new MarketRepository(db);
|
|
var providers = new List<IPlatformProvider>();
|
|
|
|
var analyticsService = new AnalyticsService(
|
|
traderRepo,
|
|
tradeRepo,
|
|
alertRepoMock.Object,
|
|
watchlistRepoMock.Object,
|
|
marketRepo,
|
|
discoveryMock.Object,
|
|
providers,
|
|
pnlEngineMock.Object,
|
|
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 = 1L, 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, bool isAutoDiscovered = true, CancellationToken ct = default)
|
|
{
|
|
return Task.FromResult(1);
|
|
}
|
|
public Task ScanTopHoldersAsync(CancellationToken ct) => Task.CompletedTask;
|
|
public Task<IReadOnlyList<DiscoveredTrader>> RunDiscoveryAsync(PlatformType platform, CancellationToken ct = default)
|
|
{
|
|
return Task.FromResult<IReadOnlyList<DiscoveredTrader>>(new List<DiscoveredTrader>());
|
|
}
|
|
}
|
|
}
|