feat(accrual): 新增 FundingLegAccrual.AccrueCompoundEod + 影子测试
第1步迁移: CalcDailyCompoundInterestByEod(EOD复利单日)。 新增纯函数 AccrueCompoundEod: - 重置日: 本金 = positionPrincipal + priorUnrealized × remainingPercent - 非重置日: 本金 = priorAccrualPrincipal + positionPrincipal - originalPv - InterestAmount = priorUnrealized × closeRatio + dayInterest - TdInterestAmount = dayInterest(不含 priorUnrealized) 影子测试(2个,全过): - 重置日场景: 旧新 InterestAmount/TdInterestAmount 一致 - 非重置日场景: 同上 旧方法暂不删——新旧并存,对比验证后再替换调用点。 验证: 编译0错误, 全量515测试7失败(基线一致)。
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
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 FundingLegAccrual.AccrueCompoundEod(新纯函数)。
|
||||
/// 构造同一组参数,两套实现并行跑,断言结果一致(到分)。
|
||||
/// </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 = FundingLegAccrual.AccrueCompoundEod(
|
||||
50_000m, Notional, Notional, 1m, Notional, 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 = FundingLegAccrual.AccrueCompoundEod(
|
||||
30_000m, Notional, Notional, 1m, Notional, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user