diff --git a/UnitTestProject/Modules/SwapModule/DividendEodNoDoubleCountTest.cs b/UnitTestProject/Modules/SwapModule/DividendEodNoDoubleCountTest.cs
new file mode 100644
index 00000000..cb588031
--- /dev/null
+++ b/UnitTestProject/Modules/SwapModule/DividendEodNoDoubleCountTest.cs
@@ -0,0 +1,163 @@
+using YLErp.DBModels;
+using YLErp.DBModels.Enums;
+
+namespace YLErp.Modules.SwapModule
+{
+ ///
+ /// 端到端:盘中收益互换(DividendIn 由生产方法 GetPreEodDividendSum 真实算出)→ 保存 → EOD,
+ /// 验证分红【不重复累计】(EOD TdCloseDividend 扣减 DividendIn)且【不丢失】(当日新计进 PosiDividendSum)。
+ ///
+ /// 与 MultiUnwindDividendConservationTest.MU_001 的区别:MU_001 的互换 DividendIn 是测试喂的常量;
+ /// 本测试的 DividendIn 由生产方法 GetPreEodDividendSum 真实算出(读 EOD 快照),再喂给 EOD——
+ /// 覆盖"预览算 DividendIn + EOD 扣减"的完整链路(MU_001 的缺口)。
+ ///
+ [TestClass]
+ public class DividendEodNoDoubleCountTest
+ {
+ private const int SwapTradeId = 9200;
+ private const long PositionId = 9201;
+ private const decimal InitialQty = 1000m;
+ private const decimal DailyRatePerUnit = 0.01m; // 每单位每天 0.01,便于手算
+ private static readonly DateTime StartDate = new(2026, 1, 5);
+
+ #region Stubs
+
+ /// SwapDealService stub:暴露 GetPreEodDividendSum,注入 EOD 数据(不连库)。
+ private sealed class DealSvcStub : SwapDealService
+ {
+ private readonly List _eodSwaps;
+ private readonly List _eodPositions;
+ public DealSvcStub(List eodSwaps, List eodPositions)
+ : base(OptUserInfo.UnitTestUser) { _eodSwaps = eodSwaps; _eodPositions = eodPositions; }
+ public decimal ExposeGetPreEodDividendSum(int tradeId, long positionId, DateTime dealDate)
+ => GetPreEodDividendSum(tradeId, positionId, dealDate);
+ protected override IQueryable QueryPreEodSwaps(int tradeId)
+ => _eodSwaps.Where(x => x.SwapTradeId == tradeId).AsQueryable();
+ protected override eod_swap_position QueryPreEodPosition(int tradeId, long positionId, DateTime valueDate)
+ => _eodPositions.FirstOrDefault(x => x.SwapTradeId == tradeId && x.PositionId == positionId && x.ValueDate == valueDate);
+ }
+
+ /// SwapEodPositionService stub:暴露 UpdateEodPosition/CopyEodPosition + 线性 CalcBondPayment。
+ private sealed class EodSvcStub : TestableSwapEodPositionService
+ {
+ public EodSvcStub() : base(nameof(DividendEodNoDoubleCountTest)) { }
+ protected override decimal CalcBondPayment(string underlyingCode, DateTime fromDate, DateTime toDate, decimal qty, int shortRatio, int directionRatio)
+ {
+ int days = Math.Max(0, (int)(toDate - fromDate).TotalDays);
+ return DailyRatePerUnit * days * qty * shortRatio * directionRatio;
+ }
+ protected override underlying_manager GetUnderlyingData(string underlyingCode)
+ => new underlying_manager { ValueAddedTax = 0m };
+ protected override decimal GetUnderlyingPrice(string code, DateTime settleDate, out decimal vobp)
+ { vobp = 0m; return 1.00m; }
+ public eod_swap_position ExecuteUpdateEodPosition(swap_position swapPosition, eod_swap_position eod, trade td, DateTime valueDate, DateTime preSettleDate, List unwindEvents)
+ => UpdateEodPosition(swapPosition, eod, null, td, valueDate, preSettleDate, unwindEvents);
+ public eod_swap_position ExecuteCopyEodPosition(eod_swap_position eod, trade td, DateTime valueDate, DateTime preSettleDate)
+ => CopyEodPosition(eod, null, td, valueDate, preSettleDate);
+ }
+
+ #endregion
+
+ #region 数据构建
+
+ private static trade CreateTrade() => new trade
+ {
+ id = SwapTradeId, TradeNumber = "UT-DIV-EOD-001", ClientId = 999999,
+ TradeType = "收益互换", TradeDate = StartDate, StartDate = StartDate,
+ ExerciseDate = new DateTime(2027, 1, 5), TradeStatus = "确认成交", ValidState = "Valid",
+ StructureType = "单标的", QuoteCurrency = "CNY", SettlementCurrency = "CNY",
+ OriginalStockEqvNotional = (double)(InitialQty * 1.00m)
+ };
+
+ private static swap_position CreatePosition() => new swap_position
+ {
+ id = PositionId, SwapTradeId = SwapTradeId,
+ PosiDirection = (int)SwapDirectionEnum.收取, PositionType = (int)PositionTypeFlag.Long,
+ UnderlyingCode = "210210.IB", ContractSize = 1m,
+ PosiQuantity = InitialQty, PosiNotionalValue = InitialQty,
+ PosiNetPrice = 1.000m, PosiGrossPrice = 1.000m,
+ PosiNetFeePrice = 1.000m, PosiNetNoFeePrice = 1.000m,
+ IsInitial = true, Invalid = false,
+ PosiTradingFee = 0, PosiTradingFeePending = 0
+ };
+
+ private static eod_swap_position CreateInitialEod() => new eod_swap_position
+ {
+ id = 1, SwapTradeId = SwapTradeId, PositionId = PositionId,
+ ValueDate = StartDate, PosiQuantity = InitialQty,
+ PosiDirection = (int)SwapDirectionEnum.收取, PositionType = (int)PositionTypeFlag.Long,
+ UnderlyingCode = "210210.IB", ContractSize = 1m,
+ PosiNetPrice = 1.000m, PosiGrossPrice = 1.000m,
+ PosiNetFeePrice = 1.000m, PosiNetNoFeePrice = 1.000m,
+ PosiDividendSum = 0m, TdPosiDividend = 0m, TdCloseDividend = 0m,
+ RealizedDividend = 0m, PosiFeePending = 0m,
+ InterestProfitSum = 0m, Invalid = false
+ };
+
+ private static swap_flow_event SwapEvent(decimal dividendIn, DateTime eventDate) => new swap_flow_event
+ {
+ SwapTradeId = SwapTradeId, EventType = (int)SwapFlowEventTypeEnum.互换,
+ PositionId = PositionId, Quantity = 0m, DividendIn = dividendIn,
+ MarkClosePnl = 0m, CloseFee = 0m, TradingFeePending = 0m,
+ EventDate = eventDate, PayDate = eventDate,
+ DataState = (int)SwapFlowDateStateEnum.完成
+ };
+
+ private static void AssertDecimalEqual(decimal expected, decimal actual, decimal tol, string msg)
+ => Assert.IsTrue(Math.Abs(expected - actual) <= tol, $"{msg}: expected={expected} actual={actual}");
+
+ #endregion
+
+ ///
+ /// 盘中收益互换:DividendIn 由 GetPreEodDividendSum 真实算(读 T-1 EOD)→ 保存 → EOD。
+ /// 验证:不重复(EOD TdCloseDividend 扣 DividendIn)+ 不丢失(当日新计进 PosiDividendSum)+ 守恒。
+ ///
+ /// 序列(StartDate=1/5,每日 0.01×1000=10):
+ /// D1=1/6 无事件 Copy:PosiDividendSum = 0 + 10 = 10
+ /// D2=1/7 盘中互换:GetPreEodDividendSum(读 D1) → DividendIn=10;保存 swap_event;EOD:新计 10 - 实现 10 → PosiDividendSum=10
+ /// 守恒:全程新计(10+10) - 全程实现(10) = 末尾 PosiDividendSum(10)
+ ///
+ [TestMethod]
+ public void 盘中收益互换_DividendIn真实算_保存后EOD_不重复不丢失()
+ {
+ var eodSvc = new EodSvcStub();
+ var td = CreateTrade();
+ var position = CreatePosition();
+ var initialEod = CreateInitialEod();
+
+ // D1=1/6 无事件 EOD
+ var d1 = new DateTime(2026, 1, 6);
+ var r1 = eodSvc.ExecuteCopyEodPosition(initialEod, td, d1, StartDate);
+ AssertDecimalEqual(10m, r1.PosiDividendSum, 0.01m, "D1 PosiDividendSum(0+1天×10)");
+
+ // D2=1/7 盘中:DividendIn 由生产方法 GetPreEodDividendSum 真实算(读 D1 EOD,当日 EOD 未生成)
+ var d2 = new DateTime(2026, 1, 7);
+ var dealSvc = new DealSvcStub(
+ new List { new eod_swap { SwapTradeId = SwapTradeId, ValueDate = d1 } },
+ new List { r1 });
+ decimal dividendIn = dealSvc.ExposeGetPreEodDividendSum(SwapTradeId, PositionId, d2);
+ AssertDecimalEqual(10m, dividendIn, 0.01m, "盘中 DividendIn=GetPreEodDividendSum 读 T-1(D1)=10");
+ Console.WriteLine($"[盘中预览] DividendIn={dividendIn}(读 T-1 EOD PosiDividendSum={r1.PosiDividendSum})");
+
+ // 保存互换事件(DividendIn=真实算出的值,模拟界面点收益互换后保存)
+ var swapEvent = SwapEvent(dividendIn, d2);
+
+ // D2=1/7 EOD(UpdateEodPosition,真实生产递推)
+ var r2 = eodSvc.ExecuteUpdateEodPosition(position, r1, td, d2, d1, new List { swapEvent });
+
+ // 断言:不重复 + 不丢失
+ AssertDecimalEqual(10m, r2.TdPosiDividend, 0.01m, "D2 当日新计(1天×10)");
+ AssertDecimalEqual(dividendIn, r2.TdCloseDividend, 0.01m, "D2 TdCloseDividend=互换DividendIn(扣减→不重复累计)");
+ AssertDecimalEqual(10m, r2.PosiDividendSum, 0.01m, "D2 PosiDividendSum=前日10+新计10-实现10=10(当日新计挂着→不丢失)");
+
+ // 守恒:全程新计 - 全程实现 = 末尾 PosiDividendSum
+ decimal totalNew = r1.TdPosiDividend + r2.TdPosiDividend;
+ decimal totalRealized = r2.TdCloseDividend;
+ AssertDecimalEqual(r2.PosiDividendSum, totalNew - totalRealized, 0.01m,
+ $"守恒:末尾 PosiDividendSum({r2.PosiDividendSum}) = 全程新计({totalNew}) - 全程实现({totalRealized})");
+
+ Console.WriteLine($"[EOD 后] TdPosiDividend={r2.TdPosiDividend} TdCloseDividend={r2.TdCloseDividend} PosiDividendSum={r2.PosiDividendSum}");
+ Console.WriteLine($"结论:互换实现 {dividendIn} 被扣减(不重复);当日新计 {r2.TdPosiDividend} 挂 PosiDividendSum(不丢失)");
+ }
+ }
+}