G3 & G4: Map UsdcSize, OutcomeIndex, compute Category ROI and add migration
This commit is contained in:
@@ -881,4 +881,61 @@ public class PositionPnLEngineTests
|
||||
Assert.Equal(6, analytics.Trades30d);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RecalculateTraderPositionsAsync_CalculatesCategoryTotalInvestedAndROI()
|
||||
{
|
||||
// Arrange
|
||||
var dbName = Guid.NewGuid().ToString();
|
||||
using (var db = CreateDbContext(dbName))
|
||||
{
|
||||
var trader = new Trader { Id = 1, PlatformUserId = "0x1", DisplayName = "Trader 1" };
|
||||
var ev = new Event { Id = 2, Platform = PlatformType.Polymarket, Slug = "e2", Title = "E2", Tags = "Crypto" };
|
||||
db.Set<Event>().Add(ev);
|
||||
|
||||
var market = new Market { Id = 20, EventId = 2, PlatformMarketId = 2L, Question = "BTC to 100k?", Category = MarketCategory.Crypto, Subcategory = "Bitcoin", IsResolved = true };
|
||||
var outcome = new MarketOutcome { Id = 200, MarketId = 20, Label = "Yes", TokenId = "t200", CurrentPrice = 1.00m };
|
||||
market.Outcomes.Add(outcome);
|
||||
|
||||
db.Traders.Add(trader);
|
||||
db.Markets.Add(market);
|
||||
|
||||
// Buy trade (Invested = 100 USD)
|
||||
db.Trades.Add(new Trade
|
||||
{
|
||||
Id = 601, TraderId = 1, DbMarketId = 20, MarketOutcomeId = 200,
|
||||
Side = TradeSide.Buy, Price = 0.50m, Size = 200m, Amount = 100m,
|
||||
ExecutedAt = DateTime.UtcNow.AddMinutes(-10),
|
||||
MarketOutcome = outcome
|
||||
});
|
||||
|
||||
// Sell trade (Payout = 200 USD -> PnL = 100 USD)
|
||||
db.Trades.Add(new Trade
|
||||
{
|
||||
Id = 602, TraderId = 1, DbMarketId = 20, MarketOutcomeId = 200,
|
||||
Side = TradeSide.Sell, Price = 1.00m, Size = 200m, Amount = 200m,
|
||||
ExecutedAt = DateTime.UtcNow,
|
||||
MarketOutcome = outcome
|
||||
});
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// Act
|
||||
using (var db = CreateDbContext(dbName))
|
||||
{
|
||||
var pnlEngine = new PositionPnLEngine(db, NullLogger<PositionPnLEngine>.Instance);
|
||||
await pnlEngine.RecalculateTraderPositionsAsync(1);
|
||||
}
|
||||
|
||||
// Assert
|
||||
using (var db = CreateDbContext(dbName))
|
||||
{
|
||||
var perf = await db.TraderCategoryPerformances.FirstOrDefaultAsync(p => p.TraderId == 1 && p.Category == MarketCategory.Crypto);
|
||||
Assert.NotNull(perf);
|
||||
Assert.Equal(100m, perf.TotalInvested);
|
||||
Assert.Equal(100m, perf.TotalPnL);
|
||||
Assert.Equal(100m, perf.AvgReturnPct); // (100 / 100) * 100 = 100% ROI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Predictalytics.Domain.Entities;
|
||||
using Predictalytics.Domain.Enums;
|
||||
using Predictalytics.Infrastructure.Data;
|
||||
using Predictalytics.Infrastructure.Data.Repositories;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Xunit;
|
||||
|
||||
namespace Predictalytics.Application.Tests.Services;
|
||||
|
||||
public class TradeRepositoryTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task AddRangeAsync_GeneratesCorrectSqlAndParameters()
|
||||
{
|
||||
using var connection = new SqliteConnection("DataSource=:memory:");
|
||||
connection.Open();
|
||||
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||
.UseSqlite(connection)
|
||||
.Options;
|
||||
|
||||
using var db = new AppDbContext(options);
|
||||
db.Database.EnsureCreated();
|
||||
|
||||
var repo = new TradeRepository(db, NullLogger<TradeRepository>.Instance);
|
||||
|
||||
var trade = new Trade
|
||||
{
|
||||
PlatformTradeId = "test_tx_usdc",
|
||||
MarketId = "cond_1",
|
||||
AssetId = "asset_1",
|
||||
Outcome = "Yes",
|
||||
Side = TradeSide.Buy,
|
||||
Price = 0.50m,
|
||||
Size = 100m,
|
||||
Amount = 50m,
|
||||
ExecutedAt = DateTime.UtcNow,
|
||||
UsdcSize = 123.45m,
|
||||
OutcomeIndex = 1
|
||||
};
|
||||
|
||||
// SQLite will throw due to MySql specific "ON DUPLICATE KEY UPDATE" syntax.
|
||||
// We catch it and verify that it failed at execution rather than mapping/parameter building.
|
||||
var exception = await Assert.ThrowsAnyAsync<Exception>(() => repo.AddRangeAsync(new[] { trade }));
|
||||
|
||||
// Assert that the exception is a syntax error near "DUPLICATE", which verifies the query was correctly built and sent
|
||||
Assert.Contains("DUPLICATE", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user