using System.Linq.Expressions; using System.Text; using YieldChain.Helpers; using YLErp.DBModels.Consts; using YLErp.QdpModule.Constants; namespace YLErp.Modules.VolatilityModule { /// /// 只用于波动率数据查询 /// public class VolatilityQueryService : YLBaseService { public VolatilityQueryService(OptUserInfo userInfo) : base(userInfo) { } public VolatilityQueryService(YLBaseService baseService) : base(baseService) { } #region----获取单个标的的曲面波动率---- /// /// 获取单个标的的曲面波动率 /// public volatility GetVolatility(string userGroup, DateTime quotationDate, string volType, string underlyingCode, bool createIfNotFound = true) { if (string.IsNullOrWhiteSpace(underlyingCode)) { throw new ArgumentException("标的代码不能为空", nameof(underlyingCode)); } var vols = GetVolatility(new SingleVolatilityRequest { UserGroup = userGroup, QuotationDate = quotationDate, VolType = volType, TradeVolWithBidAsk = false, UnderlyingCode = underlyingCode, UnderlyingId = 0 }, createIfNotFound); return vols?.FirstOrDefault(n => n.VolType == volType); } /// /// 获取单个标的的曲面波动率 /// public volatility GetVolatility(string userGroup, DateTime quotationDate, string volType, int underlyingId, bool createIfNotFound = true) { var vols = GetVolatility(new SingleVolatilityRequest { UserGroup = userGroup, QuotationDate = quotationDate, VolType = volType, TradeVolWithBidAsk = false, UnderlyingCode = string.Empty, UnderlyingId = underlyingId }, createIfNotFound); return vols?.FirstOrDefault(n => n.VolType == volType); } /// /// VolType为交易的情况下,返回["交易","报价Bid","报价Ask"]波动率 /// public IEnumerable GetVolatility(SingleVolatilityRequest request, bool createIfNotFound = true) { CheckRequest(request); underlying_manager un = null; if (!string.IsNullOrEmpty(request.UnderlyingCode)) { un = DataCacheProvider.GetUnderlyingDataSource().GetData(request.UnderlyingCode); } else if (request.UnderlyingId.HasValue) { un = DataCacheProvider.GetUnderlyingDataSource().GetData(request.UnderlyingId.Value); } else { throw new ArgumentException("标的代码或标的ID不能为空", nameof(request.UnderlyingCode)); } if (un == null) { return Enumerable.Empty(); } if (un.CommodityCode == "组合标的") { return VolatilityHelper.GetDefaultVols(request); } //先对期货标的做特殊处理 if (un.IsFutures() && un.MaturityDate < request.QuotationDate) { return VolatilityHelper.GetDefaultVols(request, 0); } var volPredicate = BuildPredicate(request); if (volPredicate == null) { return VolatilityHelper.GetDefaultVols(request); } request.UnderlyingId = un.id; request.UnderlyingCode = un.UnderlyingCode; var groupQuery = from v in DbContext.volatility.Where(volPredicate) where v.ContractCode == request.UnderlyingCode group v by new { v.UserGroup, v.ContractCode, v.VolType } into vg select new VolGroupDto { UserGroup = vg.Key.UserGroup, ContractCode = vg.Key.ContractCode, VolType = vg.Key.VolType, QuotationDate = vg.Max(n => n.QuotationDate) }; var volQuery = from vg in groupQuery join v in DbContext.volatility on new { vg.UserGroup, vg.ContractCode, vg.VolType, vg.QuotationDate } equals new { v.UserGroup, v.ContractCode, v.VolType, v.QuotationDate } orderby v.ContractCode select v; var results = volQuery.ToArray().AsEnumerable(); if (results.Any()) { foreach (var item in results) { item.QuotationDate = request.QuotationDate; } } //如果从数据库中未能获取到波动率数据 else if (createIfNotFound && !results.Any()) { results = ProcesseMissingVol(request, un); } return results; } #endregion #region----批量获取标的的曲面波动率---- /// /// 为波动率批量导出业务获取波动率列表(不需要同源波动率) /// public IEnumerable GetVolatilities(BatchVolatilityRequest request, bool createIfNotFound) { CheckRequest(request); var volPredicate = BuildPredicate(request, request.StartDate); if (volPredicate == null) { return Enumerable.Empty(); } var resultList = new List(); if (request.VarietyIds != null && request.VarietyIds.Any()) { request.VarietyIds = request.VarietyIds.ToList(); } //标的关联(返回null表示已没有可以筛选的标的) var unPredicate = BuildUnderlyingPredicate(request, resultList); if (unPredicate == null) { return resultList; } var unQuery = DbContext.underlying_manager.Where(unPredicate); //数据量小的表尽量靠前 var groupQuery = from un in unQuery join v in DbContext.volatility.Where(volPredicate) on un.UnderlyingCode equals v.ContractCode group v by new { v.UserGroup, v.ContractCode, v.VolType } into vg select new { vg.Key.UserGroup, vg.Key.ContractCode, vg.Key.VolType, QuotationDate = vg.Max(n => n.QuotationDate) }; //var count = groupQuery.Count(); var volQuery = from vg in groupQuery join v in DbContext.volatility on vg equals new { v.UserGroup, v.ContractCode, v.VolType, v.QuotationDate } orderby v.ContractCode select v; resultList.AddRange(volQuery.ToList()); foreach (var item in resultList) { item.QuotationDate = request.QuotationDate; } if (createIfNotFound) { var unIds = resultList.Select(n => n.UnderlyingId).ToHashSet(); var missingUns = DataCacheProvider.GetUnderlyingDataSource().AsQueryable() .Where(unPredicate).Where(n => !unIds.Remove(n.id)).ToArray(); foreach (var un in missingUns) { var vols = ProcesseMissingVol(new SingleVolatilityRequest(request, un.UnderlyingCode, un.id), un); resultList.AddRange(vols); } } return resultList; } //resultList用于存储过期标的的波动率 private Expression> BuildUnderlyingPredicate(BatchVolatilityRequest request, List resultList) { Expression> predicate = null; var unSource = DataCacheProvider.GetUnderlyingDataSource(); //优先级1(如果是有效过滤条件则忽略UnderlyingCodes) if (request.UnderlyingIds != null && request.UnderlyingIds.Any(n => n > 0)) { var set = request.UnderlyingIds.ToHashSet(); foreach (var unId in request.UnderlyingIds) { var un = unSource.GetData(unId); if (un == null) { } else if (un.IsFutures() && un.MaturityDate < request.QuotationDate) { var vols = VolatilityHelper.GetDefaultVols(new SingleVolatilityRequest(request, un.UnderlyingCode, un.id), 0); resultList.AddRange(vols); } else if (unId > 0) { set.Add(unId); } } if (!set.Any()) { return null; } predicate = PredicateBuilder.Create(n => set.Contains(n.id)); } //优先级2 else if (request.UnderlyingCodes != null && request.UnderlyingCodes.Any(n => !string.IsNullOrEmpty(n))) { var set = request.UnderlyingCodes.ToHashSet(StringComparer.OrdinalIgnoreCase); foreach (var unCode in request.UnderlyingCodes) { var un = unSource.GetData(unCode); if (un == null) { } else if (un.IsFutures() && un.MaturityDate < request.QuotationDate) { var vols = VolatilityHelper.GetDefaultVols(new SingleVolatilityRequest(request, un.UnderlyingCode, un.id), 0); resultList.AddRange(vols); } else if (!string.IsNullOrWhiteSpace(unCode)) { set.Add(unCode); } } if (!set.Any()) { return null; } predicate = PredicateBuilder.Create(n => set.Contains(n.UnderlyingCode)); } else { predicate = PredicateBuilder.Create(n => n.CommodityCode != "组合标的" && n.LaunchState == "1"); } if (request.VarietyIds != null && request.VarietyIds.Any(n => n > 0)) { predicate = predicate.And(n => request.VarietyIds.Contains(n.UnderlyingTypeId)); } predicate = predicate.And(n => n.UnderlyingInstrumentType != ConsGlobal.InstrumentType.CommodityFutures || n.MaturityDate >= request.QuotationDate); return predicate; } #endregion #region----处理数据库中找不到波动率的情况---- private IEnumerable ProcesseMissingVol(SingleVolatilityRequest request, underlying_manager un) { if (PS.Config.ErpElement.SkewMapVolConstruction || (request != null && !ConsVolInfos.TradeVolTypes.Contains(request.VolType))) { return Enumerable.Empty(); } if (un == null) { if (!string.IsNullOrEmpty(request.UnderlyingCode)) { un = DataCacheProvider.GetUnderlyingDataSource().GetData(request.UnderlyingCode); } else if (request.UnderlyingId > 0) { un = DataCacheProvider.GetUnderlyingDataSource().GetData(request.UnderlyingId.Value); } } if (un == null) { return Enumerable.Empty(); } var reqVolTypes = request.GetVolTypes(); request.UnderlyingId = un.id; request.UnderlyingCode = un.UnderlyingCode; request.TradeVolWithBidAsk = true; if (!PS.Config.ErpElement.SkewMapVolConstruction && ConsVolInfos.TradeVolTypes.Contains(request.VolType)) { request.VolType = "交易"; } var allVolTypes = request.GetVolTypes(); if (un.IsFutures()) { if (un.CommodityCode == "组合标的") { //避免报价获取波动率时出错 return VolatilityHelper.GetDefaultVols(request); } if (un.MaturityDate < request.QuotationDate) { return VolatilityHelper.GetDefaultVols(request, 0); } } else { var defaultVols = VolatilityHelper.GetDefaultVols(request); SaveMissingVols(defaultVols, "默认波动率"); return defaultVols.Where(n => reqVolTypes.Contains(n.VolType)).ToArray(); } //获取同源合约代码,先主力合约再历史合约 VolCopyPara sameUn = null; var match = System.Text.RegularExpressions.Regex.Match(un.UnderlyingCode, "^([a-zA-z]+)\\d+$"); if (match.Success) { var mainCode = match.Groups[1].Value + "00"; if (!mainCode.Equals(un.UnderlyingCode, StringComparison.OrdinalIgnoreCase) && DbContext.volatility.Any(v => v.UserGroup == request.UserGroup && v.ContractCode == mainCode && allVolTypes.Contains(v.VolType))) { sameUn = new VolCopyPara { id = un.id, UnderlyingCode = un.UnderlyingCode, SameCode = mainCode }; } } else { var query = from u1 in DbContext.underlying_manager.Where(n => n.id == un.id) join u2 in DbContext.underlying_manager on u1.CommodityCode equals u2.CommodityCode join v in DbContext.volatility on u2.UnderlyingCode equals v.ContractCode where v.UserGroup == request.UserGroup && u2.MaturityDate.Value < u1.MaturityDate.Value && allVolTypes.Contains(v.VolType) orderby u2.MaturityDate descending, v.QuotationDate descending select new VolCopyPara { id = u1.id, UnderlyingCode = u1.UnderlyingCode, SameCode = u2.UnderlyingCode }; sameUn = query.FirstOrDefault(); } IEnumerable vols = null; if (sameUn != null) { request.UnderlyingCode = sameUn.SameCode; vols = GetVolatility(request, false); if (vols != null && vols.Count() == allVolTypes.Count()) { foreach (var item in vols) { item.UnderlyingId = sameUn.id; item.ContractCode = sameUn.UnderlyingCode; item.QuotationDate = request.QuotationDate; } SaveMissingVols(vols, "同源复制" + sameUn.SameCode); } else { vols = null; } } if (vols == null) { vols = VolatilityHelper.GetDefaultVols(request); SaveMissingVols(vols, "默认波动率"); } return vols.Where(n => reqVolTypes.Contains(n.VolType)).ToArray(); } //保存同源波动率或默认波动率 private void SaveMissingVols(IEnumerable missingVols, string dataSource) { if (missingVols == null || !missingVols.Any() || Interlocked.Increment(ref saveLock) > 1) return; try { var date = new DateTime(2000, 1, 1); var arr = missingVols.Where(n => ConsVolInfos.VolTypes.Contains(n.VolType)) .Select(n => { //n可能是volatility类型的子类,如果clone的话会导致写入数据库出错 var clone = YLAutoMapper.Map(n); clone.OptId = 0; clone.OptName = dataSource ?? "同源复制"; clone.OptDate = DateTime.Now; clone.QuotationDate = date; return clone; }).ToArray(); if (arr.Any()) { using (var db = DbContextFactory.GetYLDbContext()) { var userGroup = arr.First().UserGroup ?? string.Empty; var ucodes = arr.Select(n => n.ContractCode).ToHashSet(); var filters = db.volatility.Where(n => n.QuotationDate == date && n.UserGroup == userGroup && ucodes.Contains(n.ContractCode)) .Select(n => n.ContractCode + "^^" + n.VolType).ToHashSet(StringComparer.OrdinalIgnoreCase); if (filters.Any()) { arr = arr.Where(n => !filters.Contains(n.ContractCode + "^^" + n.VolType)).ToArray(); } db.volatility.AddRange(arr); var changes = db.SaveChanges(); } } } catch (Exception ex) { LogFactory.GetLogger(nameof(SaveMissingVols)).Error(ex); } finally { Interlocked.Exchange(ref saveLock, 0); } } #endregion #region----内部处理---- //检查请求数据是否符合预期 private static void CheckRequest(VolatilityRequest request) { if (request is null) { throw new ArgumentNullException(nameof(request)); } if (string.IsNullOrWhiteSpace(request.VolType)) { if (!(request is BatchVolatilityRequest breq)) { throw new ArgumentException("VolType不能为空", nameof(request.VolType)); } else if (breq.VolTypes == null || !breq.VolTypes.Any()) { throw new ArgumentException("VolType不能为空", nameof(request.VolType)); } } if (ConsUserGroup.HasGroup && string.IsNullOrWhiteSpace(request.UserGroup)) { throw new ArgumentException("UserGroup不能为空", nameof(request.UserGroup)); } if (request.QuotationDate.Year < 1949) { throw new ArgumentException("QuotationDate取值不正确:" + request.QuotationDate, nameof(request.QuotationDate)); } request.QuotationDate = request.QuotationDate.Date; } //构建查询条件(没有波动率类型时返回null) private static Expression> BuildPredicate(VolatilityRequest request, DateTime? startDate = null) { if (!ConsUserGroup.HasGroup) { request.UserGroup = string.Empty; } var volTypes = request.GetVolTypes().Where(n => ConsVolInfos.VolTypes.Contains(n)).ToArray(); if (volTypes.Any()) { var predicate = PredicateBuilder.Create(v => v.QuotationDate <= request.QuotationDate); if (startDate.HasValue) { predicate.And(v => v.QuotationDate >= startDate.Value); } return predicate.And(v => v.UserGroup == request.UserGroup && volTypes.Contains(v.VolType)); } return null; } #endregion class VolGroupDto { public string UserGroup { get; set; } public string ContractCode { get; set; } public string VolType { get; set; } public DateTime QuotationDate { get; set; } } class VolCopyPara { public int id { get; set; } public string UnderlyingCode { get; set; } public string SameCode { get; set; } } static int saveLock; } #region----请求模型类---- /// /// 波动率请求基类 /// public class VolatilityRequest { public VolatilityRequest() { } public VolatilityRequest(VolatilityRequest baseRequest) { if (baseRequest is null) { throw new ArgumentNullException(nameof(baseRequest)); } UserGroup = baseRequest.UserGroup; VolType = baseRequest.VolType; TradeVolWithBidAsk = baseRequest.TradeVolWithBidAsk; QuotationDate = baseRequest.QuotationDate; } /// /// 必须有值 /// public string UserGroup { get; set; } /// /// 必须有值 /// public string VolType { get; set; } /// /// 取交易波动率时是否附带"报价Bid"和"报价Ask",默认false /// public bool TradeVolWithBidAsk { get; set; } /// /// 必须有值 /// public DateTime QuotationDate { get; set; } /// /// 获取相关波动率 /// public virtual IEnumerable GetVolTypes() { if (TradeVolWithBidAsk && !PS.Config.ErpElement.SkewMapVolConstruction && VolType == "交易") { return ConsVolInfos.TradeVolTypes; } return new[] { VolType }; } public override string ToString() { return $"{VolType}--{TradeVolWithBidAsk}--{QuotationDate:yyyy-MM-dd}--{UserGroup}"; } } /// /// 单标的波动率请求 /// public class SingleVolatilityRequest : VolatilityRequest { public SingleVolatilityRequest() { } public SingleVolatilityRequest(VolatilityRequest baseRequest, string underlyingCode, int? underlyingId = null) : base(baseRequest) { UnderlyingCode = underlyingCode; UnderlyingId = underlyingId; } /// /// 标的过滤(优先级1) /// public string UnderlyingCode { get; set; } /// ///标的过滤(优先级2) /// public int? UnderlyingId { get; set; } public SingleVolatilityRequest Clone() { return (SingleVolatilityRequest)MemberwiseClone(); } } /// /// 批量标的波动率请求 /// public class BatchVolatilityRequest : VolatilityRequest { /// /// 从这个日期开始查找数据 /// public DateTime? StartDate { get; set; } /// /// 标的过滤,优先级1(如果是有效过滤条件则忽略UnderlyingCodes和VarietyIds) /// public IEnumerable UnderlyingIds { get; set; } /// /// 标的过滤,优先级2(如果是有效过滤条件则忽略VarietyIds) /// public IEnumerable UnderlyingCodes { get; set; } /// /// 品种过滤(和标的过滤取并集) /// public IEnumerable VarietyIds { get; set; } /// /// 波动率类型过滤(如果存在则忽略VolType参数优先使用这个) /// public IEnumerable VolTypes { get; set; } public override IEnumerable GetVolTypes() { return VolTypes != null && VolTypes.Any() ? VolTypes : base.GetVolTypes(); } /// /// 获取唯一key(MD5算法) /// public string GetUniqueKey() { var sb = new StringBuilder(500); sb.Append(UserGroup).Append('^') .Append(VolType).Append('^') .Append(TradeVolWithBidAsk).Append('^') .Append(QuotationDate.ToString("yyyyMMdd")).Append('^'); if (UnderlyingIds != null) { sb.Append(string.Join(",", UnderlyingIds)).Append('^'); } if (UnderlyingCodes != null) { sb.Append(string.Join(",", UnderlyingCodes)).Append('^'); } if (VarietyIds != null) { sb.Append(string.Join(",", VarietyIds)).Append('^'); } return HashHelper.MD5(sb.ToString()); } } #endregion }