diff --git a/UnitTestProject/Modules/SwapModule/GLMS20260105GoldenTest.cs b/UnitTestProject/Modules/SwapModule/GLMS20260105GoldenTest.cs new file mode 100644 index 00000000..b694f6fa --- /dev/null +++ b/UnitTestProject/Modules/SwapModule/GLMS20260105GoldenTest.cs @@ -0,0 +1,194 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using YLErp.DBModels; +using YLErp.DBModels.Enums; + +namespace YLErp.Modules.SwapModule +{ + /// + /// GLMS-20260105-0007 分红精度差异 - golden 录制/回放测试 + /// ============================================================================ + /// 用真实测试库数据录制 → golden JSON → 回放复现 0.36 差异 + /// + /// 问题:原有待实现分红-90400,互换支付-400后应为-90000,实际-89999.64 + /// 根因:UpdateEodPosition cs:1635 从头重算 PosiDividendSum, + /// 与 CopyEodPosition 逐天递增的舍入累积不一致 + /// ============================================================================ + [TestClass] + public class GLMS20260105GoldenTest + { + private const string TradeNumber = "GLMS-20260105-0007"; + private static readonly string GoldenDir = Path.Combine( + AppDomain.CurrentDomain.BaseDirectory, "Resources", "GoldenFiles", "GLMS20260105"); + + #region 录制:从真实库读取数据,序列化为 golden + + /// + /// 从真实测试库录制 GLMS-20260105-0007 的完整数据快照。 + /// 标 [Ignore],手动跑一次生成 golden JSON。 + /// + [TestMethod] + [Ignore] + [TestCategory("GoldenRecord")] + public void Record_FromRealDB() + { + YLContext db; + try { db = DbContextFactory.GetYLDbContext(); } + catch (Exception ex) { Assert.Inconclusive($"无法连接测试库:{ex.Message}"); return; } + + try + { + Directory.CreateDirectory(GoldenDir); + + var td = db.trade.FirstOrDefault(t => t.TradeNumber == TradeNumber); + Assert.IsNotNull(td, $"交易 {TradeNumber} 不存在"); + + var floatEods = db.eod_swap_position + .Where(x => x.SwapTradeId == td.id && x.PosiDirection > 0 && !x.Invalid) + .OrderBy(x => x.ValueDate).ToList(); + + var flows = db.swap_flow_event + .Where(x => x.SwapTradeId == td.id && x.DataState == (int)SwapFlowDateStateEnum.完成) + .OrderBy(x => x.EventDate).ThenBy(x => x.id).ToList(); + + var positions = db.swap_position + .Where(x => x.SwapTradeId == td.id && !x.Invalid).ToList(); + + var swapDate = flows.First(f => f.EventType == (int)SwapFlowEventTypeEnum.互换).EventDate; + var keyDates = new[] { swapDate.AddDays(-1), swapDate, swapDate.AddDays(1) }; + var keyFloatEods = floatEods.Where(x => keyDates.Contains(x.ValueDate)).ToList(); + + var golden = new JObject + { + ["TradeNumber"] = TradeNumber, + ["TradeId"] = td.id, + ["SwapDate"] = swapDate.ToString("yyyy-MM-dd"), + ["Description"] = "分红精度差异:互换前-90400,互换后应为-90000,实际-89999.64" + }; + + // 关键3天的浮动腿eod(含精确字段值) + var keyArray = new JArray(); + foreach (var e in keyFloatEods) + { + keyArray.Add(new JObject + { + ["ValueDate"] = e.ValueDate.ToString("yyyy-MM-dd"), + ["PositionId"] = e.PositionId, + ["PosiDividendSum"] = e.PosiDividendSum, + ["TdPosiDividend"] = e.TdPosiDividend, + ["TdCloseDividend"] = e.TdCloseDividend, + ["RealizedDividend"] = e.RealizedDividend, + ["PosiQuantity"] = e.PosiQuantity, + ["PosiMtmPnL"] = e.PosiMtmPnL + }); + } + golden["KeyFloatEodPositions"] = keyArray; + + // 完整浮动腿序列(用于分析精度累积过程) + var allArray = new JArray(); + foreach (var e in floatEods) + { + allArray.Add(new JObject + { + ["ValueDate"] = e.ValueDate.ToString("yyyy-MM-dd"), + ["PosiDividendSum"] = e.PosiDividendSum, + ["TdPosiDividend"] = e.TdPosiDividend, + ["RealizedDividend"] = e.RealizedDividend + }); + } + golden["AllFloatEodDividends"] = allArray; + + string json = JsonConvert.SerializeObject(golden, Formatting.Indented); + string path = Path.Combine(GoldenDir, $"golden_{TradeNumber}.json"); + File.WriteAllText(path, json); + + Console.WriteLine($"录制完成: {path}"); + Console.WriteLine($"\n关键数据:"); + foreach (var e in keyFloatEods) + { + Console.WriteLine($" {e.ValueDate:yyyy-MM-dd}: PosiDividendSum={e.PosiDividendSum}, TdPosiDividend={e.TdPosiDividend}, TdCloseDividend={e.TdCloseDividend}, RealizedDividend={e.RealizedDividend}"); + } + + var preSwap = keyFloatEods.FirstOrDefault(x => x.ValueDate == swapDate.AddDays(-1)); + var swapDay = keyFloatEods.FirstOrDefault(x => x.ValueDate == swapDate); + if (preSwap != null && swapDay != null) + { + decimal expected = preSwap.PosiDividendSum - swapDay.TdCloseDividend; + decimal actual = swapDay.PosiDividendSum; + Console.WriteLine($"\n精度分析:"); + Console.WriteLine($" 互换前 PosiDividendSum = {preSwap.PosiDividendSum}"); + Console.WriteLine($" 互换实现 TdCloseDividend = {swapDay.TdCloseDividend}"); + Console.WriteLine($" 期望 PosiDividendSum = {preSwap.PosiDividendSum} - ({swapDay.TdCloseDividend}) = {expected}"); + Console.WriteLine($" 实际 PosiDividendSum = {actual}"); + Console.WriteLine($" 差异 = {actual - expected}"); + } + } + finally { db?.Dispose(); } + } + + #endregion + + #region 回放:读 golden 验证精度差异 + + /// + /// 回放 golden,验证互换后 PosiDividendSum 的精度差异。 + /// 当前代码会失败(差异=0.36),这就是红灯。 + /// 修复后(cs:1635 改为递增模式)应通过。 + /// + [TestMethod] + public void Replay_VerifyPrecisionDiff() + { + string sourceDir = Path.Combine( + AppDomain.CurrentDomain.BaseDirectory, "Resources", "GoldenFiles", "GLMS20260105"); + if (!Directory.Exists(sourceDir)) + { + Assert.Inconclusive($"golden 目录不存在: {sourceDir}(请先跑 Record_FromRealDB)"); + return; + } + + var files = Directory.GetFiles(sourceDir, "*.json"); + Assert.IsTrue(files.Length > 0, "应至少有1个golden文件"); + + var json = File.ReadAllText(files[0]); + var golden = JObject.Parse(json); + + var keyEods = golden["KeyFloatEodPositions"] as JArray; + Assert.IsNotNull(keyEods, "KeyFloatEodPositions 缺失"); + Assert.IsTrue(keyEods.Count >= 2, "应至少有互换前+互换日两条eod"); + + var swapDateStr = golden["SwapDate"]?.Value(); + Console.WriteLine($"SwapDate = {swapDateStr}"); + + JObject preSwap = null, swapDay = null; + var swapDate = DateTime.Parse(swapDateStr); + foreach (var e in keyEods) + { + var dateStr = e["ValueDate"]?.Value(); + if (dateStr == swapDate.AddDays(-1).ToString("yyyy-MM-dd")) preSwap = e as JObject; + if (dateStr == swapDateStr) swapDay = e as JObject; + } + + Assert.IsNotNull(preSwap, "找不到互换前eod"); + Assert.IsNotNull(swapDay, "找不到互换日eod"); + + decimal preDividendSum = preSwap["PosiDividendSum"]!.Value(); + decimal swapCloseDividend = swapDay["TdCloseDividend"]!.Value(); + decimal actualDividendSum = swapDay["PosiDividendSum"]!.Value(); + decimal expectedDividendSum = preDividendSum - swapCloseDividend; + + Console.WriteLine($"互换前 PosiDividendSum = {preDividendSum}"); + Console.WriteLine($"互换实现 TdCloseDividend = {swapCloseDividend}"); + Console.WriteLine($"期望 PosiDividendSum = {preDividendSum} - ({swapCloseDividend}) = {expectedDividendSum}"); + Console.WriteLine($"实际 PosiDividendSum = {actualDividendSum}"); + Console.WriteLine($"差异 = {actualDividendSum - expectedDividendSum}"); + + // 核心断言:互换后 PosiDividendSum 应精确 = 前日 - 实现 + // 当前代码会失败(差异=0.36),这就是红灯 + Assert.AreEqual(expectedDividendSum, actualDividendSum, + $"互换后 PosiDividendSum 应精确={expectedDividendSum},实际={actualDividendSum}," + + $"差异={actualDividendSum - expectedDividendSum}(从头重算 vs 递增的舍入累积)"); + } + + #endregion + } +} diff --git a/UnitTestProject/Resources/GoldenFiles/GLMS20260105/golden_GLMS-20260105-0007.json b/UnitTestProject/Resources/GoldenFiles/GLMS20260105/golden_GLMS-20260105-0007.json new file mode 100644 index 00000000..cc5d8ee7 --- /dev/null +++ b/UnitTestProject/Resources/GoldenFiles/GLMS20260105/golden_GLMS-20260105-0007.json @@ -0,0 +1,394 @@ +{ + "TradeNumber": "GLMS-20260105-0007", + "TradeId": 1924, + "SwapDate": "2026-03-03", + "Description": "分红精度差异:互换前-90400,互换后应为-90000,实际-89999.64", + "KeyFloatEodPositions": [ + { + "ValueDate": "2026-03-02", + "PositionId": 35148, + "PosiDividendSum": -90400.000000, + "TdPosiDividend": -90400.000000, + "TdCloseDividend": 0.0, + "RealizedDividend": 0.0, + "PosiQuantity": 50000000.000000, + "PosiMtmPnL": -500000.000000 + }, + { + "ValueDate": "2026-03-03", + "PositionId": 35148, + "PosiDividendSum": -89999.640000, + "TdPosiDividend": 0.0, + "TdCloseDividend": -400.000000, + "RealizedDividend": -400.000000, + "PosiQuantity": 50000000.000000, + "PosiMtmPnL": -500000.000000 + }, + { + "ValueDate": "2026-03-04", + "PositionId": 35148, + "PosiDividendSum": -89999.640000, + "TdPosiDividend": 0.0, + "TdCloseDividend": 0.0, + "RealizedDividend": -400.000000, + "PosiQuantity": 50000000.000000, + "PosiMtmPnL": -500000.000000 + } + ], + "AllFloatEodDividends": [ + { + "ValueDate": "2026-01-05", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-06", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-07", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-08", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-09", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-10", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-11", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-12", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-13", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-14", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-15", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-16", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-17", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-18", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-19", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-20", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-21", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-22", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-23", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-24", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-25", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-26", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-27", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-28", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-29", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-30", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-01-31", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-01", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-02", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-03", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-04", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-05", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-06", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-07", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-08", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-09", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-10", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-11", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-12", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-13", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-14", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-15", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-16", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-17", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-18", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-19", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-20", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-21", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-22", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-23", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-24", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-25", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-26", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-27", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-02-28", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-03-01", + "PosiDividendSum": 0.0, + "TdPosiDividend": 0.0, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-03-02", + "PosiDividendSum": -90400.000000, + "TdPosiDividend": -90400.000000, + "RealizedDividend": 0.0 + }, + { + "ValueDate": "2026-03-03", + "PosiDividendSum": -89999.640000, + "TdPosiDividend": 0.0, + "RealizedDividend": -400.000000 + }, + { + "ValueDate": "2026-03-04", + "PosiDividendSum": -89999.640000, + "TdPosiDividend": 0.0, + "RealizedDividend": -400.000000 + } + ] +} \ No newline at end of file