using Microsoft.Extensions.DependencyInjection; using System; using System.Linq; using System.Text; using YieldChain.Commons; using YLErp.Cache; using YLErp.Commons; using YLErp.DBModels; namespace YLErp.Modules.UnderlyingModule { /// /// 标的数据帮助类 /// public static class UnderlyingHelper { readonly static IYLCache ylCache; static UnderlyingHelper() { ylCache = YLServiceLocator.ServiceProvider.GetService(); } /// /// 获取组合标的品种对象 /// public static Variety GetSyntheticVariety() { var variety = DataCacheProvider.GetVarietyDataSource().AsQueryable() .FirstOrDefault(v => v.VarietyCode == "组合标的"); if (variety != null) { return variety; } lock (DataCacheProvider.GetVarietyDataSource()) { using (var db = DbContextFactory.GetYLDbContext()) { variety = db.variety.FirstOrDefault(n => n.VarietyCode == "组合标的"); if (variety != null) { return variety; } variety = new Variety() { VarietyCode = "组合标的", VarietyName = "组合标的", TradingMarketId = 0, TradeUnit = "1手/份", QuoteUnit = "元(人民币)/份", DeliveryType = "", HasNightMarket = false, ShortName = "组合标的", CommissionType = "固定", CloseTodayCommissionType = "固定", //UnderlyingInstrumentType = "CommodityFutures", VolatilityRate = "", VolatilityAdjust = 0, AssetType = "", OptId = 0, OptName = "系统", OptDate = DateTime.Now, }; db.variety.Add(variety); db.SaveChanges(); return variety; } } } /// /// 根据客户ID、标的代码和计算日期计算适用的保证金率 /// /// 客户ID /// 标的代码 /// 计算日期 /// 适用的保证金率配置,包含初始保证金率和维持保证金率 public static client_margin_detail GetApplicableMarginRate(int clientId, string underlyingCode, DateTime valueDate) { if (string.IsNullOrEmpty(underlyingCode)) return null; string cacheKey = $"Otc:ClientMarginRate:{clientId}:{underlyingCode}:{valueDate:yyyyMMdd}"; try { if (ylCache!=null) { // 1. 先查Redis var cacheDetail = ylCache.StringGetWithNoPrefix(cacheKey); if (cacheDetail != null) return cacheDetail; } // 2. 查数据库 string applicableTerm = GetApplicableMarginTerm(underlyingCode, valueDate); var marginConfig = GetClientMarginConfig(clientId, valueDate); if (marginConfig == null) return null; // 2.1 尝试使用标的资产上手动设置的债券期限 var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode); if (underlying != null && !string.IsNullOrEmpty(underlying.ExJson)) { var bondInfo = JsonHelper.Deserialize(underlying.ExJson); if (bondInfo != null && !string.IsNullOrEmpty(bondInfo.BondTerm)) { // 优先使用标的资产上设置的债券期限 applicableTerm = bondInfo.BondTerm; } } var marginDetail = GetMarginDetailByTerm(marginConfig.id, applicableTerm); // 3. 放入Redis if (marginDetail != null&& ylCache != null) { ylCache.StringSetWithNoPrefix(cacheKey, marginDetail,TimeSpan.FromHours(3)); } return marginDetail; } catch (Exception ex) { LogFactory.GetLogger("UnderlyingHelper").Error($"计算保证金率时发生异常: {ex.Message}", ex); return null; } } /// /// 获取客户保证金配置 /// /// 客户ID /// 估值日期 /// 客户保证金配置 private static client_margin_config GetClientMarginConfig(int clientId, DateTime valueDate) { try { using (var context = DbContextFactory.GetYLDbContext()) { // 查找指定日期有效的客户保证金配置 var config = context.clientMarginConfig .Where(c => c.client_id == clientId && c.value_date <= valueDate) .OrderByDescending(c => c.value_date) .FirstOrDefault(); // 找不到,查找通用的那条 if (config==null) { config = context.clientMarginConfig .Where(c => c.client_id == 0 && c.value_date <= valueDate) .OrderByDescending(c => c.value_date) .FirstOrDefault(); } return config; } } catch (Exception ex) { Console.WriteLine($"获取客户保证金配置时发生异常: {ex.Message}"); return null; } } /// /// 根据配置ID和期限获取保证金率详情 /// /// 配置ID /// 债券期限 /// 保证金率详情 private static client_margin_detail GetMarginDetailByTerm(int configId, string bondTerm) { try { using (var context = DbContextFactory.GetYLDbContext()) { var details = context.clientMarginDetail .Where(d => d.config_id == configId).ToList(); var detail = details.FirstOrDefault(d => d.bond_term == bondTerm); if (detail == null) { detail = details.FirstOrDefault(d => d.bond_term == ConsMarginTerm.UnderFiveYear); } return detail; } } catch (Exception ex) { Console.WriteLine($"获取保证金率详情时发生异常: {ex.Message}"); return null; } } /// /// 清除指定标的的保证金率缓存 /// /// 标的代码,为空时清除全部 public static void ClearMarginRateCache(string underlyingCode = null) { try { if (ylCache == null) return; var cachePattern = string.IsNullOrEmpty(underlyingCode) ? "ClientMarginRate:*" : $"ClientMarginRate:*:{underlyingCode}:*"; ylCache.BatchDelete(cachePattern); } catch (Exception ex) { LogFactory.GetLogger("UnderlyingHelper").Error($"清除保证金率缓存时发生异常: {ex.Message}", ex); } } /// /// 获取篮子标的品种对象 /// public static Variety GetBasketVariety() { return DataCacheProvider.GetVarietyDataSource().AsQueryable() .FirstOrDefault(v => v.VarietyCode == "篮子标的") ?? new Variety { VarietyCode = "篮子标的", VarietyName = "篮子标的", QuoteUnit = "元(人民币)/股", TradeUnit = "100股/手" }; } /// /// 获取标的品种代码 例:BU,BU-CU /// /// /// public static string GetUnderlyingVarietyCode(string underlyingCode) { var ret = ""; var un = DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode); if (un != null) { if (un.UnderlyingType == "组合标的") { var synthetic = DataCacheProvider.GetUnderlyingDataSource().GetSyntheticUnderlying(underlyingCode); if (synthetic != null) { var sb = new StringBuilder(); var va1 = DataCacheProvider.GetVariety(synthetic.UnderlyingCode1); var va2 = DataCacheProvider.GetVariety(synthetic.UnderlyingCode2); var va3 = DataCacheProvider.GetVariety(synthetic.UnderlyingCode3); var va4 = DataCacheProvider.GetVariety(synthetic.UnderlyingCode4); if (va1 != null) { sb.Append(va1.VarietyCode).Append('-'); } if (va2 != null) { sb.Append(va2.VarietyCode).Append('-'); } if (va3 != null) { sb.Append(va3.VarietyCode).Append('-'); } if (va4 != null) { sb.Append(va4.VarietyCode).Append('-'); } ret = sb.ToString().Substring(0, sb.Length - 1); } } else { var va = DataCacheProvider.GetVariety(un.UnderlyingCode); ret = va.VarietyCode; } } return ret; } /// /// 获取债券的期限,计算日期一定要小于原日期 /// /// 计算日期 /// 原日期 /// 返回3.12格式 public static double? getYearTerm(DateTime? calcDate, DateTime? sourceDate) { if (!calcDate.HasValue || !sourceDate.HasValue) return null; if (calcDate.Value > sourceDate.Value) throw new ArgumentException("计算日期必须小于原日期", nameof(calcDate)); int totalMonths = (sourceDate.Value.Year - calcDate.Value.Year) * 12 + (sourceDate.Value.Month - calcDate.Value.Month); double fractionalMonth = 0; if (sourceDate.Value.Day != calcDate.Value.Day) { int daysInMonth = DateTime.DaysInMonth(sourceDate.Value.Year, sourceDate.Value.Month); fractionalMonth = (sourceDate.Value.Day - calcDate.Value.Day) / (double)daysInMonth; } double yearTerm = (totalMonths + fractionalMonth) / 12.0; return yearTerm; } /// /// 期限计算异常提示去重:同一标的只提示一次。此前用 Console.WriteLine 直打控制台, /// 已到期标的在预付金模板取率热路径(MarginTemplateV2RateHelper)每次调用都抛 /// "计算日期必须小于原日期",造成管理端刷屏。 /// private static readonly System.Collections.Concurrent.ConcurrentDictionary _termWarnedUnderlyings = new(); /// /// 根据标的发行年限和剩余期限计算适用保证金率 /// 规则表格: /// | 剩余期限(年) | 发行期限(年) | 则适用于 | /// |------------------|---------------|----------| /// | (0.00,2.00] | 不限 | 2年 | /// | (2.00,2.25] | <5 | 2年 | /// | (2.00,2.25] | >=5 | 5年 | /// | (2.25,5.00] | 不限 | 5年 | /// | (5.00,5.25] | <7 | 5年 | /// | (5.00,5.25] | >=7 | 10年 | /// | (5.25,25.00] | 不限 | 10年 | /// | (25.00,30.00] | 不限 | 30年 | /// /// 标的代码 /// 估值日期 /// 适用的保证金率期限,如果不适用则返回空字符串 public static string GetApplicableMarginTerm(string underlyingCode, DateTime valueDate) { if (string.IsNullOrEmpty(underlyingCode)) return ConsMarginTerm.UnderFiveYear; try { // 获取标的信息 var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode); if (underlying == null || !underlying.MaturityDate.HasValue) return ConsMarginTerm.UnderFiveYear; // 已到期标的(到期日≤估值日):剩余期限按 0 计,直接走期限档规则(≤5年→默认档), // 不再进 getYearTerm——其对 calcDate>sourceDate 抛"计算日期必须小于原日期",是此前高频日志的来源 var remainingTermYears = underlying.MaturityDate.Value <= valueDate ? 0 : getYearTerm(valueDate, underlying.MaturityDate); // 根据业务规则确定适用的保证金率期限 return DetermineMarginTerm(remainingTermYears); } catch (Exception ex) { //兜底:按默认档(<5y)返回,取率不中断;同一标的只提示一次防刷屏 if (_termWarnedUnderlyings.TryAdd(underlyingCode, 0)) { LogFactory.GetLogger("计算保证金率期限").Info($"【警告】标的{underlyingCode}计算保证金率期限发生异常,按默认档(<5y)兜底:{ex.Message}"); } return ConsMarginTerm.UnderFiveYear; } } /// /// 根据剩余年限确定适用的保证金率期限(利率债TRS) /// | 剩余期限(年) | 则适用于 | /// |------------------|-------------| /// | <=5 | <5y | /// | (5,10] | 5y-10y | /// | (10,30] | 10y-30y | /// | >30 | >30y | /// 未设置或无法计算剩余期限,使用”默认” /// private static string DetermineMarginTerm(double? remainingYears) { // 利率债TRS规则:只看剩余期限 if (!remainingYears.HasValue) return ConsMarginTerm.UnderFiveYear; if (remainingYears <= 5.0) return ConsMarginTerm.UnderFiveYear; if (remainingYears > 5.0 && remainingYears <= 10.0) return ConsMarginTerm.FiveToTenYear; if (remainingYears > 10.0 && remainingYears <= 30.0) return ConsMarginTerm.TenToThirtyYear; return ConsMarginTerm.OverThirtyYear; } } }