Files
zszq-trs/UnitTestProject/Modules/SwapModule/Accrual/CompoundPeriodShadowTest.cs
T
hjhan ce6c0812e1 refactor(accrual): 参数命名对齐业界标准(QuantLib/Strata)
FundingLegAccrual 4个方法的参数命名全面纠正:

| 旧名              | 新名               | 理由 |
|-------------------|-------------------|------|
| originalPv        | referenceNotional | Pv(现值)概念错误,实际是参考本金 |
| closeRatio/closePercent | unwindFraction | 统一,对齐 ApplyUnwind.unwindPercent |
| consumedInterest  | realizedInterest  | 对齐 AccrualState.RealizedInterest |
| priorUnrealized   | priorAccrued      | 对齐 InterestResult.Accrued |
| priorAccrualPrincipal | priorNotional  | 简洁,对齐 QuantLib notional |
| positionPrincipal | notional          | 简化,去掉冗余前缀 |
| dynomicPrincipal  | accrualBasis      | 修正typo+用业界术语 |

同步更新:
- SwapDealService.CalcDailySimpleInterestByEod 调用点
- 3个影子测试文件命名参数
- SwapInterest.Round 改为 public(所有Round收口一处)

删除半成品残留: SwapInterest_CompoundInArrears_RolloverTimingTests.cs

验证: 编译0错误, 7个影子测试全过, 全量520测试7失败(基线一致)。
2026-08-12 10:11:26 +08:00

188 lines
7.7 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 FundingLegAccrual.AccrueCompoundPeriod(新分段纯函数)。
/// </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 = FundingLegAccrual.AccrueCompoundPeriod(
notional: Notional,
segmentRates: segRates,
startDate: StartDate,
endDate: EndDate,
boundary: AccrualBoundary.StartOnly,
annualDays: AnnualDays,
isAnnualized: true);
Console.WriteLine($"旧: I={oldI} Td={oldTd}");
Console.WriteLine($"新: Accrued={result.Accrued} AccruedToday={result.AccruedToday}");
Assert.AreEqual((double)oldI, (double)result.Accrued, 0.01, "InterestAmount 一致");
}
/// <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 = FundingLegAccrual.AccrueCompoundPeriod(
notional: Notional * closePct,
segmentRates: segRates,
startDate: StartDate,
endDate: EndDate,
boundary: AccrualBoundary.StartOnly,
annualDays: AnnualDays,
isAnnualized: true,
resetCarryInterest: carry,
realizedInterest: consumed,
unwindFraction: closePct);
Console.WriteLine($"旧: I={oldI} Td={oldTd}");
Console.WriteLine($"新: Accrued={result.Accrued} AccruedToday={result.AccruedToday}");
Assert.AreEqual((double)oldI, (double)result.Accrued, 0.01, "InterestAmount 一致");
}
/// <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 = FundingLegAccrual.AccrueCompoundPeriod(
notional: Notional,
segmentRates: segRates,
startDate: StartDate,
endDate: EndDate,
boundary: AccrualBoundary.Both,
annualDays: AnnualDays,
isAnnualized: true);
Console.WriteLine($"旧: I={oldI} Td={oldTd}");
Console.WriteLine($"新: Accrued={result.Accrued} AccruedToday={result.AccruedToday}");
Assert.AreEqual((double)oldI, (double)result.Accrued, 0.01, "InterestAmount 一致");
}
}
}