test(swap): EQD-6968 自洽化契约钉死——事件利率确定性+快照再定盘载体+CI_007/008 新口径
- Determinism_NoTail_EventFloatRateIndependentOfPublishTime:尾日价缺(上午) vs 有(下午)两世界,金额与事件 FloatRate 完全一致(=末段已消费利率0.01425) - CloseOnly_平仓日为重置日_剩余持仓快照再定盘(+非重置日反例):经 DealInterests 真路由断言快照 FloatRate=当日新定盘(RefixCalls 计数)——载体职责显式化验收 - CalcFirst 用例去掉 closeRate 免疫 hack:priorValueDate 根治后首重置日恒取价 - CI_007 更名+断言翻转:平仓日=重置日+不算尾 → 事件利率=末段已消费利率(旧); 原断言(取新定盘)正是被替换的旧口径契约,根因注释留痕 GLMS-JIATT-20260805 - CI_008:本金结转断言保留,利率断言改为末段旧利率 验证:ConsumedInterestScenario 9/9、InterestEodTailSnapshot+Fr007 36/36、 全量 976 例 145 败与基线 diff=0
This commit is contained in:
@@ -273,45 +273,28 @@ namespace YLErp.Modules.SwapModule
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 场景7:复现"平仓日=重置日 + calcLast=false → 重置日跳过 FR007 取价"
|
||||
// 场景7:平仓日=重置日 + calcLast=false 的事件利率口径(EQD-6968 自洽化后)
|
||||
// ================================================================
|
||||
|
||||
/// <summary>
|
||||
/// [CI_007] 平仓日恰好是重置日时,calcLast=false 不应导致该重置日的 FR007 取价被跳过
|
||||
/// [CI_007] 平仓日=重置日 + calcLast=false:排除日不取价,事件利率=末段已消费利率(确定性)
|
||||
/// ----------------------------------------------------------------
|
||||
/// 背景(GLMS-JIATT-20260805 根因):InterestCalcMode='10'(算头不算尾,calcLast=false),
|
||||
/// CalcDailyCompoundInterest 循环里 `if(!calcLast && accrueDate==endDate) continue` 会跳过平仓日当天。
|
||||
/// 若平仓日恰好是重置日(i%period==0),这个跳过会让"重置日取新FR007"的代码块永远不执行,
|
||||
/// 沿用上一个重置周期的旧利率。
|
||||
/// 历史(GLMS-JIATT-20260805):原缺陷是重置日取价被 calcLast 跳过 → flowEvent.FloatRate 停留旧值
|
||||
/// → 落库后传染 EOD。当时的修复=排除日"有价则取新定盘",事件利率因而取决于平仓时刻
|
||||
/// (上午=旧/下午=新),与金额实际使用的利率脱钩。
|
||||
///
|
||||
/// 构造:PosiStartDate=4/27, ResetPeriod=3, InterestCalcMode='10'(calcLast=false)
|
||||
/// - FR007 按日期分段:5/3之前返回 rateOld=0.001,5/3及之后返回 rateNew=0.002
|
||||
/// - 对照A:平仓日=5/5(非重置日,9? 不: (5/5-4/27)=8, 8%3=2 非重置) → 不该取新值
|
||||
/// - 对照B:平仓日=5/6(重置日,(5/6-4/27)=9, 9%3=0) → 应取新值 rateNew
|
||||
/// EQD-6968 自洽化后的新契约:
|
||||
/// ① 已平部分:排除日一概不取价(有价也不取),事件 FloatRate=末段已消费利率(rateOld),
|
||||
/// 与金额同源、与平仓时刻无关;
|
||||
/// ② 剩余持仓的新周期利率:由 EOD 快照"重置日再定盘"显式获取
|
||||
/// (InterestEodTailSnapshotTest.CloseOnly_平仓日为重置日_剩余持仓快照再定盘)。
|
||||
///
|
||||
/// 修复前:5/6 重置日被 calcLast 跳过 → 取到旧 rateOld → 与 5/5 相同
|
||||
/// 修复后:5/6 重置日正常取价 → 取到 rateNew → 与 5/5 不同
|
||||
/// ----------------------------------------------------------------
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// [CI_007] 平仓日恰好是重置日时,calcLast=false 不应导致该重置日的 FR007 取价被跳过
|
||||
/// ----------------------------------------------------------------
|
||||
/// 根因(GLMS-JIATT-20260805):InterestCalcMode='10'(calcLast=false),
|
||||
/// CalcDailyCompoundInterest 循环 `if(!calcLast && accrueDate==endDate) continue` 跳过平仓日。
|
||||
/// 若平仓日=重置日,取价代码块被跳过 → flowEvent.FloatRate 停留旧值 → 落库后传染 EOD。
|
||||
///
|
||||
/// 构造(避开周末,period=7):
|
||||
/// PosiStartDate=4/27(周一), period=7, interest_rule=0, InterestCalcMode='10'
|
||||
/// 重置日:i=0→4/27(周一), i=7→5/4(周一,工作日)
|
||||
/// 平仓日=5/4(=重置日=endDate)
|
||||
/// FR007 分界:rateDate>=5/4 返回 rateNew,否则 rateOld
|
||||
///
|
||||
/// 修复前:i=7(5/4)被 calcLast 跳过 → FloatRate=rateOld(旧值)
|
||||
/// 修复后:i=7(5/4)正常取价 → FloatRate=rateNew(新值)
|
||||
/// 构造(避开周末,period=7):PosiStartDate=6/1(周一), 平仓日=6/8(周一,重置日,7%7=0);
|
||||
/// FR007 分界:取价日>=6/8 返回 rateNew,否则 rateOld。
|
||||
/// ----------------------------------------------------------------
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void CI_007_平仓日等于重置日_calcLast_false_仍应取新FR007()
|
||||
public void CI_007_平仓日等于重置日_calcLast_false_事件利率为末段已消费利率()
|
||||
{
|
||||
const double rateOld = 0.001;
|
||||
const double rateNew = 0.002;
|
||||
@@ -363,12 +346,14 @@ namespace YLErp.Modules.SwapModule
|
||||
Assert.AreEqual(1, interests.Count);
|
||||
var result = interests[0];
|
||||
Console.WriteLine($"6/8(重置日,周一)平仓:FloatRate={result.FloatRate} Amount={result.InterestAmount:F6}");
|
||||
Console.WriteLine($" 期望 FloatRate={rateNew}(6/8 重置日查询日=6/8工作日,应取新利率)");
|
||||
Console.WriteLine($" 新口径期望 FloatRate={rateOld}(排除日不取价,事件利率=末段已消费利率)");
|
||||
|
||||
// 核心断言:6/8 是重置日,flowEvent.FloatRate 应反映新利率 rateNew
|
||||
Assert.IsTrue(Math.Abs((result.FloatRate ?? 0) - (decimal)rateNew) < 0.0001m,
|
||||
$"平仓日=重置日时 FloatRate 应={rateNew}(取到新利率)。" +
|
||||
$"实际={result.FloatRate},若={rateOld} 说明 calcLast=false 跳过了重置日取价(GLMS-JIATT-20260805 根因)");
|
||||
// 核心断言(EQD-6968 自洽化契约):排除日(不计息)一概不取价——即使 6/8 新定盘已发布,
|
||||
// 事件 FloatRate 也必须是末段已消费利率 rateOld,与金额同源、与平仓时刻无关。
|
||||
// 剩余持仓的新周期利率由 EOD 快照"重置日再定盘"显式获取(见 InterestEodTailSnapshotTest)。
|
||||
Assert.IsTrue(Math.Abs((result.FloatRate ?? 0) - (decimal)rateOld) < 0.0001m,
|
||||
$"排除日不取价:FloatRate 应=末段已消费利率 {rateOld}。实际={result.FloatRate}," +
|
||||
$"若={rateNew} 说明排除日仍在取价(旧口径:记录利率取决于平仓时刻)");
|
||||
|
||||
var interestBeforeResetDate = Principal * (FixedRate + (decimal)rateOld) * 7m / AnnualDays;
|
||||
AssertDecimal(Principal + interestBeforeResetDate, result.InterestPrincipal,
|
||||
@@ -426,10 +411,12 @@ namespace YLErp.Modules.SwapModule
|
||||
|
||||
var remainingInterest = previousInterest * remainingPrincipal / previousPrincipal;
|
||||
var expectedPrincipal = remainingPrincipal + remainingInterest;
|
||||
var expectedDailyInterest = expectedPrincipal * (fixedRate + (decimal)newFloatRate) / AnnualDays;
|
||||
AssertDecimal(expectedPrincipal, result.InterestPrincipal);
|
||||
AssertDecimal(expectedDailyInterest,
|
||||
result.InterestPrincipal * (result.InterestRate + result.FloatRate.Value) / AnnualDays);
|
||||
// EQD-6968 自洽化:排除日(平仓日=重置日)不取价,事件利率=末段已消费利率(旧)——与金额同源、
|
||||
// 与平仓时刻无关。剩余持仓的新周期利率由 EOD 快照"重置日再定盘"显式获取
|
||||
// (InterestEodTailSnapshotTest.CloseOnly_平仓日为重置日_剩余持仓快照再定盘)。
|
||||
AssertDecimal((decimal)oldFloatRate, result.FloatRate.Value,
|
||||
"排除日不取价:事件 FloatRate 应=末段已消费旧利率");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
Reference in New Issue
Block a user