using Application.Abstractions.Data; using Microsoft.EntityFrameworkCore; namespace Application.Helpers; /// /// 모의투자 평가값 계산 — 최근 일별 종가(StockDailyPrice)로 포지션·Equity·nav 를 산출한다 (d4 §③). /// Position.StockCode(char6) → Stock.ID → 최근 StockDailyPrice.Close 로 평가한다. /// public static class PaperValuation { /// 지정 종목코드들의 최근 종가 맵 (없는 코드는 제외). public static async Task> GetLatestClosesAsync( IAppDbContext db, IReadOnlyCollection stockCodes, CancellationToken ct) { if (stockCodes.Count == 0) { return []; } // 코드 → 종목 ID var idByCode = await db.Stock.AsNoTracking() .Where(c => stockCodes.Contains(c.Code)) .Select(c => new { c.Code, c.ID }) .ToListAsync(ct); var result = new Dictionary(); foreach (var s in idByCode) { var close = await db.StockDailyPrice.AsNoTracking() .Where(c => c.StockID == s.ID) .OrderByDescending(c => c.TradingDate) .Select(c => (int?)c.Close) .FirstOrDefaultAsync(ct); if (close.HasValue) { result[s.Code] = close.Value; } } return result; } /// 단일 종목코드의 최근 종가 (없으면 null). public static async Task GetLatestCloseAsync(IAppDbContext db, string stockCode, CancellationToken ct) { var stockID = await db.Stock.AsNoTracking() .Where(c => c.Code == stockCode) .Select(c => (int?)c.ID) .FirstOrDefaultAsync(ct); if (stockID is null) { return null; } var close = await db.StockDailyPrice.AsNoTracking() .Where(c => c.StockID == stockID.Value) .OrderByDescending(c => c.TradingDate) .Select(c => (int?)c.Close) .FirstOrDefaultAsync(ct); return close; } /// /// 계좌 평가자산 Equity = Token + ReservedToken + Σ(포지션 수량 × 최근 종가). /// 시세 없는 포지션은 0 으로 평가(보수적). /// public static decimal ComputeEquity( decimal token, decimal reservedToken, IEnumerable<(string StockCode, int Quantity)> positions, IReadOnlyDictionary latestCloses) { var positionsValue = 0m; foreach (var p in positions) { if (latestCloses.TryGetValue(p.StockCode, out var close)) { positionsValue += close * p.Quantity; } } return token + reservedToken + positionsValue; } /// 좌당 순자산 nav = Units > 0 ? Equity / Units : 1. public static decimal ComputeNav(decimal equity, decimal units) { return units > 0 ? equity / units : 1m; } }