using Newtonsoft.Json; using Newtonsoft.Json.Linq; using YLErp.DBModels; using YLErp.DBModels.Enums; namespace YLErp.Modules.SwapModule { #region Golden 数据模型 /// /// Golden 文件的通用数据模型。 /// 每个场景序列化为一个 JSON 文件,包含:输入数据 + 期望输出。 /// /// JSON 结构: /// { /// "Scenario": "互换结清后待实现归零", /// "Description": "攒10天后互换,验证InterestIncomeSum≈当天新计", /// "Input": { /// "Trade": { ... }, /// "Positions": [ ... ], /// "PreEodPositions": [ ... ], /// "FlowEvents": [ ... ] /// }, /// "Expected": { /// "EodPositions": [ /// { "PositionId": 1001, "InterestIncomeSum": 0.0274, "TdCloseInterest": 2.74, ... } /// ] /// } /// } /// public class GoldenScenarioModel { /// 场景名称 public string Scenario { get; set; } /// 场景描述 public string Description { get; set; } /// 输入数据 public GoldenInput Input { get; set; } /// 期望输出(精确到小数点后N位的字段值) public GoldenExpected Expected { get; set; } /// 数据来源:synthetic(合成) / recorded(真实库录制) public string Source { get; set; } = "synthetic"; /// 录制时间(如果是 recorded) public DateTime? RecordedAt { get; set; } } public class GoldenInput { public JObject Trade { get; set; } public JArray Positions { get; set; } public JArray PreEodPositions { get; set; } public JArray FlowEvents { get; set; } // 可选的配置参数 public decimal? PosiLongNotional { get; set; } public decimal? PosiShortNotional { get; set; } public decimal? CloseNational { get; set; } public decimal? GrossPrice { get; set; } public decimal? OrginPv { get; set; } public DateTime? SettleDate { get; set; } } public class GoldenExpected { /// 期望生成的 eod 持仓数量 public int? PositionCount { get; set; } /// 期望的 eod 持仓精确字段(每个 PositionId 一条) public JArray EodPositions { get; set; } } #endregion #region Golden 回放辅助 /// /// Golden 回放的通用辅助方法。 /// 提供精确字段对比(容许指定位数的误差)。 /// public static class GoldenAssert { /// 默认精度容差(小数点后9-2=7位) public static decimal DefaultTolerance => 1m / (decimal)Math.Pow(10, ConsGlobal.PriceRound - 2); /// 对比 decimal 字段,容许指定位数误差 public static void AssertField(decimal? expected, decimal actual, string fieldName, long positionId, decimal? tolerance = null) { if (expected == null) return; // golden 里没存这个字段就跳过 var tol = tolerance ?? DefaultTolerance; Assert.IsTrue(Math.Abs(expected.Value - actual) <= tol, $"PositionId={positionId} {fieldName} 不匹配: expected={expected.Value}, actual={actual}, diff={expected.Value - actual}"); } /// 对比 int 字段 public static void AssertField(int? expected, int actual, string fieldName, long positionId) { if (expected == null) return; Assert.AreEqual(expected.Value, actual, $"PositionId={positionId} {fieldName} 不匹配: expected={expected.Value}, actual={actual}"); } /// 对比 long 字段 public static void AssertField(long? expected, long actual, string fieldName, long positionId) { if (expected == null) return; Assert.AreEqual(expected.Value, actual, $"PositionId={positionId} {fieldName} 不匹配: expected={expected.Value}, actual={actual}"); } /// /// 对比一个 eod_swap_position 的所有 golden 字段。 /// golden JSON 里只存了需要验证的字段,未存的跳过。 /// public static void AssertEodPosition(JObject expected, eod_swap_position actual) { var positionId = expected["PositionId"]?.Value() ?? actual.PositionId; AssertField(expected["InterestIncomeSum"]?.Value(), actual.InterestIncomeSum, "InterestIncomeSum", positionId); AssertField(expected["InterestProfitSum"]?.Value(), actual.InterestProfitSum, "InterestProfitSum", positionId); AssertField(expected["TdInterestIncome"]?.Value(), actual.TdInterestIncome, "TdInterestIncome", positionId); AssertField(expected["TdCloseInterest"]?.Value(), actual.TdCloseInterest, "TdCloseInterest", positionId); AssertField(expected["TdInterestPrincipal"]?.Value(), actual.TdInterestPrincipal, "TdInterestPrincipal", positionId); AssertField(expected["RealizedInterest"]?.Value(), actual.RealizedInterest, "RealizedInterest", positionId); AssertField(expected["RealizedInterestFee"]?.Value(), actual.RealizedInterestFee, "RealizedInterestFee", positionId); AssertField(expected["RealizedPnl"]?.Value(), actual.RealizedPnl, "RealizedPnl", positionId); AssertField(expected["SwapPositionValue"]?.Value(), actual.SwapPositionValue, "SwapPositionValue", positionId); AssertField(expected["InterestFeeSum"]?.Value(), actual.InterestFeeSum, "InterestFeeSum", positionId); AssertField(expected["TdInterestFee"]?.Value(), actual.TdInterestFee, "TdInterestFee", positionId); AssertField(expected["TdCloseInterestFee"]?.Value(), actual.TdCloseInterestFee, "TdCloseInterestFee", positionId); } /// /// 序列化一个 eod_swap_position 到 JObject(用于生成 golden 文件)。 /// 只存关键字段,避免 JSON 过大。 /// public static JObject EodPositionToJson(eod_swap_position eod) { return new JObject { ["PositionId"] = eod.PositionId, ["InterestIncomeSum"] = eod.InterestIncomeSum, ["InterestProfitSum"] = eod.InterestProfitSum, ["TdInterestIncome"] = eod.TdInterestIncome, ["TdCloseInterest"] = eod.TdCloseInterest, ["TdInterestPrincipal"] = eod.TdInterestPrincipal, ["RealizedInterest"] = eod.RealizedInterest, ["RealizedPnl"] = eod.RealizedPnl, ["SwapPositionValue"] = eod.SwapPositionValue, ["InterestFeeSum"] = eod.InterestFeeSum }; } } #endregion }