refactor(swap-eod): 抽出期末头寸纯函数并放宽可见性
SetFixedLegRealizedPnl 替换4处复制粘贴(清理L1296双分号); NormalizeInterestSignForReport 替换16行内联报表分支; CalculateWeightedMarginInterest private->public static。均为纯静态无实例依赖,配套无库单测锁定行为。
This commit is contained in:
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user