namespace UnitTestProject.Modules.SwapModule.Accrual
{
///
/// 契约参考实现(确认书公式,TEST-MATRIX §8a)——全矩阵统一 oracle 供给。
///
/// 【独立性约束·勿破坏】本类只实现确认书公式原文,禁止引用任何生产计息引擎类
/// (YLErp.Modules.SwapModule.Accrual.* / SwapDealService),否则 oracle 与被测对象同源,
/// 失去"独立参考"资格(oracle 分级第一级,见 TEST-MATRIX §7.4)。
///
/// 确认书公式(国联民生收益互换确认书-现券/ETF 四份一致):
/// 参考利率(绝对) = ∏[i=1..k] ( 1 + (FR007i + 利差) × di / 365 ) − 1
/// 结息额(平仓部分) = 实际平掉额 × 参考利率(绝对)
/// - k = 计息期包含的重置期个数;完整重置期 di = 重置频率(生产 7 天),末段不足按实际日历日
/// - 重置期自计息期首日按重置频率依次推算;首个重置期始于计息期首日;末段收口到计息期最后一日
/// - 利率确定日 = 每个重置期首日(重置日)的上一个营业日,取该日 FR007
/// - 计息期 = 自起始日(含)至到期日(不含)——即算头不算尾 "10"(生产主力条款)
/// - 计息基准 A/365
///
/// 营业日准则:本参考实现按周末近似(周六/周日非营业日);法定节假日历由调用方通过
/// 取价委托自行吸收(如按确定日提供同一利率)。测试与生产参数对齐(§8):重置 7 天 / 365。
///
public static class ContractReferenceCalc
{
///
/// 参考利率(绝对) = ∏(1 + (FR007i+利差)×di/annualDays) − 1。
///
/// 计息期首日(含)
/// 计息期末日("10"不含/"11"含,由 calcLast 决定)
/// 重置频率天数(生产 7)
/// 利差(InterestRateDefault,如 +0.25% = 0.0025)
/// 取价委托:入参=利率确定日(重置日上一营业日),返回该日 FR007
/// 算头(生产 "10"/"11" 为 true)
/// 算尾(生产 "10" 为 false)
/// 计息基准(生产 365)
public static decimal ReferenceRateAbsolute(
DateTime startDate, DateTime endDate,
int resetDays, decimal spread,
Func fixing,
bool calcFirst = true, bool calcLast = false,
int annualDays = 365)
{
var totalDays = (endDate - startDate).Days + (calcFirst ? 0 : -1) + (calcLast ? 1 : 0);
if (totalDays <= 0) return 0m;
decimal factor = 1m;
var resetDate = startDate; // 首个重置期始于计息期首日
var remaining = totalDays;
while (remaining > 0)
{
var di = Math.Min(resetDays, remaining); // 完整期 di=resetDays,末段按实际日历日
var fixingDate = PreviousBusinessDay(resetDate);
var allIn = fixing(fixingDate) + spread;
factor *= 1m + allIn * di / annualDays;
remaining -= di;
resetDate = resetDate.AddDays(di);
}
return factor - 1m;
}
/// 结息额(平仓部分)= 实际平掉额 × 参考利率(绝对)。
public static decimal ClosedInterest(decimal closedNotional, decimal referenceRate)
=> closedNotional * referenceRate;
/// 利率确定日 = 重置日的上一营业日(周末近似)。
public static DateTime PreviousBusinessDay(DateTime date)
{
do { date = date.AddDays(-1); }
while (date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday);
return date;
}
}
}