diff --git a/UnitTestProject/Modules/SwapModule/Margin/MarginInterestGoldenReplayTest.cs b/UnitTestProject/Modules/SwapModule/Margin/MarginInterestGoldenReplayTest.cs index 2ff99fe0..d50d9c4c 100644 --- a/UnitTestProject/Modules/SwapModule/Margin/MarginInterestGoldenReplayTest.cs +++ b/UnitTestProject/Modules/SwapModule/Margin/MarginInterestGoldenReplayTest.cs @@ -128,7 +128,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin newEvt = svc.CalcMarginInterest(td, valueDate, valueDate, posClone, rate, pos.InterestPrincipalFix, pos.InterestPrincipalFix, 1.0m, annualDays, calcFirst, calcLast, preEod, - (int)SwapEventTypeEnum.自动互换, add: false, settment: true, swap: false); + (int)SwapEventTypeEnum.自动互换, add: false, settment: true, interestWindowEmpty: false); } catch (Exception ex) { diff --git a/UnitTestProject/Modules/SwapModule/Margin/MarginInterestShadowTest.cs b/UnitTestProject/Modules/SwapModule/Margin/MarginInterestShadowTest.cs index 2ff0f5ad..f7ed0b2f 100644 --- a/UnitTestProject/Modules/SwapModule/Margin/MarginInterestShadowTest.cs +++ b/UnitTestProject/Modules/SwapModule/Margin/MarginInterestShadowTest.cs @@ -90,7 +90,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin // 新方法(独立 preEod,相同初始值) var newEvt = svc.CalcMarginInterest(td, valueDate, valueDate, position, Rate, Principal, Principal, 1m, AnnualDays, calcFirst: true, calcLast: true, - CreatePreEod(StartDate.AddDays(4), profitSum), 0, add: false, settment: true, swap: false); + CreatePreEod(StartDate.AddDays(4), profitSum), 0, add: false, settment: true, interestWindowEmpty: false); Console.WriteLine($"旧: I={oldI} Td={oldTd}"); Console.WriteLine($"新: I={newEvt.InterestAmount} Td={newEvt.TdInterestAmount} ClosePnL={newEvt.InterestClosePnL}"); @@ -114,7 +114,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin var newEvt = svc.CalcMarginInterest(td, valueDate, valueDate, position, Rate, Principal, Principal, 1m, AnnualDays, calcFirst: true, calcLast: true, - new eod_swap_position { id = 0 }, 0, add: false, settment: true, swap: false); + new eod_swap_position { id = 0 }, 0, add: false, settment: true, interestWindowEmpty: false); Assert.AreEqual(oldI, newEvt.InterestAmount, "InterestAmount 一致"); Assert.AreEqual(oldTd, newEvt.TdInterestAmount, "TdInterestAmount 一致"); @@ -143,7 +143,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin // 新方法:notional = posiPrincipal(无差分、无 orginPv) var newEvt = svc.CalcMarginInterest(td, valueDate, valueDate, position, Rate, Principal, Principal, 1m, AnnualDays, calcFirst: true, calcLast: false, - CreatePreEod(StartDate.AddDays(4), profitSum), 0, add: false, settment: false, swap: false); + CreatePreEod(StartDate.AddDays(4), profitSum), 0, add: false, settment: false, interestWindowEmpty: false); Console.WriteLine($"旧: I={oldI} Td={oldTd}"); Console.WriteLine($"新: I={newEvt.InterestAmount} Td={newEvt.TdInterestAmount}"); @@ -172,7 +172,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin var newEvt = svc.CalcMarginInterest(td, valueDate, valueDate, position, Rate, Principal * closePct, Principal, closePct, AnnualDays, calcFirst: true, calcLast: false, - CreatePreEod(StartDate.AddDays(4), profitSum), 0, add: false, settment: false, swap: false); + CreatePreEod(StartDate.AddDays(4), profitSum), 0, add: false, settment: false, interestWindowEmpty: false); Console.WriteLine($"旧: I={oldI} Td={oldTd}"); Console.WriteLine($"新: I={newEvt.InterestAmount} Td={newEvt.TdInterestAmount}"); @@ -191,7 +191,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin var svc = new StubSvc(); var newEvt = svc.CalcMarginInterest(td, valueDate, valueDate, position, Rate, Principal, Principal, 1m, AnnualDays, calcFirst: true, calcLast: false, - CreatePreEod(StartDate.AddDays(4), 820m), 0, add: false, settment: false, swap: true); + CreatePreEod(StartDate.AddDays(4), 820m), 0, add: false, settment: false, interestWindowEmpty: true); Assert.AreEqual(0m, newEvt.InterestAmount, "互换利息归零"); Assert.AreEqual(0m, newEvt.TdInterestAmount, "互换 TdInterestAmount 归零"); diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs index 192b042a..41e08ecf 100644 --- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs @@ -655,8 +655,8 @@ namespace YLErp.Modules.SwapModule // 计算计息区间 int interestPeriod = position.interest_rest_days ?? 1; - // true 跳过 不计利息; false 正常利息 - bool swap = InitInterestDate(unwindDate, preDealDate, td, tdClose, out DateTime startDate, out DateTime endDate); + // true=计息窗口为空(不计利息,利率与金额归零;典型场景=互换当日已结息,但判定只看日期窗口,与事件类型无关) + bool interestWindowEmpty = InitInterestDate(unwindDate, preDealDate, td, tdClose, out DateTime startDate, out DateTime endDate); // 获取利率(保证金/融资腿共用:SwapIntervalList 取当日适用固定利率 + 精度收口) decimal rate = Math.Round(GetFixedRate(position, unwindDate), InterestCalculationPrecision, MidpointRounding.AwayFromZero); // 做精度调整 原数据有精度误差 @@ -670,7 +670,7 @@ namespace YLErp.Modules.SwapModule // 保证金腿: 计息基数 = InterestPrincipalFix(保证金余额),无融资腿差分公式与 orginPv 维度 hack interests.Add(CalcMarginInterest(td, valueDate, endDate, positionClone, rate, position.InterestPrincipalFix * closePrecent, position.InterestPrincipalFix, - closePrecent, annualDays, calcFirst, effectiveCalcLast, preEodPosition, eventType, add, settment, swap)); + closePrecent, annualDays, calcFirst, effectiveCalcLast, preEodPosition, eventType, add, settment, interestWindowEmpty)); continue; } @@ -697,7 +697,7 @@ namespace YLErp.Modules.SwapModule closePrincipal = closePosiNotionalValue; } - decimal floatRate = GetFloatRate(position, preEodPosition, td.StartDate.Value, endDate, interestPeriod, swap, positionClone, effectiveCalcLast); + decimal floatRate = GetFloatRate(position, preEodPosition, td.StartDate.Value, endDate, interestPeriod, interestWindowEmpty, positionClone, effectiveCalcLast); // 根据场景计算利息 if (settment) @@ -713,7 +713,7 @@ namespace YLErp.Modules.SwapModule ? GetConsumedInterest(td.id, position.id, endDate) : 0m; interests.Add(CalcUnwindInterest(td, valueDate, endDate, positionClone, rate, floatRate, posiPrincipal, - closePrincipal, newClosePercent, annualDays, preEodPosition, eventType, add, swap, orginPv, calcFirst, + closePrincipal, newClosePercent, annualDays, preEodPosition, eventType, add, interestWindowEmpty, orginPv, calcFirst, effectiveCalcLast, consumedInterest)); } } @@ -858,7 +858,7 @@ namespace YLErp.Modules.SwapModule /// /// 获取浮动利率 /// - private decimal GetFloatRate(swap_position position, eod_swap_position preEod, DateTime startDate, DateTime endDate, int period, bool swap, swap_position positionClone, bool calcLast = true) + private decimal GetFloatRate(swap_position position, eod_swap_position preEod, DateTime startDate, DateTime endDate, int period, bool interestWindowEmpty, swap_position positionClone, bool calcLast = true) { if (string.IsNullOrEmpty(position.FloatRateUnderlyingCode)) return position.FloatRate; @@ -883,7 +883,7 @@ namespace YLErp.Modules.SwapModule // EQD-6968 口径自洽化:不算尾(calcLast=false)时 endDate 当天不计息,其定盘一概不取 // (有价也不取)——事件/回写利率与金额同源(末段已消费利率),杜绝"上午/下午落库利率不同"。 // 剩余持仓的新周期利率由 SwapEodPositionService 的"重置日再定盘"显式获取,不靠此处顺带。 - if (!swap && !calcLast && isResetDay) + if (!interestWindowEmpty && !calcLast && isResetDay) { var keptNoFetch = preEod.id != 0 ? preEod.FloatRate : position.FloatRate; SwapCalcTrace.Critical( @@ -897,7 +897,7 @@ namespace YLErp.Modules.SwapModule position.FloatRate = positionClone.FloatRate = rate; return position.FloatRate; } - if (!swap) + if (!interestWindowEmpty) { if (calcLast) { @@ -911,7 +911,7 @@ namespace YLErp.Modules.SwapModule $"FIX GetFloatRate p{position.id} {rateDate:yyyy-MM-dd}缺价且不算尾→沿用已有利率={kept:P6}(来源={(preEod.id != 0 ? "preEod.FloatRate" : "position.FloatRate")})"); return kept; } - SwapCalcTrace.Critical($"FIX GetFloatRate p{position.id} 互换事件(swap=true)→利率不参与,返回0"); + SwapCalcTrace.Critical($"FIX GetFloatRate p{position.id} 计息窗口为空→利率不参与,返回0"); return 0m; } @@ -1013,12 +1013,12 @@ namespace YLErp.Modules.SwapModule /// 定位:SwapCalcTrace 落盘 AccrueEod/AccrualPeriod 的 notional/days/rate/accrued;盘中 accrualBasis 可从 trace 的 notional 反推。 /// /// true=收盘归档(EOD),false=盘中平仓/互换。 - /// 互换事件(仅盘中生效,true 时利息归零,同 InitSwapDealInterest)。 + /// 计息窗口为空(仅盘中生效,true 时利息归零,同 InitSwapDealInterest;典型场景=互换当日已结息)。 public swap_flow_event CalcMarginInterest( trade td, DateTime valueDate, DateTime endDate, swap_position position, decimal rate, decimal closePrincipal, decimal posiPrincipal, decimal closePercent, int annualDays, bool calcFirst, bool calcLast, - eod_swap_position preEod, int eventType, bool add, bool settment, bool swap) + eod_swap_position preEod, int eventType, bool add, bool settment, bool interestWindowEmpty) { // 当日是否计息(算头算尾)——同 CalcEodInterest bool calcToday = true; @@ -1054,8 +1054,8 @@ namespace YLErp.Modules.SwapModule UnwindDate = settment ? valueDate : endDate }; - // 互换事件:利息归零(同 InitSwapDealInterest) - if (swap && !settment) + // 计息窗口为空:利息归零(同 InitSwapDealInterest;典型场景=互换事件) + if (interestWindowEmpty && !settment) { interest.InterestAmount = 0m; interest.TdInterestAmount = 0m; @@ -1120,7 +1120,7 @@ namespace YLErp.Modules.SwapModule /// /// 计算盘中利息(平仓/互换) /// - private swap_flow_event CalcUnwindInterest(trade td, DateTime valueDate, DateTime endDate, swap_position position, decimal rate, decimal floatRate, decimal posiPrincipal, decimal closePrincipal, decimal closePercent, int annualDays, eod_swap_position preEod, int eventType, bool add, bool swap, decimal orginPv, bool calcFirst, bool calcLast, decimal consumedInterest = 0m) + private swap_flow_event CalcUnwindInterest(trade td, DateTime valueDate, DateTime endDate, swap_position position, decimal rate, decimal floatRate, decimal posiPrincipal, decimal closePrincipal, decimal closePercent, int annualDays, eod_swap_position preEod, int eventType, bool add, bool interestWindowEmpty, decimal orginPv, bool calcFirst, bool calcLast, decimal consumedInterest = 0m) { if (preEod.id == 0) { @@ -1135,7 +1135,7 @@ namespace YLErp.Modules.SwapModule preEod.ValueDate = td.StartDate.Value.AddDays(-1); } - return InitSwapDealInterest(td, valueDate, endDate, rate, position, add, swap, posiPrincipal, + return InitSwapDealInterest(td, valueDate, endDate, rate, position, add, interestWindowEmpty, posiPrincipal, closePrincipal, closePercent, annualDays, eventType, preEod, orginPv, calcFirst, calcLast, consumedInterest); } @@ -1172,7 +1172,7 @@ namespace YLErp.Modules.SwapModule /// 计息年化利率 /// 利息腿 /// 是否新增 - /// 是否已互换 + /// 计息窗口为空(InitInterestDate 判定:true=本次不计利息,利率与金额归零;典型场景=互换当日已结息) /// 上一日终归档 /// 当日适用名义本金 /// 当日平仓名义本金 @@ -1184,7 +1184,7 @@ namespace YLErp.Modules.SwapModule decimal rate, swap_position position, bool add, - bool swap, + bool interestWindowEmpty, decimal posiNotionalValue, decimal closePosiNotionalValue, decimal closePrecent, @@ -1218,7 +1218,7 @@ namespace YLErp.Modules.SwapModule // 保证金腿已走 CalcMarginInterest(不经过本方法),orginPv 维度重映射不再需要; // orginPv 此处仅对融资腿生效(差分公式 accrualBasis = TdInterestPrincipal + posiPrincipal - orginPv)。 - if (swap) + if (interestWindowEmpty) { interest.InterestAmount = 0; // 利息金额 interest.TdInterestAmount = 0; // 当日新增利息 diff --git a/YLErpDAL/Modules/SwapModule/SwapTradeBaseService.cs b/YLErpDAL/Modules/SwapModule/SwapTradeBaseService.cs index ceb5634f..2229c443 100644 --- a/YLErpDAL/Modules/SwapModule/SwapTradeBaseService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapTradeBaseService.cs @@ -365,6 +365,8 @@ namespace YLErp.Modules.SwapModule /// 计息方式 /// 计息开始日期 /// 计息结束日期 + /// true=计息窗口为空(interestStart>interestEnd,本次不计利息,调用方将利率与金额归零; + /// 典型场景=互换当日已结息/不算头首日,但判定只看日期窗口,与事件类型无关);false=正常计息。 public bool InitInterestDate(DateTime valueDate, DateTime? preSettleDate, trade td, bool tdClose, out DateTime interestStart, out DateTime interestEnd) { interestStart = td.StartDate.Value;