From ce0908c53b9067b161cce02ac14175a3631c7440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Fri, 17 Jul 2026 10:02:06 +0800 Subject: [PATCH] =?UTF-8?q?fix(swap):=20=E4=BF=AE=E5=A4=8D=E6=B5=AE?= =?UTF-8?q?=E5=8A=A8=E7=AB=AF=E5=B7=B2=E5=AE=9E=E7=8E=B0=E7=9B=88=E4=BA=8F?= =?UTF-8?q?=E8=AE=A1=E7=AE=97=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了SetFloatingRealizedPnl方法统一计算已实现盈亏 - 已实现盈亏现在正确包含盯市、分红和费用三个组成部分 - 修复了多处RealizedPnl计算错误,确保数值准确性 - 为DF_008测试场景添加了专门的单元测试验证 - 移除了重复的盈亏计算代码,提高代码可维护性 --- .../DealFloatPositionsScenarioTest.cs | 33 +++++++++++++++++++ .../SwapModule/SwapEodPositionService.cs | 21 +++++++++--- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/UnitTestProject/Modules/SwapModule/DealFloatPositionsScenarioTest.cs b/UnitTestProject/Modules/SwapModule/DealFloatPositionsScenarioTest.cs index 073d6240..4c509c8f 100644 --- a/UnitTestProject/Modules/SwapModule/DealFloatPositionsScenarioTest.cs +++ b/UnitTestProject/Modules/SwapModule/DealFloatPositionsScenarioTest.cs @@ -378,6 +378,39 @@ namespace YLErp.Modules.SwapModule Console.WriteLine($"分红增值税调整: 付息100, 税率6% → TdPosiDividend={result.TdPosiDividend}(期望{expected})✅"); } + [TestMethod] + public void DF_008_CopyBranch_RealizedPnlIncludesRealizedFee() + { + var service = new StubEodService + { + UnderlyingPrice = 1.002m, + TaxRate = 0m, + BondPayment = 0m + }; + var preEod = new eod_swap_position + { + id = 5001, + SwapTradeId = SwapTradeId, + PositionId = 3001, + ValueDate = PreSettleDate, + PosiQuantity = 10000m, + PosiGrossPrice = 1.002m, + PosiNetPrice = 1.005m, + UnderlyingCode = "210210.IB", + ContractSize = 1m, + PositionType = (int)PositionTypeFlag.Long, + PosiDirection = (int)SwapDirectionEnum.收取, + RealizedMtmPnL = 98000000m, + RealizedDividend = 0m, + RealizedFee = 100m, + RealizedPnl = 98000000m + }; + + var result = service.ExecuteCopyEodPosition(preEod, null, CreateTrade(), TradeDate, PreSettleDate); + + Assert.AreEqual(98000100m, result.RealizedPnl); + } + private static void AssertDecimalEqual(decimal expected, decimal actual, decimal tolerance, string message = "") { Assert.IsTrue(Math.Abs(expected - actual) <= tolerance, diff --git a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs index ce8e0eda..b1d5316f 100644 --- a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs @@ -1510,7 +1510,7 @@ namespace YLErp.Modules.SwapModule newEodPayPosition.RealizedFee = closeFee; newEodPayPosition.RealizedMtmPnL = newEodPayPosition.TdCloseMtmPnl; newEodPayPosition.RealizedDividend = newEodPayPosition.TdCloseDividend; - newEodPayPosition.RealizedPnl = newEodPayPosition.TdCloseMtmPnl; + SetFloatingRealizedPnl(newEodPayPosition); newEodPayPosition.PosiStatus = payQty == 0 ? 1 : 0; UpdateDbOption(newEodPayPosition); @@ -1585,7 +1585,7 @@ namespace YLErp.Modules.SwapModule curretEod.RealizedMtmPnL = eod.RealizedMtmPnL + curretEod.TdCloseMtmPnl; curretEod.RealizedDividend = eod.RealizedDividend + curretEod.TdCloseDividend; curretEod.RealizedFee = eod.RealizedFee + curretEod.TdCloseFee; - curretEod.RealizedPnl = eod.RealizedPnl + curretEod.TdCloseMtmPnl; + SetFloatingRealizedPnl(curretEod); var currencyRate = new EodCurrencyRateService(UserInfo).GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, td.StartDate.Value , seekPreday: true, currencyRateType: curretEod.PosiDirection == (int)SwapDirectionEnum.收取 ? CurrencyRateType.Buy : CurrencyRateType.Sell); curretEod.TdCurrency = Convert.ToDecimal(currencyRate); @@ -1599,6 +1599,18 @@ namespace YLErp.Modules.SwapModule } return curretEod; } + + /// + /// 浮动腿累计已实现盈亏由盯市、分红和费用三个已实现组成项汇总。 + /// 各组成项已经按本方视角落库,此处不再额外转换方向。 + /// + private static void SetFloatingRealizedPnl(eod_swap_position position) + { + position.RealizedPnl = position.RealizedMtmPnL + + position.RealizedDividend + + position.RealizedFee; + } + /// /// 更新虚拟交易费用 /// @@ -1659,7 +1671,6 @@ namespace YLErp.Modules.SwapModule // 当日浮动端平仓盈亏·分红(仅来自平仓事件 和 互换 中已实现的分红) curretEod.TdCloseDividend = unwindEvents.Sum(e => e.DividendIn); curretEod.RealizedFee = eod.RealizedFee + curretEod.TdCloseFee; - curretEod.RealizedPnl = eod.RealizedPnl + curretEod.TdCloseMtmPnl; curretEod.PosiStatus = curretEod.PosiQuantity == 0 ? 1 : 0; var closeQty = unwindEvents.Where(x => x.EventType == (int)SwapFlowEventTypeEnum.平仓).ToList().Sum(s => s.Quantity); @@ -1675,7 +1686,7 @@ namespace YLErp.Modules.SwapModule { curretEod.PosiDividendSum = 0; } - curretEod.RealizedPnl += curretEod.TdCloseDividend; + SetFloatingRealizedPnl(curretEod); curretEod.SwapPositionValue -= curretEod.TdCloseDividend; curretEod.PosiProfitSum = curretEod.PosiMtmPnL + curretEod.PosiDividendSum + curretEod.PosiFeePending; @@ -1853,7 +1864,7 @@ namespace YLErp.Modules.SwapModule curretEod.RealizedMtmPnL = curretEod.TdCloseMtmPnl; curretEod.RealizedDividend = curretEod.TdCloseDividend; curretEod.RealizedFee = curretEod.TdCloseFee; - curretEod.RealizedPnl = curretEod.TdCloseMtmPnl; + SetFloatingRealizedPnl(curretEod); curretEod.PosiStatus = curretEod.PosiQuantity == 0 ? 1 : 0; if (curretEod.PosiStatus == 1) {