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

View File

@ -496,15 +496,17 @@ public class PortfolioService : IPortfolioService
decimal positionValue = pos.Shares * CurrentPrice; decimal positionValue = pos.Shares * CurrentPrice;
decimal cost = pos.Shares * pos.AvgPrice; 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 TodayProfit = previousClose > 0 ? pos.Shares * (CurrentPrice - previousClose) : 0;
// 转换为组合本位币 // 转换为组合本位币(先转换,再计算盈亏率,避免汇率变化影响)
decimal positionValueInTarget = await _exchangeRateService.ConvertAmountAsync(positionValue, pos.Currency, targetCurrency); decimal positionValueInTarget = await _exchangeRateService.ConvertAmountAsync(positionValue, pos.Currency, targetCurrency);
decimal costInTarget = await _exchangeRateService.ConvertAmountAsync(cost, pos.Currency, targetCurrency); decimal costInTarget = await _exchangeRateService.ConvertAmountAsync(cost, pos.Currency, targetCurrency);
decimal todayProfitInTarget = await _exchangeRateService.ConvertAmountAsync(TodayProfit, 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; totalPortfolioValue += positionValueInTarget;
totalCost += costInTarget; totalCost += costInTarget;
totalTodayProfit += todayProfitInTarget; totalTodayProfit += todayProfitInTarget;