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,150 @@
using Microsoft.EntityFrameworkCore;
using Predictalytics.Domain.Entities;
using Predictalytics.Domain.Enums;
using Predictalytics.Infrastructure.Data;
using Predictalytics.Infrastructure.Services;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Threading.Tasks;
using Xunit;
namespace Predictalytics.Application.Tests.Services;
public class PositionPnLEngineTests
{
private AppDbContext CreateDbContext()
{
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
return new AppDbContext(options);
}
[Fact]
public async Task RecalculateTraderPositionsAsync_BuyTrade_CreatesTraderPosition()
{
// Arrange
using var db = CreateDbContext();
var pnlEngine = new PositionPnLEngine(db, NullLogger<PositionPnLEngine>.Instance);
var trader = new Trader { Id = 1, PlatformUserId = "0x1", DisplayName = "Trader 1" };
var market = new Market { Id = 10, PlatformMarketId = "pm1", Question = "Q?" };
var outcome = new MarketOutcome { Id = 100, MarketId = 10, Label = "Yes", TokenId = "t100", CurrentPrice = 0.60m };
market.Outcomes.Add(outcome);
db.Traders.Add(trader);
db.Markets.Add(market);
var buyTrade = new Trade
{
Id = 500,
TraderId = 1,
DbMarketId = 10,
MarketOutcomeId = 100,
Side = TradeSide.Buy,
Price = 0.50m,
Size = 100m,
Amount = 50m,
ExecutedAt = DateTime.UtcNow
};
db.Trades.Add(buyTrade);
await db.SaveChangesAsync();
// Act
await pnlEngine.RecalculateTraderPositionsAsync(1);
// Assert
var pos = await db.TraderPositions.FirstOrDefaultAsync(p => p.TraderId == 1 && p.MarketOutcomeId == 100);
Assert.NotNull(pos);
Assert.Equal(100m, pos.SharesHeld);
Assert.Equal(0.50m, pos.AvgCost);
Assert.Equal(0m, pos.RealizedPnl);
}
[Fact]
public async Task RecalculateTraderPositionsAsync_SellTrade_CalculatesRealizedPnL()
{
// Arrange
using var db = CreateDbContext();
var pnlEngine = new PositionPnLEngine(db, NullLogger<PositionPnLEngine>.Instance);
var trader = new Trader { Id = 1, PlatformUserId = "0x1", DisplayName = "Trader 1" };
var market = new Market { Id = 10, PlatformMarketId = "pm1", Question = "Q?" };
var outcome = new MarketOutcome { Id = 100, MarketId = 10, Label = "Yes", TokenId = "t100", CurrentPrice = 0.60m };
market.Outcomes.Add(outcome);
db.Traders.Add(trader);
db.Markets.Add(market);
// Buy 100 shares at 0.40
db.Trades.Add(new Trade
{
Id = 501, TraderId = 1, DbMarketId = 10, MarketOutcomeId = 100,
Side = TradeSide.Buy, Price = 0.40m, Size = 100m, Amount = 40m,
ExecutedAt = DateTime.UtcNow.AddMinutes(-10)
});
// Sell 50 shares at 0.60
db.Trades.Add(new Trade
{
Id = 502, TraderId = 1, DbMarketId = 10, MarketOutcomeId = 100,
Side = TradeSide.Sell, Price = 0.60m, Size = 50m, Amount = 30m,
ExecutedAt = DateTime.UtcNow
});
await db.SaveChangesAsync();
// Act
await pnlEngine.RecalculateTraderPositionsAsync(1);
// Assert
var pos = await db.TraderPositions.FirstOrDefaultAsync(p => p.TraderId == 1 && p.MarketOutcomeId == 100);
Assert.NotNull(pos);
Assert.Equal(50m, pos.SharesHeld);
Assert.Equal(0.40m, pos.AvgCost); // AvgCost stays at 0.40
Assert.Equal(10m, pos.RealizedPnl); // 50 * (0.60 - 0.40) = 10
}
[Fact]
public async Task RecalculateTraderPositionsAsync_RedeemTrade_CalculatesRedeemPnL()
{
// Arrange
using var db = CreateDbContext();
var pnlEngine = new PositionPnLEngine(db, NullLogger<PositionPnLEngine>.Instance);
var trader = new Trader { Id = 1, PlatformUserId = "0x1", DisplayName = "Trader 1" };
var market = new Market { Id = 10, PlatformMarketId = "pm1", Question = "Q?", IsResolved = true, ResolutionOutcome = "Yes" };
var outcome = new MarketOutcome { Id = 100, MarketId = 10, Label = "Yes", TokenId = "t100", CurrentPrice = 1.00m };
market.Outcomes.Add(outcome);
db.Traders.Add(trader);
db.Markets.Add(market);
// Buy 100 shares at 0.40
db.Trades.Add(new Trade
{
Id = 501, TraderId = 1, DbMarketId = 10, MarketOutcomeId = 100,
Side = TradeSide.Buy, Price = 0.40m, Size = 100m, Amount = 40m,
ExecutedAt = DateTime.UtcNow.AddMinutes(-10)
});
// Redeem at 1.00 (Win)
db.Trades.Add(new Trade
{
Id = 503, TraderId = 1, DbMarketId = 10, MarketOutcomeId = 100,
Side = TradeSide.Redeem, Price = 1.00m, Size = 100m, Amount = 100m,
ExecutedAt = DateTime.UtcNow
});
await db.SaveChangesAsync();
// Act
await pnlEngine.RecalculateTraderPositionsAsync(1);
// Assert
var pos = await db.TraderPositions.FirstOrDefaultAsync(p => p.TraderId == 1 && p.MarketOutcomeId == 100);
Assert.NotNull(pos);
Assert.Equal(0m, pos.SharesHeld);
Assert.Equal(60m, pos.RealizedPnl); // 100 * (1.00 - 0.40) = 60
}
}