From 8d4650d806d32c24bf2e9d338f0988d0c24edeac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E5=86=B0=E5=86=B0?= <437394478@qq.com> Date: Fri, 28 Aug 2026 11:13:38 +0800 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E6=A0=87=E7=9A=84=E6=95=B0=E6=8D=AE=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- YLErpDAL/Modules/UnderlyingModule/FundManagerLookupService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/YLErpDAL/Modules/UnderlyingModule/FundManagerLookupService.cs b/YLErpDAL/Modules/UnderlyingModule/FundManagerLookupService.cs index 6f1d0235..0b07fba8 100644 --- a/YLErpDAL/Modules/UnderlyingModule/FundManagerLookupService.cs +++ b/YLErpDAL/Modules/UnderlyingModule/FundManagerLookupService.cs @@ -34,8 +34,8 @@ namespace YLErp.Modules.UnderlyingModule SELECT ia.investadvisorcode AS InvestAdvisorCode, ia.investadvisorname AS InvestAdvisorName -FROM glms_bigdata.mf_fundarchives AS fa -INNER JOIN glms_bigdata.mf_investadvisoroutline AS ia +FROM bigdata.mf_fundarchives AS fa +INNER JOIN bigdata.mf_investadvisoroutline AS ia ON CONVERT(fa.investadvisorcode USING utf8mb4) COLLATE utf8mb4_unicode_ci = CONVERT(ia.investadvisorcode USING utf8mb4) COLLATE utf8mb4_unicode_ci WHERE CONVERT(fa.secucode USING utf8mb4) COLLATE utf8mb4_unicode_ci = From e52f5bfeb56f2406fa68105dc64b214399aa4c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E5=86=B0=E5=86=B0?= <437394478@qq.com> Date: Fri, 28 Aug 2026 11:28:41 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=E7=BB=93=E7=AE=97=E5=8D=95-?= =?UTF-8?q?=E6=9C=9F=E9=97=B4=E5=88=86=E7=BA=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TradeSettleBillGenerator.cs | 6 ------ .../SwapSettlementBillRowBuilder.cs | 17 +++++++++-------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeSettleBillGenerator.cs b/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeSettleBillGenerator.cs index 1521fbb4..1109b485 100644 --- a/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeSettleBillGenerator.cs +++ b/Plugins/YLErp.Plugins.GuoLian/DocumentGenerator/TradeSettleBillGenerator.cs @@ -74,11 +74,6 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator var underlying = Context.GetTradeUnderlying(flowEventGroup.UnderlyingCode); var closeNotionalValue = unwindData?.CloseNotionalValue ?? flowEventGroup.Quantity * flowEventGroup.ContractSize * posi.PosiGrossPrice; - var settlementDate = flowEventGroup.UnwindDate - ?? throw new ServiceException($"平仓事件{flowEventGroup.id}缺少结算日"); - var currentDayFloatingDividend = Context.GetEodPositions(tradeId, settlementDate) - .FirstOrDefault(x => x.PositionId == flowEventGroup.PositionId) - ?.TdPosiDividend ?? 0m; // 行构造器统一处理客户视角、结算公式、品种差异和模板展示精度。 var row = SwapSettlementBillRowBuilder.Build(new SwapSettlementBillRowInput @@ -91,7 +86,6 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator Positions = positions, UnderlyingInstrumentType = underlying?.UnderlyingInstrumentType, CloseNotionalValue = closeNotionalValue, - CurrentDayFloatingDividend = currentDayFloatingDividend, // 与提前终止详情页保持同一来源:读取 swap_flow_event 中的平仓浮动腿记录。 ExitYtm = flowEventGroup.ExitYtm, IncludePeriodPaymentInNetting = (tradeExtend?.ExtendObj?.DividendPayDate ?? 1) == 0 diff --git a/YLErpDAL/Modules/TradeModule/DocGenerateModule/SwapSettlementBillRowBuilder.cs b/YLErpDAL/Modules/TradeModule/DocGenerateModule/SwapSettlementBillRowBuilder.cs index d6063274..70e81e8a 100644 --- a/YLErpDAL/Modules/TradeModule/DocGenerateModule/SwapSettlementBillRowBuilder.cs +++ b/YLErpDAL/Modules/TradeModule/DocGenerateModule/SwapSettlementBillRowBuilder.cs @@ -9,7 +9,7 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule; /// /// 构造普通收益互换结算单单行数据所需的业务输入。 -/// 数据来自平仓事件、关联交易及结算日日终持仓。 +/// 数据来自平仓事件及其关联交易。 /// public sealed class SwapSettlementBillRowInput { @@ -40,9 +40,6 @@ public sealed class SwapSettlementBillRowInput /// 平仓事件浮动腿记录的期末结算收益率(展示态数值)。 public decimal? ExitYtm { get; set; } - /// 结算日日终持仓中的当日浮动端分红,保留原始收付方向。 - public decimal CurrentDayFloatingDividend { get; set; } - /// 期间付息或分红是否计入本次净额结算。 public bool IncludePeriodPaymentInNetting { get; set; } } @@ -89,6 +86,7 @@ public static class SwapSettlementBillRowBuilder var floatingAmount = -input.CloseFlow.MarkClosePnl; var fee = -(input.CloseFlow.TradingFee + input.CloseFlow.TradingFeePending); var marginInterest = -marginEvents.Sum(x => x.InterestClosePnL); + var periodAmount = -input.CloseFlow.DividendPending; var initialMargin = SumMargin(effectiveMargins, InterestModeEnum.初始预付金); var additionalMarginPositions = positions @@ -106,6 +104,9 @@ public static class SwapSettlementBillRowBuilder var isCashBond = ConsGlobal.InstrumentType.IsBond(input.UnderlyingInstrumentType); + var isEtf = ConsGlobal.InstrumentType.Fund.Equals( + input.UnderlyingInstrumentType, + StringComparison.OrdinalIgnoreCase); return new ExcelReportModel @@ -121,11 +122,11 @@ public static class SwapSettlementBillRowBuilder PosiNotionalValue = input.CloseNotionalValue.ToString("0.00"), Quantity = input.CloseFlow.Quantity.ToString("0.00"), DividendIn = isCashBond - ? input.CurrentDayFloatingDividend.ToString("0.00") + ? periodAmount.ToString("0.00") + : string.Empty, + PeriodDividend = isEtf + ? periodAmount.ToString("0.00") : string.Empty, - PeriodDividend = isCashBond - ? string.Empty - : input.CurrentDayFloatingDividend.ToString("0.00"), PosiNetPrice = ((settlementPosition?.PosiGrossPrice ?? 0m) * 100m).ToString("0.00000000"), InitYtm = isCashBond && input.Trade.InitYtm.HasValue ? input.Trade.InitYtm.Value.ToString("0.####%") From 03629a6dcd1bb4d546584adbedb8df2517ce32f6 Mon Sep 17 00:00:00 2001 From: hjhan Date: Fri, 28 Aug 2026 12:08:00 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=E4=BF=9D=E8=AF=81=E9=87=91?= =?UTF-8?q?=E9=93=BE=E8=B7=AF=E8=A1=A5=E5=85=A8=E6=8E=92=E6=9F=A5=E6=97=A5?= =?UTF-8?q?=E5=BF=97=EF=BC=88Info=E4=B8=BA=E4=B8=BB=EF=BC=8C=E4=BB=85?= =?UTF-8?q?=E8=BF=BD=E4=BF=9D=E9=A1=B6=E5=B1=82=E5=BC=82=E5=B8=B8=E7=94=A8?= =?UTF-8?q?Error=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EOD追保生成主流程:入口汇总/六处跳过原因/决策中间量(维持·已缴·目标·已补足·增量)/建腿落库/幂等清理/收尾汇总,顶层异常Error留栈 - 预付金模板取数:三级层级命中来源(交易绑定/客户默认/全局默认)+明细行命中(品种·期限档·x/y),null返回各留一句 - 规则15引擎:成功路径补收盘价→维持保证金,与既有无价格告警闭环 - EOD/实时客户资金:每客户一行维度分流结果(MarginWatchRule·可用资金·追保金额·可取资金及全部输入) - 预付金缺口查询:非规则15剔除数与按客户追加合计;预付金簿记:授信/现金拆分结果 --- .../BLL/EodSettlement/ClientBalanceUtility.cs | 3 ++ .../RealTimeClientBanlanceService.cs | 3 ++ .../MarginCalculationBase.cs | 7 ++- .../MarginTemplateV2RateHelper.cs | 27 +++++++++-- .../Margin/SwapAdditionalMarginService.cs | 47 ++++++++++++++++++- .../Margin/SwapSpanBalanceQueryService.cs | 10 ++++ .../Modules/SwapModule/SwapFundTagService.cs | 4 ++ 7 files changed, 95 insertions(+), 6 deletions(-) diff --git a/YLErpDAL/BLL/EodSettlement/ClientBalanceUtility.cs b/YLErpDAL/BLL/EodSettlement/ClientBalanceUtility.cs index c5de8504..38c28444 100644 --- a/YLErpDAL/BLL/EodSettlement/ClientBalanceUtility.cs +++ b/YLErpDAL/BLL/EodSettlement/ClientBalanceUtility.cs @@ -21,6 +21,8 @@ namespace YLErp.BLL.EodSettlement /// public class ClientBalanceUtility { + private static readonly IYcLogger logger = LogFactory.GetLogger("客户资金结算"); + /// /// 获取startDate至endDate 所有客户结算信息统计 /// @@ -441,6 +443,7 @@ namespace YLErp.BLL.EodSettlement { balance.DesirableFund = Math.Max(balance.MarginBalance + Math.Min(balance.RoundedPositionPnl, 0), 0); } + logger.Info($"客户资金结算:客户{client?.id}({client?.Name}){balance.ValueDate:yyyy-MM-dd} MarginWatchRule={client?.MarginWatchRule?.ToString() ?? "NULL"} 可用资金={balance.AvailableAmount:0.00} 追保金额={balance.MarginByPayableMarginTotal:0.00} 是否追保={balance.NeedAddMargin} 可取资金={balance.DesirableFund:0.00}(期末结存{balance.AmountFund:0.00} 授信{balance.TotalCredit:0.00} 已用授信{balance.UsedCredit:0.00} 互换初始{balance.SwapInitMargin:0.00} 维持{-balance.MySideMargin:0.00} 追加合计{balance.SwapAdditionalMarginTotal:0.00})"); } } diff --git a/YLErpDAL/BLL/EodSettlement/RealTimeClientBanlanceService.cs b/YLErpDAL/BLL/EodSettlement/RealTimeClientBanlanceService.cs index 563dd71f..8a5fa7d1 100644 --- a/YLErpDAL/BLL/EodSettlement/RealTimeClientBanlanceService.cs +++ b/YLErpDAL/BLL/EodSettlement/RealTimeClientBanlanceService.cs @@ -30,6 +30,8 @@ namespace YLErp.BLL.Eod /// public class RealTimeClientBanlanceService : YLBaseService { + private static readonly IYcLogger logger = LogFactory.GetLogger("实时客户资金"); + readonly valuedate _systemDate; //系统参数 readonly DateTime _valueDate; //系统交易日 readonly Dictionary _clientBalanceDic; @@ -341,6 +343,7 @@ namespace YLErp.BLL.Eod { item.DesirableFund = Math.Max(item.MarginBalance - item.FrozenMarginMoney + Math.Min(item.RoundedPositionPnl, 0), 0); } + logger.Info($"实时客户资金:客户{clientId} MarginWatchRule={ruleClient?.MarginWatchRule?.ToString() ?? "NULL"} 可用资金={item.AvailableAmount:0.00} 追保金额={item.MarginByPayableMarginTotal:0.00} 是否追保={item.NeedAddMargin} 可取资金={item.DesirableFund:0.00}(期末结存{item.AmountFund:0.00} 授信{item.TotalCredit:0.00} 已用授信{item.UsedCredit:0.00} 互换初始{item.SwapInitMargin:0.00} 维持{-item.MySideMargin:0.00} 追加合计{item.SwapAdditionalMarginTotal:0.00})"); } return _clientBalanceDic.Values; diff --git a/YLErpDAL/BLL/MarginCalculation/MarginCalculationBase.cs b/YLErpDAL/BLL/MarginCalculation/MarginCalculationBase.cs index 4c84413d..dacdb6f6 100644 --- a/YLErpDAL/BLL/MarginCalculation/MarginCalculationBase.cs +++ b/YLErpDAL/BLL/MarginCalculation/MarginCalculationBase.cs @@ -531,7 +531,12 @@ namespace YLErp.BLL.MarginCalculation } } - return SwapSpanMarginCalc.CalcTradeMaintenanceMargin(trade.InitialMargin, spanCfg, legs, isInitialCalc, closePrice); + var margin = SwapSpanMarginCalc.CalcTradeMaintenanceMargin(trade.InitialMargin, spanCfg, legs, isInitialCalc, closePrice); + if (margin.HasValue) + { + logger.Info($"规则15新引擎:交易{trade.id} 标的{trade.UnderlyingCode} 收盘价={closePrice:0.####}(试算初始={isInitialCalc})→ 维持保证金={margin.Value:0.00}"); + } + return margin; } } } diff --git a/YLErpDAL/Modules/MarginModule/MarginTemplateV2RateHelper.cs b/YLErpDAL/Modules/MarginModule/MarginTemplateV2RateHelper.cs index 25999088..b6c4d466 100644 --- a/YLErpDAL/Modules/MarginModule/MarginTemplateV2RateHelper.cs +++ b/YLErpDAL/Modules/MarginModule/MarginTemplateV2RateHelper.cs @@ -110,12 +110,14 @@ namespace YLErp.Modules.MarginModule //2.无预付金规则:率直接为 0 if (template.RuleType == (int)MarginRuleTypeEnum.无预付金) { + logger.Info($"预付金模板取数:模板{template.id}(规则=无预付金)标的{underlyingCode},x=y=0"); return new MarginRateResult { Template = template, InitRate = 0m, MaintainRate = 0m }; } if (template.RuleType != (int)MarginRuleTypeEnum.区间追保结构) { //其他规则不在本帮助类支持范围,显式返回 null + logger.Info($"【警告】预付金模板取数:模板{template.id} 规则{template.RuleType}不在取数支持范围(仅 无预付金/区间追保结构),返回null由调用方兜底"); return null; } @@ -124,6 +126,7 @@ namespace YLErp.Modules.MarginModule .Where(x => x.MarginTemplateId == template.id && x.ValueDate <= valueDate); if (!detailQuery.Any()) { + logger.Info($"【警告】预付金模板取数:模板{template.id} 无生效明细行(ValueDate≤{valueDate:yyyy-MM-dd}),返回null由调用方兜底"); return null; } var latestValueDate = detailQuery.Max(x => x.ValueDate); @@ -158,6 +161,7 @@ namespace YLErp.Modules.MarginModule } if (!matched.Any()) { + logger.Info($"【警告】预付金模板取数:模板{template.id}(生效日{latestValueDate:yyyy-MM-dd})标的{underlyingCode}(品种{underlyingInstrumentType},期限档{term})无匹配明细行,返回null由调用方兜底"); return null; } @@ -170,12 +174,15 @@ namespace YLErp.Modules.MarginModule } var detail = matched.First(); + var initRate = ToDecimalRate(detail.MarginRatio1); + var maintainRate = ToDecimalRate(detail.MarginRatio2); + logger.Info($"预付金模板取数:模板{template.id}(生效日{latestValueDate:yyyy-MM-dd})标的{underlyingCode}(品种{underlyingInstrumentType},期限档{term})命中明细行{detail.id}:初始x={(initRate?.ToString("0.####") ?? "未配")},维持y={(maintainRate?.ToString("0.####") ?? "未配")}"); return new MarginRateResult { Template = template, Detail = detail, - InitRate = ToDecimalRate(detail.MarginRatio1), - MaintainRate = ToDecimalRate(detail.MarginRatio2) + InitRate = initRate, + MaintainRate = maintainRate }; } @@ -226,6 +233,7 @@ namespace YLErp.Modules.MarginModule var bound = db.margin_template_v2.AsNoTracking().FirstOrDefault(x => x.id == bindingTemplateId.Value && x.IsValid); if (bound != null && bound.IsApplicableToBook(tradeAssetId)) { + logger.Info($"预付金模板取数:交易{tradeId}(客户{clientId})命中层级=交易绑定 → 模板{bound.id}"); return bound; } if (bound != null) @@ -264,17 +272,23 @@ namespace YLErp.Modules.MarginModule var ret = clientTemplate.FirstOrDefault(x => x.IsApplicableToBook(tradeAssetId)); if (ret != null) { + logger.Info($"预付金模板取数:交易{tradeId}(客户{clientId},等级{levelName ?? "无"})命中层级=客户默认 → 模板{ret.id}"); return ret; } } //3.全局默认 - return db.margin_template_v2.AsNoTracking() + var globalDefault = db.margin_template_v2.AsNoTracking() .Where(x => x.IsDefault && !x.IsForClient && x.IsValid && x.TradeTypes.Contains("收益互换") && x.ValueDate <= valueDate) .OrderByDescending(x => x.ValueDate) .ThenByDescending(x => x.id) .ToList() .FirstOrDefault(x => x.IsApplicableToBook(tradeAssetId)); + if (globalDefault != null) + { + logger.Info($"预付金模板取数:交易{tradeId}(客户{clientId})命中层级=全局默认 → 模板{globalDefault.id}"); + } + return globalDefault; } /// @@ -373,6 +387,7 @@ namespace YLErp.Modules.MarginModule if (boundTemplates.TryGetValue(templateId, out var bound) && bound.IsApplicableToBook(trade.AssetId)) { result[trade.id] = bound; + logger.Info($"预付金模板取数:交易{trade.id}(客户{trade.ClientId})命中层级=交易绑定 → 模板{bound.id}"); } else if (boundTemplates.ContainsKey(templateId)) { @@ -391,12 +406,18 @@ namespace YLErp.Modules.MarginModule if (clientTemplate != null) { result[trade.id] = clientTemplate.Template; + logger.Info($"预付金模板取数:交易{trade.id}(客户{trade.ClientId},等级{levelName ?? "无"})命中层级=客户默认 → 模板{clientTemplate.Template.id}"); continue; } var globalDefault = globalDefaults.FirstOrDefault(x => x.IsApplicableToBook(trade.AssetId)); if (globalDefault != null) { result[trade.id] = globalDefault; + logger.Info($"预付金模板取数:交易{trade.id}(客户{trade.ClientId})命中层级=全局默认 → 模板{globalDefault.id}"); + } + else + { + logger.Info($"【警告】预付金模板取数:交易{trade.id}(客户{trade.ClientId})三层级(交易绑定/客户默认/全局默认)均未命中有效模板"); } } diff --git a/YLErpDAL/Modules/SwapModule/Margin/SwapAdditionalMarginService.cs b/YLErpDAL/Modules/SwapModule/Margin/SwapAdditionalMarginService.cs index 02b31082..84be8991 100644 --- a/YLErpDAL/Modules/SwapModule/Margin/SwapAdditionalMarginService.cs +++ b/YLErpDAL/Modules/SwapModule/Margin/SwapAdditionalMarginService.cs @@ -3,6 +3,7 @@ using YLErp.DBModels; using YLErp.Enums; using YLErp.Modules.MarginModule; using YLErp.Modules.TradeModule; +using YLErp.Helpers; namespace YLErp.Modules.SwapModule.Margin { @@ -24,6 +25,8 @@ namespace YLErp.Modules.SwapModule.Margin /// public class SwapAdditionalMarginService : YLBaseService { + private static readonly IYcLogger logger = LogFactory.GetLogger("EOD追保"); + /// /// EOD 追保腿打标(OptName):与手工追加预付金腿(OptName=操作员实名)区分, /// 幂等清理、RemoveByTrade 保护与时间轴回退清理均以此识别。拆单现金腿落库时被打服务身份,簿记后回打本标识。 @@ -79,8 +82,23 @@ namespace YLErp.Modules.SwapModule.Margin /// /// 结算日逐客户逐交易产生追加保证金(clientFilter 为部分结算的客户过滤,与 EOD 请求一致)。 + /// 顶层兜异常日志(Error 级)后原样抛出,避免异常栈被 EOD 框架层吞掉无从定位。 /// public void SettleAdditionalMargin(DateTime settleDate, List clientFilter = null) + { + try + { + SettleAdditionalMarginCore(settleDate, clientFilter); + } + catch (Exception ex) + { + var filterDesc = clientFilter != null && clientFilter.Any() ? string.Join(",", clientFilter) : "全部"; + logger.Error($"EOD追保:结算日{settleDate:yyyy-MM-dd}(客户范围:{filterDesc})追保腿生成异常", ex); + throw; + } + } + + private void SettleAdditionalMarginCore(DateTime settleDate, List clientFilter) { //合约维度盯市客户 var watchClientIds = DbContextFactory.GetClientDbContext(OptUser).client.AsNoTracking() @@ -93,8 +111,10 @@ namespace YLErp.Modules.SwapModule.Margin } if (watchClientIds.Count == 0) { + logger.Info($"EOD追保:结算日{settleDate:yyyy-MM-dd} 无合约维度盯市(MarginWatchRule=0)客户,结束"); return; } + logger.Info($"EOD追保:结算日{settleDate:yyyy-MM-dd} 合约维度盯市客户{watchClientIds.Count}个({string.Join(",", watchClientIds)})"); //存续中的互换交易(状态口径与 eodSwapQuery 一致,含当日已了结) var tradeStatuses = ConsTrade.TradeStatusAfterConfirmed; @@ -107,17 +127,24 @@ namespace YLErp.Modules.SwapModule.Margin .ToList(); if (trades.Count == 0) { + logger.Info($"EOD追保:结算日{settleDate:yyyy-MM-dd} 盯市客户名下无存续收益互换交易,结束"); return; } //规则15(区间追保结构)交易:R1 三层级解析(BUG-02 修正,与引擎/确认书同口径)—— //交易绑定→客户默认→全局默认 找到即停,只配客户/全局默认模板的交易同样纳入追保结算 + var candidateCount = trades.Count; var templatesByTrade = MarginTemplateV2RateHelper.ResolveTieredTemplates(trades, settleDate, DbContext); trades = trades.Where(t => templatesByTrade.TryGetValue(t.id, out var tpl) && tpl.RuleType == (int)MarginRuleTypeEnum.区间追保结构).ToList(); + if (candidateCount > trades.Count) + { + logger.Info($"EOD追保:{candidateCount - trades.Count}笔交易模板非区间追保结构(或三级未命中),不参与追保结算"); + } var tradeIds = trades.Select(t => t.id).ToList(); if (tradeIds.Count == 0) { + logger.Info($"EOD追保:结算日{settleDate:yyyy-MM-dd} 规则15交易为0,结束"); return; } @@ -175,21 +202,28 @@ namespace YLErp.Modules.SwapModule.Margin var eodPositionService = new SwapEodPositionService(this); var flowEventService = new SwapFlowEventService(this); + var settledTrades = 0; + var settledLegs = 0; + var totalIncrement = 0d; + foreach (var clientGroup in trades.GroupBy(t => t.ClientId).OrderBy(g => g.Key)) { foreach (var td in clientGroup.OrderBy(t => t.id)) { if (!maintenanceByTrade.TryGetValue(td.id, out var maintenance) || maintenance <= 0) { + logger.Info($"EOD追保:交易{td.id}(客户{td.ClientId})跳过——当日无trade_span维持保证金或维持≤0(maintenance={maintenance:0.00})"); continue; } //目标追加 = 维持保证金 − 已缴初始保证金净额(现金应付预付金净收额 + 授信初始占用净额, //授信垫付与现金垫付同等对待,杜绝授信初始预付金被重复追加) - var payableNet = (payableNetByTrade.TryGetValue(td.id, out var payable) ? payable : 0) - + (initCreditByTrade.TryGetValue(td.id, out var initCredit) ? initCredit : 0); + var payable = payableNetByTrade.TryGetValue(td.id, out var p) ? p : 0; + var initCredit = initCreditByTrade.TryGetValue(td.id, out var ic) ? ic : 0; + var payableNet = payable + initCredit; var target = SwapAdditionalMarginCalc.CalcTarget(maintenance, payableNet); if (target <= 0) { + logger.Info($"EOD追保:交易{td.id}(客户{td.ClientId})跳过——目标追加≤0:维持={maintenance:0.00},已缴={payableNet:0.00}(应付净额{payable:0.00}+初始授信占用{initCredit:0.00})已覆盖"); continue; } var fundedCash = addRecordByTrade.TryGetValue(td.id, out var cash) ? cash : 0; @@ -198,8 +232,10 @@ namespace YLErp.Modules.SwapModule.Margin if (increment <= 0) { //已补足;追保回落(目标下降)不返还——负缺口在可用资金公式(Σ维持−累计)体现 + logger.Info($"EOD追保:交易{td.id}(客户{td.ClientId})跳过——增量≤0:目标={target:0.00},已补足(现金{fundedCash:0.00}+授信{fundedCredit:0.00}),追保回落不返还"); continue; } + logger.Info($"EOD追保:交易{td.id}(客户{td.ClientId})需追加:维持={maintenance:0.00},已缴={payableNet:0.00}(应付净额{payable:0.00}+初始授信占用{initCredit:0.00}),目标={target:0.00},已补足(现金{fundedCash:0.00}+授信{fundedCredit:0.00}),本次增量={increment:0.00}"); //幂等清理:先删本结算日起 EOD 旧追保腿及其簿记(腿/流水/占用/快照同生共死),再按最新增量重建; //手工追加预付金腿(OptName≠EOD追保)不受影响 @@ -228,6 +264,10 @@ namespace YLErp.Modules.SwapModule.Margin .ToList(); newLegs.ForEach(x => x.OptName = EodOptName); DbContext.SaveChanges(); + logger.Info($"EOD追保:交易{td.id} 追保腿落库完成——新腿{newLegs.Count}条(id=[{string.Join(",", newLegs.Select(x => x.id))}],含拆单现金腿)"); + settledTrades++; + settledLegs += newLegs.Count; + totalIncrement += increment; //实时持仓克隆 + 开仓事件(参照 TradeConfirmService 簿记后动作,但只针对本次新腿—— //整交易 InitialPosition 会把浮动腿实时持仓重置回开仓态、AddPositionEvent 会为全部腿重复建开仓事件,EOD 场景不可用); @@ -252,6 +292,8 @@ namespace YLErp.Modules.SwapModule.Margin } } } + + logger.Info($"EOD追保:结算日{settleDate:yyyy-MM-dd} 完成——规则15交易{tradeIds.Count}笔,{settledTrades}笔产生追保(腿{settledLegs}条,合计增量{totalIncrement:0.00})"); } /// @@ -289,6 +331,7 @@ namespace YLErp.Modules.SwapModule.Margin var oldEodRows = DbContext.eod_swap_position.Where(x => x.SwapTradeId == td.id && x.ValueDate >= settleDate && oldInitialIds.Contains(x.PositionId)) .ToList(); + logger.Info($"EOD追保:交易{td.id} 幂等清理(重跑)——删EOD追保腿{oldLegs.Count}条(id=[{string.Join(",", oldLegs.Select(x => x.id))}])及其簿记:现金流水{oldCashRecords.Count}条、授信占用{oldCreditRecords.Count}条、开仓事件{oldEvents.Count}条、eod快照{oldEodRows.Count}行"); DbContext.eod_swap_position.RemoveRange(oldEodRows); DbContext.swap_position.RemoveRange(oldLegs); DbContext.SaveChanges(); diff --git a/YLErpDAL/Modules/SwapModule/Margin/SwapSpanBalanceQueryService.cs b/YLErpDAL/Modules/SwapModule/Margin/SwapSpanBalanceQueryService.cs index ec26f6cf..607c5068 100644 --- a/YLErpDAL/Modules/SwapModule/Margin/SwapSpanBalanceQueryService.cs +++ b/YLErpDAL/Modules/SwapModule/Margin/SwapSpanBalanceQueryService.cs @@ -1,6 +1,7 @@ using YLErp.BLL; using YLErp.DBModels; using YLErp.Enums; +using YLErp.Helpers; using YLErp.Modules.MarginModule; namespace YLErp.Modules.SwapModule.Margin @@ -18,6 +19,8 @@ namespace YLErp.Modules.SwapModule.Margin /// public static class SwapSpanBalanceQueryService { + private static readonly IYcLogger logger = LogFactory.GetLogger("预付金缺口查询"); + /// /// 客户维度输入:互换初始保证金(净收取为正,按客户汇总)。 /// 初始保证金 = 应付预付金流水收付净额 + 初始预付金授信占用净额(非"追加保证金"前缀、关联交易)—— @@ -95,6 +98,11 @@ namespace YLErp.Modules.SwapModule.Margin .Select(t => t.id) .ToHashSet(); maintenance = maintenance.Where(x => rule15TradeIds.Contains(x.TradeId)).ToList(); + var droppedCount = spanTradeIds.Count - rule15TradeIds.Count; + if (droppedCount > 0) + { + logger.Info($"预付金缺口查询:{valueDate:yyyy-MM-dd} {droppedCount}笔有span交易模板非规则15(或三级未命中),不计入交易维度追加合计"); + } if (maintenance.Count == 0) { return result; @@ -142,6 +150,8 @@ namespace YLErp.Modules.SwapModule.Margin result[group.Key] = total; } + logger.Info($"预付金缺口查询:{valueDate:yyyy-MM-dd} 规则15交易{rule15TradeIds.Count}笔,客户{result.Count}个,交易维度追加合计:{string.Join(";", result.Select(kv => $"客户{kv.Key}={kv.Value:0.00}"))}"); + return result; } } diff --git a/YLErpDAL/Modules/SwapModule/SwapFundTagService.cs b/YLErpDAL/Modules/SwapModule/SwapFundTagService.cs index 68570f69..8ec004f9 100644 --- a/YLErpDAL/Modules/SwapModule/SwapFundTagService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapFundTagService.cs @@ -2,6 +2,7 @@ using YLErp.BLL; using YLErp.DBModels; using YLErp.Modules.SwapModule.Margin; using YLErp.Modules.TradeModule; +using YLErp.Helpers; namespace YLErp.Modules.SwapModule { @@ -15,6 +16,8 @@ namespace YLErp.Modules.SwapModule /// public class SwapFundTagService : YLBaseService { + private static readonly IYcLogger logger = LogFactory.GetLogger("预付金簿记"); + public SwapFundTagService(OptUserInfo userInfo) : base(userInfo) { } @@ -167,6 +170,7 @@ namespace YLErp.Modules.SwapModule .ThenBy(x => x.Leg.id) .ToList(); var plans = FundTagCalc.AllocateByLegPreference(allocateLegs, (decimal)creditAvailable, ignoreMoneyCheck); + logger.Info($"预付金簿记:交易{td.id}(客户{td.ClientId})簿记日{valueDate:yyyy-MM-dd}(Action={cashAction})可用授信={creditAvailable:0.00},{marginLegs.Count}条预付金腿分配:{(plans.Any() ? string.Join(";", plans.Select(p => $"腿{p.Leg.id}应付{p.Amount:0.00}→授信{p.CreditAmount:0.00}/现金{p.CashAmount:0.00}{(p.NeedSplit ? "(拆单)" : "")}")) : "无正应付腿,全部直通现金标签")}"); //先落库拆分的新现金腿(需要 id 才能绑定现金流水) foreach (var plan in plans.Where(p => p.NeedSplit)) From 1857f9a356b0449564a6c7ccdaf52e9aca5c0d5a Mon Sep 17 00:00:00 2001 From: tengyufan <1532636164@qq.com> Date: Fri, 28 Aug 2026 12:28:15 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=E9=9A=90=E8=97=8FOA=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E5=A4=B1=E8=B4=A5=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- YLErpDAL/Modules/TradeModule/TradeBLL.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/YLErpDAL/Modules/TradeModule/TradeBLL.cs b/YLErpDAL/Modules/TradeModule/TradeBLL.cs index 261f1ef8..d629451b 100644 --- a/YLErpDAL/Modules/TradeModule/TradeBLL.cs +++ b/YLErpDAL/Modules/TradeModule/TradeBLL.cs @@ -334,7 +334,9 @@ namespace YLErp.BLL .FirstOrDefault(); if (oa != null) { - tradeLinq.OaRemark = string.IsNullOrWhiteSpace(oa.oa_msg) + tradeLinq.OaRemark = oa.status == "提交失败" + ? oa.status + : string.IsNullOrWhiteSpace(oa.oa_msg) ? oa.status : oa.status + ":" + oa.oa_msg; }