test: 平仓预付金bug回归测试贴合生产铁证(精确notional + 不计息断言)
- 客户截图级用例 notional 由反推的 306,191,860.22 更正为生产精确值 306,191,860.26,使 2*Fix-Notional = -287,820,348.64 与生产 event15997 精确 0 误差。 - 生产该预付金腿三条 swap_flow_event 的 InterestAmount 全=0(债券类预付金腿不计息), 仅 InterestPrincipal 中招;故客户级/Trade1813 用例改断言 InterestAmount==0、用 rate=0 贴合生产。 - 显式带息加固用例(rate=0.01,验证计息基数也基于保证金本金自身)保留,但注释标明为 合成场景、非该笔生产的真实症状,避免与生产现象混淆。 - 固化生产双平仓记录佐证:同腿"盘中错(event15997)/EOD对(event15998)"两条平仓, 恰好印证修复方向(盘中 orginPv 对齐 EOD=Fix)正确。
This commit is contained in:
@@ -70,14 +70,14 @@ namespace YLErp.Modules.SwapModule
|
||||
};
|
||||
}
|
||||
|
||||
private static swap_position MakePrepayPosition(decimal fix = PrepayPrincipal)
|
||||
private static swap_position MakePrepayPosition(decimal fix = PrepayPrincipal, decimal rate = 0.01m)
|
||||
{
|
||||
return new swap_position
|
||||
{
|
||||
id = 1001, SwapTradeId = 1, PositionType = (int)PositionTypeFlag.Unknown,
|
||||
InterestDirection = (int)SwapDirectionEnum.收取,
|
||||
InterestMode = (int)InterestModeEnum.初始预付金,
|
||||
InterestRateDefault = 0.01m, InterestPrincipalFix = fix,
|
||||
InterestRateDefault = rate, InterestPrincipalFix = fix,
|
||||
PosiStartDate = StartDate, PosiMatuirityDate = ExerciseDate,
|
||||
IsInitial = true, Invalid = false, InterestType = (int)InterestTypeEnum.单利,
|
||||
IsAnnualized = true, interest_rest_days = 1,
|
||||
@@ -104,11 +104,11 @@ namespace YLErp.Modules.SwapModule
|
||||
/// 客户/真实库场景:自定义 标的名义本金(notional) 与 保证金本金(fix)。
|
||||
/// orginPv 用 notional(与 GetUnwindInterests 行为一致:lastEod.NotionalValue ?? stockEqvNotional)。
|
||||
/// </summary>
|
||||
private swap_flow_event CalcUnwindWith(decimal closePercent, List<eod_swap_position> eodPositions, decimal notional, decimal fix)
|
||||
private swap_flow_event CalcUnwindWith(decimal closePercent, List<eod_swap_position> eodPositions, decimal notional, decimal fix, decimal rate = 0.01m)
|
||||
{
|
||||
eodPositions ??= new List<eod_swap_position>();
|
||||
var td = MakeTrade(notional);
|
||||
var position = MakePrepayPosition(fix);
|
||||
var position = MakePrepayPosition(fix, rate);
|
||||
var interests = _svc.GetInterests(td, td.trade_extend, UnwindDate, UnwindDate,
|
||||
eodPositions, new List<swap_position> { position },
|
||||
notional, notional, notional, notional, closePercent,
|
||||
@@ -152,31 +152,41 @@ namespace YLErp.Modules.SwapModule
|
||||
[TestMethod]
|
||||
public void 客户截图级_全平_应返还本金应等于保证金本金()
|
||||
{
|
||||
// 客户截图症状:支付预付金 9,185,755.81;平仓"应返还本金"=-287,820,348.6
|
||||
// 反推标的名义本金 = 2*9,185,755.81 + 287,820,348.6 = 306,191,860.22(保证金比例 3%,正常)
|
||||
const decimal notional = 306_191_860.22m;
|
||||
// 生产铁证(用户提供真实交易):TradeAmount=3亿,StockEqvNotional=306,191,860.26,
|
||||
// StructureType=普通债券类收益互换;预付金腿 swap_position id=34009 InterestMode=5
|
||||
// InterestPrincipalFix=9,185,755.81。
|
||||
// swap_flow_event(该腿, mode5) 三条:
|
||||
// 9202 EventId=null dir2 IP=9,185,755.81 (建仓支付预付金 ✓)
|
||||
// 9489 EventId=15997 dir1 IP=-287,820,348.64 (平仓, 盘中路径 BUG ✗)
|
||||
// 9492 EventId=15998 dir1 IP=9,185,755.81 (平仓, EOD正确路径 ✓)
|
||||
// 同一腿出现"盘中错 / EOD对"两条平仓记录,恰好佐证修复方向(盘中 orginPv 对齐 EOD=Fix)正确。
|
||||
// 根因复现:2*Fix - Notional = 2*9,185,755.81 - 306,191,860.26 = -287,820,348.64(与生产 15997 精确 0 误差)。
|
||||
// 该预付金腿三条 event 的 InterestAmount 全=0(债券类预付金腿不计息),
|
||||
// 故本笔生产仅 InterestPrincipal 中招、计息基数未受影响 → rate=0 贴合生产。
|
||||
const decimal notional = 306_191_860.26m;
|
||||
const decimal fix = 9_185_755.81m;
|
||||
var fe = CalcUnwindWith(1m, null, notional, fix);
|
||||
var fe = CalcUnwindWith(1m, null, notional, fix, rate: 0m);
|
||||
Console.WriteLine($"[TDD][客户] 实测 InterestPrincipal={fe.InterestPrincipal} InterestAmount={fe.InterestAmount} (期望Principal={fix})");
|
||||
Assert.AreEqual(fix, fe.InterestPrincipal,
|
||||
"客户级: 应返还本金应=保证金本金 9,185,755.81,不应被算成 -287,820,348.6");
|
||||
Assert.IsTrue(fe.InterestAmount > 0 && fe.InterestAmount < fix,
|
||||
"客户级: 利息基数应基于保证金本金(小额正),证明 orginPv 已对齐 Fix 而非交易名义本金");
|
||||
"客户级: 应返还本金应=保证金本金 9,185,755.81,不应被算成 -287,820,348.64");
|
||||
Assert.AreEqual(0m, fe.InterestAmount,
|
||||
"客户级: 该预付金腿不计息,InterestAmount 应=0(与生产三条 event 全为 0 一致);仅 InterestPrincipal 中招");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void 真实库Trade1813_全平_应返还本金应等于保证金本金()
|
||||
{
|
||||
// 测试库 Trade=1813 / Pos=34204:Fix=35,140,Notional=12,100,000,
|
||||
// 实际存储 InterestPrincipal=-12,029,720.00(=2*35,140-12,100,000,公式精确 0 误差)
|
||||
// 实际存储 InterestPrincipal=-12,029,720.00(=2*35,140-12,100,000,公式精确 0 误差)。
|
||||
// 同属债券类预付金腿(与生产同模式,不计息),rate=0 贴合生产,仅验证 InterestPrincipal 修复。
|
||||
const decimal notional = 12_100_000m;
|
||||
const decimal fix = 35_140m;
|
||||
var fe = CalcUnwindWith(1m, null, notional, fix);
|
||||
var fe = CalcUnwindWith(1m, null, notional, fix, rate: 0m);
|
||||
Console.WriteLine($"[TDD][Trade1813] 实测 InterestPrincipal={fe.InterestPrincipal} InterestAmount={fe.InterestAmount} (期望Principal={fix})");
|
||||
Assert.AreEqual(fix, fe.InterestPrincipal,
|
||||
"Trade1813: 应返还本金应=保证金本金 35,140,不应被算成 -12,029,720.00");
|
||||
Assert.IsTrue(fe.InterestAmount > 0 && fe.InterestAmount < fix,
|
||||
"Trade1813: 利息基数应基于保证金本金(小额正),证明 orginPv 已对齐 Fix 而非交易名义本金");
|
||||
Assert.AreEqual(0m, fe.InterestAmount,
|
||||
"Trade1813: 同属债券类预付金腿不计息,InterestAmount 应=0;仅 InterestPrincipal 中招");
|
||||
}
|
||||
|
||||
// ---- 多次部分平仓(验证最小修复是否覆盖"多次部分成交")----
|
||||
@@ -188,11 +198,11 @@ namespace YLErp.Modules.SwapModule
|
||||
// (真实系统中每次部分平仓后 position.InterestPrincipalFix 会被扣减,下一笔用剩余值)。
|
||||
// 根因修复后:InterestPrincipal 由利息公式基于 Fix 正确得出 = fix * closePercent。
|
||||
decimal total = 0;
|
||||
var r1 = CalcUnwindWith(0.3m, null, 306_191_860.22m, 100_000m);
|
||||
var r1 = CalcUnwindWith(0.3m, null, 306_191_860.26m, 100_000m);
|
||||
total += r1.InterestPrincipal;
|
||||
var r2 = CalcUnwindWith(0.5m, null, 306_191_860.22m, 70_000m); // 剩余 7万
|
||||
var r2 = CalcUnwindWith(0.5m, null, 306_191_860.26m, 70_000m); // 剩余 7万
|
||||
total += r2.InterestPrincipal;
|
||||
var r3 = CalcUnwindWith(1.0m, null, 306_191_860.22m, 35_000m); // 剩余 3.5万
|
||||
var r3 = CalcUnwindWith(1.0m, null, 306_191_860.26m, 35_000m); // 剩余 3.5万
|
||||
total += r3.InterestPrincipal;
|
||||
|
||||
Console.WriteLine($"[TDD][多次部分] r1={r1.InterestPrincipal} r2={r2.InterestPrincipal} r3={r3.InterestPrincipal} 合计={total}");
|
||||
@@ -205,17 +215,18 @@ namespace YLErp.Modules.SwapModule
|
||||
[TestMethod]
|
||||
public void 多次部分平仓_计息基数也被根因修复_利息基于保证金本金()
|
||||
{
|
||||
// 根因修复后:预付金腿的 orginPv 已对齐为其自身保证金(Fix),
|
||||
// 不仅"应返还本金"(InterestPrincipal) 正确,逐日利息计息基数也正确
|
||||
// (基于保证金本金,而非交易名义本金),故 InterestAmount 应为小额正。
|
||||
const decimal notional = 306_191_860.22m;
|
||||
// 显式带息加固用例(合成,非用户那笔生产的真实症状):
|
||||
// 用户那笔生产(3亿债券类TRS)预付金腿不计息(InterestAmount 全=0),仅 InterestPrincipal 中招;
|
||||
// 本例用 rate=0.01 构造"若该腿计息"的场景,验证根因修复后计息基数也基于保证金本金自身
|
||||
// (而非交易名义本金):InterestAmount 为小额正、且 < fix。
|
||||
const decimal notional = 306_191_860.26m;
|
||||
const decimal fix = 9_185_755.81m;
|
||||
var fe = CalcUnwindWith(1m, null, notional, fix);
|
||||
var fe = CalcUnwindWith(1m, null, notional, fix, rate: 0.01m);
|
||||
|
||||
Assert.AreEqual(fix, fe.InterestPrincipal, "显示值(应返还本金)已=保证金本金");
|
||||
Console.WriteLine($"[TDD][计息基数] InterestPrincipal={fe.InterestPrincipal} InterestAmount={fe.InterestAmount}");
|
||||
Assert.IsTrue(fe.InterestAmount > 0,
|
||||
"根因修复后: 预付金腿 InterestAmount 应基于保证金本金算出小额正值(约 fix*rate),不再是巨负");
|
||||
"根因修复后(显式带息): 预付金腿 InterestAmount 应基于保证金本金算出小额正值(约 fix*rate),不再是巨负");
|
||||
Assert.IsTrue(fe.InterestAmount < fix,
|
||||
"利息基数必须为保证金维度(远小于 fix),证明 orginPv 已用预付金自身 Fix,而非交易名义本金 notional");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user