From 07dd3e8eb52ba851f5b571b1a53f1b681481d0d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=94=A6=E9=BA=9F=20=E7=8E=8B?= Date: Fri, 28 Aug 2026 13:31:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=90=88=E7=BA=A6=E7=BB=B4?= =?UTF-8?q?=E5=BA=A6=E8=BF=BD=E4=BF=9D=E9=87=91=E9=A2=9D=E8=AE=A1=E7=AE=97?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SwapModule/SwapSpanBalanceCalcTest.cs | 22 +++++++------- .../BLL/EodSettlement/ClientBalanceUtility.cs | 6 ++-- .../RealTimeClientBanlanceService.cs | 7 +++-- .../SwapModule/Margin/SwapSpanBalanceCalc.cs | 12 +++++--- .../Margin/SwapSpanBalanceQueryService.cs | 29 +++++++++++++++++++ 5 files changed, 58 insertions(+), 18 deletions(-) diff --git a/UnitTestProject/Modules/SwapModule/SwapSpanBalanceCalcTest.cs b/UnitTestProject/Modules/SwapModule/SwapSpanBalanceCalcTest.cs index e1f487d9..0c0f2588 100644 --- a/UnitTestProject/Modules/SwapModule/SwapSpanBalanceCalcTest.cs +++ b/UnitTestProject/Modules/SwapModule/SwapSpanBalanceCalcTest.cs @@ -6,7 +6,8 @@ namespace YLErp.Modules.SwapModule /// R2 阶段三 §3.2 可用资金/追保/可取资金公式测试(定稿 2026-08-28 口径,实时/EOD/报告三处共用 SwapSpanBalanceCalc): /// 客户维度可用 = Max(现金结存 + 授信 − 已使用授信 + 初始保证金 − 维持保证金, 0); /// 合约维度可用 = Max(现金结存 + 授信 − 已使用授信, 0)(定稿删除"−交易维度追加保证金",避免与现金结存/已使用授信双重扣减); - /// 追保金额两维度均 Max(...,0) 截断、恒 ≥ 0(只追不退); + /// 追保金额两维度均 Max(...,0) 截断、恒 ≥ 0(只追不退);合约维度(2026-08-28 调整)= 当日追加保证金现金部分合计 + /// (不与现金结存轧差、闲置现金不冲抵、授信不追),客户维度 = Max((维持−初始) − (现金+授信−已使用), 0); /// 可取资金只算现金部分:合约维度 = Max(现金结存 + min(持仓盈亏,0), 0),客户维度 = Max(现金结存 + 初始 − 维持 + min(持仓盈亏,0), 0)。 /// 授信额度为 credit.Credit 合计(保存时已折算比例),现金结存=期末结存(阶段二起授信不进资金)。 /// @@ -99,22 +100,23 @@ namespace YLErp.Modules.SwapModule } /// - /// 合约维度追保金额(Max((现金+授信−已使用)×−1, 0)):账户透支为正=应补足,盈余截断为 0。 + /// 合约维度追保金额(2026-08-28 新口径):当日产生的追加保证金现金部分合计,不与现金结存轧差、 + /// 闲置现金不冲抵、授信部分不追;Max(...,0) 兜底截断(正常无负记录,追保回落不返还)。 /// [TestMethod] - public void SB_013_合约维度追保金额_透支为正() + public void SB_013_合约维度追保金额_当日现金追加全额() { - //现金−80 + 授信100 − 已使用30 = −10 → 追保 = 10(应补足) - Assert.AreEqual(10, SwapSpanBalanceCalc.CalcContractDimensionCallMargin( - cashBalance: -80, totalCredit: 100, usedCredit: 30), 1e-6); + //场景:入金60万、现金初保50万(闲置10万),当日需追加40万(现金)—— + //旧口径缺口法追 30万(闲置现金被冲抵);新口径追当日现金追加全额 40万 + Assert.AreEqual(400000, SwapSpanBalanceCalc.CalcContractDimensionCallMargin(400000), 1e-6); } [TestMethod] - public void SB_014_合约维度追保金额_盈余截断为0() + public void SB_014_合约维度追保金额_无当日追加为0() { - //现金50 + 授信100 − 已使用20 = 130 → 盈余,Max(...,0) 截断 → 追保 = 0 - Assert.AreEqual(0, SwapSpanBalanceCalc.CalcContractDimensionCallMargin( - cashBalance: 50, totalCredit: 100, usedCredit: 20), 1e-6); + //当日追加全走授信(无现金流水)/当日无追保 → 0;负值兜底截断为 0 + Assert.AreEqual(0, SwapSpanBalanceCalc.CalcContractDimensionCallMargin(0), 1e-6); + Assert.AreEqual(0, SwapSpanBalanceCalc.CalcContractDimensionCallMargin(-100), 1e-6); } /// diff --git a/YLErpDAL/BLL/EodSettlement/ClientBalanceUtility.cs b/YLErpDAL/BLL/EodSettlement/ClientBalanceUtility.cs index 38c28444..51dfe898 100644 --- a/YLErpDAL/BLL/EodSettlement/ClientBalanceUtility.cs +++ b/YLErpDAL/BLL/EodSettlement/ClientBalanceUtility.cs @@ -305,6 +305,8 @@ namespace YLErp.BLL.EodSettlement var usedCreditDic = ClientCreditInoutService.GetUsedCreditByClients(clientIdS, db); var swapInitMarginDic = SwapSpanBalanceQueryService.GetSwapInitMarginByClients(clientIdS, lastDate, db); var swapAdditionalDic = SwapSpanBalanceQueryService.GetTradeAdditionalMarginByClients(clientIdS, lastDate, db); + //合约维度追保金额新口径(2026-08-28):当日追加保证金现金部分合计 + var todayCashAddDic = SwapSpanBalanceQueryService.GetTodayCashAdditionalMarginByClients(clientIdS, lastDate, db); //原始授信额度(展示用):与 EOD 写入 clientbalancedaily.Credit 同批过滤条件(EodClientBalanceCalc :68), //取 Σ(OriginalCredit ?? Credit) 不经比例折算;TotalCredit 仍为折算后值供公式使用 var originalCreditDic = db.credit.AsNoTracking() @@ -406,7 +408,7 @@ namespace YLErp.BLL.EodSettlement } // 是否追保/追保金额(按维度分流,定稿 2026-08-28 起均 Max(...,0) 截断、恒 ≥ 0,只追不退): // 客户维度(==1)= Max((维持−初始) − (现金+授信−已使用), 0); - // 合约维度(==0)= Max(−(现金+授信−已使用), 0)(需求原文公式,盈余截断为 0); + // 合约维度(==0)= 当日追加保证金现金部分合计(2026-08-28 新口径:不与现金结存轧差、闲置现金不冲抵、授信部分不追); // 未配置(NULL 存量)维持旧口径:盯市低于维持时 = 初始保证金金额−盯市金额,否则 0 if (client?.MarginWatchRule == 1) { @@ -417,7 +419,7 @@ namespace YLErp.BLL.EodSettlement else if (client?.MarginWatchRule == 0) { balance.MarginByPayableMarginTotal = SwapSpanBalanceCalc.CalcContractDimensionCallMargin( - balance.AmountFund, balance.TotalCredit, balance.UsedCredit); + todayCashAddDic.TryGetValue(data.ClientId, out var todayCashAdd) ? todayCashAdd : 0); balance.NeedAddMargin = balance.MarginByPayableMarginTotal > 0; } else diff --git a/YLErpDAL/BLL/EodSettlement/RealTimeClientBanlanceService.cs b/YLErpDAL/BLL/EodSettlement/RealTimeClientBanlanceService.cs index 8a5fa7d1..1c392885 100644 --- a/YLErpDAL/BLL/EodSettlement/RealTimeClientBanlanceService.cs +++ b/YLErpDAL/BLL/EodSettlement/RealTimeClientBanlanceService.cs @@ -243,6 +243,8 @@ namespace YLErp.BLL.Eod var usedCreditDic = Modules.SwapModule.ClientCreditInoutService.GetUsedCreditByClients(clientIdList, DbContext); var swapInitMarginDic = SwapSpanBalanceQueryService.GetSwapInitMarginByClients(clientIdList, calcDate.Value, DbContext); var swapAdditionalDic = SwapSpanBalanceQueryService.GetTradeAdditionalMarginByClients(clientIdList, calcDate.Value, DbContext); + //合约维度追保金额新口径(2026-08-28):当日追加保证金现金部分合计(日间 EOD 未跑时无当日记录 → 0,与通知书口径一致) + var todayCashAddDic = SwapSpanBalanceQueryService.GetTodayCashAdditionalMarginByClients(clientIdList, calcDate.Value, DbContext); foreach (var item in _clientBalanceDic.Values) { @@ -305,7 +307,8 @@ namespace YLErp.BLL.Eod } // 是否追保/追保金额(按维度分流,与 ClientBalanceUtility 报告口径一致,定稿 2026-08-28 起均 Max(...,0) 截断): // 客户维度(==1)= Max((维持−初始) − (现金+授信−已使用), 0); - // 合约维度(==0)= Max(−(现金+授信−已使用), 0),盈余截断为 0;NULL 存量维持旧口径 + // 合约维度(==0)= 当日追加保证金现金部分合计(2026-08-28 新口径:不与现金结存轧差、闲置现金不冲抵、授信部分不追); + // NULL 存量维持旧口径 if (ruleClient?.MarginWatchRule == 1) { item.MarginByPayableMarginTotal = SwapSpanBalanceCalc.CalcClientDimensionCallMargin( @@ -315,7 +318,7 @@ namespace YLErp.BLL.Eod else if (ruleClient?.MarginWatchRule == 0) { item.MarginByPayableMarginTotal = SwapSpanBalanceCalc.CalcContractDimensionCallMargin( - item.AmountFund, item.TotalCredit, item.UsedCredit); + todayCashAddDic.TryGetValue(clientId, out var todayCashAdd) ? todayCashAdd : 0); item.NeedAddMargin = item.MarginByPayableMarginTotal > 0; } else diff --git a/YLErpDAL/Modules/SwapModule/Margin/SwapSpanBalanceCalc.cs b/YLErpDAL/Modules/SwapModule/Margin/SwapSpanBalanceCalc.cs index 2374f39a..c8b6246f 100644 --- a/YLErpDAL/Modules/SwapModule/Margin/SwapSpanBalanceCalc.cs +++ b/YLErpDAL/Modules/SwapModule/Margin/SwapSpanBalanceCalc.cs @@ -46,12 +46,16 @@ namespace YLErp.Modules.SwapModule.Margin } /// - /// 追保金额(合约维度,MarginWatchRule=0):Max(−(现金结存 + 授信额度 − 已使用授信), 0)。 - /// 即需求原文公式 Max((现金结存+授信额度−已使用授信)×−1, 0):账户透支(现金+授信不足)为正=应补足,盈余截断为 0(不展示负数)。 + /// 追保金额(合约维度,MarginWatchRule=0,2026-08-28 新口径): + /// = 当日产生的追加保证金资金记录中"资金来源=现金"的金额合计(入参即该值,见 + /// SwapSpanBalanceQueryService.GetTodayCashAdditionalMarginByClients),Max(...,0) 兜底截断。 + /// 语义:当日该笔合约产生的现金追保额(不与现金结存轧差、闲置现金不冲抵;授信部分不追); + /// 恒 ≥ 0(只追不退,退还走平仓/出金)。通知书按当日现金追保全额展示, + /// 累计未补足欠款体现在账户余额(现金结存)中,不并入本字段。 /// - public static double CalcContractDimensionCallMargin(double cashBalance, double totalCredit, double usedCredit) + public static double CalcContractDimensionCallMargin(double todayCashAdditionalMargin) { - return Math.Max(Math.Round(-(cashBalance + totalCredit - usedCredit), 2, MidpointRounding.AwayFromZero), 0); + return Math.Max(Math.Round(todayCashAdditionalMargin, 2, MidpointRounding.AwayFromZero), 0); } /// diff --git a/YLErpDAL/Modules/SwapModule/Margin/SwapSpanBalanceQueryService.cs b/YLErpDAL/Modules/SwapModule/Margin/SwapSpanBalanceQueryService.cs index 607c5068..091c6d94 100644 --- a/YLErpDAL/Modules/SwapModule/Margin/SwapSpanBalanceQueryService.cs +++ b/YLErpDAL/Modules/SwapModule/Margin/SwapSpanBalanceQueryService.cs @@ -154,5 +154,34 @@ namespace YLErp.Modules.SwapModule.Margin return result; } + + /// + /// 合约维度追保金额输入(2026-08-28 新口径):当日产生的追加保证金资金记录中"资金来源=现金"的金额合计(按客户)。 + /// 仅统计 Action=系统操作_追加保证金 且 HappenDate=当日 的现金流水(−Σ(Money),收取为正)—— + /// 授信部分只写 client_credit_inout 不产生资金记录(授信不追、现金才追); + /// 腿化改造后现金流水逐日增量(每日新腿一条、HappenDate=结算日,同日重跑先清后建,幂等); + /// 该 Action 仅 EOD 追保会写(手工链路写应付预付金,语义天然隔离);追保回落不返还(无负记录),结果天然 ≥ 0。 + /// 注意:EOD 未跑(日间实时)时当日无记录,结果为 0——与通知书口径一致。 + /// + public static Dictionary GetTodayCashAdditionalMarginByClients(List clientIds, DateTime valueDate, YLContext db) + { + if (clientIds == null || clientIds.Count == 0) + { + return new Dictionary(); + } + + var dayStart = valueDate.Date; + var dayEnd = dayStart.AddDays(1); + return db.ClientCashInCashOut.AsNoTracking() + .Where(x => clientIds.Contains(x.ClientId ?? 0) + && x.Action == ClientCashInCashOut.系统操作_追加保证金 + && x.HappenDate >= dayStart && x.HappenDate < dayEnd + && x.ValidState != ConsGlobal.InValid + && (x.State == ClientCashInCashOut.已确认 || x.State == ClientCashInCashOut.已结算) + && x.Money != null) + .GroupBy(x => x.ClientId ?? 0) + .Select(g => new { ClientId = g.Key, Sum = -g.Sum(x => x.Money ?? 0d) }) + .ToDictionary(x => x.ClientId, x => x.Sum); + } } }