using System; using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using YLErp; using YLErp.DBModels; using YLErp.DBModels.Enums; using YLErp.Modules.SwapModule; using YLErp.Modules.SwapModule.Accrual; namespace UnitTestProject.Modules.SwapModule.Accrual { /// /// 影子测试:CalcDailyCompoundInterestByEod(旧逐日循环)vs CompoundInterestAccrual.AccrueEod(新纯函数)。 /// 构造同一组参数,两套实现并行跑,断言结果一致(到分)。 /// [TestClass] public class CompoundEodShadowTest { private const decimal Notional = 100_000_000m; private const decimal FixedRate = 0.03m; private const int AnnualDays = 365; private static readonly DateTime TradeDate = new(2026, 4, 21); private static readonly DateTime EodDate = new(2026, 4, 28); // 第7天=重置日 private static trade CreateTrade() { return new trade { id = 1, TradeNumber = "UT-SHADOW", ClientId = 999998, TradeType = "收益互换", TradeDate = TradeDate, StartDate = TradeDate, ExerciseDate = TradeDate.AddYears(1), TradeStatus = "确认成交", ValidState = "Valid", trade_extend = new trade_extend { TradeId = 1, ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson { AnnualDays = AnnualDays, InterestCalcMode = "11", SettlementRules = 0 }) } }; } private static swap_position CreatePosition(int interestMode, int interestType, int resetDays) { return new swap_position { id = 1001, SwapTradeId = 1, PosiDirection = 0, InterestDirection = (int)SwapDirectionEnum.收取, InterestMode = interestMode, InterestRateDefault = FixedRate, InterestPrincipalFix = Notional, PosiStartDate = TradeDate, PosiMatuirityDate = TradeDate.AddYears(1), IsInitial = true, Invalid = false, InterestType = interestType, IsAnnualized = true, interest_rest_days = resetDays, interest_rule = 0, FloatRateUnderlyingCode = null, InterestSwapInterval = "[]" }; } private static eod_swap_position CreatePreEod(decimal tdPrincipal, decimal unrealized) { return new eod_swap_position { id = 1, SwapTradeId = 1, PositionId = 1001, ValueDate = EodDate.AddDays(-1), TdInterestPrincipal = tdPrincipal, InterestProfitSum = unrealized, PosiNotionalValue = Notional, FloatRate = 0m }; } /// /// 重置日场景:EOD 恰为重置日(7天周期,第7天)。 /// [TestMethod] public void 影子_重置日_旧新一致() { var position = CreatePosition((int)InterestModeEnum.合约名义本金规模, (int)InterestTypeEnum.复利, 7); var preEod = CreatePreEod(Notional, 50_000m); var flowEvent = new swap_flow_event { InterestRate = FixedRate }; // 旧方法 decimal oldInterest = 0, oldTd = 0; var svc = new StubSvc(); svc.CalcDailyCompoundInterestByEod(preEod, EodDate, TradeDate, position, Notional, Notional, flowEvent, AnnualDays, 0m, 1m, ref oldInterest, ref oldTd); // 新方法 var rate = FundingLegRate.Fixed(FixedRate); var policy = new AccrualPolicy(AccrualBoundary.Both, true, 7, AnnualDays, true); var remainingPercent = Math.Max(0m, Math.Min(1m, Notional / Notional)); var result = CompoundInterestAccrual.AccrueEod( 50_000m, Notional, Notional, 1m, rate, policy, isResetDay: true, remainingPercent, EodDate); Console.WriteLine($"重置日: 旧 InterestAmount={oldInterest} Td={oldTd}"); Console.WriteLine($"重置日: 新 Accrued={result.Accrued} AccruedToday={result.AccruedToday}"); Assert.AreEqual((double)oldInterest, (double)result.Accrued, 0.01, "InterestAmount 一致"); Assert.AreEqual((double)oldTd, (double)result.AccruedToday, 0.01, "TdInterestAmount 一致"); } /// /// 非重置日场景:第3天(非7的倍数)。 /// [TestMethod] public void 影子_非重置日_旧新一致() { var nonResetDate = new DateTime(2026, 4, 24); // 第3天 var position = CreatePosition((int)InterestModeEnum.合约名义本金规模, (int)InterestTypeEnum.复利, 7); var preEod = CreatePreEod(Notional, 30_000m); preEod.ValueDate = nonResetDate.AddDays(-1); var flowEvent = new swap_flow_event { InterestRate = FixedRate }; // 旧方法 decimal oldInterest = 0, oldTd = 0; var svc = new StubSvc(); svc.CalcDailyCompoundInterestByEod(preEod, nonResetDate, TradeDate, position, Notional, Notional, flowEvent, AnnualDays, 0m, 1m, ref oldInterest, ref oldTd); // 新方法 var rate = FundingLegRate.Fixed(FixedRate); var policy = new AccrualPolicy(AccrualBoundary.Both, true, 7, AnnualDays, true); var result = CompoundInterestAccrual.AccrueEod( 30_000m, Notional, Notional, 1m, rate, policy, isResetDay: false, 0m, nonResetDate); Console.WriteLine($"非重置日: 旧 InterestAmount={oldInterest} Td={oldTd}"); Console.WriteLine($"非重置日: 新 Accrued={result.Accrued} AccruedToday={result.AccruedToday}"); Assert.AreEqual((double)oldInterest, (double)result.Accrued, 0.01, "InterestAmount 一致"); Assert.AreEqual((double)oldTd, (double)result.AccruedToday, 0.01, "TdInterestAmount 一致"); } private sealed class StubSvc : SwapDealService { public StubSvc() : base(new OptUserInfo(0, nameof(CompoundEodShadowTest), OptUserFrom.UnitTest)) { } public override decimal GetConsumedInterest(int tradeId, long positionId, DateTime beforeDate) => 0m; } } }