108 lines
5.0 KiB
C#
108 lines
5.0 KiB
C#
using Newtonsoft.Json;
|
||
using YLErp;
|
||
using YLErp.Modules.SwapModule;
|
||
using YLErp.Modules.SwapModule.Accrual;
|
||
|
||
namespace UnitTestProject.Modules.SwapModule.Accrual
|
||
{
|
||
/// <summary>
|
||
/// EQD-6977 carryInInterest 契约测试:
|
||
/// 1) 默认 0 与旧逐日循环逐位一致(加参零行为变化的安全证明);
|
||
/// 2) carry-in 仅在【首个重置日】并入计息基数(非窗口首日起息)——
|
||
/// 与"持有至到期"全期轨迹对齐的数学不变量:增量 = carryIn × 后续段日利率 × 后续段天数。
|
||
/// </summary>
|
||
[TestClass]
|
||
public class CompoundCarryInTest
|
||
{
|
||
private const decimal Notional = 100_000_000m;
|
||
private const decimal Spread = 0.0025m;
|
||
private const int AnnualDays = 365;
|
||
private static readonly DateTime StartDate = new(2026, 4, 21);
|
||
private static readonly DateTime EndDate = new(2026, 5, 11); // 21天 = 3×7,末日是重置日
|
||
|
||
private static swap_position CreatePosition()
|
||
{
|
||
return new swap_position
|
||
{
|
||
id = 1001, SwapTradeId = 1, PosiDirection = 0,
|
||
InterestDirection = (int)SwapDirectionEnum.收取,
|
||
InterestMode = (int)InterestModeEnum.标的期初全价,
|
||
InterestRateDefault = Spread,
|
||
InterestPrincipalFix = Notional,
|
||
PosiStartDate = StartDate, PosiMatuirityDate = StartDate.AddYears(1),
|
||
IsInitial = true, Invalid = false,
|
||
InterestType = (int)InterestTypeEnum.复利,
|
||
IsAnnualized = true, interest_rest_days = 7, interest_rule = 0,
|
||
FloatRateUnderlyingCode = null,
|
||
InterestSwapInterval = "[]"
|
||
};
|
||
}
|
||
|
||
private static List<(DateTime, decimal)> Segments()
|
||
=> new()
|
||
{
|
||
(StartDate, Spread),
|
||
(StartDate.AddDays(7), Spread),
|
||
(StartDate.AddDays(14), Spread),
|
||
};
|
||
|
||
private sealed class StubSvc : SwapDealService
|
||
{
|
||
public StubSvc() : base(new OptUserInfo(0, nameof(CompoundCarryInTest), OptUserFrom.UnitTest)) { }
|
||
}
|
||
|
||
[TestMethod]
|
||
public void carryIn_默认省略_与旧逐日循环一致()
|
||
{
|
||
var position = CreatePosition();
|
||
var flowEvent = new swap_flow_event { InterestRate = Spread };
|
||
|
||
decimal oldI = 0, oldTd = 0;
|
||
new StubSvc().CalcDailyCompoundInterest(EndDate, position, Notional, flowEvent,
|
||
AnnualDays, 0m, 1m, true, false, ref oldI, ref oldTd);
|
||
|
||
// 省略 carryInInterest(默认 0)
|
||
var r1 = CompoundInterestAccrual.AccruePeriod(
|
||
notional: Notional, segmentRates: Segments(),
|
||
startDate: StartDate, endDate: EndDate,
|
||
boundary: AccrualBoundary.StartOnly, annualDays: AnnualDays, isAnnualized: true,
|
||
resetCarryInterest: 0m, realizedInterest: 0m, unwindFraction: 1m,
|
||
finalBasis: out _);
|
||
// 显式传 0 与省略等价
|
||
var r2 = CompoundInterestAccrual.AccruePeriod(
|
||
notional: Notional, segmentRates: Segments(),
|
||
startDate: StartDate, endDate: EndDate,
|
||
boundary: AccrualBoundary.StartOnly, annualDays: AnnualDays, isAnnualized: true,
|
||
resetCarryInterest: 0m, realizedInterest: 0m, unwindFraction: 1m,
|
||
finalBasis: out _, trace: null, carryInInterest: 0m);
|
||
|
||
Assert.AreEqual((double)oldI, (double)r1.Accrued, 0.0000001, "省略 carryIn 与旧实现一致");
|
||
Assert.AreEqual((double)r1.Accrued, (double)r2.Accrued, 0.0000001, "省略与显式0一致");
|
||
}
|
||
|
||
[TestMethod]
|
||
public void carryIn_仅在首重置日起息_增量等于后续两段复利()
|
||
{
|
||
const decimal carryIn = 1_000_000m;
|
||
|
||
decimal Accrued(decimal c)
|
||
=> CompoundInterestAccrual.AccruePeriod(
|
||
notional: Notional, segmentRates: Segments(),
|
||
startDate: StartDate, endDate: EndDate,
|
||
boundary: AccrualBoundary.Both, annualDays: AnnualDays, isAnnualized: true,
|
||
resetCarryInterest: 0m, realizedInterest: 0m, unwindFraction: 1m,
|
||
finalBasis: out _, trace: null, carryInInterest: c).Accrued;
|
||
|
||
var delta = Accrued(carryIn) - Accrued(0m);
|
||
|
||
// Both 边界下三段各 7 天。carryIn 于 4/28(首个重置日)并入基数:
|
||
// 首段 [4/21,4/28] 不含 carryIn;其后两段 carryIn 自身起息且其首段利息再复利。
|
||
// 精确增量 = c×d + (c + c×d)×d = c×(2d + d²) = c×((1+d)² − 1),d = 7天利率因子。
|
||
var d = Spread * 7m / AnnualDays;
|
||
var expected = carryIn * (2m * d + d * d);
|
||
Assert.AreEqual((double)expected, (double)delta, 0.001,
|
||
"carryIn 增量 = 首个重置日起息的两段复利,首段不含 carryIn");
|
||
}
|
||
}
|
||
}
|