refactor(swap-eod): 抽出期末头寸纯函数并放宽可见性

SetFixedLegRealizedPnl 替换4处复制粘贴(清理L1296双分号); NormalizeInterestSignForReport 替换16行内联报表分支; CalculateWeightedMarginInterest private->public static。均为纯静态无实例依赖,配套无库单测锁定行为。
This commit is contained in:
hjhan
2026-07-21 15:28:13 +08:00
parent 494a7a06cc
commit a153a1d1a6
4 changed files with 566 additions and 21 deletions
@@ -0,0 +1,150 @@
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
};
}
}
}