抽 CalcSwapCloseNotionalFromEventData 和 CalcOptionCloseNotional 为 public static 纯函数, BuildTriggerContext 了结场景改为调用这两个函数. 新增 13 个单元测试覆盖: 互换分支 EventData null/空/非法JSON/正数/负数/零 容错与绝对值, 期权分支 两者null/单边null/正数相乘/负数容错 绝对值语义. 重点锁定静默 catch 容错契约, 防止 EventData 异常时名义本金变 0 绕过审批阈值.
134 lines
6.1 KiB
C#
134 lines
6.1 KiB
C#
using Newtonsoft.Json;
|
||
using YLErp.DBModels;
|
||
using YLErp.Modules.TradeModule;
|
||
|
||
namespace YLErp.Modules.TradeModule
|
||
{
|
||
/// <summary>
|
||
/// TradeServiceBase.CalcSwapCloseNotionalFromEventData / CalcOptionCloseNotional 的回归测试。
|
||
/// ---------------------------------------------------------------
|
||
/// 守卫锦麟王提交 23108016 "BugFix 互换本次名义本金取错"。
|
||
///
|
||
/// 旧 bug:BuildTriggerContext 了结场景统一用 trade_cash.UnwindPercentRate × 期初名义本金
|
||
/// 算本次名义本金,但收益互换的 trade_cash.UnwindPercentRate 口径与期权不同,
|
||
/// 导致互换审批触发条件用错本金,可能绕过/误触发审批阈值。
|
||
///
|
||
/// 修复:互换分支从 swap_event.EventData 反序列化取 CloseNotionalValue 绝对值;
|
||
/// 期权分支保留旧逻辑(期初名义本金 × UnwindPercentRate 绝对值)。
|
||
///
|
||
/// 抽出两个静态纯函数以支持无库单测,重点验证容错(null/空/非法 JSON)不会抛异常
|
||
/// 而是返回 0,避免静默吞异常导致名义本金为 0 进而绕过审批阈值。
|
||
/// </summary>
|
||
[TestClass]
|
||
public class TradeServiceBaseCloseNotionalCalcTest
|
||
{
|
||
// ================================================================
|
||
// 一、CalcSwapCloseNotionalFromEventData 容错与绝对值语义
|
||
// ================================================================
|
||
|
||
[TestMethod]
|
||
public void 互换_EventData为null_返回0_不抛异常()
|
||
{
|
||
var result = TradeServiceBase.CalcSwapCloseNotionalFromEventData(null);
|
||
Assert.AreEqual(0d, result, 0.0001, "null EventData 应容错返回 0");
|
||
}
|
||
|
||
[TestMethod]
|
||
public void 互换_EventData为空字符串_返回0_不抛异常()
|
||
{
|
||
var result = TradeServiceBase.CalcSwapCloseNotionalFromEventData("");
|
||
Assert.AreEqual(0d, result, 0.0001, "空字符串 EventData 应容错返回 0");
|
||
}
|
||
|
||
[TestMethod]
|
||
public void 互换_EventData为非法JSON_返回0_不抛异常()
|
||
{
|
||
// 旧实现 catch{} 静默吞异常,抽函数后必须保持此容错契约
|
||
var result = TradeServiceBase.CalcSwapCloseNotionalFromEventData("not-a-json");
|
||
Assert.AreEqual(0d, result, 0.0001, "非法 JSON 应被 catch 返回 0,不能抛异常");
|
||
}
|
||
|
||
[TestMethod]
|
||
public void 互换_EventData为合法JSON_正数CloseNotionalValue_原值返回()
|
||
{
|
||
var eventData = JsonConvert.SerializeObject(new UnwindData { CloseNotionalValue = 500_000m });
|
||
var result = TradeServiceBase.CalcSwapCloseNotionalFromEventData(eventData);
|
||
Assert.AreEqual(500_000d, result, 0.01, "正数 CloseNotionalValue 应原值返回");
|
||
}
|
||
|
||
[TestMethod]
|
||
public void 互换_EventData为合法JSON_负数CloseNotionalValue_取绝对值()
|
||
{
|
||
// 修复的核心契约:Math.Abs 取绝对值,防止方向反向导致名义本金变负
|
||
var eventData = JsonConvert.SerializeObject(new UnwindData { CloseNotionalValue = -500_000m });
|
||
var result = TradeServiceBase.CalcSwapCloseNotionalFromEventData(eventData);
|
||
Assert.AreEqual(500_000d, result, 0.01, "负数 CloseNotionalValue 应取绝对值返回 500000");
|
||
}
|
||
|
||
[TestMethod]
|
||
public void 互换_EventData为合法JSON_CloseNotionalValue为零_返回0()
|
||
{
|
||
var eventData = JsonConvert.SerializeObject(new UnwindData { CloseNotionalValue = 0m });
|
||
var result = TradeServiceBase.CalcSwapCloseNotionalFromEventData(eventData);
|
||
Assert.AreEqual(0d, result, 0.0001, "CloseNotionalValue=0 应返回 0");
|
||
}
|
||
|
||
// ================================================================
|
||
// 二、CalcOptionCloseNotional 容错与绝对值语义
|
||
// ================================================================
|
||
|
||
[TestMethod]
|
||
public void 期权_两者都为null_返回0()
|
||
{
|
||
var result = TradeServiceBase.CalcOptionCloseNotional(null, null);
|
||
Assert.AreEqual(0d, result, 0.0001, "两者 null 应返回 0");
|
||
}
|
||
|
||
[TestMethod]
|
||
public void 期权_期初名义本金为null_返回0()
|
||
{
|
||
var result = TradeServiceBase.CalcOptionCloseNotional(null, 0.5d);
|
||
Assert.AreEqual(0d, result, 0.0001, "originalStockEqvNotional=null 应返回 0");
|
||
}
|
||
|
||
[TestMethod]
|
||
public void 期权_平仓比例为null_返回0()
|
||
{
|
||
var result = TradeServiceBase.CalcOptionCloseNotional(1_000_000d, null);
|
||
Assert.AreEqual(0d, result, 0.0001, "unwindPercentRate=null 应返回 0");
|
||
}
|
||
|
||
[TestMethod]
|
||
public void 期权_两者都有值_正数相乘_返回乘积()
|
||
{
|
||
// 1,000,000 × 0.3 = 300,000
|
||
var result = TradeServiceBase.CalcOptionCloseNotional(1_000_000d, 0.3d);
|
||
Assert.AreEqual(300_000d, result, 0.01, "1M × 0.3 = 300K");
|
||
}
|
||
|
||
[TestMethod]
|
||
public void 期权_期初名义本金为负数_取绝对值后相乘()
|
||
{
|
||
// 异常但容错:-1,000,000 × 0.3 → Abs → 300,000
|
||
var result = TradeServiceBase.CalcOptionCloseNotional(-1_000_000d, 0.3d);
|
||
Assert.AreEqual(300_000d, result, 0.01, "期初名义本金为负数应取绝对值后相乘");
|
||
}
|
||
|
||
[TestMethod]
|
||
public void 期权_平仓比例为负数_取绝对值后相乘()
|
||
{
|
||
// 异常但容错:1,000,000 × -0.3 → Abs → 300,000
|
||
var result = TradeServiceBase.CalcOptionCloseNotional(1_000_000d, -0.3d);
|
||
Assert.AreEqual(300_000d, result, 0.01, "平仓比例为负数应取绝对值后相乘");
|
||
}
|
||
|
||
[TestMethod]
|
||
public void 期权_两者都为负数_取绝对值后相乘()
|
||
{
|
||
// -1,000,000 × -0.3 = 300,000(先乘后取 Abs,结果一致)
|
||
var result = TradeServiceBase.CalcOptionCloseNotional(-1_000_000d, -0.3d);
|
||
Assert.AreEqual(300_000d, result, 0.01, "两者都为负数应取绝对值后相乘");
|
||
}
|
||
}
|
||
}
|