Files
zszq-trs/UnitTestProject/Modules/SwapModule/Accrual/CompoundPeriodShadowTest.cs
T
hjhan 9d8cfb5ad0 refactor(accrual): 拆分FundingLegAccrual→SimpleInterestAccrual/CompoundInterestAccrual
单利与复利语义完全不同(单利本金恒定/复利重置日并本金),
拆成两个独立静态类,各自只含自己的方法:

SimpleInterestAccrual:
- AccrueEod (原AccrueSimpleEod)
- AccruePeriod (原AccrueSimplePeriod)

CompoundInterestAccrual:
- EodBasis (原CompoundEodBasis)
- AccrueEod (原AccrueCompoundEod)
- AccruePeriod (原AccrueCompoundPeriod)

方法名去掉Simple/Compound前缀(类名已携带类型),消除冗余
SwapModule零回归(7基线/510通过)
2026-08-12 13:40:47 +08:00

200 lines
8.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Newtonsoft.Json;
using YLErp;
using YLErp.Derivatives.Interest;
using YLErp.Modules.SwapModule;
using YLErp.Modules.SwapModule.Accrual;
namespace UnitTestProject.Modules.SwapModule.Accrual
{
/// <summary>
/// 影子测试:CalcDailyCompoundInterest(旧逐日循环)vs CompoundInterestAccrual.AccruePeriod(新分段纯函数)。
/// </summary>
[TestClass]
public class CompoundPeriodShadowTest
{
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 trade CreateTrade()
{
return new trade
{
id = 1, TradeNumber = "UT-COMPOUND-SHADOW", ClientId = 999998,
TradeType = "收益互换", TradeDate = StartDate, StartDate = StartDate,
ExerciseDate = StartDate.AddYears(1), TradeStatus = "确认成交", ValidState = "Valid",
trade_extend = new trade_extend
{
TradeId = 1,
ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson
{
AnnualDays = AnnualDays, InterestCalcMode = "10", SettlementRules = 0
})
}
};
}
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 sealed class StubSvc : SwapDealService
{
public StubSvc() : base(new OptUserInfo(0, nameof(CompoundPeriodShadowTest), OptUserFrom.UnitTest)) { }
}
/// <summary>
/// 固定利率(无FR007)算头不算尾,全平。
/// 重置日 4/28, 5/5, 末日 5/11 恰为重置日(21天=3×7)。
/// </summary>
[TestMethod]
public void 影子_固定利率_全平_末日重置日_旧新一致()
{
var position = CreatePosition();
var flowEvent = new swap_flow_event { InterestRate = Spread };
var preEod = new eod_swap_position { id = 0 };
// 旧方法
decimal oldI = 0, oldTd = 0;
var svc = new StubSvc();
svc.CalcDailyCompoundInterest(EndDate, position, Notional, flowEvent,
AnnualDays, false, 0m, 1m, Notional, true, false,
ref oldI, ref oldTd);
// 新方法:固定利率全段相同,分段点 = PosiStartDate + k×7
// 段: [4/21,4/28), [4/28,5/5), [5/5,5/11] → 注意旧代码 calcLast=false 不算末日
var allInRate = Spread; // 无浮动利率
var segRates = new List<(DateTime, decimal)>
{
(StartDate, allInRate),
(StartDate.AddDays(7), allInRate),
(StartDate.AddDays(14), allInRate),
};
var result = CompoundInterestAccrual.AccruePeriod(
notional: Notional,
segmentRates: segRates,
startDate: StartDate,
endDate: EndDate,
boundary: AccrualBoundary.StartOnly,
annualDays: AnnualDays,
isAnnualized: true,
resetCarryInterest: 0m,
realizedInterest: 0m,
unwindFraction: 1m,
finalBasis: out _);
Console.WriteLine($"旧: I={oldI} Td={oldTd}");
Console.WriteLine($"新: Accrued={result.Accrued} AccruedToday={result.AccruedToday}");
Assert.AreEqual((double)oldI, (double)result.Accrued, 0.01, "InterestAmount 一致");
Assert.AreEqual((double)oldTd, (double)result.AccruedToday, 0.01, "TdInterestAmount 一致");
}
/// <summary>
/// 部分平仓 30% + consumedInterest 扣除。
/// </summary>
[TestMethod]
public void 影子_部分平仓30pct_consumedInterest_旧新一致()
{
var position = CreatePosition();
var flowEvent = new swap_flow_event { InterestRate = Spread };
var preEod = new eod_swap_position { id = 0 };
const decimal consumed = 50_000m;
const decimal closePct = 0.3m;
const decimal carry = 0m; // 无历史归档
// 旧方法
decimal oldI = 0, oldTd = 0;
var svc = new StubSvc();
svc.CalcDailyCompoundInterest(EndDate, position, Notional * closePct, flowEvent,
AnnualDays, false, 0m, closePct, Notional, true, false,
ref oldI, ref oldTd, consumedInterest: consumed, resetCarryInterest: carry);
// 新方法
var allInRate = Spread;
var segRates = new List<(DateTime, decimal)>
{
(StartDate, allInRate),
(StartDate.AddDays(7), allInRate),
(StartDate.AddDays(14), allInRate),
};
var result = CompoundInterestAccrual.AccruePeriod(
notional: Notional * closePct,
segmentRates: segRates,
startDate: StartDate,
endDate: EndDate,
boundary: AccrualBoundary.StartOnly,
annualDays: AnnualDays,
isAnnualized: true,
resetCarryInterest: carry,
realizedInterest: consumed,
unwindFraction: closePct,
finalBasis: out _);
Console.WriteLine($"旧: I={oldI} Td={oldTd}");
Console.WriteLine($"新: Accrued={result.Accrued} AccruedToday={result.AccruedToday}");
Assert.AreEqual((double)oldI, (double)result.Accrued, 0.01, "InterestAmount 一致");
Assert.AreEqual((double)oldTd, (double)result.AccruedToday, 0.01, "TdInterestAmount 一致");
}
/// <summary>
/// 算头算尾(calcMode="11")对比。
/// </summary>
[TestMethod]
public void 影子_算头算尾_旧新一致()
{
var position = CreatePosition();
var flowEvent = new swap_flow_event { InterestRate = Spread };
var preEod = new eod_swap_position { id = 0 };
// 旧方法
decimal oldI = 0, oldTd = 0;
var svc = new StubSvc();
svc.CalcDailyCompoundInterest(EndDate, position, Notional, flowEvent,
AnnualDays, false, 0m, 1m, Notional, true, true,
ref oldI, ref oldTd);
// 新方法
var allInRate = Spread;
var segRates = new List<(DateTime, decimal)>
{
(StartDate, allInRate),
(StartDate.AddDays(7), allInRate),
(StartDate.AddDays(14), allInRate),
};
var result = CompoundInterestAccrual.AccruePeriod(
notional: Notional,
segmentRates: segRates,
startDate: StartDate,
endDate: EndDate,
boundary: AccrualBoundary.Both,
annualDays: AnnualDays,
isAnnualized: true,
resetCarryInterest: 0m,
realizedInterest: 0m,
unwindFraction: 1m,
finalBasis: out _);
Console.WriteLine($"旧: I={oldI} Td={oldTd}");
Console.WriteLine($"新: Accrued={result.Accrued} AccruedToday={result.AccruedToday}");
Assert.AreEqual((double)oldI, (double)result.Accrued, 0.01, "InterestAmount 一致");
Assert.AreEqual((double)oldTd, (double)result.AccruedToday, 0.01, "TdInterestAmount 一致");
}
}
}