fix: 修复更多金融计算bug

1. PortfolioNavService: CalculateAndSaveDailyNavAsync 卖出成本计算
   - 使用动态持仓数量字典,而非当前持仓状态
   - 确保多次卖出时比例计算正确

2. PortfolioService: GetPortfolioByIdAsync 盈亏率计算
   - 先转换汇率再计算盈亏率,避免汇率变化影响
   - 正确处理跨币种持仓的盈亏计算
This commit is contained in:
OpenClaw Agent 2026-03-25 04:09:55 +00:00
parent 2a297081b0
commit 82264ecc25
2 changed files with 19 additions and 18 deletions

View File

@ -189,7 +189,9 @@ public class PortfolioNavService : IPortfolioNavService
.ToListAsync();
decimal totalCost = 0;
var holdingsCost = new Dictionary<string, decimal>(); // 每个标的的累计成本
// 每个标的的累计成本和数量(用于准确计算卖出比例)
var holdingsCost = new Dictionary<string, decimal>();
var holdingsShares = new Dictionary<string, decimal>();
foreach (var tx in transactions)
{
@ -202,36 +204,33 @@ public class PortfolioNavService : IPortfolioNavService
txAmount, tx.Currency, targetCurrency);
totalCost += txAmountInTarget;
// 更新该标的的累计成本
// 更新该标的的累计成本和数量
if (holdingsCost.ContainsKey(tx.StockCode))
{
holdingsCost[tx.StockCode] += txAmountInTarget;
holdingsShares[tx.StockCode] += tx.Amount;
}
else
{
holdingsCost[tx.StockCode] = txAmountInTarget;
holdingsShares[tx.StockCode] = tx.Amount;
}
}
else if (tx.Type == "sell")
{
// 卖出时按比例减少该标的的累计成本
if (holdingsCost.ContainsKey(tx.StockCode) && tx.Amount > 0)
if (holdingsCost.ContainsKey(tx.StockCode) && holdingsShares[tx.StockCode] > 0)
{
// 需要知道当时该标的的总数量来计算比例
// 从 Position 表获取当前持仓数量(不精确,但作为近似)
var position = positions.FirstOrDefault(p => p.StockCode == tx.StockCode);
if (position != null && position.Shares > 0)
{
// 近似:用当前持仓数量 + 卖出数量 作为原来数量
decimal originalShares = position.Shares + tx.Amount;
decimal soldRatio = tx.Amount / originalShares;
decimal currentShares = holdingsShares[tx.StockCode];
decimal soldRatio = tx.Amount / currentShares;
decimal costToReduce = holdingsCost[tx.StockCode] * soldRatio;
holdingsCost[tx.StockCode] -= costToReduce;
holdingsShares[tx.StockCode] -= tx.Amount;
totalCost -= costToReduce;
}
}
}
}
// 计算净值
decimal nav = totalCost > 0 ? totalValue / totalCost : 1.0m;

View File

@ -496,15 +496,17 @@ public class PortfolioService : IPortfolioService
decimal positionValue = pos.Shares * CurrentPrice;
decimal cost = pos.Shares * pos.AvgPrice;
decimal Profit = positionValue - cost;
double ProfitRate = cost > 0 ? (double)(Profit / cost * 100) : 0;
decimal TodayProfit = previousClose > 0 ? pos.Shares * (CurrentPrice - previousClose) : 0;
// 转换为组合本位币
// 转换为组合本位币(先转换,再计算盈亏率,避免汇率变化影响)
decimal positionValueInTarget = await _exchangeRateService.ConvertAmountAsync(positionValue, pos.Currency, targetCurrency);
decimal costInTarget = await _exchangeRateService.ConvertAmountAsync(cost, pos.Currency, targetCurrency);
decimal todayProfitInTarget = await _exchangeRateService.ConvertAmountAsync(TodayProfit, pos.Currency, targetCurrency);
// 用目标币种计算盈亏率(正确处理汇率变化)
decimal ProfitInTarget = positionValueInTarget - costInTarget;
double ProfitRate = costInTarget > 0 ? (double)(ProfitInTarget / costInTarget * 100) : 0;
totalPortfolioValue += positionValueInTarget;
totalCost += costInTarget;
totalTodayProfit += todayProfitInTarget;