Files
zszq-trs/UnitTestProject/Modules/SwapModule/SwapEodRealizedPnlCalcTest.cs
hjhan 2395aa9856 test(swap): 补 6676b625 掉期保证金利息方向反向单元测试
抽 CalculateSwapRealizedPnl 为 public static 纯函数(用 ConsTrade.InterestMarginModels 替代实例 marginTypes), 新增 8 个单元测试覆盖: 非保证金腿收取/支付原向, 保证金腿(初始预付金/追加预付金)收取/支付反向, 完整 5 字段汇总两种方向, RealizedInterest=0 边界. 锁定 6676b625 修复的保证金腿利息方向反向契约, 防止后续误改回归.
2026-07-17 08:40:19 +08:00

207 lines
9.5 KiB
C#
Raw Permalink 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.CalculateSwapRealizedPnl 的回归测试。
/// ---------------------------------------------------------------
/// 守卫张名锐提交 6676b625 "fix(swap): 修正掉期产品保证金利息计算逻辑"。
///
/// 旧 bugeod_swap.RealizedPnL 直接 Sum(s.RealizedPnl),未对保证金腿利息做方向反向,
/// 导致"收取对手方保证金"产生的利息被错误计入我方收益(实际是我方支付给对手方的成本),
/// 框架合约已实现收益虚高。
///
/// 修复:新增 CalculateSwapRealizedPnl ——
/// 非保证金腿:interestRatio = Direction==收取 ? 1 : -1(维持数据库方向)
/// 保证金腿(初始预付金 5 / 追加预付金 6):interestRatio 反向
/// 最终:RealizedInterest × interestRatio + 其他 4 字段
///
/// 抽为 public static 纯函数以支持无库单测(marginTypes 等价于 ConsTrade.InterestMarginModels)。
/// 本测试直接锁定方向反向契约,防止后续误改回归。
/// </summary>
[TestClass]
public class SwapEodRealizedPnlCalcTest
{
// ================================================================
// 场景1:非保证金腿收取方向 → RealizedInterest × +1(维持原向)
// ================================================================
[TestMethod]
public void 非保证金腿_收取方向_利息维持原向系数为1()
{
var pos = NewPosition(
interestMode: (int)InterestModeEnum.固定值,
interestDirection: (int)SwapDirectionEnum.收取,
realizedInterest: 1000m);
var result = SwapEodPositionService.CalculateSwapRealizedPnl(pos);
Assert.AreEqual(1000m, result, 0.0001m,
"非保证金腿收取方向:利息 ×(+1)=1000");
}
// ================================================================
// 场景2:非保证金腿支付方向 → RealizedInterest × -1(维持原向)
// ================================================================
[TestMethod]
public void 非保证金腿_支付方向_利息维持原向系数为负1()
{
var pos = NewPosition(
interestMode: (int)InterestModeEnum.固定值,
interestDirection: (int)SwapDirectionEnum.支付,
realizedInterest: 1000m);
var result = SwapEodPositionService.CalculateSwapRealizedPnl(pos);
Assert.AreEqual(-1000m, result, 0.0001m,
"非保证金腿支付方向:利息 ×(-1)=-1000");
}
// ================================================================
// 场景3:保证金腿(初始预付金)收取方向 → 利息反向,系数 -1
// 这是 6676b625 修复的核心场景:收取对手方保证金产生的利息是我方支付成本
// ================================================================
[TestMethod]
public void 保证金腿_初始预付金_收取方向_利息反向系数为负1()
{
var pos = NewPosition(
interestMode: (int)InterestModeEnum.初始预付金,
interestDirection: (int)SwapDirectionEnum.收取,
realizedInterest: 1000m);
var result = SwapEodPositionService.CalculateSwapRealizedPnl(pos);
Assert.AreEqual(-1000m, result, 0.0001m,
"保证金腿收取方向:利息应反向 ×(-1)=-1000(修复前会错误得 +1000");
}
// ================================================================
// 场景4:保证金腿(初始预付金)支付方向 → 利息反向,系数 +1
// ================================================================
[TestMethod]
public void 保证金腿_初始预付金_支付方向_利息反向系数为1()
{
var pos = NewPosition(
interestMode: (int)InterestModeEnum.初始预付金,
interestDirection: (int)SwapDirectionEnum.支付,
realizedInterest: 1000m);
var result = SwapEodPositionService.CalculateSwapRealizedPnl(pos);
Assert.AreEqual(1000m, result, 0.0001m,
"保证金腿支付方向:利息应反向 ×(+1)=1000");
}
// ================================================================
// 场景5:追加预付金同初始预付金,同样走反向逻辑
// ================================================================
[TestMethod]
public void 保证金腿_追加预付金_收取方向_利息反向()
{
var pos = NewPosition(
interestMode: (int)InterestModeEnum.追加预付金,
interestDirection: (int)SwapDirectionEnum.收取,
realizedInterest: 500m);
var result = SwapEodPositionService.CalculateSwapRealizedPnl(pos);
Assert.AreEqual(-500m, result, 0.0001m,
"追加预付金(mode=6)与初始预付金(mode=5)同走反向逻辑");
}
// ================================================================
// 场景6:完整 5 字段汇总(MtmPnL + Dividend + Fee + Interest×ratio + InterestFee
// 保证金腿收取方向,Interest=200, 其他各 100
// 期望:100 + 100 + 100 + 200×(-1) + 100 = 200
// ================================================================
[TestMethod]
public void 完整5字段汇总_保证金腿收取方向_利息反向后合计正确()
{
var pos = NewPosition(
interestMode: (int)InterestModeEnum.初始预付金,
interestDirection: (int)SwapDirectionEnum.收取,
realizedMtmPnL: 100m,
realizedDividend: 100m,
realizedFee: 100m,
realizedInterest: 200m,
realizedInterestFee: 100m);
var result = SwapEodPositionService.CalculateSwapRealizedPnl(pos);
// 100 + 100 + 100 + 200×(-1) + 100 = 200
Assert.AreEqual(200m, result, 0.0001m,
"5 字段汇总:保证金腿收取方向,Interest×(-1) 后合计=200,验证所有字段都参与计算");
}
// ================================================================
// 场景7:完整 5 字段汇总(非保证金腿收取方向)
// 非保证金腿收取方向,Interest=200, 其他各 100
// 期望:100 + 100 + 100 + 200×(+1) + 100 = 600
// ================================================================
[TestMethod]
public void 完整5字段汇总_非保证金腿收取方向_利息原向合计正确()
{
var pos = NewPosition(
interestMode: (int)InterestModeEnum.固定值,
interestDirection: (int)SwapDirectionEnum.收取,
realizedMtmPnL: 100m,
realizedDividend: 100m,
realizedFee: 100m,
realizedInterest: 200m,
realizedInterestFee: 100m);
var result = SwapEodPositionService.CalculateSwapRealizedPnl(pos);
// 100 + 100 + 100 + 200×(+1) + 100 = 600
Assert.AreEqual(600m, result, 0.0001m,
"5 字段汇总:非保证金腿收取方向,Interest×(+1) 后合计=600");
}
// ================================================================
// 场景8RealizedInterest=0 边界 —— 方向反向无影响,结果为其他 4 字段之和
// ================================================================
[TestMethod]
public void 利息为零_方向反向无影响_结果为其他4字段之和()
{
var pos = NewPosition(
interestMode: (int)InterestModeEnum.初始预付金,
interestDirection: (int)SwapDirectionEnum.收取,
realizedMtmPnL: 100m,
realizedDividend: 50m,
realizedFee: 30m,
realizedInterest: 0m,
realizedInterestFee: 20m);
var result = SwapEodPositionService.CalculateSwapRealizedPnl(pos);
// 100 + 50 + 30 + 0×(-1) + 20 = 200
Assert.AreEqual(200m, result, 0.0001m,
"RealizedInterest=0 时方向反向无影响,结果为其他 4 字段之和");
}
// ================================================================
// Helper:构造 eod_swap_position(只设置参与计算的 7 个字段)
// ================================================================
private static eod_swap_position NewPosition(
int interestMode,
int interestDirection,
decimal realizedMtmPnL = 0m,
decimal realizedDividend = 0m,
decimal realizedFee = 0m,
decimal realizedInterest = 0m,
decimal realizedInterestFee = 0m)
{
return new eod_swap_position
{
InterestMode = interestMode,
InterestDirection = interestDirection,
RealizedMtmPnL = realizedMtmPnL,
RealizedDividend = realizedDividend,
RealizedFee = realizedFee,
RealizedInterest = realizedInterest,
RealizedInterestFee = realizedInterestFee
};
}
}
}