using YLErp.BLL; using YLErp.BLL.Calculation; using YLErp.BLL.Calculation.V2; using YLErp.BLL.Calculation.V2.Parameter; using YLErp.Commons; using YLErp.Model; using YLErp.Modules.CalculationModule; using YLErp.Modules.ClientModule; using YLErp.Modules.VolatilityModule; using YLErp.QdpModule; namespace YLErp.Modules.TQuoteModule { /// /// /// public class TQuoteService : YLBaseService { public TQuoteService(OptUserInfo optUser) : base(optUser) { } public TQuoteResult TQuoteWithCustomizedVol(TQuoteWithCustomizedVolRequest request) { if (request == null || request.Records == null) { return new TQuoteResult(); } var valueDate = CalculatorHelper.RealtimeQuoteValueDate(); var valueDateStr = valueDate.ToString("yyyy-MM-dd"); underlying_manager underlying = null; Variety variety = null; using (var db = new YLContext()) { underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(request.UnderlyingCode); variety = db.variety.FirstOrDefault(x => x.VarietyCode != null && x.id == underlying.UnderlyingTypeId); } if (underlying == null) { return new TQuoteResult(); } var qdpMarketId = Guid.NewGuid().ToString(); var marketProxy = QdpMarketManager.Instance.GetPrebuiltMarketProxy(qdpMarketId); //使用全局的DiscountCurve以提高计算效率 var discountCurveName = Guid.NewGuid().ToString(); var discountCurve = CalculatorHelper.CreateConstantRiskFreeCurve(discountCurveName, valuedateBLL.RiskFreeRate / 100.0); marketProxy.AddYieldCurve(discountCurveName, valueDateStr, discountCurve); var tQuoteResult = new TQuoteResult { quotaList = new List() }; //根据用户设置的bid/ask天数调整规则来分别调整到期日 //TODO: bidMaturityShift和askMaturityShift应该从某数据库表读取 var bidMaturityShift = 0; var askMaturityShift = 0; var otherInfo = ""; var client_param = ClientPricingParamService.GetPricingParam(valueDate.Date, Convert.ToDateTime(request.MaturityDate).Date); if (client_param != null) { askMaturityShift = client_param.ask_tuning_day ?? 0; bidMaturityShift = client_param.bid_tuning_day ?? 0; otherInfo = client_param.ToJson();//$"ask到期日偏离{askMaturityShift}天,bid到期日偏离{bidMaturityShift}天"; } var dayCount = CalculatorHelper.GetTradeDayCount(); var bidMaturityDate = QdpCalendarHelper.ShiftDate(request.MaturityDate, dayCount, bidMaturityShift); var askMaturityDate = QdpCalendarHelper.ShiftDate(request.MaturityDate, dayCount, askMaturityShift); var hasNightMarket = (variety?.HasNightMarket) ?? false; var trade = new trade() { TradeType = "香草期权", TradeDate = valueDate, MaturityDate = DateTime.Parse(bidMaturityDate), ExerciseDate = DateTime.Parse(bidMaturityDate), OptionType = "看涨", ExerciseMode = "European", BuySell = "Buy", Notional = 1, UnderlyingCode = request.UnderlyingCode, UnderlyingInstrumentType = underlying.UnderlyingInstrumentType }; var parameter = new VanillaOptionParameter() { ValueDate = valueDate, DiscountCurveName = discountCurveName, SpotPrices = new Dictionary { { underlying.UnderlyingCode, request.SpotPrice } }, HasNightMarket = hasNightMarket, PreciseTimeMode = true }; foreach (var record in request.Records) { trade.OptionType = "看涨"; trade.Strike = record.Strike; parameter.Volatility = record.CallBidVol; var callBidResult = ValueCalculator.CalculateTradeValue(qdpMarketId, trade, underlying, parameter); trade.OptionType = "看跌"; trade.Strike = record.Strike; parameter.Volatility = record.PutBidVol; var putBidResult = ValueCalculator.CalculateTradeValue(qdpMarketId, trade, underlying, parameter); trade.OptionType = "看涨"; trade.Strike = record.Strike; parameter.Volatility = record.CallAskVol; var callAskResult = ValueCalculator.CalculateTradeValue(qdpMarketId, trade, underlying, parameter); trade.OptionType = "看跌"; trade.Strike = record.Strike; parameter.Volatility = record.PutAskVol; var putAskResult = ValueCalculator.CalculateTradeValue(qdpMarketId, trade, underlying, parameter); tQuoteResult.quotaList.Add(new TQuoteRecord() { Strike = record.Strike, CallBidPrice = callBidResult.Pv, CallBidPricePercent = underlying.Price.HasValue ? callBidResult.Pv / underlying.Price.Value : 0.0, CallBidVol = callBidResult.Vol, PutBidPrice = putBidResult.Pv, PutBidPricePercent = underlying.Price.HasValue ? putBidResult.Pv / underlying.Price.Value : 0.0, PutBidVol = putBidResult.Vol, CallAskPrice = callAskResult.Pv, CallAskPricePercent = underlying.Price.HasValue ? callAskResult.Pv / underlying.Price.Value : 0.0, CallAskVol = callAskResult.Vol, PutAskPrice = putAskResult.Pv, PutAskPricePercent = underlying.Price.HasValue ? putAskResult.Pv / underlying.Price.Value : 0.0, PutAskVol = putAskResult.Vol, }); } QdpMarketManager.Instance.RemovePrebuiltMarketProxy(qdpMarketId); return tQuoteResult; } public TQuoteResult Execute(TQuoteRequest quoteRequest, string userGroup = "") { try { var valueDate = CalculatorHelper.RealtimeQuoteValueDate(); if (quoteRequest.maturityDate < valueDate) { return new TQuoteResult() { Info = "到期日小于估值日", StatusCode = -1 }; } var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(quoteRequest.underlyingCode); if (underlying == null) { return null; } var valueDateStr = valueDate.ToString("yyyy-MM-dd"); var actualStrikeInteranl = quoteRequest.strikeInterval ?? 100; var actualStrikeAccount = quoteRequest.strikeCount ?? 11; Variety variety = null; if (underlying != null) { variety = DataCacheProvider.GetVarietyDataSource().GetData(underlying.UnderlyingTypeId); if (!quoteRequest.strikeInterval.HasValue) { using (var db = DbContextFactory.GetYLDbContext()) { var contract = db.underlying_main_contract.FirstOrDefault(x => x.UnderlyingType == underlying.UnderlyingType); if (contract != null && contract.StrikeInterval.HasValue) { actualStrikeInteranl = contract.StrikeInterval.Value; } } } } if (quoteRequest.spotPrice.HasValue) { underlying.Price = quoteRequest.spotPrice; } if (underlying?.Price == null || underlying.Price < 0.0001) { return new TQuoteResult() { Info = "价格无效", StatusCode = -1 }; } underlying.QuotationDate = valueDate; var hasNightMarket = (variety?.HasNightMarket) ?? false; var strikes = GetStrikes(underlying.Price ?? 0, actualStrikeInteranl, actualStrikeAccount); //根据用户设置的bid/ask天数调整规则来分别调整到期日 //TODO: bidMaturityShift和askMaturityShift应该从某数据库表读取 var bidMaturityShift = 0; var askMaturityShift = 0; var otherInfo = ""; var client_param = ClientPricingParamService.GetPricingParam(valueDate.Date, quoteRequest.maturityDate); if (client_param != null) { askMaturityShift = client_param.ask_tuning_day ?? 0; bidMaturityShift = client_param.bid_tuning_day ?? 0; otherInfo = client_param.ToJson(); // $"ask到期日偏离{askMaturityShift}天,bid到期日偏离{bidMaturityShift}天"; } var dayCount = CalculatorHelper.GetTradeDayCount(); //查询 var bidMaturityDate = QdpCalendarHelper.ShiftDate(quoteRequest.maturityDate, dayCount, bidMaturityShift).DateTime; var askMaturityDate = QdpCalendarHelper.ShiftDate(quoteRequest.maturityDate, dayCount, askMaturityShift).DateTime; var qdpMarketId = Guid.NewGuid().ToString(); var marketProxy = QdpMarketManager.Instance.GetPrebuiltMarketProxy(qdpMarketId); //使用全局的DiscountCurve以提高计算效率 var discountCurveName = Guid.NewGuid().ToString(); var discountCurve = CalculatorHelper.CreateConstantRiskFreeCurve(discountCurveName, valuedateBLL.RiskFreeRate / 100.0); marketProxy.AddYieldCurve(discountCurveName, valueDateStr, discountCurve); trade newTrade(int tradeId, double strike, DateTime exerciseDate, string buySell, string optionType) { return new trade { id = tradeId, TradeType = "香草期权", TradeDate = valueDate, ExerciseMode = "European", Notional = 1, BuySell = buySell, OptionType = optionType, Strike = strike, SpotPrice = underlying.Price, UnderlyingCode = quoteRequest.underlyingCode, UnderlyingInstrumentType = underlying.UnderlyingInstrumentType, MaturityDate = exerciseDate, ExerciseDate = exerciseDate, }; } if (PS.Config.ErpElement.SkewMapVolConstruction) { var vol = VolatilityHelper.GetVol(DateTime.Today, "交易", underlying.UnderlyingCode, userGroup); var parameter = new VanillaOptionParameter() { ValueDate = valueDate, SpotPrices = new Dictionary { { underlying.UnderlyingCode, underlying.Price ?? 0.0 } }, DiscountCurveName = discountCurveName, PreciseTimeMode = true, HasNightMarket = hasNightMarket }; var tQuoteResult = new TQuoteResult() { OtherInfo = otherInfo }; tQuoteResult.quotaList = new List(); foreach (var strike in strikes) { var buyCallTrade = newTrade(0, strike, bidMaturityDate, "买入", "看涨"); parameter.Volatility = SkewMapVolHelper.GetInterpolatedVol( volSurface: vol, valueDate: valueDate, underlyingCode: underlying.UnderlyingCode, exerciseDate: bidMaturityDate, strikePrice: strike, isBuy: true, isCall: true, spotPrice: underlying.Price ?? 0); var result = ValueCalculator.CalculateTradeValue(qdpMarketId, buyCallTrade, underlying, parameter); var resultRecord = new TQuoteRecord() { Strike = strike, CallBidPrice = Math.Abs(result.Pv), CallBidPricePercent = underlying.Price.HasValue ? Math.Abs(result.Pv) / underlying.Price.Value : 0.0, CallBidVol = result.Vol, }; var buyPutTrade = newTrade(0, strike, bidMaturityDate, "买入", "看跌"); parameter.Volatility = SkewMapVolHelper.GetInterpolatedVol( volSurface: vol, valueDate: valueDate, underlyingCode: underlying.UnderlyingCode, exerciseDate: bidMaturityDate, strikePrice: strike, isBuy: true, isCall: false, spotPrice: underlying.Price ?? 0); result = ValueCalculator.CalculateTradeValue(qdpMarketId, buyPutTrade, underlying, parameter); resultRecord.PutBidPrice = Math.Abs(result.Pv); resultRecord.PutBidPricePercent = underlying.Price.HasValue ? Math.Abs(result.Pv) / underlying.Price.Value : 0.0; resultRecord.PutBidVol = result.Vol; var sellCallTrade = newTrade(0, strike, askMaturityDate, "卖出", "看涨"); parameter.Volatility = SkewMapVolHelper.GetInterpolatedVol( volSurface: vol, valueDate: valueDate, underlyingCode: underlying.UnderlyingCode, exerciseDate: askMaturityDate, strikePrice: strike, isBuy: false, isCall: true, spotPrice: underlying.Price ?? 0); result = ValueCalculator.CalculateTradeValue(qdpMarketId, sellCallTrade, underlying, parameter); resultRecord.CallAskPrice = Math.Abs(result.Pv); resultRecord.CallAskPricePercent = underlying.Price.HasValue ? Math.Abs(result.Pv) / underlying.Price.Value : 0.0; resultRecord.CallAskVol = result.Vol; var sellPutTrade = newTrade(0, strike, askMaturityDate, "卖出", "看跌"); parameter.Volatility = SkewMapVolHelper.GetInterpolatedVol( volSurface: vol, valueDate: valueDate, underlyingCode: underlying.UnderlyingCode, exerciseDate: askMaturityDate, strikePrice: strike, isBuy: false, isCall: false, spotPrice: underlying.Price ?? 0); result = ValueCalculator.CalculateTradeValue(qdpMarketId, sellPutTrade, underlying, parameter); resultRecord.PutAskPrice = Math.Abs(result.Pv); resultRecord.PutAskPricePercent = underlying.Price.HasValue ? Math.Abs(result.Pv) / underlying.Price.Value : 0.0; resultRecord.PutAskVol = result.Vol; tQuoteResult.quotaList.Add(resultRecord); } tQuoteResult.LatestPrice = underlying.Price.Value; if (underlying.PrevClosePrice.HasValue && underlying.PrevClosePrice.Value > 0.0) { tQuoteResult.Change = OtcFormatHelper.FormatValue(underlying.Price.Value - underlying.PrevClosePrice.Value, 2); tQuoteResult.ChangePercent = OtcFormatHelper.FormatValue(tQuoteResult.Change / underlying.PrevClosePrice.Value, 4); } else { tQuoteResult.Change = 0.0; tQuoteResult.ChangePercent = 0.0; } QdpMarketManager.Instance.RemovePrebuiltMarketProxy(qdpMarketId); return tQuoteResult; } else { var buyCallTrades = new List(); var buyPutTrades = new List(); var sellCallTrades = new List(); var sellPutTrades = new List(); var tradeId = 0; foreach (var strike in strikes) { buyCallTrades.Add(newTrade(tradeId, strike, bidMaturityDate, "买入", "看涨")); buyPutTrades.Add(newTrade(tradeId, strike, bidMaturityDate, "买入", "看跌")); sellCallTrades.Add(newTrade(tradeId, strike, askMaturityDate, "卖出", "看涨")); sellPutTrades.Add(newTrade(tradeId, strike, askMaturityDate, "卖出", "看跌")); ++tradeId; } var bidVol = VolatilityHelper.GetVol(DateTime.Today, "报价Bid", underlying.UnderlyingCode, userGroup); var askVol = VolatilityHelper.GetVol(DateTime.Today, "报价Ask", underlying.UnderlyingCode, userGroup); var parameter = new VanillaOptionParameter() { ValueDate = valueDate, DiscountCurveName = discountCurveName, SpotPrices = new Dictionary() { { underlying.UnderlyingCode, underlying.Price ?? 0 } }, PreciseTimeMode = true, HasNightMarket = hasNightMarket }; if (PS.Config.Is润和) { parameter.OverrideTTM = TradeCalcHelper.CalculateTTMDays(valueDate, bidMaturityDate, 0, false); } var callBidResults = ValueCalculator.CalculateOptionsWithSharedVolSurface( qdpMarketId, buyCallTrades, underlying, parameter, bidVol, bidMaturityDate); var putBidResults = ValueCalculator.CalculateOptionsWithSharedVolSurface( qdpMarketId, buyPutTrades, underlying, parameter, bidVol, bidMaturityDate); if (PS.Config.Is润和) { parameter.OverrideTTM = TradeCalcHelper.CalculateTTMDays(valueDate, askMaturityDate, 0, false); } var callAskResults = ValueCalculator.CalculateOptionsWithSharedVolSurface( qdpMarketId, sellCallTrades, underlying, parameter, askVol, askMaturityDate); var putAskResults = ValueCalculator.CalculateOptionsWithSharedVolSurface( qdpMarketId, sellPutTrades, underlying, parameter, askVol, askMaturityDate); var tQuoteResult = new TQuoteResult() { OtherInfo = otherInfo }; tQuoteResult.quotaList = new List(); for (var i = 0; i < strikes.Length; ++i) { tQuoteResult.quotaList.Add(new TQuoteRecord() { Strike = strikes[i], CallAskPrice = Math.Abs(callAskResults[i].Pv), CallAskPricePercent = underlying.Price.HasValue ? Math.Abs(callAskResults[i].Pv) / underlying.Price.Value : 0.0, CallAskVol = callAskResults[i].Vol, CallBidPrice = Math.Abs(callBidResults[i].Pv), CallBidPricePercent = underlying.Price.HasValue ? Math.Abs(callBidResults[i].Pv) / underlying.Price.Value : 0.0, CallBidVol = callBidResults[i].Vol, PutAskPrice = Math.Abs(putAskResults[i].Pv), PutAskPricePercent = underlying.Price.HasValue ? Math.Abs(putAskResults[i].Pv) / underlying.Price.Value : 0.0, PutAskVol = putAskResults[i].Vol, PutBidPrice = Math.Abs(putBidResults[i].Pv), PutBidPricePercent = underlying.Price.HasValue ? Math.Abs(putBidResults[i].Pv) / underlying.Price.Value : 0.0, PutBidVol = putBidResults[i].Vol }); } tQuoteResult.LatestPrice = underlying.Price.Value; if (underlying.PrevClosePrice.HasValue && underlying.PrevClosePrice.Value > 0.0) { tQuoteResult.Change = OtcFormatHelper.FormatValue(underlying.Price.Value - underlying.PrevClosePrice.Value, 2); tQuoteResult.ChangePercent = OtcFormatHelper.FormatValue(tQuoteResult.Change / underlying.PrevClosePrice.Value, 4); } else { tQuoteResult.Change = 0.0; tQuoteResult.ChangePercent = 0.0; } QdpMarketManager.Instance.RemovePrebuiltMarketProxy(qdpMarketId); return tQuoteResult; } } catch (Exception ex) { LogFactory.GetLogger().Error("TQuote", ex); return new TQuoteResult() { Info = ex.Message, StatusCode = -1 }; } } private double[] GetStrikes(double spotPrice, int strikeInterval, int strikeCount = 11, bool isAsc = true) { // 强制将行权价个数调整为奇数 if (strikeCount % 2 == 0) { strikeCount += 1; } var minInterval = strikeInterval >= 10 ? 10 : strikeInterval; if (strikeCount <= 0) { throw new Exception($"非法的行权价个数{strikeCount}"); } var strikes = new double[strikeCount]; var midIndex = (strikeCount - 1) / 2; strikes[midIndex] = spotPrice; var baseStrike = (int)strikes[midIndex]; var priceDigit = (int)strikes[midIndex] % minInterval; //OTC-4700: //当报价间隔大于现价的个位数时,才按10位取整。否则,会出现strike序列错误 //例如AU1812现价269.60,报价间隔3。如果不做以下判断,baseStrike会是260 //这样会出现257,269.30,263,266这样错误的序列 if (strikeInterval > priceDigit) { baseStrike -= priceDigit; } if (isAsc) { for (var i = midIndex + 1; i < strikeCount; ++i) { strikes[i] = baseStrike + strikeInterval; baseStrike += strikeInterval; } if (strikeInterval > priceDigit) { baseStrike = (int)strikes[midIndex] - priceDigit; } for (var i = midIndex - 1; i >= 0; --i) { strikes[i] = baseStrike - strikeInterval; baseStrike -= strikeInterval; } } else { for (var i = midIndex - 1; i >= 0; --i) { strikes[i] = baseStrike + strikeInterval; baseStrike += strikeInterval; } if (strikeInterval > priceDigit) { baseStrike = (int)strikes[midIndex] - priceDigit; } for (var i = midIndex + 1; i < strikeCount; ++i) { strikes[i] = baseStrike - strikeInterval; baseStrike -= strikeInterval; } } return strikes; } } }