源码实证orginPv在5/6个CalcDaily方法中是死参数(方法体不读取): - CalcDailyCompoundInterest: 删 decimal orginPv(3个调用点同步删) - CalcDailyCompoundInterestByEod: 删(1个调用点+4个测试同步删) - CalcDailySimpleInterestByEod: 删(1个调用点+2个测试同步删) 保留orginPv的方法(真实消费者): - CalcDailySimpleInterest: 差分公式 posiPrincipal - orginPv - GetInterests/CalcUnwindInterest/InitSwapDealInterest: 转发链 SwapModule零回归(7基线/510通过)
151 lines
6.6 KiB
C#
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,
|
|
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,
|
|
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;
|
|
}
|
|
}
|
|
}
|