using YLErp.BLL; using YLErp.DBModels; using YLErp.Enums; using YLErp.Modules.MarginModule; namespace YLErp.Modules.SwapModule.Margin { /// /// R2 阶段三 §3.2 估值报告/可用资金查询输入(静态查询,供 ClientBalanceUtility 与 RealTimeClientBanlanceService 共用,保证三处口径一致)。 /// 口径: /// - 互换初始保证金(净收取为正)= 客户 应付预付金 流水收付净额取反(客户应付入金记负、平仓返还为正,取负号后净收取为正); /// - 交易维度追加保证金(合约维度)= Σ(维持保证金 − 累计保证金): /// 维持保证金取当日 trade_span.Spv(区间追保结构引擎产出,我方净收取为正); /// 累计保证金 = 该交易 应付预付金+追加保证金 流水收付净额 + 追加保证金授信占用净额(§0 口径,阶段四 §4.1 起); /// 仅统计规则15(区间追保结构,R1 三层级解析,与引擎同口径)且有当日 trade_span 的交易。 /// public static class SwapSpanBalanceQueryService { /// /// 客户维度输入:互换初始保证金(净收取为正,按客户汇总)。 /// public static Dictionary GetSwapInitMarginByClients(List clientIds, DateTime valueDate, YLContext db) { if (clientIds == null || clientIds.Count == 0) { return new Dictionary(); } var flows = db.ClientCashInCashOut.AsNoTracking() .Where(x => clientIds.Contains(x.ClientId ?? 0) && x.Action == ClientCashInCashOut.系统操作_应付预付金 && x.HappenDate <= valueDate && x.ValidState != ConsGlobal.InValid && (x.State == ClientCashInCashOut.已确认 || x.State == ClientCashInCashOut.已结算) && x.Money != null) .Select(x => new { ClientId = x.ClientId ?? 0, Money = x.Money ?? 0d }) .ToList(); return flows.GroupBy(x => x.ClientId) .ToDictionary(g => g.Key, g => -g.Sum(x => x.Money)); } /// /// 合约维度输入:交易维度追加保证金合计 = Σ(维持保证金 − 累计保证金),按客户汇总。 /// public static Dictionary GetTradeAdditionalMarginByClients(List clientIds, DateTime valueDate, YLContext db) { var result = new Dictionary(); if (clientIds == null || clientIds.Count == 0) { return result; } //当日维持保证金(引擎产出:我方净收取为正)——先取当日有 span 的客户交易,再按规则15过滤 var maintenance = db.trade_span.AsNoTracking() .Where(x => x.ValueDate == valueDate && x.Spv != null && x.ClientId != null && clientIds.Contains(x.ClientId.Value)) .Select(x => new { x.TradeId, x.ClientId, Spv = x.Spv ?? 0d }) .ToList(); if (maintenance.Count == 0) { return result; } var spanTradeIds = maintenance.Select(x => x.TradeId).Distinct().ToList(); //规则15(区间追保结构)交易:R1 三层级解析(BUG-02 修正,与引擎/结算判定同口径)—— //交易绑定→客户默认→全局默认 找到即停;无预付金等其他规则产出/留存的 span 行不计入追保缺口 var spanTrades = db.trade.AsNoTracking().Where(t => spanTradeIds.Contains(t.id)).ToList(); var templatesByTrade = MarginTemplateV2RateHelper.ResolveTieredTemplates(spanTrades, valueDate, db); var rule15TradeIds = spanTrades .Where(t => templatesByTrade.TryGetValue(t.id, out var tpl) && tpl.RuleType == (int)MarginRuleTypeEnum.区间追保结构) .Select(t => t.id) .ToHashSet(); maintenance = maintenance.Where(x => rule15TradeIds.Contains(x.TradeId)).ToList(); if (maintenance.Count == 0) { return result; } //累计保证金:该交易 应付预付金+追加保证金 流水收付净额取反(收取为正); //追加保证金的授信占用部分不产生资金流水(阶段二口径),阶段四 §4.1 起一并计入—— //否则结算后缺口残留(=授信部分),与可用资金公式里的 −已使用授信 形成双扣 var marginActions = new List { ClientCashInCashOut.系统操作_应付预付金, ClientCashInCashOut.系统操作_追加保证金 }; var accumulated = db.ClientCashInCashOut.AsNoTracking() .Where(x => x.TradeId != null && spanTradeIds.Contains(x.TradeId ?? 0) && marginActions.Contains(x.Action) && x.HappenDate <= valueDate && x.ValidState != ConsGlobal.InValid && (x.State == ClientCashInCashOut.已确认 || x.State == ClientCashInCashOut.已结算) && x.Money != null) .GroupBy(x => x.TradeId) .Select(g => new { TradeId = g.Key ?? 0, Sum = -g.Sum(x => x.Money ?? 0d) }) .ToDictionary(x => x.TradeId, x => x.Sum); //追加保证金的授信占用净额(amount 占用记正 → 直接求和(BUG-01 修正口径);remark 前缀标识) var addCreditOccupied = db.client_credit_inout.AsNoTracking() .Where(x => x.trade_id != null && spanTradeIds.Contains(x.trade_id ?? 0) && x.remark.StartsWith(ClientCreditInoutService.AdditionalMarginRemark)) .GroupBy(x => x.trade_id) .Select(g => new { TradeId = g.Key ?? 0, Sum = g.Sum(x => x.amount) }) .ToDictionary(x => x.TradeId, x => x.Sum); foreach (var group in maintenance.GroupBy(x => x.ClientId ?? 0)) { var total = group.Sum(x => x.Spv - (accumulated.TryGetValue(x.TradeId, out var acc) ? acc : 0d) - (addCreditOccupied.TryGetValue(x.TradeId, out var occupied) ? occupied : 0d)); result[group.Key] = total; } return result; } } }