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:
@@ -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 = posiPrincipal:eod.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=1,InterestPrincipal 恒=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.5(ByEod 正确变体,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 正确,不受影响)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user