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;
}