Files
zszq-trs/UnitTestProject/Modules/SwapModule/AutoUnwindMultiPartialDividendTest.cs
T

70 lines
4.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 YLErp.Modules.EodModule;
namespace YLErp.Modules.SwapModule
{
/// <summary>
/// 自动平仓路径(AuotoSwapUnwind → EnrichDividendIn, SwapDealService.cs:1668-1687)多次部分平仓是否多算的实证。
/// EnrichDividendIn 核心:GetBondPayments(td.StartDate, closeDate) × unwindQty(当次平仓量,非剩余持仓)。
/// 本测试直接驱动真实 BondPaymentService.CalcPayment(与 EnrichDividendIn 等价:GetBondPayments 按 reg_date 过滤 + CalcPayment × unwindQty),
/// 内存注入 reg_date 数据,不连库。完整 AuotoSwapUnwind 链路因 EnrichDividendIn 直接 new BondPaymentService 查库、无内存 seam 注入点,故用计算核心等价验证。
///
/// 结论验证:多次跨越登记日的部分平仓,每次 × 当次平仓量 → 总额 = 各批按登记日持有 × 平仓量分摊,
/// 不自洽多算、不重复计入重叠窗口。
/// (纠正此前"从建仓日重算导致重复计入"的推断:该推断误以为 CalcPayment 乘剩余持仓,实际乘当次 unwindQty。)
/// </summary>
[TestClass]
public class AutoUnwindMultiPartialDividendTest
{
private const string BondCode = "230004.IB";
private static readonly DateTime StartDate = new(2026, 1, 5);
private static readonly DateTime Reg1 = new(2026, 5, 15); // 每百元付息 10
private static readonly DateTime Reg2 = new(2026, 6, 15); // 每百元付息 12
private sealed class BridgeBps : BondPaymentService
{
public BridgeBps(OptUserInfo u) : base(u) { }
protected override IQueryable<BondPayment> QueryBondPayments(string underlyingCode)
=> new List<BondPayment>
{
new BondPayment { underlyingCode = BondCode, reg_date = Reg1, payment_date_pl = Reg1, payment_date = Reg1, payment_interest = 10m },
new BondPayment { underlyingCode = BondCode, reg_date = Reg2, payment_date_pl = Reg2, payment_date = Reg2, payment_interest = 12m },
}.Where(x => x.underlyingCode == underlyingCode).AsQueryable();
}
// 等价于 EnrichDividendIn 的数值核心:GetBondPayments(StartDate, closeDate) × unwindQty
private static decimal EnrichOnce(DateTime closeDate, decimal unwindQty)
{
var svc = new BridgeBps(OptUserInfo.UnitTestUser);
return svc.CalcPayment(BondCode, StartDate, closeDate, unwindQty, 1, 1);
}
[TestMethod]
public void 多次部分平仓_自动路径总额按登记日持仓分摊_不自洽多算()
{
decimal totalFace = 10_000m; // 总面额 1 万元
decimal halfFace = totalFace / 2m; // 每次平一半
// 第一次 5/20 平一半:窗口(Start,5/20] 仅含 reg1 → 10 × 5000/100 = 500
var d1 = EnrichOnce(new DateTime(2026, 5, 20), halfFace);
// 第二次 6/20 平一半:窗口(Start,6/20] 含 reg1+reg2 → (10+12) × 5000/100 = 1100
var d2 = EnrichOnce(new DateTime(2026, 6, 20), halfFace);
var total = d1 + d2;
// 经济应得(登记日持有规则):
// 第一批5000元:5/15持有✓(10)、6/15未持有✗ → 10×5000/100 = 500
// 第二批5000元:5/15持有✓(10)、6/15持有✓(12) → 22×5000/100 = 1100
decimal expected = 10m * halfFace / 100m + (10m + 12m) * halfFace / 100m;
Assert.AreEqual(500m, d1, 0.001m, "第一次(5/20)只含 reg1 = 500");
Assert.AreEqual(1100m, d2, 0.001m, "第二次(6/20)含 reg1+reg2 = 1100");
Assert.AreEqual(expected, total, 0.001m,
"两次部分平仓总额 = 按登记日持有×平仓量分摊的应得值,重叠窗口不重复计同量(纠正:乘当次 unwindQty 而非剩余持仓)");
// 反证:若手动路径口径(第一次平仓即给全量待实现 = 两次分红×总面额)会多算
decimal manualFullIfFirst = (10m + 12m) * totalFace / 100m; // 2200
Assert.IsTrue(manualFullIfFirst > total,
"反证:手动全量落袋口径(2200) > 自动分摊口径(1600),多算方是手动路径而非自动路径");
}
}
}