diff --git a/UnitTestProject/Modules/SwapModule/Margin/MarginInterestGoldenReplayTest.cs b/UnitTestProject/Modules/SwapModule/Margin/MarginInterestGoldenReplayTest.cs index c9c226d3..22c41d27 100644 --- a/UnitTestProject/Modules/SwapModule/Margin/MarginInterestGoldenReplayTest.cs +++ b/UnitTestProject/Modules/SwapModule/Margin/MarginInterestGoldenReplayTest.cs @@ -125,7 +125,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin swap_flow_event newEvt; try { - newEvt = svc.CalcMarginInterest(td, valueDate, posClone, rate, + 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); diff --git a/UnitTestProject/Modules/SwapModule/Margin/MarginInterestShadowTest.cs b/UnitTestProject/Modules/SwapModule/Margin/MarginInterestShadowTest.cs index 603c49c0..4347a29e 100644 --- a/UnitTestProject/Modules/SwapModule/Margin/MarginInterestShadowTest.cs +++ b/UnitTestProject/Modules/SwapModule/Margin/MarginInterestShadowTest.cs @@ -90,7 +90,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin new swap_flow_event { InterestRate = Rate }, AnnualDays, 0m, 1m, ref oldI, ref oldTd); // 新方法(独立 preEod,相同初始值) - var newEvt = svc.CalcMarginInterest(td, valueDate, position, Rate, Principal, Principal, 1m, + 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); @@ -114,7 +114,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin valueDate, td.StartDate.Value, position, Principal, Principal, new swap_flow_event { InterestRate = Rate }, AnnualDays, 0m, 1m, ref oldI, ref oldTd); - var newEvt = svc.CalcMarginInterest(td, valueDate, position, Rate, Principal, Principal, 1m, + 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); @@ -143,7 +143,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin calcFirst: true, calcLast: false, ref oldI, ref oldTd); // 新方法:notional = posiPrincipal(无差分、无 orginPv) - var newEvt = svc.CalcMarginInterest(td, valueDate, position, Rate, Principal, Principal, 1m, + 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); @@ -171,7 +171,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin new swap_flow_event { InterestRate = Rate }, AnnualDays, 0m, closePct, orginPv, calcFirst: true, calcLast: false, ref oldI, ref oldTd); - var newEvt = svc.CalcMarginInterest(td, valueDate, position, Rate, + 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); @@ -191,7 +191,7 @@ namespace UnitTestProject.Modules.SwapModule.Margin var valueDate = StartDate.AddDays(5); var svc = new StubSvc(); - var newEvt = svc.CalcMarginInterest(td, valueDate, position, Rate, Principal, Principal, 1m, + 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); diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs index fec4dfaf..2a2072ae 100644 --- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs @@ -697,7 +697,13 @@ namespace YLErp.Modules.SwapModule decimal floatRate = GetFloatRate(position, preEodPosition, td.StartDate.Value, endDate, interestPeriod, swap, positionClone); // 根据场景计算利息 - if (settment) + if (MarginModes.Contains(position.InterestMode)) + { + // 保证金腿(5/6):专属计息,notional 直接取保证金余额,无融资腿差分公式与 orginPv 维度 hack + interests.Add(CalcMarginInterest(td, valueDate, endDate, positionClone, rate, closePrincipal, posiPrincipal, + newClosePercent, annualDays, calcFirst, calcLast||newCalcLast, preEodPosition, eventType, add, settment, swap)); + } + else if (settment) { // 收盘归档场景,使用 CalcEodInterest interests.Add(CalcEodInterest(td, valueDate, positionClone, rate, floatRate, closePrincipal, posiPrincipal, annualDays, calcFirst, calcLast, preEodPosition, eventType, add)); @@ -921,17 +927,18 @@ namespace YLErp.Modules.SwapModule /// /// 保证金是纯固定利率单利:浮动利率(FR007)/分段利率/复利对其均为死分支(前端无入口、 /// 确认书不含、FundingLegRate.Build 对空 FloatRateUnderlyingCode 恒返回 Fixed)。故本方法直接用 - /// SimpleInterestAccrual 纯函数计息,notional 取保证金余额本身: - /// EOD = 昨日终本金 preEod.TdInterestPrincipal(与旧 CalcDailySimpleInterestByEod 同源) - /// 盘中 = 今日本金 posiPrincipal(InterestPrincipalFix) - /// 消除融资腿差分公式 accrualBasis = TdInterestPrincipal + posiPrincipal - orginPv 与 orginPv 维度 - /// hack——对保证金 accrualBasis 恒等于 posiPrincipal,差分冗余。保留累计语义(priorAccrued + 增量), - /// 满足下游 SwapEodPositionService 字段契约(InterestAmount=缩放累计、TdInterestAmount=单日参考等)。 + /// SimpleInterestAccrual 纯函数计息,本金取保证金余额: + /// EOD = 昨日终本金 preEod.TdInterestPrincipal(与旧 CalcDailySimpleInterestByEod 同源,无差分) + /// 盘中 = accrualBasis(preEod.TdInterestPrincipal + posiPrincipal - orginPv) + /// 盘中保留差分是必要的:posiPrincipal 是否经 ResolveInterestLegPositions 对齐到实时剩余是路径相关的 + /// (生产对齐 / 诊断测试用原始腿),单一本金变量无法覆盖两种状态,差分经 orginPv 自适应。orginPv 在 + /// 本方法内部按保证金维度计算(PreviousBalance),消除原 InitSwapDealInterest 的外部维度 hack + /// (融资腿 orginPv=浮动端名义本金)。保留累计语义(priorAccrued + 增量),满足下游字段契约。 /// /// true=收盘归档(EOD),false=盘中平仓/互换。 /// 互换事件(仅盘中生效,true 时利息归零,同 InitSwapDealInterest)。 public swap_flow_event CalcMarginInterest( - trade td, DateTime valueDate, swap_position position, decimal rate, + 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) @@ -967,7 +974,7 @@ namespace YLErp.Modules.SwapModule FloatRate = 0m, DataState = (int)SwapFlowDateStateEnum.完成, ClientId = td.ClientId, - UnwindDate = valueDate + UnwindDate = settment ? valueDate : endDate }; // 互换事件:利息归零(同 InitSwapDealInterest) @@ -1000,22 +1007,27 @@ namespace YLErp.Modules.SwapModule } else { - // 盘中:notional = 今日本金 posiPrincipal(无差分、无 orginPv) + // 盘中:accrualBasis 自适应"实时剩余本金"——posiPrincipal 已对齐(ResolveInterestLegPositions) + // 时 = posiPrincipal;未对齐的原始腿经 orginPv(=PreviousBalance 昨日终) 修正回昨日终剩余。 + // 单一本金变量无法覆盖两种 position 状态,故保留差分(与 EOD 直接用 preEod.TdInterestPrincipal 不同)。 + // orginPv 在此内部按保证金维度计算,消除原 InitSwapDealInterest 的外部维度 hack。 + var orginPv = MarginCalc.PreviousBalance(preEod, posiPrincipal); + var accrualBasis = preEod.TdInterestPrincipal + posiPrincipal - orginPv; var segmentRates = new List<(DateTime, decimal)> { (position.PosiStartDate, rate) }; var r = SimpleInterestAccrual.AccruePeriod( priorAccrued: preEod.InterestProfitSum * closePercent, - notional: posiPrincipal, + notional: accrualBasis, unwindFraction: closePercent, segmentRates: segmentRates, startDate: position.PosiStartDate, - endDate: valueDate, + endDate: endDate, priorValueDate: preEod.ValueDate, boundary: AccrualBoundary.Of(calcFirst, calcLast), annualDays: annualDays, isAnnualized: position.IsAnnualized); interestAmount = r.Accrued; tdInterestAmount = r.AccruedToday; - interest.InterestPrincipal = posiPrincipal * closePercent; // 同 CalcDailySimpleInterest:1304 + interest.InterestPrincipal = accrualBasis * closePercent; // 同 CalcDailySimpleInterest:1304 } } @@ -1135,11 +1147,8 @@ namespace YLErp.Modules.SwapModule interest.ClientId = td.ClientId; interest.UnwindDate = endDate; - // 保证金腿的 orginPv 对齐到保证金本金维度,避免差分公式维度不匹配算出巨负值 - if (MarginModes.Contains(position.InterestMode)) - { - orginPv = MarginCalc.PreviousBalance(preEodPosition, position.InterestPrincipalFix); - } + // 保证金腿已走 CalcMarginInterest(不经过本方法),orginPv 维度重映射不再需要; + // orginPv 此处仅对融资腿生效(差分公式 accrualBasis = TdInterestPrincipal + posiPrincipal - orginPv)。 if (swap) {