SetFixedLegRealizedPnl 替换4处复制粘贴(清理L1296双分号); NormalizeInterestSignForReport 替换16行内联报表分支; CalculateWeightedMarginInterest private->public static。均为纯静态无实例依赖,配套无库单测锁定行为。
213 lines
10 KiB
C#
213 lines
10 KiB
C#
using YLErp.DBModels;
|
||
using YLErp.DBModels.Enums;
|
||
|
||
namespace YLErp.Modules.SwapModule
|
||
{
|
||
/// <summary>
|
||
/// SwapEodPositionService.NormalizeInterestSignForReport 的回归测试。
|
||
/// -----------------------------------------------------------------
|
||
/// 守卫提交 f4ffe710 中报表分支的符号归一化逻辑(原内联于 SearchEodSwapList)。
|
||
///
|
||
/// 业务口径:
|
||
/// - 仅当 InterestDirection > 0 时执行(兼容历史 0 方向脏数据)
|
||
/// - 普通利息腿:收取为正、支付为负(interestRatio = Direction==收取 ? 1 : -1)
|
||
/// - 预付金腿(初始预付金/追加预付金):利息方向与保证金本金方向相反,interestRatio 取反
|
||
/// - TdCloseInterest / RealizedInterest 统一按 Math.Abs × interestRatio 重写
|
||
/// - RealizedPnl 重算为 RealizedInterest + RealizedInterestFee(兼容历史未同步落库)
|
||
///
|
||
/// 抽为 public static 纯函数以支持无库单测。
|
||
/// </summary>
|
||
[TestClass]
|
||
public class SwapReportInterestSignNormalizeTest
|
||
{
|
||
// ================================================================
|
||
// 场景1:普通利息腿收取方向 → 利息维持正号
|
||
// TdCloseInterest=-1000(历史脏数据符号错) → 归一化为 +1000
|
||
// RealizedInterest=-2000 → +2000
|
||
// RealizedPnl = 2000 + 100 = 2100
|
||
// ================================================================
|
||
[TestMethod]
|
||
public void 普通利息腿_收取方向_利息归一化为正()
|
||
{
|
||
var pos = NewPosition(
|
||
interestMode: (int)InterestModeEnum.固定值,
|
||
interestDirection: (int)SwapDirectionEnum.收取,
|
||
tdCloseInterest: -1000m,
|
||
realizedInterest: -2000m,
|
||
realizedInterestFee: 100m);
|
||
|
||
SwapEodPositionService.NormalizeInterestSignForReport(pos);
|
||
|
||
Assert.AreEqual(1000m, pos.TdCloseInterest, 0.0001m, "TdCloseInterest 应归一化为 +1000");
|
||
Assert.AreEqual(2000m, pos.RealizedInterest, 0.0001m, "RealizedInterest 应归一化为 +2000");
|
||
Assert.AreEqual(2100m, pos.RealizedPnl, 0.0001m, "RealizedPnl = 2000 + 100 = 2100");
|
||
}
|
||
|
||
// ================================================================
|
||
// 场景2:普通利息腿支付方向 → 利息归一化为负
|
||
// TdCloseInterest=1000(历史脏数据符号错) → 归一化为 -1000
|
||
// RealizedInterest=2000 → -2000
|
||
// RealizedPnl = -2000 + 100 = -1900
|
||
// ================================================================
|
||
[TestMethod]
|
||
public void 普通利息腿_支付方向_利息归一化为负()
|
||
{
|
||
var pos = NewPosition(
|
||
interestMode: (int)InterestModeEnum.固定值,
|
||
interestDirection: (int)SwapDirectionEnum.支付,
|
||
tdCloseInterest: 1000m,
|
||
realizedInterest: 2000m,
|
||
realizedInterestFee: 100m);
|
||
|
||
SwapEodPositionService.NormalizeInterestSignForReport(pos);
|
||
|
||
Assert.AreEqual(-1000m, pos.TdCloseInterest, 0.0001m, "TdCloseInterest 应归一化为 -1000");
|
||
Assert.AreEqual(-2000m, pos.RealizedInterest, 0.0001m, "RealizedInterest 应归一化为 -2000");
|
||
Assert.AreEqual(-1900m, pos.RealizedPnl, 0.0001m, "RealizedPnl = -2000 + 100 = -1900");
|
||
}
|
||
|
||
// ================================================================
|
||
// 场景3:预付金腿收取方向 → 利息方向反向为负
|
||
// 原因:预付金腿的利息方向与保证金本金方向相反
|
||
// TdCloseInterest=1000 → -1000
|
||
// RealizedInterest=2000 → -2000
|
||
// ================================================================
|
||
[TestMethod]
|
||
public void 预付金腿_收取方向_利息反向为负()
|
||
{
|
||
var pos = NewPosition(
|
||
interestMode: (int)InterestModeEnum.初始预付金,
|
||
interestDirection: (int)SwapDirectionEnum.收取,
|
||
tdCloseInterest: 1000m,
|
||
realizedInterest: 2000m,
|
||
realizedInterestFee: 0m);
|
||
|
||
SwapEodPositionService.NormalizeInterestSignForReport(pos);
|
||
|
||
Assert.AreEqual(-1000m, pos.TdCloseInterest, 0.0001m,
|
||
"预付金腿收取方向:利息反向为负");
|
||
Assert.AreEqual(-2000m, pos.RealizedInterest, 0.0001m,
|
||
"预付金腿收取方向:累计利息反向为负");
|
||
Assert.AreEqual(-2000m, pos.RealizedPnl, 0.0001m,
|
||
"RealizedPnl = -2000 + 0 = -2000");
|
||
}
|
||
|
||
// ================================================================
|
||
// 场景4:预付金腿支付方向 → 利息方向反向为正
|
||
// ================================================================
|
||
[TestMethod]
|
||
public void 预付金腿_支付方向_利息反向为正()
|
||
{
|
||
var pos = NewPosition(
|
||
interestMode: (int)InterestModeEnum.追加预付金,
|
||
interestDirection: (int)SwapDirectionEnum.支付,
|
||
tdCloseInterest: -1000m,
|
||
realizedInterest: -2000m,
|
||
realizedInterestFee: 50m);
|
||
|
||
SwapEodPositionService.NormalizeInterestSignForReport(pos);
|
||
|
||
Assert.AreEqual(1000m, pos.TdCloseInterest, 0.0001m,
|
||
"预付金腿支付方向:利息反向为正");
|
||
Assert.AreEqual(2000m, pos.RealizedInterest, 0.0001m,
|
||
"预付金腿支付方向:累计利息反向为正");
|
||
Assert.AreEqual(2050m, pos.RealizedPnl, 0.0001m,
|
||
"RealizedPnl = 2000 + 50 = 2050");
|
||
}
|
||
|
||
// ================================================================
|
||
// 场景5:InterestDirection=0 → 不处理(兼容历史 0 方向脏数据)
|
||
// 所有字段保持原值不变
|
||
// ================================================================
|
||
[TestMethod]
|
||
public void 方向为零_不处理_字段保持原值()
|
||
{
|
||
var pos = NewPosition(
|
||
interestMode: (int)InterestModeEnum.固定值,
|
||
interestDirection: 0,
|
||
tdCloseInterest: -999m,
|
||
realizedInterest: -888m,
|
||
realizedInterestFee: 77m);
|
||
pos.RealizedPnl = 555m; // 预置旧值
|
||
|
||
SwapEodPositionService.NormalizeInterestSignForReport(pos);
|
||
|
||
Assert.AreEqual(-999m, pos.TdCloseInterest, 0.0001m, "方向=0:TdCloseInterest 不变");
|
||
Assert.AreEqual(-888m, pos.RealizedInterest, 0.0001m, "方向=0:RealizedInterest 不变");
|
||
Assert.AreEqual(555m, pos.RealizedPnl, 0.0001m, "方向=0:RealizedPnl 不重算");
|
||
}
|
||
|
||
// ================================================================
|
||
// 场景6:InterestDirection 为负 → 不处理(防御性,对应原 "> 0" 判断)
|
||
// ================================================================
|
||
[TestMethod]
|
||
public void 方向为负_不处理_字段保持原值()
|
||
{
|
||
var pos = NewPosition(
|
||
interestMode: (int)InterestModeEnum.固定值,
|
||
interestDirection: -1,
|
||
tdCloseInterest: -999m,
|
||
realizedInterest: -888m,
|
||
realizedInterestFee: 77m);
|
||
pos.RealizedPnl = 555m;
|
||
|
||
SwapEodPositionService.NormalizeInterestSignForReport(pos);
|
||
|
||
Assert.AreEqual(-999m, pos.TdCloseInterest, 0.0001m, "方向<0:TdCloseInterest 不变");
|
||
Assert.AreEqual(555m, pos.RealizedPnl, 0.0001m, "方向<0:RealizedPnl 不重算");
|
||
}
|
||
|
||
// ================================================================
|
||
// 场景7:利息为零 → Math.Abs(0)=0,归一化后仍为 0,RealizedPnl=费用
|
||
// ================================================================
|
||
[TestMethod]
|
||
public void 利息为零_归一化后仍为零_盈亏等于费用()
|
||
{
|
||
var pos = NewPosition(
|
||
interestMode: (int)InterestModeEnum.固定值,
|
||
interestDirection: (int)SwapDirectionEnum.收取,
|
||
tdCloseInterest: 0m,
|
||
realizedInterest: 0m,
|
||
realizedInterestFee: 300m);
|
||
|
||
SwapEodPositionService.NormalizeInterestSignForReport(pos);
|
||
|
||
Assert.AreEqual(0m, pos.TdCloseInterest, 0.0001m);
|
||
Assert.AreEqual(0m, pos.RealizedInterest, 0.0001m);
|
||
Assert.AreEqual(300m, pos.RealizedPnl, 0.0001m, "RealizedPnl = 0 + 300 = 300");
|
||
}
|
||
|
||
// ================================================================
|
||
// 场景8:null 入参 —— 抛 NullReferenceException(现状锚点)
|
||
// 生产代码未加 null 检查,InterestDirection 解引用即 NRE。
|
||
// 若未来改为 ArgumentNullException,此处需同步更新。
|
||
// ================================================================
|
||
[TestMethod]
|
||
public void Null入参_抛NullReferenceException()
|
||
{
|
||
Assert.ThrowsException<NullReferenceException>(() =>
|
||
SwapEodPositionService.NormalizeInterestSignForReport(null!));
|
||
}
|
||
|
||
// ================================================================
|
||
// Helper:构造 eod_swap_position
|
||
// ================================================================
|
||
private static eod_swap_position NewPosition(
|
||
int interestMode,
|
||
int interestDirection,
|
||
decimal tdCloseInterest,
|
||
decimal realizedInterest,
|
||
decimal realizedInterestFee)
|
||
{
|
||
return new eod_swap_position
|
||
{
|
||
InterestMode = interestMode,
|
||
InterestDirection = interestDirection,
|
||
TdCloseInterest = tdCloseInterest,
|
||
RealizedInterest = realizedInterest,
|
||
RealizedInterestFee = realizedInterestFee
|
||
};
|
||
}
|
||
}
|
||
}
|