using Qdp.ComputeService.Data.CommonModels.MarketInfos; using Qdp.ComputeService.Data.CommonModels.MarketInfos.CurveDefinitions; using Qdp.Foundation.Implementations; using Qdp.Pricing.Base.Enums; using Qdp.Pricing.Base.Implementations; using Qdp.Pricing.Library.Base.Curves.Interpolators; using Qdp.Pricing.Library.Common.Market; using Qdp.Pricing.Library.Equity.Utilities; using Qdp.Pricing.Library.Options.Products.Vanilla; using System.Text.RegularExpressions; using YLErp.Abstract.DataProviders; using YLErp.BLL.Calculation.V2; using YLErp.BLL.Calculation.V2.Parameter; using YLErp.Enums; using YLErp.Model; using YLErp.Models; using YLErp.Modules; using YLErp.Modules.CalculationModule; using YLErp.Modules.ClientModule; using YLErp.Modules.DataProviderModule; using YLErp.Modules.VolatilityModule; using YLErp.QdpModule; using YLErp.QdpModule.Constants; namespace YLErp.BLL.Calculation { public static class CalculatorHelper { static readonly IYcLogger logger; static CalculatorHelper() { logger = LogFactory.GetLogger(nameof(CalculatorHelper)); } public static InstrumentCurveDefinition CreateConstantRiskFreeCurve(string curveName, double riskFreeRate) { return QdpHelper.CreateRiskFreeCurve(curveName, riskFreeRate, valuedateBLL.SystemDate.CurveDayCount); } public static YieldCurve BuildIRCurve(string curveName, DateTime curveDate, List curveData) { if (curveData == null || curveData.Count < 2) { return null; } var rateMktData = curveData.Where(c => c.Rate > 0).Select(c => new RateMktData(c.Tenor, c.Rate / 100, "Spot", "None", curveName)).ToArray(); if (!rateMktData.Any()) { return null; } var curveDayCount = valuedateBLL.SystemDate.CurveDayCount.TrimToNull() ?? "Act365"; var curveConvention = new CurveConvention(Guid.NewGuid().ToString(), "CNY", "ModifiedFollowing", "chn", curveDayCount, "Continuous", "Linear"); var curveDefinition = new InstrumentCurveDefinition( curveName, curveConvention, rateMktData, "SpotCurve"); return PrebuiltQdpMarketProxy.BuildYieldCurve(new Date(curveDate), curveDefinition); } public static string GetTradeDayCount() { return valuedateBLL.TradeDayCount == "" ? "Act365" : valuedateBLL.TradeDayCount; } /// /// 计算一组交易的风险指标 /// /// 波动率类型 /// 用户自定义的交易波动率 /// 是否使用交易波动率 public static TradeRiskResult CalculateRisksForTrades( DateTime valueDate, IEnumerable tradeList, CalcScenarioEnum calcScenario, IPriceProvider priceProvider, PricingRequest pricingRequest, Dictionary addVolRateDic = null, string volType = "交易", SettlementTypeEnum settlementType = SettlementTypeEnum.ClosePrice, Dictionary overrideVolsForTrade = null, bool isUseTradeVol = false, bool preciseTimeMode = true, bool isAddVolPercent = true, bool canUseManual = false) { var scalc = new SimpleOtcTradeRiskCalc { AddVolRateDic = addVolRateDic, IsAddVolPercent = isAddVolPercent, IsMarginCalc = false, IsUseTradeVol = isUseTradeVol, OverrideVolsForTrade = overrideVolsForTrade, PreciseTimeMode = preciseTimeMode, PricingRequest = pricingRequest, VolType = volType, SettlementType = settlementType, CalcScenario = calcScenario, CanUseManual = canUseManual }; return scalc.CalculateRisksForTrades(valueDate, tradeList, priceProvider, calcScenario); } /// /// 计算一组交易的风险指标 /// 暂时不处理多标的交易 /// public static TradeRiskResult CalculateRisksForTrades(CalculateRisksForTradesReq req) { var scalc = new SimpleOtcTradeRiskCalc { AddVolRateDic = req.addVolRateDic, IsAddVolPercent = req.isAddVolPercent, IsMarginCalc = req.isMarginCalc, IsUseTradeVol = req.isUseTradeVol, OverrideVolsForTrade = req.overrideVolsForTrade, PreciseTimeMode = req.PreciseTimeMode, PricingRequest = req.pricingRequest, VolType = req.volType, CalcScenario = req.calcScenario, CanUseManual = req.canUseManual }; return scalc.CalculateRisksForTrades(req.valueDate, req.tradeList, req.priceProvider,req.calcScenario); } /// /// 应通过api调用 /// /// /// 波动率使用的用户组 /// /// public static List AtMoneyOptionQuotesV2(string maturityDate, string userGroup, bool onlyMainContract = true) { var atMoneyQuotes = new List(); try { var valueDate = RealtimeQuoteValueDate(); var valueDateStr = valueDate.ToString("yyyy-MM-dd"); ////最小的合约到期日,应晚于的到期日的前一个月的15日 //var oneMonthBefore = DateTime.Parse(maturityDate).AddMonths(1); //var minMaturityDate = new DateTime(oneMonthBefore.Year, oneMonthBefore.Month, 15); //根据查询valueDate查询合约列表 var maturityDateValue = DateTime.Parse(maturityDate); //用户会从OTC系统中设置修改报价合约列表,这里要强制重新从数据库中读取 underlying_main_contractBLL.IsListOld = true; var underlyings = underlying_main_contractBLL.GetDefaultAtMoneyQuoteList(maturityDateValue, onlyMainContract); var varieties = VarietyBLL.GetAllvarietyModel(); underlyings.ForEach(u => { u.Variety = varieties.FirstOrDefault(x => x.VarietyCode == u.CommodityCode); }); //计算平值期权 var qdpMarketId = Guid.NewGuid().ToString(); var marketProxy = QdpMarketManager.Instance.GetPrebuiltMarketProxy(qdpMarketId); //使用全局的DiscountCurve以提高计算效率 var discountCurveName = Guid.NewGuid().ToString(); var discountCurve = CreateConstantRiskFreeCurve(discountCurveName, valuedateBLL.RiskFreeRate / 100.0); marketProxy.AddYieldCurve(discountCurveName, valueDateStr, discountCurve); //获取当天所有波动率 var volitities = new VolatilityQueryService(OptUserInfo.SystemUser) .GetVolatilities(new BatchVolatilityRequest { QuotationDate = valueDate.Date, TradeVolWithBidAsk = true, VolType = "交易", UserGroup = userGroup, UnderlyingIds = underlyings.Select(n => n.id).ToArray() }, true); //.GetVols(valueDate.Date, new List() { "交易", "报价Bid", "报价Ask" }, underlyings.Select(O => O.UnderlyingCode).ToList(), userGroup); //根据用户设置的bid/ask天数调整规则来分别调整到期日 //TODO: bidMaturityShift和askMaturityShift应该从某数据库表读取 var bidMaturityShift = 0; var askMaturityShift = 0; var client_param = ClientPricingParamService.GetPricingParam(valueDate.Date, maturityDateValue.Date); if (client_param != null) { askMaturityShift = client_param.ask_tuning_day ?? 0; bidMaturityShift = client_param.bid_tuning_day ?? 0; } var dayCount = CalculatorHelper.GetTradeDayCount(); var bidMaturityDate = QdpCalendarHelper.ShiftDate(maturityDateValue, dayCount, bidMaturityShift).DateTime; var askMaturityDate = QdpCalendarHelper.ShiftDate(maturityDateValue, dayCount, askMaturityShift).DateTime; var buyTrade = new trade() { TradeType = "香草期权", TradeDate = valueDate, ExerciseDate = bidMaturityDate, OptionType = "看涨", ExerciseMode = "European", BuySell = "Buy", Notional = 1.0 }; var sellTrade = new trade() { TradeType = "香草期权", TradeDate = valueDate, ExerciseDate = askMaturityDate, OptionType = "看涨", ExerciseMode = "European", BuySell = "Sell", Notional = 1.0 }; var parameter = new VanillaOptionParameter() { ValueDate = valueDate, DiscountCurveName = discountCurveName, PreciseTimeMode = true }; foreach (var underlying in underlyings) { if (!underlying.Price.HasValue) { logger.Error($"{underlying.UnderlyingCode}缺少价格,无法报价"); continue; } underlying.QuotationDate = valueDate; var variety = VarietyBLL.GetAllvarietyModel().FirstOrDefault(v => v.id == underlying.UnderlyingTypeId); if (variety == null) { logger.Error($"{underlying.UnderlyingCode}没有品种,无法报价"); continue; //throw new Exception($"{underlying.UnderlyingCode}没有品种,请去设置!"); } buyTrade.SpotPrice = underlying.Price; buyTrade.UnderlyingCode = underlying.UnderlyingCode; buyTrade.UnderlyingInstrumentType = underlying.UnderlyingInstrumentType; sellTrade.SpotPrice = underlying.Price; sellTrade.UnderlyingCode = underlying.UnderlyingCode; sellTrade.UnderlyingInstrumentType = underlying.UnderlyingInstrumentType; volatility volsBid = null, volsAsk = null; if (PS.Config.ErpElement.SkewMapVolConstruction) { volsBid = volsAsk = volitities.FirstOrDefault(v => v.VolType == "交易" && v.ContractCode.ToLower() == underlying.UnderlyingCode.ToLower() && v.VolSurfaceMode == "MoneynessVol"); } else { volsBid = volitities.FirstOrDefault(v => v.VolType == "报价Bid" && v.ContractCode.ToLower() == underlying.UnderlyingCode.ToLower() && v.VolSurfaceMode == "MoneynessVol"); //volatilityBLL.GetVolsFromDb("报价Bid", underlying, "MoneynessVol"); volsAsk = volitities.FirstOrDefault(v => v.VolType == "报价Ask" && v.ContractCode.ToLower() == underlying.UnderlyingCode.ToLower() && v.VolSurfaceMode == "MoneynessVol");//volatilityBLL.GetVolsFromDb("报价Ask", underlying, "MoneynessVol"); if (volsBid == null) { volsBid = VolatilityBuilder.CreateMoneynessVolBuilder(valueDate, "报价Bid").SetUnderlying(underlying).Build(ConsVolInfos.defVol); } if (volsAsk == null) { volsAsk = VolatilityBuilder.CreateMoneynessVolBuilder(valueDate, "报价Ask").SetUnderlying(underlying).Build(ConsVolInfos.defVol); } } if (volsBid == null || volsAsk == null) { logger.Error($"{underlying.UnderlyingCode}没有波动率,无法报价"); continue; } parameter.HasNightMarket = variety.HasNightMarket; parameter.SpotPrices = new Dictionary() { { underlying.UnderlyingCode, underlying.Price ?? 0.0 } }; buyTrade.MaturityDate = underlying.MaturityDate ?? DateTime.MaxValue; buyTrade.Strike = underlying.Price ?? 0.0; var bidVol = VolatilityHelper.GetInterpolatedVol( volConstructionType: PS.Config.ErpElement.SkewMapVolConstruction ? VolConstructionType.SkewMap : VolConstructionType.Normal, volSurface: volsBid, valueDate: valueDate, underlyingCode: underlying.UnderlyingCode, exerciseDate: bidMaturityDate, strike: buyTrade.Strike.Value, isBuy: true, isCall: true, spotPrice: underlying.Price ?? 0, isMoneynessOption: false); parameter.Volatility = bidVol; if (PS.Config.Is润和) { parameter.OverrideTTM = TradeCalcHelper.CalculateTTMDays(valueDate, buyTrade.ExerciseDate.Value, underlying?.UnderlyingTypeId ?? 0, false); } var result = ValueCalculator.CalculateTradeValue(qdpMarketId, buyTrade, underlying, parameter); sellTrade.MaturityDate = underlying.MaturityDate ?? DateTime.MaxValue; sellTrade.Strike = underlying.Price ?? 0.0; var askVol = VolatilityHelper.GetInterpolatedVol( volConstructionType: PS.Config.ErpElement.SkewMapVolConstruction ? VolConstructionType.SkewMap : VolConstructionType.Normal, volSurface: volsAsk, valueDate: valueDate, underlyingCode: underlying.UnderlyingCode, exerciseDate: askMaturityDate, strike: sellTrade.Strike.Value, isBuy: false, isCall: true, spotPrice: underlying.Price ?? 0, isMoneynessOption: false); parameter.Volatility = askVol; if (PS.Config.Is润和) { parameter.OverrideTTM = TradeCalcHelper.CalculateTTMDays(valueDate, sellTrade.ExerciseDate.Value, underlying?.UnderlyingTypeId ?? 0, false); } var result2 = ValueCalculator.CalculateTradeValue(qdpMarketId, sellTrade, underlying, parameter); if (result != null && result2 != null) { var record = new AtMoneyOptionQuoteRecord() { marketCode = underlying.MarketCode, marketName = underlying.MarketName, underlyingType = underlying.UnderlyingType, underlyingTypeShortName = underlying.Variety != null ? underlying.Variety.ShortName : underlying.UnderlyingType, underlyingCode = underlying.UnderlyingCode, spotPrice = underlying.Price.Normalize(), comparedToLastDayPrice = (underlying.PrevClosePrice.HasValue && underlying.Price.HasValue) ? (underlying.Price.Normalize() > underlying.PrevClosePrice.Normalize()) ? 1 : ((underlying.Price.Normalize() < underlying.PrevClosePrice.Normalize()) ? -1 : 0) : 1, buyOptionPrice = Math.Abs(result.Pv).Normalize(), buyOptionVol = result.Vol.Normalize(), sellOptionPrice = Math.Abs(result2.Pv).Normalize(), sellOptionVol = result2.Vol.Normalize() }; atMoneyQuotes.Add(record); } } QdpMarketManager.Instance.RemovePrebuiltMarketProxy(qdpMarketId); } catch (Exception ex) { logger.Error("AtMoneyOptionQuotes", ex); } return atMoneyQuotes; } /// /// 计算平值报价的默认到期日,仅用于手机实时报价。 /// 报价预览页面的到期日计算请参见函数FlatPriceQuotationBLL.GetAtmOptionQuotePreviewMaturity /// public static string RealtimeQuoteDefaultMaturityDate() { var valueDate = RealtimeQuoteValueDate(); // 1个月平值期权的到期日是报价日后一个月减一天 var maturityDate = valueDate.AddMonths(1).AddDays(-1); return AtmQuoteMaturityDateShift(maturityDate).ToString(); } /// /// 计算平值报价的最大到期日,仅用于光大光子手机实时报价。 /// /// public static string RealtimeQuoteMaxMaturityDate() { var date = DateTime.MaxValue; var term = valuedateBLL.SystemDate.QuotationCycle; if (string.IsNullOrWhiteSpace(term)) { term = "3M"; } var m = Regex.Match(term, @"^(?\d+)(?[D|W|M|Y|d|w|m|y])$"); if (m.Success) { var num = int.Parse(m.Groups["num"].Value); switch (m.Groups["unit"].Value.ToUpper()) { case "D": date = DateTime.Now.AddDays(num).AddDays(-1); break; case "W": date = DateTime.Now.AddDays(num * 7).AddDays(-1); break; case "M": date = DateTime.Now.AddMonths(num).AddDays(-1); break; case "Y": date = DateTime.Now.AddYears(num).AddDays(-1); break; } date = QdpCalendarHelper.GetNonHolidayDefore(date); } return date.ToString("yyyy-MM-dd"); } /// /// 根据AtmQuotePreviousBizDayAdjust参数对报价到期日进行调整 /// /// 原到期日 /// 调整后的到期日 public static Date AtmQuoteMaturityDateShift(DateTime date) { return AtmQuoteMaturityDateShift(new Date(date)); } /// /// 根据AtmQuotePreviousBizDayAdjust参数对报价到期日进行调整 /// /// 原到期日 /// 调整后的到期日 private static Date AtmQuoteMaturityDateShift(Date date) { var calendar = CalendarImpl.Get("chn"); // 如果那天是非交易日,根据参数往前或往后移 return calendar.IsBizDay(date) ? date : (PS.Config.ErpElement.AtmQuotePreviousBizDayAdjust ? calendar.PrevBizDay(date) : calendar.NextBizDay(date)); } /// /// 对实时报价,计算报价日 /// /// public static DateTime RealtimeQuoteValueDate() { var calendar = CalendarImpl.Get("chn"); var now = DateTime.Now; var qdpValueDate = new Date(now); if (now.Hour >= 21 || QdpCalendarHelper.IsHoliday(now.Date)) { // 如果已经是夜盘开盘或者当前为节假日,则认为报价日期是下一个交易日 qdpValueDate = calendar.NextBizDay(qdpValueDate); } return qdpValueDate.DateTime; } /// /// 根据合约的到期日和合约代码来获取在报价中所使用的合约到期日 /// 主要是为了处理像SC2001这样的合约 /// 合约代码 | 实时到期日 | 代码月份 | 在报价中使用的到期日 /// RB1912 | 2019-12-15 | 12 | 2019-12-15 /// RB2001 | 2020-01-15 | 1 | 2020-01-15 /// SC1912 | 2019-11-20 | 12 | 2019-11-20 /// SC2001 | 2019-12-20 | 1 | 2020-01-20 /// /// 合约实际到期日 /// 合约代码 /// public static DateTime GetContractMaturityDateForQuote(DateTime contractMaturityDate, string contractCode) { int.TryParse(contractCode.Substring(contractCode.Length - 2), out var monthInCode); if (monthInCode == contractMaturityDate.Month) { return contractMaturityDate; } else if (monthInCode < 1 || monthInCode > 12) { LogFactory.GetLogger("GetContractMaturityDateForQuote").Error($"未能正确解析:{contractMaturityDate} -- {contractCode}"); return contractMaturityDate; } else { var year = contractMaturityDate.Year + (monthInCode == 1 ? 1 : 0); var days = DateTime.DaysInMonth(year, monthInCode); return new DateTime(year, monthInCode, days < contractMaturityDate.Day ? days : contractMaturityDate.Day); } } /// /// 为特定的波动率曲面设置自定义波动率 /// /// /// /// /// public static VolSurfaceInitParams PrepareOverrideVol(DateTime valueDate, string userId, string volSurfaceNameKey, double vol) { //用自定义波动率值构造一个水平的波动率曲面 var vols = new List { new SingleVol { Strike = 1, Expire = "1D", Vol = vol }, new SingleVol { Strike = 1, Expire = "1Y", Vol = vol } }; var initParam = new VolSurfaceInitParamsBuilder(userId) .SetValueDate(valueDate).Build("MoneynessVol", vols, volSurfaceNameKey); VolSurfaceInitializerSingleton.GetInitializer(true).InitializeMarketProxy(initParam); return initParam; } public static string GenerateVolSurfaceName(this trade trade, string secondUnderlyingCode = null) { return string.IsNullOrWhiteSpace(secondUnderlyingCode) ? $"{trade.id}{QdpVolHelper.VOL_SURFACE_SUFFIX}" : $"{trade.id}_{secondUnderlyingCode}{QdpVolHelper.VOL_SURFACE_SUFFIX}"; } public static string GenerateVolSurfaceName(this underlying_manager underlying) { return $"{underlying.UnderlyingCode}{QdpVolHelper.VOL_SURFACE_SUFFIX}"; } /// /// 根据传入的香草期权组合,生成Payoff图的关键点 /// /// 期权交易组合 /// public static List GetPayoffPlotPoints(IEnumerable trades) { var options = new List(); foreach (var trade in trades) { var option = ConvertTradeForPayoffCalc(trade); if (option == null) { throw new Exception("只支持香草期权组合的Payoff图形展示."); } options.Add(option); } return PayoffPlotHelper.GetPayoffPoints(options); } /// /// 将trade对象转换为OptionTradeForPayoff对象,用作展现Payoff图 /// /// /// private static OptionTradeForPayoff ConvertTradeForPayoffCalc(trade trade) { if (trade.TradeType != "香草期权") { return null; } var startDate = new Date(trade.StartDate.Value); var endDate = new Date(trade.ExerciseDate.Value); var vanillaOption = new VanillaOption( startDate, endDate, OptionExercise.European, trade.CallPut == "Put" ? OptionType.Put : OptionType.Call, trade.IsMoneynessOptionData ? (trade.Strike ?? 0.0) * (trade.SpotPrice ?? 0.0) : (trade.Strike ?? 0.0), (InstrumentType)Enum.Parse(typeof(InstrumentType), trade.UnderlyingInstrumentType), CalendarImpl.Get("chn"), new Bus244(), CurrencyCode.CNY, CurrencyCode.CNY, new Date[] { endDate }, new Date[] { endDate }, trade.Notional, optionPremium: trade.TradePrice ?? 0.0); return new OptionTradeForPayoff() { Option = vanillaOption, BuySell = trade.BuySell == "卖出" ? Position.Sell : Position.Buy }; } /// /// 根据UnderyingCode来判断获取商品现货价格 /// /// /// /// /// public static double GetCommoditySpotPriceByValueDate(DateTime valueDate, DateTime maturityDate, bool isClosePrice, string UnderlyingCode) { double CommoditySpotPrice = 0; using (var db = new YLContext()) { var BasisCurveList = new List(); var underlyingcodelist = new List(); var tenors = new List(); var spreads = new List(); var GetBasisCurve = db.underlying_basis_curve.FirstOrDefault(c => c.UnderlyingCode == UnderlyingCode); if (GetBasisCurve == null) { if (isClosePrice) { EodPriceQueryService.TryGetClosePrice(valueDate, UnderlyingCode, out CommoditySpotPrice); } else { DataCacheProvider.GetUnderlyingDataSource().TryGetPrice(UnderlyingCode, out CommoditySpotPrice); } } else { BasisCurveList = JsonHelper.Deserialize>(GetBasisCurve.InterpolationData); foreach (var item in BasisCurveList) { underlyingcodelist.Add(item.UnderlyingCodes); tenors.Add(item.term); spreads.Add(item.BasisCurve); } CommoditySpotPrice = GetSpotPriceFromBasisSpread(valueDate, maturityDate, isClosePrice, underlyingcodelist, tenors, spreads); } } return CommoditySpotPrice; } /// /// 根据期货价格和基差曲线计算现货价格 /// /// 估值日 /// 交易到期日 /// 期货价格 /// 基差曲线的关键期限列表 /// 基差曲线的基差列表 /// public static double GetSpotPriceFromBasisSpread(DateTime valueDate, DateTime maturityDate, bool isClosePrice, List underlyingcodelist, List tenors, List spreads) { if (tenors == null || spreads == null || tenors.Count != spreads.Count) { return 0; } var qdpStartDate = new Date(valueDate); var keyTs = tenors.Select(t => (double)((new Term(t).Next(qdpStartDate)).DateTime - valueDate).Days).ToArray(); var keyPoints = keyTs.Zip(spreads, (x, y) => Tuple.Create(x, y)).ToArray(); var codelist = underlyingcodelist.ToArray(); var underlyingcode = ""; var days = (maturityDate - valueDate).Days; for (var i = 0; i < keyPoints.Length; i++) { if (days <= keyPoints[i].Item1 && (i == 0 || i > 0 && days > keyPoints[i - 1].Item1)) { underlyingcode = codelist[i]; } } double CommoditySpotPrice = 0; if (isClosePrice) { EodPriceQueryService.TryGetClosePrice(valueDate, underlyingcode, out CommoditySpotPrice); } else { DataCacheProvider.GetUnderlyingDataSource().TryGetPrice(underlyingcode, out CommoditySpotPrice); } var interpolator = new LinearInterpolator(keyPoints); return (CommoditySpotPrice + interpolator.GetValue(days)); } } public class SpreadOptionPricingInput { public underlying_manager[] Underlyings { get; set; } public double[] SpotPrices { get; set; } public double[] Correlations { get; set; } } #region T型报价的结果类 public class AtMoneyOptionQuoteRecord { public string marketCode { get; set; } public string marketName { get; set; } public string underlyingType { get; set; } public string underlyingTypeShortName { get; set; } public string underlyingCode { get; set; } public double spotPrice { get; set; } public int comparedToLastDayPrice { get; set; } public double buyOptionPrice { get; set; } public double sellOptionPrice { get; set; } public double buyOptionVol { get; set; } public double sellOptionVol { get; set; } } #endregion [Serializable] public class SimpleIRCurvePoint { public string Tenor { get; set; } public double Rate { get; set; } } }