Files
zszq-trs/UnitTestProject/Modules/SwapModule/SwapWeightedMarginInterestTest.cs
T
hjhan a153a1d1a6 refactor(swap-eod): 抽出期末头寸纯函数并放宽可见性
SetFixedLegRealizedPnl 替换4处复制粘贴(清理L1296双分号); NormalizeInterestSignForReport 替换16行内联报表分支; CalculateWeightedMarginInterest private->public static。均为纯静态无实例依赖,配套无库单测锁定行为。
2026-07-21 15:28:13 +08:00

151 lines
6.7 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.DBModels;
using YLErp.DBModels.Enums;
namespace YLErp.Modules.SwapModule
{
/// <summary>
/// SwapEodPositionService.CalculateWeightedMarginInterest 的回归测试。
/// -----------------------------------------------------------------
/// 守卫提交 a0f8dc9d "refactor(SwapModule): 简化预付金利息计算逻辑"。
///
/// 旧实现:按 InterestPrincipalFix 绝对值加权平均利率 × 总本金,对方向不敏感,
/// 当收取/支付双腿并存时会把支付方向的利息错误计为收益。
/// 新实现:InterestIncomeSum 已是各腿利息金额,按方向(收取=+1,支付=-1)轧差求和。
///
/// 抽为 public static 纯函数以支持无库单测。本测试锁定方向轧差契约。
/// </summary>
[TestClass]
public class SwapWeightedMarginInterestTest
{
// ================================================================
// 场景1:空集合 → 0(Sum 空序列默认值)
// ================================================================
[TestMethod]
public void 空集合_返回0()
{
var result = SwapEodPositionService.CalculateWeightedMarginInterest(
Enumerable.Empty<eod_swap_position>());
Assert.AreEqual(0m, result, 0.0001m, "空集合轧差应为 0");
}
// ================================================================
// 场景2:单腿收取 → InterestIncomeSum 原值
// ================================================================
[TestMethod]
public void 单腿收取_利息原值计入()
{
var margins = new[]
{
NewMargin(interestDirection: (int)SwapDirectionEnum.收取, interestIncomeSum: 1000m)
};
var result = SwapEodPositionService.CalculateWeightedMarginInterest(margins);
Assert.AreEqual(1000m, result, 0.0001m, "单腿收取:+1000");
}
// ================================================================
// 场景3:单腿支付 → InterestIncomeSum 取负
// ================================================================
[TestMethod]
public void 单腿支付_利息取负计入()
{
var margins = new[]
{
NewMargin(interestDirection: (int)SwapDirectionEnum.支付, interestIncomeSum: 1000m)
};
var result = SwapEodPositionService.CalculateWeightedMarginInterest(margins);
Assert.AreEqual(-1000m, result, 0.0001m, "单腿支付:-1000");
}
// ================================================================
// 场景4:双腿轧差(收取 1000 + 支付 600 → 400
// 旧 bug:按本金加权会忽略方向,结果不是 400
// ================================================================
[TestMethod]
public void 双腿轧差_收取大于支付_净额为正()
{
var margins = new[]
{
NewMargin(interestDirection: (int)SwapDirectionEnum.收取, interestIncomeSum: 1000m),
NewMargin(interestDirection: (int)SwapDirectionEnum.支付, interestIncomeSum: 600m)
};
var result = SwapEodPositionService.CalculateWeightedMarginInterest(margins);
Assert.AreEqual(400m, result, 0.0001m, "双腿轧差:1000 - 600 = 400");
}
// ================================================================
// 场景5:本金为零但 InterestIncomeSum 非零 —— 回归旧 bug 关键场景
// 旧实现:totalWeight=0 → 返回 0,丢失利息
// 新实现:不看本金,按方向轧差 InterestIncomeSum
// ================================================================
[TestMethod]
public void 本金为零_利息仍按方向轧差_不丢失()
{
var margins = new[]
{
NewMargin(interestDirection: (int)SwapDirectionEnum.收取,
interestIncomeSum: 500m, interestPrincipalFix: 0m),
NewMargin(interestDirection: (int)SwapDirectionEnum.支付,
interestIncomeSum: 200m, interestPrincipalFix: 0m)
};
var result = SwapEodPositionService.CalculateWeightedMarginInterest(margins);
Assert.AreEqual(300m, result, 0.0001m,
"本金为零时旧实现返回 0 丢失利息,新实现应按方向轧差 = 500 - 200 = 300");
}
// ================================================================
// 场景6:多腿混合方向 —— 收取 100+200,支付 50+80 → 170
// ================================================================
[TestMethod]
public void 多腿混合方向_正确轧差()
{
var margins = new[]
{
NewMargin(interestDirection: (int)SwapDirectionEnum.收取, interestIncomeSum: 100m),
NewMargin(interestDirection: (int)SwapDirectionEnum.支付, interestIncomeSum: 50m),
NewMargin(interestDirection: (int)SwapDirectionEnum.收取, interestIncomeSum: 200m),
NewMargin(interestDirection: (int)SwapDirectionEnum.支付, interestIncomeSum: 80m)
};
var result = SwapEodPositionService.CalculateWeightedMarginInterest(margins);
Assert.AreEqual(170m, result, 0.0001m, "多腿轧差:(100+200) - (50+80) = 170");
}
// ================================================================
// 场景7:null 入参防御 —— Sum 对 null 抛 ArgumentNullException
// 仅作行为锚点:若未来改为 null 安全,此处需同步更新
// ================================================================
[TestMethod]
public void Null入参_抛ArgumentNullException()
{
Assert.ThrowsException<ArgumentNullException>(() =>
SwapEodPositionService.CalculateWeightedMarginInterest(null!));
}
// ================================================================
// Helper:构造 eod_swap_position(只设置参与计算的 2 个字段 + 可选本金)
// ================================================================
private static eod_swap_position NewMargin(
int interestDirection,
decimal interestIncomeSum,
decimal interestPrincipalFix = 0m)
{
return new eod_swap_position
{
InterestDirection = interestDirection,
InterestIncomeSum = interestIncomeSum,
InterestPrincipalFix = interestPrincipalFix
};
}
}
}