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失败(基线一致)。
151 lines
6.3 KiB
C#
151 lines
6.3 KiB
C#
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;
|
|
using YLErp.Derivatives.Interest;
|
|
|
|
namespace UnitTestProject.Modules.SwapModule.Accrual
|
|
{
|
|
/// <summary>
|
|
/// 影子测试:CalcDailySimpleInterest(旧逐日循环)vs FundingLegAccrual.AccrueSimplePeriod(新分段纯函数)。
|
|
/// </summary>
|
|
[TestClass]
|
|
public class SimplePeriodShadowTest
|
|
{
|
|
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天, 7天周期→重置日 4/28, 5/5
|
|
|
|
private static trade CreateTrade()
|
|
{
|
|
return new trade
|
|
{
|
|
id = 1, TradeNumber = "UT-SIMPLE-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(SimplePeriodShadowTest), OptUserFrom.UnitTest)) { }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 固定利率(无FR007)算头不算尾,全平,无历史归档。
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void 影子_固定利率_无归档_旧新一致()
|
|
{
|
|
var td = CreateTrade();
|
|
var position = CreatePosition();
|
|
var flowEvent = new swap_flow_event { InterestRate = Spread };
|
|
var preEod = new eod_swap_position { id = 0, TdInterestPrincipal = 0, InterestProfitSum = 0 };
|
|
|
|
// 旧方法
|
|
decimal oldI = 0, oldTd = 0;
|
|
var svc = new StubSvc();
|
|
svc.CalcDailySimpleInterest(preEod, EndDate, position, Notional, flowEvent,
|
|
AnnualDays, false, 0m, 1m, Notional, true, false, ref oldI, ref oldTd);
|
|
|
|
// 新方法:固定利率全段相同
|
|
// 旧代码差分: dynomicPrincipal = preEod.TdInterestPrincipal(=0) + posiPrincipal - orginPv = 0
|
|
var segRates = new List<(DateTime, decimal)> { (StartDate, Spread) };
|
|
var accrualPrincipal = 0m + Notional - Notional; // 差分 = 0
|
|
var result = FundingLegAccrual.AccrueSimplePeriod(
|
|
priorAccrued: 0m,
|
|
notional: 0m,
|
|
unwindFraction: 1m,
|
|
segmentRates: segRates,
|
|
startDate: StartDate,
|
|
endDate: EndDate,
|
|
priorValueDate: DateTime.MinValue,
|
|
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>
|
|
/// 有历史归档(preEod.id != 0),续接上一日终。
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void 影子_有归档_续接_旧新一致()
|
|
{
|
|
var position = CreatePosition();
|
|
var preEodDate = new DateTime(2026, 5, 4); // 上一日终 = 第14天
|
|
var preEod = new eod_swap_position
|
|
{
|
|
id = 1, SwapTradeId = 1, PositionId = 1001,
|
|
ValueDate = preEodDate,
|
|
TdInterestPrincipal = Notional,
|
|
InterestProfitSum = 200_000m,
|
|
PosiNotionalValue = Notional, FloatRate = 0m
|
|
};
|
|
var flowEvent = new swap_flow_event { InterestRate = Spread };
|
|
|
|
// 旧方法
|
|
decimal oldI = 0, oldTd = 0;
|
|
var svc = new StubSvc();
|
|
svc.CalcDailySimpleInterest(preEod, EndDate, position, Notional, flowEvent,
|
|
AnnualDays, false, 0m, 0.5m, Notional, true, false, ref oldI, ref oldTd);
|
|
|
|
// 新方法
|
|
// 差分本金 = preEod.TdInterestPrincipal + posiPrincipal - orginPv
|
|
var accrualPrincipal = Notional + Notional - Notional;
|
|
var segRates = new List<(DateTime, decimal)> { (StartDate, Spread) };
|
|
var result = FundingLegAccrual.AccrueSimplePeriod(
|
|
priorAccrued: 200_000m * 0.5m,
|
|
notional: accrualPrincipal,
|
|
unwindFraction: 0.5m,
|
|
segmentRates: segRates,
|
|
startDate: StartDate,
|
|
endDate: EndDate,
|
|
priorValueDate: preEodDate,
|
|
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 一致");
|
|
}
|
|
}
|
|
}
|