using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Application.Helpers; using Microsoft.EntityFrameworkCore; namespace Application.Features.Api.Paper.GetPositions; internal sealed class Handler(IAppDbContext db) : IQueryHandler { public async Task Handle(Query request, CancellationToken ct) { var accountID = await db.PaperAccount.AsNoTracking().Where(c => c.MemberID == request.MemberID).Select(c => (int?)c.ID).FirstOrDefaultAsync(ct); if (accountID is null) { return new Response([]); } var positions = await db.PaperPosition.AsNoTracking() .Where(c => c.AccountID == accountID.Value && c.Quantity > 0) .Select(c => new { c.StockCode, c.Quantity, c.ReservedQuantity, c.AvgPrice }) .ToListAsync(ct); if (positions.Count == 0) { return new Response([]); } var codes = positions.Select(c => c.StockCode).Distinct().ToList(); var latestCloses = await PaperValuation.GetLatestClosesAsync(db, codes, ct); var names = await db.Stock.AsNoTracking() .Where(c => codes.Contains(c.Code)) .Select(c => new { c.Code, c.Name }) .ToDictionaryAsync(c => c.Code, c => c.Name, ct); var rows = new List(); foreach (var p in positions) { decimal? latestClose = latestCloses.TryGetValue(p.StockCode, out var close) ? close : null; var marketValue = (latestClose ?? 0m) * p.Quantity; var costBasis = p.AvgPrice * p.Quantity; var unrealizedPnL = marketValue - costBasis; var unrealizedBp = costBasis > 0 ? (int)Math.Round(unrealizedPnL / costBasis * 10000m, MidpointRounding.AwayFromZero) : 0; rows.Add(new Response.Row( p.StockCode, names.GetValueOrDefault(p.StockCode, p.StockCode), p.Quantity, p.ReservedQuantity, p.AvgPrice, latestClose, marketValue, unrealizedPnL, unrealizedBp)); } return new Response(rows); } }