Files
zszq-trs/UnitTestProject/Modules/SwapModule/PrepaidPrincipalCloseTraceTest.cs
T
hjhan ef37c8e71d refactor(swap): 合并多空名义本金参数为单一 posiTotalNotional 并加固测试
- DealInterests 的 posiLongNotionalValue + posiShortNotionalValue 合并为 posiTotalNotional(调用点以 posiLongNotional+posiShortNotional 求和传入),净减一个参数

- SwapDealService / SwapEodPositionService / InterestCalcRequest 同步收敛多空死管道参数

- 19 个测试调用点适配新签名

- SwapEodPositionServiceIntegrationTest 参数计数断言由裸数字改为参数名集合断言(CollectionAssert.AreEquivalent,对增删/重排/改名敏感)
2026-08-14 17:30:46 +08:00

138 lines
7.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using YLErp.DBModels;
using YLErp.DBModels.Enums;
namespace YLErp.Modules.SwapModule
{
/// <summary>
/// 预付金(保证金)腿"部分平仓后再全平"的计息过程暴露测试。
///
/// 背景:用户截图(国联民生-债券TRS期间结算)显示 8.4 部分平仓40% → 8.7 全平剩余60%,
/// 预付金端系统给 20.83,而 Excel 预期 10.41(本次利息,3天)或 24.29(平仓盈亏,7天)。
/// 经验证,单利计息核心 CalcDailySimpleInterest 只在 accrueDate > preEod.ValueDate 的日子累加,
/// 计息基数 dynomicPrincipal = preEod.TdInterestPrincipal + posiPrincipal - orginPv。
/// 因此第二次平仓的利息完全由"第一次部分平仓后日终归档态"决定——这正是截图看不到、却决定系统值的要素。
///
/// 本测试开启 SwapCalcTrace,把逐步过程打印出来,直接暴露"6天/3天"的来源(ValueDate 地板)。
/// 同时用两个归档 ValueDate8.4 期望 / 8.1 疑似生产落地值)对比,证明 ValueDate 是杠杆。
/// </summary>
[TestClass]
public class PrepaidPrincipalCloseTraceTest
{
private sealed class Stub : SwapDealService
{
public Stub(OptUserInfo u) : base(u) { }
protected override bool TryGetFloatRate(DateTime d, string c, out double r) { r = 0; return false; }
}
private const decimal PrepayFix = 100_000.23m; // 预付金(保证金)本金(截图 100,000.23
private const decimal PrepayRemaining = 60_000.138m; // 部分平仓40%后剩余 60%
private const decimal Rate = 0.021111m; // 2.1111%
private static readonly DateTime Start = new(2026, 7, 28);
private static readonly DateTime PartialDate = new(2026, 8, 4);
private static readonly DateTime FullDate = new(2026, 8, 7);
private const int AnnualDays = 365;
private SwapDealService _svc;
[TestInitialize]
public void Init() => _svc = new Stub(new OptUserInfo(0, nameof(PrepaidPrincipalCloseTraceTest), OptUserFrom.UnitTest));
private static trade MakeTrade()
{
var extend = new trade_extend
{
TradeId = 1,
ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson
{
AnnualDays = AnnualDays,
InterestCalcMode = "10", // 算头不算尾(与生产一致,具体算尾与否由场景验证)
SettlementRules = 0
})
};
return new trade
{
id = 1, TradeNumber = "UT-PREPAY-TRACE", ClientId = 999998,
TradeType = "收益互换", TradeDate = Start, StartDate = Start,
ExerciseDate = new DateTime(2027, 7, 28), TradeStatus = "确认成交",
ValidState = "Valid", StockEqvNotional = (double)PrepayFix, Notional = (double)PrepayFix,
trade_extend = extend
};
}
private static swap_position MakePrepay()
{
return new swap_position
{
id = 1001, SwapTradeId = 1, PositionType = (int)PositionTypeFlag.Unknown,
InterestDirection = (int)SwapDirectionEnum.收取,
InterestMode = (int)InterestModeEnum.初始预付金,
// GetUnwindInterests 会用实时腿覆盖初始腿本金;部分平仓后这里应为剩余 60%。
InterestRateDefault = Rate, InterestPrincipalFix = PrepayRemaining,
PosiStartDate = Start, PosiMatuirityDate = new DateTime(2027, 7, 28),
IsInitial = true, Invalid = false, InterestType = (int)InterestTypeEnum.单利,
IsAnnualized = true, interest_rest_days = 1,
interest_rule = 0, FloatRateUnderlyingCode = null,
InterestSwapInterval = "[]"
};
}
/// <summary>构造"8.4 部分平仓40%后"应有的日终归档态。</summary>
private static eod_swap_position MakeEod(DateTime valueDate, decimal tdPrincipal, decimal profitSum)
=> new eod_swap_position
{
id = 1, SwapTradeId = 1, PositionId = 1001,
ValueDate = valueDate, TdInterestPrincipal = tdPrincipal,
PosiNotionalValue = tdPrincipal, InterestProfitSum = profitSum
};
[TestMethod]
public void 预付金腿_部分平仓后再全平_暴露计息过程_定位天数来源()
{
var td = MakeTrade();
var pos = MakePrepay();
// 运行一次计息并打印逐步 trace。calcLast=true 表示"算尾"(生产该腿实际口径,见下)。
(swap_flow_event fe, string trace) Run(DateTime valueDate, bool calcLast)
{
SwapCalcTrace.IsEnabled = true;
SwapCalcTrace.Reset();
var eod = new List<eod_swap_position> { MakeEod(valueDate, PrepayRemaining, 0m) };
var fe = _svc.GetInterests(td, td.trade_extend, FullDate, FullDate, eod,
new List<swap_position> { pos }, PrepayFix, PrepayFix, 1m,
(int)SwapEventTypeEnum.平仓, false, PrepayFix, false,
settment: false, newCalcLast: calcLast, closeList: null)[0];
var trace = SwapCalcTrace.Dump();
Console.WriteLine(trace);
Console.WriteLine($">> InterestAmount={fe.InterestAmount}");
return (fe, trace);
}
// 场景A(正确归档 ValueDate=8.4,算尾):应得 3天 = 10.41(Excel「本次利息」)
Console.WriteLine("=== 场景A: eod.ValueDate=8.4 + 算尾(期望正确值)===");
var feA = Run(PartialDate, calcLast: true).fe;
// 场景B(错误归档 ValueDate=8.1,算尾):复现生产 6天 = 20.83(系统截图)
Console.WriteLine("=== 场景B: eod.ValueDate=8.1 + 算尾(疑似生产落地值,复现 bug)===");
var feB = Run(new DateTime(2026, 8, 1), calcLast: true).fe;
// 守卫1:正确归档应产出与 Excel「本次利息」一致的 10.41(证明给定正确状态后计算逻辑本身正确)
Assert.AreEqual(10.41m, Math.Round(feA.InterestAmount, 2),
"正确归档(ValueDate=8.4)+算尾 应得 3天利息=10.41,与 Excel 本次利息一致");
// 守卫2:错误归档(ValueDate=8.1) 复现系统截图的 ~20.836天计息),且证明 ValueDate 就是杠杆(缺的要素)。
// 20.8219 与截图 20.83 的 0.01 差异仅为四舍五入呈现方式,量级与天数(6天)一致即证明复现成功。
Assert.IsTrue(Math.Abs(feB.InterestAmount - 20.83m) < 0.05m,
$"错误归档(ValueDate=8.1)+算尾 应复现系统截图 ~20.83(6天计息),实测={feB.InterestAmount}");
Assert.AreNotEqual(feA.InterestAmount, feB.InterestAmount,
"ValueDate 不同应导致计息天数/金额不同");
SwapCalcTrace.IsEnabled = false;
}
}
}