diff --git a/AssetManager.Services/PortfolioService.cs b/AssetManager.Services/PortfolioService.cs index 9a6eeda..88fa613 100755 --- a/AssetManager.Services/PortfolioService.cs +++ b/AssetManager.Services/PortfolioService.cs @@ -176,30 +176,60 @@ public class PortfolioService : IPortfolioService foreach (var p in portfolios) { - // 获取持仓数量 - var positionCount = _db.Queryable() + // 获取该组合的所有持仓 + var positions = _db.Queryable() .Where(pos => pos.PortfolioId == p.Id) - .Count(); + .ToList(); - // 获取今日盈亏(从净值历史) - var todayNav = _db.Queryable() - .Where(n => n.PortfolioId == p.Id && n.NavDate == DateTime.Today) - .First(); + int positionCount = positions.Count; - double todayProfit = 0; - if (todayNav != null && p.TotalValue > 0) + // 实时计算当前市值和总成本 + decimal totalValue = 0; + decimal totalCost = 0; + decimal todayProfit = 0; + string portfolioCurrency = p.Currency ?? "CNY"; + + foreach (var pos in positions) { - // 今日盈亏 = 今日市值 - 昨日市值 - var yesterdayNav = _db.Queryable() - .Where(n => n.PortfolioId == p.Id && n.NavDate < DateTime.Today) - .OrderByDescending(n => n.NavDate) - .First(); - - if (yesterdayNav != null) + if (pos.StockCode == null || pos.Currency == null) { - todayProfit = (double)(todayNav.TotalValue - yesterdayNav.TotalValue); + continue; } + + // 获取实时价格 + decimal currentPrice = pos.AvgPrice; + decimal previousClose = pos.AvgPrice; + try + { + var priceResponse = _marketDataService.GetPriceAsync(pos.StockCode, pos.AssetType ?? "Stock").GetAwaiter().GetResult(); + if (priceResponse.Price > 0) + { + currentPrice = priceResponse.Price; + previousClose = priceResponse.PreviousClose > 0 ? priceResponse.PreviousClose : currentPrice; + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "获取标的 {StockCode} 实时价格失败,使用成本价", pos.StockCode); + } + + decimal positionValue = pos.Shares * currentPrice; + decimal positionCost = pos.Shares * pos.AvgPrice; + decimal positionTodayProfit = previousClose > 0 ? pos.Shares * (currentPrice - previousClose) : 0; + + // 汇率转换到组合本位币 + var valueInTarget = _exchangeRateService.ConvertAmountAsync(positionValue, pos.Currency, portfolioCurrency).GetAwaiter().GetResult(); + var costInTarget = _exchangeRateService.ConvertAmountAsync(positionCost, pos.Currency, portfolioCurrency).GetAwaiter().GetResult(); + var todayProfitInTarget = _exchangeRateService.ConvertAmountAsync(positionTodayProfit, pos.Currency, portfolioCurrency).GetAwaiter().GetResult(); + + totalValue += valueInTarget; + totalCost += costInTarget; + todayProfit += todayProfitInTarget; } + + // 计算收益率 + double returnRate = totalCost > 0 ? (double)((totalValue - totalCost) / totalCost * 100) : 0; + returnRate = Math.Round(returnRate, 2); result.Add(new PortfolioListItem { @@ -211,11 +241,11 @@ public class PortfolioService : IPortfolioService IconChar = p.Name?.Substring(0, 1).ToUpper() ?? "P", IconBgClass = "bg-blue-100", IconTextClass = "text-blue-700", - Value = (double)p.TotalValue, + Value = (double)totalValue, Currency = p.Currency, - ReturnRate = (double)p.ReturnRate, - ReturnType = p.ReturnRate >= 0 ? "positive" : "negative", - TodayProfit = todayProfit, + ReturnRate = returnRate, + ReturnType = returnRate >= 0 ? "positive" : "negative", + TodayProfit = (double)todayProfit, TodayProfitCurrency = p.Currency }); }