using YLErp.Abstract.DataProviders; using YLErp.BLL.Calculation; using YLErp.Enums; using YLErp.Helpers; using YLErp.Model.Enum; using YLErp.Modules; using YLErp.DBModels.Enums; using YLErp.Modules.CalculationModule; using YLErp.Modules.DataProviderModule; using YLErp.Modules.MarginModule; using YLErp.Modules.SwapModule.Margin; using YLErp.Modules.TradeModule; namespace YLErp.BLL.MarginCalculation { /// /// 预付金计算 /// public partial class MarginCalculationBase { protected static readonly IYcLogger logger = LogFactory.GetLogger("预付金计算"); protected readonly UnderlyingDataProvider _underlyingDataProvider; protected MarginCalculationBase() { _underlyingDataProvider = new UnderlyingDataProvider(); } public virtual List CalcClientMargin(CalcClientMarginReq req) { var clientSpanNews = new List(); using (var db = new YLContext()) { if (req.tradeSpans != null && req.tradeSpans.Count > 0) { var tradeIds = req.tradeSpans.Select(t => t.TradeId).ToList(); var tradeList = db.trade.AsNoTracking().Where(t => tradeIds.Contains(t.id)).ToList(); var tradeSpanInfo = (from tradeSpan in req.tradeSpans join trade in tradeList on tradeSpan.TradeId equals trade.id where tradeSpan.ValueDate == req.settleDate select new { trade, tradeSpan }).ToList(); var clientGroups = tradeSpanInfo.GroupBy(t => t.trade.ClientId); foreach (var clientGroup in clientGroups) { var client = DataCacheProvider.GetClientDataSource().GetData(clientGroup.Key); var underlyingGroup = clientGroup.GroupBy(t => t.trade.UnderlyingId).Select(t => new ClientSpan { UnderlyingId = t.Key, ClientId = clientGroup.Key, ValueDate = req.settleDate, Spv1 = t.Sum(g => g.tradeSpan.Spv1) * (-1), Spv2 = t.Sum(g => g.tradeSpan.Spv2) * (-1), Spv3 = t.Sum(g => g.tradeSpan.Spv3) * (-1), Spv4 = t.Sum(g => g.tradeSpan.Spv4) * (-1), SwapWorstCastClientPayable = t.Where(g => g.trade.TradeType == "收益互换").Sum(g => g.tradeSpan.WorstCastClientPayable) * (-1), OptId = req.userId, OptName = req.userName, OptDate = DateTime.Now, SpanType = req.SpanType }).ToList(); foreach (var item in underlyingGroup) { item.WorstCastClientPayable = Math.Min(Math.Min(Math.Min(item.Spv1 ?? 0, item.Spv2 ?? 0), item.Spv3 ?? 0), item.Spv4 ?? 0); item.TwoSideMargin = Math.Min(Math.Min(Math.Min(item.Spv1 ?? 0, item.Spv2 ?? 0), item.Spv3 ?? 0), item.Spv4 ?? 0); #region 更新tradeSpan,使得每笔交易的持仓预付金和客户预付金计算用的Spv组保持一致 var tradeIdList = clientGroup.Select(x => x.tradeSpan.TradeId); var tradeSpansUpdate = db.trade_span.Where(x => tradeIdList.Contains(x.TradeId) && x.ClientId == item.ClientId && x.UnderlyingId == item.UnderlyingId && x.ValueDate == req.settleDate).ToList(); var tradeSpansReq = req.tradeSpans.Where(x => tradeIdList.Contains(x.TradeId) && x.ClientId == item.ClientId && x.UnderlyingId == item.UnderlyingId && x.ValueDate == req.settleDate).ToList(); if (item.WorstCastClientPayable == item.Spv1) { tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv1); tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv1); } else if (item.WorstCastClientPayable == item.Spv2) { tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv2); tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv2); } else if (item.WorstCastClientPayable == item.Spv3) { tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv3); tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv3); } else if (item.WorstCastClientPayable == item.Spv4) { tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv4); tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv4); } #endregion } var clientSpan = new ClientSpan { ClientId = clientGroup.Key, ValueDate = req.settleDate, Spv1 = underlyingGroup.Sum(g => g.Spv1), Spv2 = underlyingGroup.Sum(g => g.Spv2), Spv3 = underlyingGroup.Sum(g => g.Spv3), Spv4 = underlyingGroup.Sum(g => g.Spv4), //负数代表客户应缴预付金,正数代表客户应收预付金 WorstCastClientPayable = underlyingGroup.Sum(g => g.WorstCastClientPayable), SwapWorstCastClientPayable = underlyingGroup.Sum(g => g.SwapWorstCastClientPayable), MySideMargin = underlyingGroup.Sum(g => g.WorstCastClientPayable), TwoSideMargin = underlyingGroup.Sum(g => g.TwoSideMargin), OptId = req.userId, OptName = req.userName, OptDate = DateTime.Now, SpanType = req.SpanType, AdditionalWorstCastClientPayable = req.clientAdditionalMarginDic != null && req.clientAdditionalMarginDic.TryGetValue(clientGroup.Key, out var dd) ? dd : 0 }; if (client.MarginOptionType == (int)MarginOptionEnum.单向追保) { clientSpan.WorstCastClientPayable = Math.Min(clientSpan.WorstCastClientPayable.Value, 0); } else if (client.MarginOptionType == (int)MarginOptionEnum.对手方单向追保) { clientSpan.WorstCastClientPayable = Math.Max(clientSpan.WorstCastClientPayable.Value, 0); } clientSpanNews.Add(clientSpan); } } //span类型为实时删除所有实时计算的交易的预付金信息 if (req.SpanType == ClientSpan.SpanType_RealTime) { if (req.RefreshClientIds != null) { db.BulkDelete($"{nameof(ClientSpan.ClientId)} in @ids", new { ids = req.RefreshClientIds }); } else { db.BulkDelete($"{nameof(ClientSpan.SpanType)}={req.SpanType}"); } } else { if (req.ClientIds != null) { var sql = $"{nameof(ClientSpan.ClientId)} in @ids and {nameof(ClientSpan.ValueDate)}='{req.settleDate.ToSqlDate()}' and {nameof(ClientSpan.SpanType)}={req.SpanType} and {nameof(ClientSpan.ModifiedFlag)}=0"; db.BulkDelete(sql, new { ids = req.ClientIds }); } else { var sql = $"{nameof(ClientSpan.ValueDate)}='{req.settleDate.ToSqlDate()}' and {nameof(ClientSpan.SpanType)}={req.SpanType} and {nameof(ClientSpan.ModifiedFlag)}=0"; db.BulkDelete(sql); } var clientSpanOldsWithFlag = db.client_span.Where(t => t.ValueDate == req.settleDate && t.SpanType == req.SpanType && t.ModifiedFlag) .Select(n => new { n.ValueDate, n.ClientId }).ToList(); //筛选出可以修改的clientSpan clientSpanNews = clientSpanNews.Where(c => !clientSpanOldsWithFlag.Any(t => t.ValueDate == c.ValueDate && t.ClientId == c.ClientId)).ToList(); } if (clientSpanNews.Count > 0) { //MySqlBulkExtensions.BulkInsert(db, clientSpanNews); db.client_span.AddRange(clientSpanNews); } db.SaveChanges(); return req.tradeSpans; } } public virtual List RunMarginCalculation(RunMarginCalculationReq req) { if (req.tradeList == null || !req.tradeList.Any()) { return new List(); } var tradeSpanList = new List(); //收益互换交易单独计算trade_span if (req.tradeList.Any(t => t.TradeType == "收益互换")) { var tradeList = req.tradeList.Where(t => t.TradeType == "收益互换").ToList(); tradeSpanList.AddRange(SwapTradeMarginCalculation(req.Clone(tradeList))); } //剔除收益互换交易 req.tradeList = req.tradeList.Where(t => t.TradeType != "收益互换").ToList(); var helper = new RunMarginCalculationHelper(req, _underlyingDataProvider); var resultMap = new Dictionary(); //为了算客户角度的一个预付金数值 helper.ReverseTradeSide(); helper.SetFieldsByTradeType(); helper.GetUpDownLimitPrices(out var upLimitPrices, out var downLimitPrices); helper.GetTradVolRateDic(out var tradeVolRateDic); var vols = new[] { null, tradeVolRateDic }; var prices = new (string, IPriceProvider)[] { ("up", upLimitPrices), ("down", downLimitPrices) }; var loops = prices.SelectMany(n => vols.Select(m => new { pricekey = n.Item1, priceProvider = n.Item2, addVolRateDic = m })).ToArray(); foreach (var loop in loops) { var calcReq = helper.GetCalculateRisksForTradesReq(priceProvider: loop.priceProvider, addVolRateDic: loop.addVolRateDic, overrideVols: null); var tradeRiskResult = CalculatorHelper.CalculateRisksForTrades(calcReq); if (tradeRiskResult.Results == null || tradeRiskResult.Results.Count < 1) { continue; } var key = $"{loop.pricekey}_{(loop.addVolRateDic == null ? 0 : 1)}"; foreach (var item in tradeRiskResult.Results) { var pv = item.ValueResult.Pv; if (!helper.GetSpecialMargin(item.Trade, pv, out var value)) { var clientRatio = helper.GetClient(item.Trade)?.Ratio ?? 1.0; value = double.IsNaN(pv) ? 0 : pv * clientRatio; } var contains = resultMap.TryGetValue(item.Trade.id, out var tempTradeSpan); if (!contains) { resultMap[item.Trade.id] = tempTradeSpan = helper.CreateTradeSpan(item.Trade); } switch (key) { case "up_0": tempTradeSpan.Spv1 = value; break; case "up_1": tempTradeSpan.Spv2 = value; break; case "down_0": tempTradeSpan.Spv3 = value; break; case "down_1": tempTradeSpan.Spv4 = value; break; } if (contains) { tempTradeSpan.SetWorstCastClientPayable(); } } } tradeSpanList.AddRange(resultMap.Values.ToList()); return tradeSpanList; } public virtual double GetTradeMargin(GetTradeMarginReq req) { using (var db = new YLContext()) { var marginReq = req.GetRunMarginCalculationReq(); if (req.trade.IsGroup == 1) { marginReq.tradeList = db.trade.Where(x => x.ParentTradeId == req.realTradeId).ToList(); if (marginReq.CalcMarginType == CalcMarginTypeEnum.InitialMargin) { foreach (var item in marginReq.tradeList) { item.id = 0; } } } var tradeMargin = RunMarginCalculation(marginReq); if (null != tradeMargin) { var margin = req.trade.IsGroup == 1 ? tradeMargin.Sum(x => x.WorstCastClientPayable ?? 0) : (tradeMargin.FirstOrDefault()?.WorstCastClientPayable ?? 0); return margin; } return 0.0; } } //为了算客户角度的一个预付金数值 //RunMarginCalculation时forOtherSide为true时调用 protected List RunMarginCalculationOtherSide(RunMarginCalculationReq req) { void RevertBuySell() { foreach (var x in req.tradeList) { x.BuySell = x.BuySell == "买入" ? "卖出" : "买入"; if (x.SubTrades != null && x.SubTrades.Any()) { foreach (var xs in x.SubTrades) { xs.BuySell = xs.BuySell == "买入" ? "卖出" : "买入"; } } } } RevertBuySell(); req.forOtherSide = false; var results = RunMarginCalculation(req); RevertBuySell(); return results; } /// /// 是否客户有双向预付金 /// protected bool HasTwoSideMargin(int clientId) { if (PS.Config.ErpElement.TwoSideMargin) { var client = DataCacheProvider.GetClientDataSource().GetData(clientId); return client?.MarginOptionType == (int)MarginOptionEnum.双向追保; } return false; } /// /// 获取客户信息 /// protected InnerClient GetClientInfo(int clientId) { if (clientId < 1) { return null; } using (var db = DbContextFactory.GetClientDbContext(null)) { var query = from c in db.client join cl in db.clientlevel on c.LevelId equals cl.id into t_cl from cl in t_cl.DefaultIfEmpty() where clientId == c.id select new InnerClient { ClientId = c.id, Ratio = cl == null ? null : cl.Ratio, Ratio1 = cl == null ? null : cl.Ratio1, AddRatio = cl == null ? null : cl.AddRatio, MarginOptionType = c.MarginOptionType, ProperClientClass = c.ProperClientClass, QuestionnaireScore = c.QuestionnaireScore, RuleT0orT1 = c.RuleT0orT1 }; return query.FirstOrDefault(); } } //收益互换预付金计算 //预付金模板V2迁移:按 R1 三层级(交易绑定→客户默认→全局默认,ResolveTieredTemplates 找到即停)解析模板—— //无预付金=0;区间追保结构=名义本金×x(初始)/名义本金×y(持仓),x/y 由 MarginTemplateV2RateHelper 按 期限档+资产类型 匹配明细取得。 //三级均未命中或命中其他规则的互换交易维持现状(不产出 trade_span),与确认书/取数口径一致(BUG-02 修正)。 //阶段三 §3.1:区间追保结构明细按方案B录入新区间结构(SpanConfig.LongSpans/ShortSpans)时切换新引擎—— //每日取标的收盘价落档(债券净价/指数及ETF收盘价),维持保证金 = (初始保证金 + 总追加保证金) × 我方净收取方向; //存量 x/y 配置(无新区间结构)维持 名义×y 公式不变。 private static List SwapTradeMarginCalculation(RunMarginCalculationReq req) { var result = new List(); var calcTrades = req.tradeList.Where(t => t.ParentTradeId == 0 && t.id > 0).ToList(); if (calcTrades.Count == 0) { return result; } var swapTradeIds = calcTrades.Select(t => t.id).ToList(); using (var db = new YLContext()) { //R1 三层级找到即停(BUG-02 修正):交易绑定 → 客户默认 → 全局默认 统一解析, //只配了客户/全局默认模板的交易与交易级绑定同权生效,与确认书/取数口径一致 var templatesByTrade = MarginTemplateV2RateHelper.ResolveTieredTemplates(calcTrades, req.settleDate, db); //期初腿数据:标的腿(多空)取 期初价格/数量/方向,初始预付金腿取 初始保证金与收取方向 var swapPositions = db.swap_position.AsNoTracking() .Where(x => swapTradeIds.Contains(x.SwapTradeId) && !x.Invalid && x.IsInitial) .ToList(); var positionsByTrade = swapPositions.GroupBy(x => x.SwapTradeId).ToDictionary(g => g.Key, g => g.ToList()); foreach (var trade in req.tradeList) { //剔除多空组合子交易 if (trade.ParentTradeId > 0 || trade.id <= 0) { continue; } if (!templatesByTrade.TryGetValue(trade.id, out var template)) { continue; } double? margin; if (template.RuleType == (int)MarginRuleTypeEnum.无预付金) { margin = 0; } else if (template.RuleType == (int)MarginRuleTypeEnum.区间追保结构) { var rateResult = MarginTemplateV2RateHelper.GetRateByTemplate(template, trade.UnderlyingCode, trade.UnderlyingInstrumentType, req.settleDate, db); if (rateResult == null) { continue; } var spanCfg = rateResult.Detail?.SpanConfig; if (SwapSpanMarginCalc.HasSpanConfig(spanCfg)) { //方案B新结构:收盘价落档 → 维持保证金 = (初始 + 总追加) × 方向 margin = CalcSwapSpanMaintenanceMargin(trade, spanCfg, positionsByTrade.TryGetValue(trade.id, out var legs) ? legs : new List(), req.CalcMarginType == CalcMarginTypeEnum.InitialMargin, req.settleDate); } else { //存量 x/y 配置:初始预付金=名义本金×x;持仓预付金=名义本金×y var rate = (double)(req.CalcMarginType == CalcMarginTypeEnum.InitialMargin ? rateResult.InitRate ?? 0m : rateResult.MaintainRate ?? 0m); margin = rate * trade.StockEqvNotional; } } else { //其他规则不在默认引擎支持范围,显式跳过 continue; } if (margin == null) { //新引擎无法计算(缺标的腿等),不产出 trade_span,与未匹配明细行为一致 continue; } result.Add(new trade_span() { TradeId = trade.id, OptDate = DateTime.Now, OptId = req.userId, OptName = req.userName, ClientId = trade.ClientId, UnderlyingId = trade.UnderlyingId, UnderlyingCode = trade.UnderlyingCode, ValueDate = req.settleDate, Spv1 = margin.Value, Spv2 = margin.Value, Spv3 = margin.Value, Spv4 = margin.Value, Spv5 = margin.Value, Spv6 = margin.Value, Spv7 = margin.Value, Spv8 = margin.Value, Spv = margin.Value, WorstCastClientPayable = margin.Value, Margin = 0 }); } } return result; } /// /// 阶段三 §3.1 新引擎:规则15 方案B配置的维持保证金(DB 胶水层——只负责解析收盘价,计算在 SwapSpanMarginCalc.CalcTradeMaintenanceMargin 纯函数,单测覆盖)。 /// 收盘价口径:债券取中债估值净价(GetBondPrice 口径 SettlePrice=净价;取 ≤计算日 最近一条,盘中跑用最近已有估值), /// 指数/ETF取收盘价(InnerGetEodPrice 将日期规整到最近交易日);未取到 → closePrice=0(追加按0、维持=初始),仅记日志不抛错。 /// private static double? CalcSwapSpanMaintenanceMargin(trade trade, SpanConfig spanCfg, List legs, bool isInitialCalc, DateTime settleDate) { //标的腿(多空):期初价格、数量、客户方向 var underlyingLeg = legs.FirstOrDefault(x => x.PositionType == (int)PositionTypeFlag.Long || x.PositionType == (int)PositionTypeFlag.Short); if (underlyingLeg == null || underlyingLeg.PosiGrossPrice <= 0) { logger.Info($"【警告】规则15新引擎:交易{trade.id}无有效标的腿(期初价),跳过新公式计算"); return null; } double closePrice; if (isInitialCalc) { //试算初始不依赖收盘价 closePrice = 0; } else { var underlyingCode = !string.IsNullOrEmpty(underlyingLeg.UnderlyingCode) ? underlyingLeg.UnderlyingCode : trade.UnderlyingCode; var isBond = DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode)?.IsBond() ?? false; if (isBond) { if (!EodPriceQueryService.TryGetBondEodPrice(settleDate, underlyingCode, out var bondPrice)) { logger.Info($"【警告】规则15新引擎:交易{trade.id}标的{underlyingCode}无债券估值净价,追加保证金按0计(维持=初始)"); closePrice = 0; } else { closePrice = bondPrice.GetPrice(SettlementTypeEnum.SettlePrice); } } else { if (!EodPriceQueryService.TryGetEodPrice(settleDate, underlyingCode, out var eodPrice)) { logger.Info($"【警告】规则15新引擎:交易{trade.id}标的{underlyingCode}无收盘价,追加保证金按0计(维持=初始)"); closePrice = 0; } else { closePrice = eodPrice.GetPrice(SettlementTypeEnum.ClosePrice); } } } return SwapSpanMarginCalc.CalcTradeMaintenanceMargin(trade.InitialMargin, spanCfg, legs, isInitialCalc, closePrice); } } }