fix: 修复多个金融计算问题

1. CreatePortfolioAsync: 初始建仓交易保存汇率信息
   - 设置 ExchangeRate 和 TotalAmountBase 字段
   - 支持跨币种初始建仓

2. ExchangeRateService: 增强 Mock 汇率降级
   - 扩展支持 EUR、GBP、JPY
   - 未知货币对记录 Error 级别日志

3. PositionItem: 增加 Shares 属性
   - 保留完整精度(解决 Amount int 截断问题)
This commit is contained in:
OpenClaw Agent 2026-03-25 05:31:53 +00:00
parent 19f3cc8679
commit 64c1fe60e7
3 changed files with 51 additions and 7 deletions

View File

@ -116,11 +116,35 @@ public class ExchangeRateService : IExchangeRateService
{ "CNY-HKD", 1.09m }, { "CNY-HKD", 1.09m },
{ "HKD-CNY", 0.92m }, { "HKD-CNY", 0.92m },
{ "USD-HKD", 7.75m }, { "USD-HKD", 7.75m },
{ "HKD-USD", 0.13m } { "HKD-USD", 0.13m },
{ "EUR-USD", 1.08m },
{ "USD-EUR", 0.93m },
{ "EUR-CNY", 7.70m },
{ "CNY-EUR", 0.13m },
{ "GBP-USD", 1.27m },
{ "USD-GBP", 0.79m },
{ "GBP-CNY", 9.00m },
{ "CNY-GBP", 0.11m },
{ "JPY-USD", 0.0067m },
{ "USD-JPY", 149.50m },
{ "JPY-CNY", 0.048m },
{ "CNY-JPY", 20.90m }
}; };
string key = $"{fromCurrency.ToUpper()}-{toCurrency.ToUpper()}"; string key = $"{fromCurrency.ToUpper()}-{toCurrency.ToUpper()}";
return Task.FromResult(mockRates.TryGetValue(key, out var rate) ? rate : 1.00m);
if (mockRates.TryGetValue(key, out var rate))
{
_logger.LogWarning("Mock汇率命中: {Key} = {Rate},计算结果可能不准确", key, rate);
return Task.FromResult(rate);
}
// 未知货币对,记录严重警告
_logger.LogError("未知货币对无法提供Mock汇率: {FromCurrency} -> {ToCurrency}返回1.0可能导致计算错误",
fromCurrency, toCurrency);
// 返回1.0但记录严重警告,调用方应该检查日志
return Task.FromResult(1.00m);
} }
} }

View File

@ -90,7 +90,8 @@ public class PositionItem
public string? StockCode { get; set; } public string? StockCode { get; set; }
public string? StockName { get; set; } public string? StockName { get; set; }
public string? Symbol { get; set; } public string? Symbol { get; set; }
public int Amount { get; set; } public int Amount { get; set; } // 已弃用,保留兼容性
public double Shares { get; set; } // 完整精度的持仓数量
public double AveragePrice { get; set; } public double AveragePrice { get; set; }
public double CurrentPrice { get; set; } public double CurrentPrice { get; set; }
public double TotalValue { get; set; } public double TotalValue { get; set; }

View File

@ -138,7 +138,23 @@ public class PortfolioService : IPortfolioService
_db.Insertable(position).ExecuteCommand(); _db.Insertable(position).ExecuteCommand();
// 创建交易记录 // 创建交易记录(保存汇率信息)
decimal totalAmount = (decimal)(stock.Price * stock.Amount);
decimal? exchangeRate = null;
decimal? totalAmountBase = null;
// 如果持仓币种与组合币种不同,需要保存汇率
if (!string.IsNullOrEmpty(stock.Currency) && !stock.Currency.Equals(request.Currency, StringComparison.OrdinalIgnoreCase))
{
exchangeRate = await _exchangeRateService.GetExchangeRateAsync(stock.Currency, request.Currency);
totalAmountBase = totalAmount * exchangeRate.Value;
}
else
{
exchangeRate = 1.0m;
totalAmountBase = totalAmount;
}
var transaction = new Transaction var transaction = new Transaction
{ {
Id = "trans-" + Guid.NewGuid().ToString().Substring(0, 8), Id = "trans-" + Guid.NewGuid().ToString().Substring(0, 8),
@ -149,8 +165,10 @@ public class PortfolioService : IPortfolioService
Title = "初始建仓", Title = "初始建仓",
Amount = (decimal)stock.Amount, Amount = (decimal)stock.Amount,
Price = (decimal)stock.Price, Price = (decimal)stock.Price,
TotalAmount = (decimal)(stock.Price * stock.Amount), TotalAmount = totalAmount,
Currency = request.Currency, Currency = stock.Currency ?? request.Currency,
ExchangeRate = exchangeRate,
TotalAmountBase = totalAmountBase,
Status = "completed", Status = "completed",
Remark = "初始建仓", Remark = "初始建仓",
TransactionTime = buyTime, TransactionTime = buyTime,
@ -524,7 +542,8 @@ public class PortfolioService : IPortfolioService
StockCode = pos.StockCode, StockCode = pos.StockCode,
StockName = pos.StockName, StockName = pos.StockName,
Symbol = pos.StockCode, Symbol = pos.StockCode,
Amount = (int)pos.Shares, Amount = (int)pos.Shares, // 注意:此处精度丢失,仅用于显示
Shares = (double)pos.Shares, // 新增:保留完整精度
AveragePrice = (double)pos.AvgPrice, AveragePrice = (double)pos.AvgPrice,
CurrentPrice = (double)CurrentPrice, CurrentPrice = (double)CurrentPrice,
TotalValue = (double)positionValueInTarget, TotalValue = (double)positionValueInTarget,