using YLErp.DBModels.Enums; using YLErp.Modules.SwapModule; using YLErp.Modules.SwapModule.Accrual; using YLErp.Modules.SwapModule.Penalty; namespace UnitTestProject.Modules.SwapModule.Penalty { /// /// EQD-6977 罚息冻结利率解析契约测试。 /// 规则(需求 2.2.2):冻结为「最后一个重置区间」定盘;终止日为重置日也取上一区间。 /// [TestClass] public class PenaltyLegRateResolverTest { private const decimal Spread = 0.05m; // +500bp private static readonly DateTime UnwindDate = new(2026, 8, 25); private static swap_position CreateFloatPosition(int interestRule = 0) => new() { id = 1001, SwapTradeId = 1, PosiDirection = 0, InterestDirection = (int)SwapDirectionEnum.支付, InterestMode = (int)InterestModeEnum.标的期初全价, InterestRateDefault = Spread, PosiStartDate = new DateTime(2026, 7, 31), interest_rest_days = 7, interest_rule = interestRule, FloatRateUnderlyingCode = "FR007", FloatRate = 0.0185m }; [TestMethod] public void 浮动腿_preEod快照优先_重置日下午仍取上一区间() { // 8/25 为重置日且下午已出新价的边缘场景:preEod.FloatRate(昨日区间定盘)仍优先, // 解析器不做任何取价——「终止日取上一区间」由快照语义天然覆盖。 var p = CreateFloatPosition(); var rate = PenaltyLegRateResolver.ResolveFrozenRate( p, spread: Spread, preEodFloatRate: 0.0210m, unwindDate: UnwindDate, tryGetFixing: _ => throw new AssertFailedException("preEod 在场时不应取价")); Assert.AreEqual(Spread + 0.0210m, rate.AllInRate, "冻结 all-in = 利差 + 上一区间定盘"); } [TestMethod] public void 浮动腿_无preEod_按前一营业日取价日取定盘() { var p = CreateFloatPosition(interestRule: 0); // 当前营业日规则 DateTime? askedDate = null; var rate = PenaltyLegRateResolver.ResolveFrozenRate( p, spread: Spread, preEodFloatRate: null, unwindDate: UnwindDate, tryGetFixing: d => { askedDate = d; return 0.0195m; }); Assert.AreEqual(new DateTime(2026, 8, 24), askedDate, "取价日 = GetFixingDate(8/24, rule=0)"); Assert.AreEqual(Spread + 0.0195m, rate.AllInRate); } [TestMethod] public void 浮动腿_无preEod_缺价抛异常() { var p = CreateFloatPosition(); Assert.ThrowsException(() => PenaltyLegRateResolver.ResolveFrozenRate( p, spread: Spread, preEodFloatRate: null, unwindDate: UnwindDate, tryGetFixing: _ => null)); } [TestMethod] public void 固定腿_不取价_直接固定利率() { var p = CreateFloatPosition(); p.FloatRateUnderlyingCode = null; var rate = PenaltyLegRateResolver.ResolveFrozenRate( p, spread: Spread, preEodFloatRate: null, unwindDate: UnwindDate, tryGetFixing: _ => throw new AssertFailedException("固定腿不应取价")); Assert.AreEqual(Spread, rate.AllInRate); } } }