Files
zszq-trs/UnitTestProject/Modules/SwapModule/Accrual/CompoundEodShadowTest.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

151 lines
6.6 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;
using YLErp.Core.Interest;
namespace UnitTestProject.Modules.SwapModule.Accrual
{
/// <summary>
/// 影子测试:CalcDailyCompoundInterestByEod(旧逐日循环)vs CompoundInterestAccrual.AccrueEod(新纯函数)。
/// 构造同一组参数,两套实现并行跑,断言结果一致(到分)。
/// </summary>
[TestClass]
public class CompoundEodShadowTest
{
private const decimal Notional = 100_000_000m;
private const decimal FixedRate = 0.03m;
private const int AnnualDays = 365;
private static readonly DateTime TradeDate = new(2026, 4, 21);
private static readonly DateTime EodDate = new(2026, 4, 28); // 第7天=重置日
private static trade CreateTrade()
{
return new trade
{
id = 1, TradeNumber = "UT-SHADOW", ClientId = 999998,
TradeType = "收益互换", TradeDate = TradeDate, StartDate = TradeDate,
ExerciseDate = TradeDate.AddYears(1), TradeStatus = "确认成交", ValidState = "Valid",
trade_extend = new trade_extend
{
TradeId = 1,
ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson
{
AnnualDays = AnnualDays, InterestCalcMode = "11", SettlementRules = 0
})
}
};
}
private static swap_position CreatePosition(int interestMode, int interestType, int resetDays)
{
return new swap_position
{
id = 1001, SwapTradeId = 1,
PosiDirection = 0,
InterestDirection = (int)SwapDirectionEnum.收取,
InterestMode = interestMode,
InterestRateDefault = FixedRate,
InterestPrincipalFix = Notional,
PosiStartDate = TradeDate,
PosiMatuirityDate = TradeDate.AddYears(1),
IsInitial = true, Invalid = false,
InterestType = interestType,
IsAnnualized = true,
interest_rest_days = resetDays,
interest_rule = 0,
FloatRateUnderlyingCode = null,
InterestSwapInterval = "[]"
};
}
private static eod_swap_position CreatePreEod(decimal tdPrincipal, decimal unrealized)
{
return new eod_swap_position
{
id = 1, SwapTradeId = 1, PositionId = 1001,
ValueDate = EodDate.AddDays(-1),
TdInterestPrincipal = tdPrincipal,
InterestProfitSum = unrealized,
PosiNotionalValue = Notional,
FloatRate = 0m
};
}
/// <summary>
/// 重置日场景:EOD 恰为重置日(7天周期,第7天)。
/// </summary>
[TestMethod]
public void 影子_重置日_旧新一致()
{
var position = CreatePosition((int)InterestModeEnum.合约名义本金规模, (int)InterestTypeEnum.复利, 7);
var preEod = CreatePreEod(Notional, 50_000m);
var flowEvent = new swap_flow_event { InterestRate = FixedRate };
// 旧方法
decimal oldInterest = 0, oldTd = 0;
var svc = new StubSvc();
svc.CalcDailyCompoundInterestByEod(preEod, EodDate, TradeDate, position,
Notional, Notional, flowEvent, AnnualDays, false, 0m, 1m, Notional,
ref oldInterest, ref oldTd);
// 新方法
var rate = FundingLegRate.Fixed(FixedRate);
var policy = new AccrualPolicy(AccrualBoundary.Both, true, 7, AnnualDays, true);
var remainingPercent = Math.Max(0m, Math.Min(1m, Notional / Notional));
var result = CompoundInterestAccrual.AccrueEod(
50_000m, Notional, Notional, 1m, rate, policy,
isResetDay: true, remainingPercent, EodDate);
Console.WriteLine($"重置日: 旧 InterestAmount={oldInterest} Td={oldTd}");
Console.WriteLine($"重置日: 新 Accrued={result.Accrued} AccruedToday={result.AccruedToday}");
Assert.AreEqual((double)oldInterest, (double)result.Accrued, 0.01, "InterestAmount 一致");
Assert.AreEqual((double)oldTd, (double)result.AccruedToday, 0.01, "TdInterestAmount 一致");
}
/// <summary>
/// 非重置日场景:第3天(非7的倍数)。
/// </summary>
[TestMethod]
public void 影子_非重置日_旧新一致()
{
var nonResetDate = new DateTime(2026, 4, 24); // 第3天
var position = CreatePosition((int)InterestModeEnum.合约名义本金规模, (int)InterestTypeEnum.复利, 7);
var preEod = CreatePreEod(Notional, 30_000m);
preEod.ValueDate = nonResetDate.AddDays(-1);
var flowEvent = new swap_flow_event { InterestRate = FixedRate };
// 旧方法
decimal oldInterest = 0, oldTd = 0;
var svc = new StubSvc();
svc.CalcDailyCompoundInterestByEod(preEod, nonResetDate, TradeDate, position,
Notional, Notional, flowEvent, AnnualDays, false, 0m, 1m, Notional,
ref oldInterest, ref oldTd);
// 新方法
var rate = FundingLegRate.Fixed(FixedRate);
var policy = new AccrualPolicy(AccrualBoundary.Both, true, 7, AnnualDays, true);
var result = CompoundInterestAccrual.AccrueEod(
30_000m, Notional, Notional, 1m, rate, policy,
isResetDay: false, 0m, nonResetDate);
Console.WriteLine($"非重置日: 旧 InterestAmount={oldInterest} Td={oldTd}");
Console.WriteLine($"非重置日: 新 Accrued={result.Accrued} AccruedToday={result.AccruedToday}");
Assert.AreEqual((double)oldInterest, (double)result.Accrued, 0.01, "InterestAmount 一致");
Assert.AreEqual((double)oldTd, (double)result.AccruedToday, 0.01, "TdInterestAmount 一致");
}
private sealed class StubSvc : SwapDealService
{
public StubSvc() : base(new OptUserInfo(0, nameof(CompoundEodShadowTest), OptUserFrom.UnitTest)) { }
public override decimal GetConsumedInterest(int tradeId, long positionId, DateTime beforeDate) => 0m;
}
}
}