From 43fb83ed009720d1c311285224a2c2a7f3b2ea7c Mon Sep 17 00:00:00 2001 From: hjhan Date: Wed, 19 Aug 2026 11:41:18 +0800 Subject: [PATCH] =?UTF-8?q?refactor(swap):=20relaxedFixingFromDate=20?= =?UTF-8?q?=E6=94=B9=E5=90=8D=20exclusionStart=E2=80=94=E2=80=94=E5=90=8D?= =?UTF-8?q?=E5=AE=9E=E5=BD=92=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 该参数诞生于"有价则取/缺价回退"口径(55363e9f 由 exclusionEndDate 改名而来), 自洽化(fafae5ca)后排除日一概不取价,"放宽取价"语义已不存在——名字回到 排除区间本义,并与 FIX 日志既有用词"排除起点"对齐: - BuildSegmentRates/CalcDailyCompoundInterest/CalcDailySimpleInterest 三签名 + 全平重放传参点 + 方法摘要补 exclusionStart 参数语义文档 零行为变更。 验证:内存套件 113/113;全量 981 例 145 败与基线 diff=0 --- .../GLMS20260817Fr007UnwindMorningTest.cs | 4 ++-- .../Modules/SwapModule/SwapDealService.cs | 20 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/UnitTestProject/Modules/SwapModule/GLMS20260817Fr007UnwindMorningTest.cs b/UnitTestProject/Modules/SwapModule/GLMS20260817Fr007UnwindMorningTest.cs index b62a2fd3..a83a0c41 100644 --- a/UnitTestProject/Modules/SwapModule/GLMS20260817Fr007UnwindMorningTest.cs +++ b/UnitTestProject/Modules/SwapModule/GLMS20260817Fr007UnwindMorningTest.cs @@ -577,9 +577,9 @@ namespace YLErp.Modules.SwapModule StringAssert.Contains(o.Ex.Message, "FR007"); } - // ── 到期日当天全平:replayEndDate=endDate+1 补计分支(relaxedFixingFromDate 的存在理由)── + // ── 到期日当天全平:replayEndDate=endDate+1 补计分支(exclusionStart 的存在理由)── // 不算尾时 InitInterestDate 把 endDate 回拨一天;最终全平的历史差分重放需把窗口补回真实 - // 平仓/到期日(replayEndDate=endDate+1),但该边界日的定盘经 relaxedFixingFromDate 标记为 + // 平仓/到期日(replayEndDate=endDate+1),但该边界日的定盘经 exclusionStart 标记为 // "有价则取/缺价跳过"——否则到期日上午全平会被尾日价误拦(EQD-6968 在到期日的镜像场景)。 [TestMethod] diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs index 62e90352..44714bd6 100644 --- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs @@ -1294,7 +1294,7 @@ namespace YLErp.Modules.SwapModule // 计算截至本次平仓日的累计利息 amountAtEnd CalcDailyCompoundInterest(replayEndDate, position, closePosiNotionalValue, interestAtEnd, annualDays, floateRate, closePrecent, - calcFirst, calcLast, ref amountAtEnd, ref tdAmountAtEnd, consumedInterest, relaxedFixingFromDate: endDate); + calcFirst, calcLast, ref amountAtEnd, ref tdAmountAtEnd, consumedInterest, exclusionStart: endDate); var interestAtPreviousEod = new swap_flow_event { InterestRate = rate }; decimal amountAtPreviousEod = 0m; decimal tdAmountAtPreviousEod = 0m; @@ -1356,11 +1356,13 @@ namespace YLErp.Modules.SwapModule /// /// 按重置周期切分利率段,每段记录 all-in 利率(spread+fixing)。返回 (分段列表, 末段浮动利率)。 /// fetchAfterDate: 仅该日期之后的重置日才取 FR007(单利传 ValueDate,复利传 null 全程取)。 + /// exclusionStart: 排除区间起点——该日期起(含)的重置日视为"排除日"(不算尾的不计息边界日), + /// 一概不取价;缺省=endDate。仅不算尾(calcLast=false)生效;算尾所有重置日照常强制取价。 /// private (List<(DateTime StartDate, decimal Rate)> Segments, decimal LastFloat) BuildSegmentRates( DateTime startDate, DateTime endDate, int interestPeriod, swap_position position, decimal spread, decimal initialFloat, - DateTime? fetchAfterDate, bool calcLast = true, DateTime? relaxedFixingFromDate = null) + DateTime? fetchAfterDate, bool calcLast = true, DateTime? exclusionStart = null) { var rates = new List<(DateTime, decimal)>(); var calcDays = (endDate - startDate).Days; @@ -1368,7 +1370,7 @@ namespace YLErp.Modules.SwapModule SwapCalcTrace.Critical( $"FIX Segments p{position.id} [{startDate:yyyy-MM-dd}→{endDate:yyyy-MM-dd}] period={interestPeriod} " + $"fetchAfter={(fetchAfterDate?.ToString("yyyy-MM-dd") ?? "全程")} calcLast={calcLast} " + - $"排除起点={(relaxedFixingFromDate?.ToString("yyyy-MM-dd") ?? (calcLast ? "无" : endDate.ToString("yyyy-MM-dd")))} seed={initialFloat:P6} spread={spread:P6}"); + $"排除起点={(exclusionStart?.ToString("yyyy-MM-dd") ?? (calcLast ? "无" : endDate.ToString("yyyy-MM-dd")))} seed={initialFloat:P6} spread={spread:P6}"); for (int i = 0; i <= calcDays; i += interestPeriod) { var resetDate = startDate.AddDays(i); @@ -1377,7 +1379,7 @@ namespace YLErp.Modules.SwapModule // 排除日(EQD-6968 自洽化)一概不取价(有价也不取),currentFloat 保持末段已消费利率: // 事件/快照回写的浮动利率与金额同源、与平仓时刻无关。剩余持仓的新周期利率由 // SwapEodPositionService 的"重置日再定盘"显式获取,不靠排除日顺带。算尾照常强制取价。 - bool isExcludedEnd = !calcLast && resetDate >= (relaxedFixingFromDate ?? endDate); + bool isExcludedEnd = !calcLast && resetDate >= (exclusionStart ?? endDate); if (needFetch && !isExcludedEnd) { currentFloat = ResolveFloatRate(position, resetDate, currentFloat); @@ -1405,17 +1407,17 @@ namespace YLErp.Modules.SwapModule /// public void CalcDailyCompoundInterest(DateTime endDate, swap_position position, decimal principal, swap_flow_event flowEvent, int annualDays, decimal floateRate, decimal closePercent, bool calcFirst, bool calcLast, - ref decimal InterestAmount, ref decimal TdInterestAmount, decimal consumedInterest = 0m, decimal resetCarryInterest = 0m, DateTime? relaxedFixingFromDate = null) + ref decimal InterestAmount, ref decimal TdInterestAmount, decimal consumedInterest = 0m, decimal resetCarryInterest = 0m, DateTime? exclusionStart = null) { var startDate = position.PosiStartDate; int interestPeriod = position.interest_rest_days ?? 1; // 分段取率:复利全程重放,每个重置日(含 startDate)取 FR007(fetchAfterDate=null)。 // calcLast 透传,与下方 AccruePeriod 的边界同源,避免两处漂移; - // relaxedFixingFromDate 仅重放补计不算尾漏计利息时非 null(=真实平仓日),锚定尾日跳过取价起点。 + // exclusionStart 仅重放补计不算尾漏计利息时非 null(=真实平仓日),锚定尾日跳过取价起点。 var (segmentRates, currentFloat) = BuildSegmentRates( startDate, endDate, interestPeriod, position, flowEvent.InterestRate, floateRate, - fetchAfterDate: null, calcLast: calcLast, relaxedFixingFromDate: relaxedFixingFromDate); + fetchAfterDate: null, calcLast: calcLast, exclusionStart: exclusionStart); // 纯函数复利计息:分段重置日并本金 + resetCarryInterest + 扣 consumedInterest var interestTrace = new AccrualTrace(); @@ -1445,7 +1447,7 @@ namespace YLErp.Modules.SwapModule /// /// 计算单利 盘中(按重置天数分段,每段使用对应浮动利率) /// - public void CalcDailySimpleInterest(eod_swap_position preEodPosition, DateTime endDate, swap_position position, decimal posiPrincipal, swap_flow_event flowEvent, int annualDays, decimal floateRate, decimal closePercent, decimal orginPv, bool calcFirst, bool calcLast, ref decimal InterestAmount, ref decimal TdInterestAmount, decimal consumedInterest = 0m, DateTime? relaxedFixingFromDate = null) + public void CalcDailySimpleInterest(eod_swap_position preEodPosition, DateTime endDate, swap_position position, decimal posiPrincipal, swap_flow_event flowEvent, int annualDays, decimal floateRate, decimal closePercent, decimal orginPv, bool calcFirst, bool calcLast, ref decimal InterestAmount, ref decimal TdInterestAmount, decimal consumedInterest = 0m, DateTime? exclusionStart = null) { var startDate = position.PosiStartDate; int interestPeriod = position.interest_rest_days ?? 1; @@ -1458,7 +1460,7 @@ namespace YLErp.Modules.SwapModule // calcLast 透传,与下方 AccruePeriod 的边界同源,避免两处漂移。 var (segmentRates, currentFloat) = BuildSegmentRates( startDate, endDate, interestPeriod, position, flowEvent.InterestRate, floateRate, - fetchAfterDate: preEodPosition.ValueDate, calcLast: calcLast, relaxedFixingFromDate: relaxedFixingFromDate); + fetchAfterDate: preEodPosition.ValueDate, calcLast: calcLast, exclusionStart: exclusionStart); // 纯函数计息:Accrued=缩放累计(InterestAmount),AccruedToday=未缩放累计(TdInterestAmount) var interestTrace = new AccrualTrace();