From 877cf9dc4fc8603aefeb4f435fe879be974dca55 Mon Sep 17 00:00:00 2001 From: tengyufan <1532636164@qq.com> Date: Thu, 27 Aug 2026 13:16:01 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=85=BC=E5=AE=B9TRS=E6=97=A5=E7=BB=88?= =?UTF-8?q?=E6=8E=A8=E9=80=81=E5=8E=86=E5=8F=B2=E5=88=A9=E6=81=AF=E8=85=BF?= =?UTF-8?q?=E7=B1=BB=E5=88=AB=E4=B8=BA=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TrsContractKafkaPushServiceTest.cs | 82 +++++++++++++++++++ .../EodModule/TrsContractKafkaPushService.cs | 25 ++++-- 2 files changed, 99 insertions(+), 8 deletions(-) diff --git a/UnitTestProject/Modules/EodModule/TrsContractKafkaPushServiceTest.cs b/UnitTestProject/Modules/EodModule/TrsContractKafkaPushServiceTest.cs index 88ffb133..5c31e45a 100644 --- a/UnitTestProject/Modules/EodModule/TrsContractKafkaPushServiceTest.cs +++ b/UnitTestProject/Modules/EodModule/TrsContractKafkaPushServiceTest.cs @@ -142,6 +142,88 @@ namespace YLErp.Modules.EodModuleTests Assert.AreEqual(0m, item.InitMarginLoss); } + [TestMethod] + public void BuildContract_历史类别为空_仍推送全部TRS合约() + { + var valueDate = new DateTime(2026, 2, 10); + var eodSwap = new eod_swap { ValueDate = valueDate, SwapTradeId = 1645, SwapTradeNo = "ZSZQ-IS-202602090001" }; + var positions = new List + { + new() { SwapTradeId = 1645, PositionId = 33306, UnderlyingCode = "220208.IB", PositionType = 1 }, + new() { SwapTradeId = 1645, PositionId = 33307, InterestMode = (int)InterestModeEnum.标的期初全价, InterestRateDefault = 0.001m, InterestDirection = 1 } + }; + var swapPositions = new Dictionary + { + [33306] = new() { id = 33306, category_tag = null }, + [33307] = new() { id = 33307, category_tag = null } + }; + + var item = TrsContractKafkaPushService.BuildContract( + eodSwap, + new Dictionary { [1645] = new() { id = 1645, UnderlyingCode = "220208.IB" } }, + positions, + swapPositions); + + Assert.AreEqual(0m, item.FixedRate); + Assert.AreEqual(1, item.InterestDirection); + Assert.AreEqual(1, item.FloatingDirection); + } + + [TestMethod] + public void BuildContract_增强收益腿不参与固定利率取值() + { + var eodSwap = new eod_swap { ValueDate = new DateTime(2026, 8, 24), SwapTradeId = 7 }; + var positions = new List + { + new() { SwapTradeId = 7, PositionId = 101, UnderlyingCode = "600000.SH", PositionType = 2 }, + new() { SwapTradeId = 7, PositionId = 102, InterestMode = (int)InterestModeEnum.固定值, InterestRateDefault = 0.0123m, InterestDirection = 1 }, + new() { SwapTradeId = 7, PositionId = 103, InterestMode = (int)InterestModeEnum.固定值, InterestRateDefault = 0.0999m, InterestDirection = 2 } + }; + var swapPositions = new Dictionary + { + [101] = new() { id = 101, category_tag = null }, + [102] = new() { id = 102, category_tag = "互换利率" }, + [103] = new() { id = 103, category_tag = "增强收益" } + }; + + var item = TrsContractKafkaPushService.BuildContract( + eodSwap, + new Dictionary { [7] = new() { id = 7, UnderlyingCode = "600000.SH" } }, + positions, + swapPositions); + + Assert.AreEqual(0.0123m, item.FixedRate); + Assert.AreEqual(1, item.InterestDirection); + Assert.AreEqual(2, item.FloatingDirection); + } + + [TestMethod] + public void BuildContract_多条互换利率腿_取第一条() + { + var eodSwap = new eod_swap { ValueDate = new DateTime(2026, 8, 24), SwapTradeId = 7 }; + var positions = new List + { + new() { SwapTradeId = 7, PositionId = 101, UnderlyingCode = "600000.SH", PositionType = 1 }, + new() { SwapTradeId = 7, PositionId = 102, InterestMode = (int)InterestModeEnum.固定值, InterestRateDefault = 0.0123m, InterestDirection = 1 }, + new() { SwapTradeId = 7, PositionId = 103, InterestMode = (int)InterestModeEnum.固定值, InterestRateDefault = 0.0456m, InterestDirection = 2 } + }; + var swapPositions = new Dictionary + { + [101] = new() { id = 101, category_tag = null }, + [102] = new() { id = 102, category_tag = "互换利率" }, + [103] = new() { id = 103, category_tag = "互换利率" } + }; + + var item = TrsContractKafkaPushService.BuildContract( + eodSwap, + new Dictionary { [7] = new() { id = 7, UnderlyingCode = "600000.SH" } }, + positions, + swapPositions); + + Assert.AreEqual(0.0123m, item.FixedRate); + Assert.AreEqual(1, item.InterestDirection); + } + private static TestableTrsContractKafkaPushService CreateService(RecordingKafkaProducer producer, DateTime valueDate) { return new TestableTrsContractKafkaPushService( diff --git a/YLErpDAL/Modules/EodModule/TrsContractKafkaPushService.cs b/YLErpDAL/Modules/EodModule/TrsContractKafkaPushService.cs index 0efc85e1..60f59edf 100644 --- a/YLErpDAL/Modules/EodModule/TrsContractKafkaPushService.cs +++ b/YLErpDAL/Modules/EodModule/TrsContractKafkaPushService.cs @@ -140,7 +140,7 @@ namespace YLErp.Modules.EodModule .ToList(); var positionIds = eodPositions.Select(x => x.PositionId).Distinct().ToList(); var swapPositions = _dbContext.swap_position - .Where(x => positionIds.Contains(x.id) && !x.Invalid && x.category_tag == InterestCategory) + .Where(x => positionIds.Contains(x.id) && !x.Invalid) .AsNoTracking() .ToDictionary(x => x.id); @@ -167,18 +167,27 @@ namespace YLErp.Modules.EodModule } var positions = eodPositions.Where(x => x.SwapTradeId == eodSwap.SwapTradeId).ToList(); - var floating = positions.Where(x => !string.IsNullOrWhiteSpace(x.UnderlyingCode) && swapPositions.ContainsKey(x.PositionId)).ToList(); - var interest = positions.Where(x => string.IsNullOrWhiteSpace(x.UnderlyingCode) + var floating = positions.Where(x => !string.IsNullOrWhiteSpace(x.UnderlyingCode)).ToList(); + var interestCandidates = positions.Where(x => string.IsNullOrWhiteSpace(x.UnderlyingCode) && ConsTrade.InterestModels.Contains(x.InterestMode) && swapPositions.TryGetValue(x.PositionId, out var swapPosition) - && swapPosition.category_tag == InterestCategory).ToList(); + && (swapPosition.category_tag == InterestCategory || string.IsNullOrWhiteSpace(swapPosition.category_tag))) + .ToList(); - if (floating.Count != 1 || interest.Count != 1) + // 互换利率腿优先;同类别多腿按当前查询顺序取第一条。历史类别为空时保留利息方向, + // 但 fixedRate 按约定置 0,避免把未标注类别的历史值当作已确认利率。 + var interest = interestCandidates.FirstOrDefault(x => + swapPositions.TryGetValue(x.PositionId, out var swapPosition) + && swapPosition.category_tag == InterestCategory); + var isUncategorizedInterest = interest == null && interestCandidates.Count > 0; + interest ??= interestCandidates.FirstOrDefault(); + + if (floating.Count != 1 || interest == null) { - throw new InvalidOperationException($"TRS legs invalid, swapTradeId:{eodSwap.SwapTradeId}, floating:{floating.Count}, interest:{interest.Count}"); + throw new InvalidOperationException($"TRS legs invalid, swapTradeId:{eodSwap.SwapTradeId}, floating:{floating.Count}, interest:{(interest == null ? 0 : 1)}"); } - var interestLeg = interest[0]; + var interestLeg = interest; var floatingLeg = floating[0]; return new TrsContractSnapshotItem { @@ -193,7 +202,7 @@ namespace YLErp.Modules.EodModule Dv01 = eodSwap.dv01 ?? 0, StartDate = trade.StartDate?.ToString(DateFormat), MaturityDate = trade.ExerciseDate?.ToString(DateFormat), - FixedRate = interestLeg.InterestRateDefault, + FixedRate = isUncategorizedInterest ? 0 : interestLeg.InterestRateDefault, InterestDirection = interestLeg.InterestDirection, FloatingDirection = floatingLeg.PositionType, InitMarginGain = eodSwap.InitMarginGain,