EQD-6968: 重命名 FR007 不计尾放宽取率参数为准确语义命名,提升可读性

纯改名与化简:BuildSegmentRates 的 includeEnd 别名->calcLast、exclusionEndDate->relaxedFixingFromDate(语义=从该日起放宽定盘缺失拦截而非排除某日);GetFloatRate 不算尾回退改正向结构;GetInterests 抽出 effectiveCalcLast 替代重复 calcLast||newCalcLast。计息逻辑不变,12 用例内存单测全绿。
This commit is contained in:
hjhan
2026-08-17 13:00:08 +08:00
parent 56c9a0652b
commit 55363e9f51
+26 -21
View File
@@ -644,6 +644,8 @@ namespace YLErp.Modules.SwapModule
var annualDays = tradeExtend == null ? 365 : tradeExtend.ExtendObj.AnnualDays;
bool calcFirst = tradeExtend?.ExtendObj.InterestCalcMode?.StartsWith("1") ?? true;
bool calcLast = tradeExtend?.ExtendObj.InterestCalcMode.EndsWith("1") ?? true;
// 计息到尾日(含平仓场景覆盖):交易本身算尾 或 本次平仓指定算尾(newCalcLast)
bool effectiveCalcLast = calcLast || newCalcLast;
foreach (var position in positions)
{
// 初始化持仓信息
@@ -668,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, calcLast || newCalcLast, preEodPosition, eventType, add, settment, swap));
closePrecent, annualDays, calcFirst, effectiveCalcLast, preEodPosition, eventType, add, settment, swap));
continue;
}
@@ -695,7 +697,7 @@ namespace YLErp.Modules.SwapModule
closePrincipal = closePosiNotionalValue;
}
decimal floatRate = GetFloatRate(position, preEodPosition, td.StartDate.Value, endDate, interestPeriod, swap, positionClone, calcLast || newCalcLast);
decimal floatRate = GetFloatRate(position, preEodPosition, td.StartDate.Value, endDate, interestPeriod, swap, positionClone, effectiveCalcLast);
// 根据场景计算利息
if (settment)
@@ -712,7 +714,7 @@ namespace YLErp.Modules.SwapModule
: 0m;
interests.Add(CalcUnwindInterest(td, valueDate, endDate, positionClone, rate, floatRate, posiPrincipal,
closePrincipal, newClosePercent, annualDays, preEodPosition, eventType, add, swap, orginPv, calcFirst,
calcLast || newCalcLast, consumedInterest));
effectiveCalcLast, consumedInterest));
}
}
//当日有平仓或互换记录时,避免重复结算
@@ -732,7 +734,7 @@ namespace YLErp.Modules.SwapModule
item.InterestClosePnL = 0;
}
}
else if (!calcLast && !newCalcLast)
else if (!effectiveCalcLast)
{
// 平仓不算尾:扣除已结算的利息(算尾时利息已包含关闭日,无重叠)
var closePnl = closeEvent.Sum(s => s.InterestClosePnL);
@@ -868,12 +870,13 @@ namespace YLErp.Modules.SwapModule
}
if (!swap)
{
// EQD-6968不算尾时平仓日 FR007 缺失不拦截(该日利率不参与计息),回退期初定盘;算尾(calcLast=true)仍抛(正确依赖)。
if (!calcLast && IndexFixer.TryGetFixing(IndexFixerBase.GetFixingDate(startDate, position.interest_rule), position.FloatRateUnderlyingCode, out decimal fb))
// EQD-6968算尾(calcLast=true)时结算日 FR007 必须落表,缺失即依赖报错(既有正确行为);
// 不算尾(calcLast=false)时结算日 FR007 不强制,缺失则回退期初定盘,避免上午未发布误拦平仓。
if (calcLast)
throw new Exception($"获取不到{position.FloatRateUnderlyingCode}在{rateDate:yyyy年MM月dd日}的价格");
if (IndexFixer.TryGetFixing(IndexFixerBase.GetFixingDate(startDate, position.interest_rule), position.FloatRateUnderlyingCode, out decimal fb))
return fb;
if (!calcLast)
return position.FloatRate;
throw new Exception($"获取不到{position.FloatRateUnderlyingCode}在{rateDate:yyyy年MM月dd日}的价格");
return position.FloatRate;
}
return 0m;
}
@@ -1257,7 +1260,7 @@ namespace YLErp.Modules.SwapModule
// 计算截至本次平仓日的累计利息 amountAtEnd
CalcDailyCompoundInterest(replayEndDate, position, closePosiNotionalValue,
interestAtEnd, annualDays, floateRate, closePrecent,
calcFirst, calcLast, ref amountAtEnd, ref tdAmountAtEnd, consumedInterest, exclusionEndDate: endDate);
calcFirst, calcLast, ref amountAtEnd, ref tdAmountAtEnd, consumedInterest, relaxedFixingFromDate: endDate);
var interestAtPreviousEod = new swap_flow_event { InterestRate = rate };
decimal amountAtPreviousEod = 0m;
decimal tdAmountAtPreviousEod = 0m;
@@ -1311,7 +1314,7 @@ namespace YLErp.Modules.SwapModule
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 includeEnd = true, DateTime? exclusionEndDate = null)
DateTime? fetchAfterDate, bool calcLast = true, DateTime? relaxedFixingFromDate = null)
{
var rates = new List<(DateTime, decimal)>();
var calcDays = (endDate - startDate).Days;
@@ -1320,12 +1323,13 @@ namespace YLErp.Modules.SwapModule
{
var resetDate = startDate.AddDays(i);
bool needFetch = (fetchAfterDate == null || resetDate > fetchAfterDate.Value);
// EQD-6968:不算尾(includeEnd=false)的排除日(真实平仓日)重置——优先取新利率(满足 GLMS-JIATT-20260805
// 重置日=平仓日用新利率)缺失则沿用上一重置利率(该日利率不参与计息),不抛异常以免上午 FR007 未发布误拦平仓。
bool isExcludedEnd = !includeEnd && resetDate >= (exclusionEndDate ?? endDate);
// EQD-6968:不算尾(calcLast=false)时,从真实平仓日(relaxedFixingFromDate,缺省取 endDate)起的重置
// 优先取新利率(满足 GLMS-JIATT-20260805 重置日=平仓日用新利率)缺失则沿用上一重置利率
// 不抛异常,以免上午 FR007 未发布误拦平仓。算尾(calcLast=true)时尾日定盘照常强制。
bool relaxEndFixing = !calcLast && resetDate >= (relaxedFixingFromDate ?? endDate);
if (needFetch)
{
if (!isExcludedEnd)
if (!relaxEndFixing)
{
currentFloat = ResolveFloatRate(position, resetDate, currentFloat);
}
@@ -1355,16 +1359,17 @@ namespace YLErp.Modules.SwapModule
/// <returns></returns>
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? exclusionEndDate = null)
ref decimal InterestAmount, ref decimal TdInterestAmount, decimal consumedInterest = 0m, decimal resetCarryInterest = 0m, DateTime? relaxedFixingFromDate = null)
{
var startDate = position.PosiStartDate;
int interestPeriod = position.interest_rest_days ?? 1;
// 分段取率:复利全程重放,每个重置日(含 startDate)取 FR007fetchAfterDate=null)。
// EQD-6968includeEnd 透传 calcLast,与下方 AccruePeriod 的边界同源,避免两处漂移
// EQD-6968calcLast 透传,与下方 AccruePeriod 的边界同源,避免两处漂移
// relaxedFixingFromDate 仅重放补计不算尾漏计利息时非 null(=真实平仓日),锚定尾日定盘放松点。
var (segmentRates, currentFloat) = BuildSegmentRates(
startDate, endDate, interestPeriod, position, flowEvent.InterestRate, floateRate,
fetchAfterDate: null, includeEnd: calcLast, exclusionEndDate: exclusionEndDate);
fetchAfterDate: null, calcLast: calcLast, relaxedFixingFromDate: relaxedFixingFromDate);
// 纯函数复利计息:分段重置日并本金 + resetCarryInterest + 扣 consumedInterest
var interestTrace = new AccrualTrace();
@@ -1394,7 +1399,7 @@ namespace YLErp.Modules.SwapModule
/// <summary>
/// 计算单利 盘中(按重置天数分段,每段使用对应浮动利率)
/// </summary>
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? exclusionEndDate = 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? relaxedFixingFromDate = null)
{
var startDate = position.PosiStartDate;
int interestPeriod = position.interest_rest_days ?? 1;
@@ -1404,10 +1409,10 @@ namespace YLErp.Modules.SwapModule
var accrualBasis = preEodPosition.TdInterestPrincipal + posiPrincipal - orginPv;
// 分段取率:仅 ValueDate 之后的重置日才取 FR007fetchAfterDate=ValueDate)。
// EQD-6968includeEnd 透传 calcLast,与下方 AccruePeriod 的边界同源,避免两处漂移。
// EQD-6968calcLast 透传,与下方 AccruePeriod 的边界同源,避免两处漂移。
var (segmentRates, currentFloat) = BuildSegmentRates(
startDate, endDate, interestPeriod, position, flowEvent.InterestRate, floateRate,
fetchAfterDate: preEodPosition.ValueDate, includeEnd: calcLast, exclusionEndDate: exclusionEndDate);
fetchAfterDate: preEodPosition.ValueDate, calcLast: calcLast, relaxedFixingFromDate: relaxedFixingFromDate);
// 纯函数计息:Accrued=缩放累计(InterestAmount)AccruedToday=未缩放累计(TdInterestAmount)
var interestTrace = new AccrualTrace();