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)
{