fix(预付金返回/平仓比例): 恢复占期初语义并在后端入口统一转占剩余

之前提交 4f46e263 误将平仓比例改为占剩余持仓(Definition B)语义,与需求不符。
正确需求:每次打开平仓/提前终止均基于最开始原始名义本金(Definition A),
但每次返回的预付金金额需正确(后端按占剩余计算)。

- SwapDealService: 新增 ToRemainingClosePercent/ToOriginalClosePercent 双语义转换;
  SwapUnwind 入口把前端传来的占期初(A)转为占剩余(B),全计算链公式不动;
  SaveSwapDealInternal 序列化前还原为 A 落库,事件列表读取即显示占期初比例。
- SwapTrade2Controller.GetUnwindInterestList: 接收期初/剩余名义本金,预览利息按 B 计算。
- unwindSwapTrade.js: 回退为占期初语义(ClosePercent/平仓名义本金用 NotionalValue,
  oriClosePercent=剩余/期初),提前终止同理。
- 单测 UW_007/008 覆盖 A→B 全平/部分平仓判定。

不影响收盘后盯市/估值(仅平仓入口与展示口径)。
This commit is contained in:
hjhan
2026-07-16 19:54:26 +08:00
parent d5b43e3769
commit 238824f4f4
4 changed files with 101 additions and 17 deletions
@@ -187,5 +187,55 @@ namespace YLErp.Modules.SwapModule
"SwapRealizedPnL 应=SwapCloseAmount(6000)");
Console.WriteLine($"UW_006: CloseReCheck={service.CloseReCheckCallCount}次, SwapRealizedPnL={service.SaveSwapDealCalls[0].data.SwapRealizedPnL} ✅");
}
// ================================================================
// 场景7:前端传"占期初(A)"语义,后端入口转"占剩余(B)" —— 全平判定
// 原始名义本金 100M / 剩余 60M,前端传 A=0.6(平掉原始 60M = 剩余全部)
// B = A × Notional/Posi = 0.6 × 100/60 = 1.0 → 触发全平
// ================================================================
[TestMethod]
public void UW_007_SwapUnwind_占期初A转占剩余B_全平判定正确()
{
var td = SwapDealTestFactory.CreateTrade();
var service = new TestableSwapDealService(td);
var unwindData = SwapDealTestFactory.CreateUnwindData(
swapRealizedPnL: 0m, closeMethod: (int)CloseMethodEnum., closePercent: 0.6m,
closeQty: 600000m, closeNotionalValue: 600000m, positionQty: 600000m);
unwindData.NotionalValue = 1000000m; // 期初名义本金
unwindData.PosiNotionalValue = 600000m; // 剩余名义本金
service.SwapUnwind(unwindData);
// 桩 SaveSwapDeal 收集的是转换后的 B(落库 A 还原在生产 SaveSwapDealInternal 中,桩跳过)
Assert.AreEqual(1.0m, service.SaveSwapDealCalls[0].data.ClosePercent, 0.0001m,
"入口 A=0.6 应转为 B=1.0(占剩余全平)");
Assert.AreEqual("已平仓", td.TradeStatus, "B==1 触发全平 TradeStatus=已平仓");
Console.WriteLine($"UW_007: A=0.6→B={service.SaveSwapDealCalls[0].data.ClosePercent}, TradeStatus={td.TradeStatus} ✅");
}
// ================================================================
// 场景8:占期初(A)转占剩余(B) —— 部分平仓
// 原始 100M / 剩余 60M,前端传 A=0.3(平掉原始 30M = 剩余的 50%)
// B = A × Notional/Posi = 0.3 × 100/60 = 0.5 → 部分平仓
// ================================================================
[TestMethod]
public void UW_008_SwapUnwind_占期初A转占剩余B_部分平仓正确()
{
var td = SwapDealTestFactory.CreateTrade();
var service = new TestableSwapDealService(td);
var unwindData = SwapDealTestFactory.CreateUnwindData(
swapRealizedPnL: 0m, closeMethod: (int)CloseMethodEnum., closePercent: 0.3m,
closeQty: 300000m, closeNotionalValue: 300000m, positionQty: 600000m);
unwindData.NotionalValue = 1000000m; // 期初名义本金
unwindData.PosiNotionalValue = 600000m; // 剩余名义本金
service.SwapUnwind(unwindData);
Assert.AreEqual(0.5m, service.SaveSwapDealCalls[0].data.ClosePercent, 0.0001m,
"入口 A=0.3 应转为 B=0.5(占剩余 50%");
Assert.AreEqual(1, td.HasPartialUnWind, "B≠1 应为部分平仓,设 HasPartialUnWind=1");
Assert.AreEqual("确认成交", td.TradeStatus, "部分平仓 TradeStatus 保持不变");
Console.WriteLine($"UW_008: A=0.3→B={service.SaveSwapDealCalls[0].data.ClosePercent}, HasPartialUnWind={td.HasPartialUnWind} ✅");
}
}
}