using YLErp.Commons; using YLErp.DBModels.Consts; using YLErp.Models; using YLErp.QdpModule.Constants; namespace YLErp.Modules.VolatilityModule { /// /// 波动率保存服务 /// public class VolatilitySaveService : YLBaseService { public VolatilitySaveService(OptUserInfo userInfo) : base(userInfo) { } public VolatilitySaveService(YLBaseService baseService) : base(baseService) { } /// /// /// public List SaveVol(volatility vol) { if (vol is null) { throw new ArgumentNullException(nameof(vol)); } return SaveVols(new List { vol }); } /// /// /// public List SaveVols(List vols, bool overridByMainCode = false) { if (ConsUserGroup.HasGroup) { if (string.IsNullOrEmpty(OptUser.UserGroup)) { throw new ServiceException("缺少用户组"); } if (!ConsUserGroup.GetGroups().Any(n => n.ItemValue == OptUser.UserGroup)) { throw new ServiceException("用户组不存在:" + OptUser.UserGroup); } } var checkResult = PrepareVols(vols); if (!checkResult.IsSuccess) { throw new ServiceException(checkResult.Message); } var date = vols[0].QuotationDate; if (overridByMainCode) { Dictionary mainDic = null; //PrepareVols方法处理,可能会产生多个波动率类型(比如交易会分出来mid,ask,bid) foreach (var vl in vols.GroupBy(n => n.ContractCode.ToLowerInvariant())) { var ContractCode = vl.Key; if (System.Text.RegularExpressions.Regex.IsMatch(ContractCode, "^[a-zA-Z]+00$")) { var un = DataCacheProvider.GetUnderlyingDataSource().GetData(ContractCode); if (un != null && un.IsFutures() && !string.IsNullOrEmpty(un.CommodityCode)) { if (mainDic == null) { mainDic = new Dictionary(StringComparer.OrdinalIgnoreCase); } mainDic[un.CommodityCode] = vl.ToArray(); } } } if (mainDic != null) { var existsCodes = vols.Select(n => n.ContractCode).ToHashSet(StringComparer.OrdinalIgnoreCase); var query = from un in DbContext.underlying_manager where un.CommodityCode != null && (un.MaturityDate == null || un.MaturityDate.Value >= date) && mainDic.Keys.Contains(un.CommodityCode) && !existsCodes.Contains(un.UnderlyingCode) select new { un.CommodityCode, un.UnderlyingCode, un.id }; var overrideUns = query.ToArray(); var overrideVols = overrideUns.SelectMany(un => mainDic[un.CommodityCode].Select(n => { var clone = n.Clone(); clone.UnderlyingId = un.id; clone.ContractCode = un.UnderlyingCode; return clone; })); vols.AddRange(overrideVols); } } foreach (var v in vols) { v.UserGroup = OptUser.UserGroup; var dbVol = DbContext.volatility.FirstOrDefault(n => n.ContractCode == v.ContractCode && n.VolType == v.VolType && n.UserGroup == v.UserGroup && n.QuotationDate == date); if (v.InterpolationMethod == null) { v.InterpolationMethod = ConsVolMethod.Default; } if (dbVol == null) { dbVol = DbContext.volatility.Add(v).Entity; } else { dbVol.Data = v.Data; dbVol.InterpolationMethod = v.InterpolationMethod; } dbVol.OptId = UserId; dbVol.OptName = UserName; dbVol.OptDate = OptDate; } int changes = DbContext.SaveChanges(); return vols; } /// /// /// private HandleResult PrepareVols(List vols) { if (vols is null || !vols.Any()) { throw new ArgumentException("参数不能为空", nameof(vols)); } if (vols.Any(n => string.IsNullOrEmpty(n.ContractCode))) { return "标的代码不允许为空!"; } var firstVol = vols.First(); if (firstVol.QuotationDate.Year < 2000) { return "报价日期不正确:" + firstVol.QuotationDate.ToString("yyyy-MM-dd"); } if (vols.Select(O => O.QuotationDate).Distinct().Count() > 1) { return "存在多个报价日期的波动率!"; } //检查标的信息 if (!firstVol.UnderlyingId.HasValue) { var unCodes = vols.Select(n => n.ContractCode).ToHashSet(); var unCodeMap = DbContext.underlying_manager.Where(n => unCodes.Contains(n.UnderlyingCode)) .ToDictionary(n => n.UnderlyingCode, n => n.id); foreach (var vol in vols) { if (!unCodeMap.TryGetValue(vol.ContractCode, out var unId)) { return "标的信息未找到:" + vol.ContractCode; } vol.UnderlyingId = unId; } } //检查标的到期日期是否大于等于报价日期 var unIds = vols.Select(n => n.UnderlyingId).ToHashSet(); var maturedUns = DbContext.underlying_manager.Where(n => unIds.Contains(n.id) && n.UnderlyingInstrumentType == "CommodityFutures" && n.MaturityDate.Value < firstVol.QuotationDate) .Select(n => new { n.UnderlyingCode, MaturityDate = n.MaturityDate.Value }).ToArray(); if (maturedUns.Any()) { var arr = maturedUns.Select(n => $"{n.UnderlyingCode}(到期日:{n.MaturityDate:yyyyMMdd})").ToArray(); return $"标的到期日期需大于等于报价日期({firstVol.QuotationDate:yyyyMMdd}),到期标的:{string.Join(",", arr)}"; } //检查到期日和行权价是否一致 if (vols.Any(O => ConsVolInfos.subTradeVolType.Contains(O.VolType))) { var dict = vols.GroupBy(O => $"{O.ContractCode}").ToDictionary(K => K.Key, V => V.ToList()); var volTypeGroup = dict.Select(O => O.Value.Count()).Distinct().ToArray(); if (volTypeGroup.Length > 1) { return "列表中存在多个波动率类型!"; } if (volTypeGroup[0] == 3) { if (!CheckVolTables(vols, out var errMessage)) { return errMessage; } } else if (volTypeGroup[0] == 2) { if (!CheckVolTables(vols, out var errMessage)) { return errMessage; } foreach (var item in dict) { if (item.Value.Count != 2) { return $"列表中 {item.Key} 波动率有重复!"; } volatility vol1 = item.Value[0]; volatility vol2 = item.Value[1]; volatility vol = vol1.Clone(); vol.VolType = "交易"; var volTable = new List(); foreach (var volItem in vol2.VolTable) { var singleVol1 = vol1.VolTable.Find(O => O.Expire == volItem.Expire && O.Strike == volItem.Strike); volTable.Add(new SingleVol(volItem.Strike, volItem.Expire, ((volItem.Vol + singleVol1.Vol) / 2).FormatValue(2))); } vol.Data = volTable.ToJson(); vols.Add(vol); } } else if (volTypeGroup[0] == 1) { var dbVols = new VolatilityQueryService(OptUser).GetVolatilities(new BatchVolatilityRequest { QuotationDate = vols[0].QuotationDate, TradeVolWithBidAsk = false, UnderlyingCodes = dict.Keys, UserGroup = OptUser.UserGroup, VolType = "交易" }, false); foreach (var item in dict) { volatility tempVol = null; if ((tempVol = dbVols.FirstOrDefault(O => O.ContractCode == item.Key)) == null) { return $"系统中不存在 {item.Key} 的波动率!"; } if (!CheckVolTables(new List() { item.Value[0], tempVol }, out var errMessage)) { return errMessage; } } } } else if (vols.GroupBy(O => O.VolType).Count() > 1) { return "列表中存在多个波动率类型的波动率!"; } else if (vols.All(O => O.VolType == "交易") && !PS.Config.Is光大光子) { List tempList = new List(); foreach (var item in vols) { volatility bidVol = item.Clone(); bidVol.VolType = "报价Bid"; volatility askVol = item.Clone(); askVol.VolType = "报价Ask"; List bidVolTable = new List(); List askVolTable = new List(); foreach (var volItem in item.VolTable) { double bVol = volItem.Vol + (item.Bid_Deviation ?? 0); double aVol = volItem.Vol + (item.Ask_Deviation ?? 0); if (bVol < 0 || aVol < 0) { return "波动率偏离后不应小于0"; } bidVolTable.Add(new SingleVol(volItem.Strike, volItem.Expire, bVol)); askVolTable.Add(new SingleVol(volItem.Strike, volItem.Expire, aVol)); } bidVol.Data = bidVolTable.ToJson(); askVol.Data = askVolTable.ToJson(); tempList.Add(bidVol); tempList.Add(askVol); } vols.AddRange(tempList); } return HandleResult.Success; } /// /// 检查VolTable是否匹配 /// private bool CheckVolTables(IEnumerable vols, out string errMessage) { Dictionary> volDict = vols.GroupBy(O => $"{O.ContractCode}{O.UserGroup}{O.QuotationDate}").ToDictionary(K => K.Key, V => V.ToList()); errMessage = ""; foreach (var singleVols in volDict) { if (singleVols.Value.Count() < 2) { return true; } var first = singleVols.Value.First(); var set = first.VolTable.Select(n => n.Expire.ToUpper() + "^" + n.Strike.ToString("F6")).ToHashSet(); foreach (var item in singleVols.Value.Skip(1)) { if (first.VolTable.Count != item.VolTable.Count) { errMessage = $"{first.ContractCode} 波动率行列不匹配!"; return false; } if (!item.VolTable.All(n => set.Contains(n.Expire.ToUpper() + "^" + n.Strike.ToString("F6")))) { errMessage = $"{first.ContractCode} 行权价或期限不匹配!"; return false; } } } return true; } } }