test(swap): 补 CalcDailySimpleInterest 全腿模式+日终路径覆盖,锁定无更大影响

用户要求确认 CalcDailySimpleInterest 所有调用方(含日终)覆盖完整性。
代码事实纠正:日终(settment=true)走 CalcEodInterest → CalcDailySimpleInterestByEod
(closePercent 硬编码 1m、且该函数从不改写 InterestPrincipal),根本不调用本函数;
本函数唯一真实调用链 = GetInterests(settment=false) → CalcUnwindInterest。
故'含日终'的正确命题是:日终不受此 bug 影响,应有用例锁定该不变量。

新增 CalcByMode 驱动各 InterestMode + 日终路径(rest_days=7, closePercent<1):
- 追加预付金(6)/多头存续(7)/空头存续(8)/合约名义本金规模(2):断言线性 closePrincipal
- 固定值(1):强制 newClosePercent=1,对平仓比例免疫,断言恒=Fix
- 日终 预付金(5)/标的期初全价(9):断言结果恒为线性 closePrincipal(证明 ByEod 正确变体不受影响)
SwapUnwindPrepayPrincipalBugTdd 现 19 绿;SwapModule 全量 196 通过/4 跳过,无回归。
This commit is contained in:
hjhan
2026-07-14 17:23:33 +08:00
parent fdbb7b363a
commit 7689436364
@@ -427,5 +427,141 @@ namespace YLErp.Modules.SwapModule
Assert.IsTrue(fe.InterestAmount < fix,
"利息基数必须为保证金维度(远小于 fix),证明 orginPv 已用预付金自身 Fix,而非交易名义本金 notional");
}
// ===== 覆盖完整性补强:所有单利腿模式 + 日终路径 =====
// 调用链事实(已用代码确认):
// CalcDailySimpleInterest 的唯一真实调用链 = GetInterests(settment=false) → CalcUnwindInterest → 本函数。
// 日终(settment=true)走 CalcEodInterest → CalcDailySimpleInterestByEod(closePercent 硬编码 1m、
// 且该函数从不改写 InterestPrincipal),根本不调用本函数。故"含日终"的正确命题是:
// 日终不受本 bug 影响,且应有用例锁定这一不变量。
// 本组用同一入口驱动各 InterestMode 在 closePercent<1 + rest_days=7 多天场景,断言
// InterestPrincipal = closePrincipal(线性),捕捉任何指数级回归;并显式加日终(settment=true)用例,
// 断言日终结果恒为线性 closePrincipal(证明日终不受盘中 bug 影响,与正确的 ByEod 变体对齐)。
private swap_flow_event CalcByMode(int mode, decimal baseP, decimal closePercent, int restDays = 7, bool eodPath = false)
{
var extend = new trade_extend
{
TradeId = 1,
ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson
{
AnnualDays = AnnualDays,
InterestCalcMode = "10", // 算头不算尾(与生产一致)
SettlementRules = 0
})
};
var td = new trade
{
id = 1, TradeNumber = "UT-MODE-COV", ClientId = 999998,
TradeType = "收益互换", TradeDate = ProdPosiStart, StartDate = ProdPosiStart,
ExerciseDate = ProdUnwindDate.AddYears(1), TradeStatus = "确认成交", ValidState = "Valid",
StockEqvNotional = (double)baseP, Notional = (double)baseP,
trade_extend = extend
};
bool isPrepayOrFixed = mode == (int)InterestModeEnum.
|| mode == (int)InterestModeEnum.
|| mode == (int)InterestModeEnum.;
var position = new swap_position
{
id = 3003, SwapTradeId = 1, PositionType = (int)PositionTypeFlag.Unknown,
InterestDirection = (int)SwapDirectionEnum.,
InterestMode = mode,
InterestRateDefault = 0m,
InterestPrincipalFix = isPrepayOrFixed ? baseP : 0m,
PosiStartDate = ProdPosiStart, PosiMatuirityDate = ProdUnwindDate.AddYears(1),
IsInitial = true, Invalid = false, InterestType = (int)InterestTypeEnum.,
IsAnnualized = true, interest_rest_days = restDays,
interest_rule = 0, FloatRateUnderlyingCode = null,
InterestSwapInterval = "[]"
};
// 使 dynomicPrincipal = posiPrincipaleod.TdInterestPrincipal = orginPv(=baseP)
// 非预付金腿 orginPv 传 baseP;预付金/固定值腿 orginPv 被内部对齐为 Fix=baseP(同样成立)。
var eodPos = new List<eod_swap_position>
{
new eod_swap_position
{
id = 30, SwapTradeId = 1, PositionId = 3003,
ValueDate = ProdEodValueDate,
TdInterestPrincipal = baseP,
PosiNotionalValue = baseP,
InterestProfitSum = 0m, FloatRate = 0m
}
};
var interests = _svc.GetInterests(td, td.trade_extend, ProdUnwindDate, ProdUnwindDate,
eodPos, new List<swap_position> { position },
baseP, baseP, baseP, baseP * closePercent, closePercent,
(int)SwapEventTypeEnum.,
false, false, 0, baseP, false, settment: eodPath, newCalcLast: false, closeList: null);
Assert.AreEqual(1, interests.Count, $"mode={mode} 应生成 1 条 flow_event");
return interests[0];
}
// ---- 追加预付金(6):与初始预付金(5)同源修复,显式覆盖避免遗漏 ----
[TestMethod]
public void _盘中_部分平仓重置周期7天_应线性缩放()
{
var fe = CalcByMode((int)InterestModeEnum., ProdPrepayFix, 0.5m);
Assert.AreEqual(4_590_000m, fe.InterestPrincipal, "追加预付金 50% 应=Fix×0.5(与初始预付金同源修复)");
var fe1 = CalcByMode((int)InterestModeEnum., ProdPrepayFix, 0.1m);
Assert.AreEqual(918_000m, fe1.InterestPrincipal, "追加预付金 10% 应=Fix×0.1");
}
// ---- 多头/空头存续名义本金(7/8):经同一 CalcDailySimpleInterest,需证明修复通用 ----
[TestMethod]
public void _盘中_部分平仓重置周期7天_应线性缩放()
{
const decimal baseP = 2_000_000m;
var fe = CalcByMode((int)InterestModeEnum., baseP, 0.5m);
Assert.AreEqual(1_000_000m, fe.InterestPrincipal, "多头存续 50% 应=posiLong×0.5");
var fe1 = CalcByMode((int)InterestModeEnum., baseP, 0.1m);
Assert.AreEqual(200_000m, fe1.InterestPrincipal, "多头存续 10% 应=posiLong×0.1");
}
[TestMethod]
public void _盘中_部分平仓重置周期7天_应线性缩放()
{
const decimal baseP = 2_000_000m;
var fe = CalcByMode((int)InterestModeEnum., baseP, 0.5m);
Assert.AreEqual(1_000_000m, fe.InterestPrincipal, "空头存续 50% 应=posiShort×0.5");
var fe1 = CalcByMode((int)InterestModeEnum., baseP, 0.1m);
Assert.AreEqual(200_000m, fe1.InterestPrincipal, "空头存续 10% 应=posiShort×0.1");
}
// ---- 合约名义本金规模(2)CalcNotionalByMode 默认分支(posiNotional×cp ----
[TestMethod]
public void _盘中_部分平仓重置周期7天_应线性缩放()
{
const decimal baseP = 2_000_000m;
var fe = CalcByMode((int)InterestModeEnum., baseP, 0.5m);
Assert.AreEqual(1_000_000m, fe.InterestPrincipal, "合约名义本金规模 50% 应=posiNotional×0.5");
}
// ---- 固定值(1)CalcNotionalByMode 强制 newClosePercent=1,对 closePercent 免疫(输入 0.5 也不缩放) ----
[TestMethod]
public void _盘中_部分平仓_对平仓比例免疫_返回Fix本金()
{
const decimal baseP = 2_000_000m;
var fe = CalcByMode((int)InterestModeEnum., baseP, 0.5m);
Assert.AreEqual(baseP, fe.InterestPrincipal, "固定值腿 newClosePercent=1InterestPrincipal 恒=Fix,不随平仓比例缩放");
}
// ---- 日终路径(settment=true):证明走 CalcDailySimpleInterestByEod,结果恒为线性 closePrincipal,不受盘中 bug 影响 ----
[TestMethod]
public void _预付金腿_部分平仓_结果应线性且不受盘中bug影响()
{
var fe = CalcByMode((int)InterestModeEnum., ProdPrepayFix, 0.5m, eodPath: true);
Console.WriteLine($"[TDD][EOD 预付金50%] InterestPrincipal={fe.InterestPrincipal} (期望={4_590_000m})");
Assert.AreEqual(4_590_000m, fe.InterestPrincipal, "日终预付金 50% 应=Fix×0.5ByEod 正确变体,closePercent 走 closePrincipal 线性)");
var fe1 = CalcByMode((int)InterestModeEnum., ProdPrepayFix, 0.1m, eodPath: true);
Assert.AreEqual(918_000m, fe1.InterestPrincipal, "日终预付金 10% 应=Fix×0.1");
}
[TestMethod]
public void _非预付金腿_部分平仓_结果应线性且不受盘中bug影响()
{
const decimal baseP = 2_000_000m;
var fe = CalcByMode((int)InterestModeEnum., baseP, 0.5m, eodPath: true);
Console.WriteLine($"[TDD][EOD 标的期初全价50%] InterestPrincipal={fe.InterestPrincipal} (期望={1_000_000m})");
Assert.AreEqual(1_000_000m, fe.InterestPrincipal, "日终非预付金腿 50% 应=名义本金×0.5(ByEod 正确,不受影响)");
}
}
}