using YLErp.BLL; using YLErp.BLL.Calculation; using YLErp.BLL.Calculation.V2; using YLErp.BLL.Calculation.V2.Parameter; using YLErp.Modules.ClientModule; using YLErp.Modules.VolatilityModule; using YLErp.QdpModule; namespace YLErp.Modules.TQuoteModule { /// /// WebApi-QdpCalculationController-helper /// public static class QdpCalculationApiHelpere { /// /// 计算某一个期权的买卖价格 /// public static ClientOptionQuoteResult ValueOptionV2(OptionValueRequest req, string userGroup = null) { try { var valueDate = req.ValueDate ?? CalculatorHelper.RealtimeQuoteValueDate(); if (req.MaturityDate < valueDate) { return new ClientOptionQuoteResult() { Info = "到期日小于估值日", StatusCode = -1 }; } var qdpMarketId = Guid.NewGuid().ToString(); var marketProxy = QdpMarketManager.Instance.GetPrebuiltMarketProxy(qdpMarketId); volatility bidVol = null, askVol = null; underlying_manager underlying = null; Variety variety = null; using (var db = DbContextFactory.GetYLDbContext()) { underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(req.UnderlyingCode); if (underlying != null) { underlying.QuotationDate = valueDate; if (PS.Config.ErpElement.SkewMapVolConstruction) { bidVol = askVol = VolatilityHelper.GetVol(valueDate.Date, "交易", underlying.UnderlyingCode, userGroup ?? ""); } else { bidVol = VolatilityHelper.GetVol(valueDate.Date, "报价Bid", underlying.UnderlyingCode, userGroup ?? ""); askVol = VolatilityHelper.GetVol(valueDate.Date, "报价Ask", underlying.UnderlyingCode, userGroup ?? ""); } variety = db.variety.FirstOrDefault(x => x.VarietyCode != null && x.id == underlying.UnderlyingTypeId); } } if (underlying != null && bidVol != null && askVol != null) { //var maturityDate = req.MaturityDate.ToString("yyyy-MM-dd"); //根据用户设置的bid/ask天数调整规则来分别调整到期日 //TODO: bidMaturityShift和askMaturityShift应该从某数据库表读取 var bidMaturityShift = 0; var askMaturityShift = 0; var otherInfo = ""; var client_param = ClientPricingParamService.GetPricingParam(valueDate.Date, req.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(req.MaturityDate, dayCount, bidMaturityShift).DateTime; var askMaturityDate = QdpCalendarHelper.ShiftDate(req.MaturityDate, dayCount, askMaturityShift).DateTime; //使用全局的DiscountCurve以提高计算效率 var discountCurveName = Guid.NewGuid().ToString(); var discountCurve = CalculatorHelper.CreateConstantRiskFreeCurve(discountCurveName, valuedateBLL.RiskFreeRate / 100.0); marketProxy.AddYieldCurve(discountCurveName, valueDate.ToString("yyyy-MM-dd"), discountCurve); var volSurfaceName = underlying.GenerateVolSurfaceName(); var parameter = new VanillaOptionParameter() { ValueDate = valueDate, SpotPrices = new Dictionary() { { req.UnderlyingCode, req.SpotPrice } }, DiscountCurveName = discountCurveName, HasNightMarket = variety.HasNightMarket, PreciseTimeMode = req.commodityFuturesPreciseTimeMode }; var trade = new trade() { TradeType = "香草期权", TradeDate = valueDate, MaturityDate = bidMaturityDate, ExerciseDate = bidMaturityDate, OptionType = req.OptionType, ExerciseMode = req.Exercise, BuySell = "Buy", Strike = req.Strike, Notional = req.Notional, UnderlyingCode = req.UnderlyingCode }; parameter.Volatility = VolatilityHelper.GetInterpolatedVol( volConstructionType: PS.Config.ErpElement.SkewMapVolConstruction ? VolConstructionType.SkewMap : VolConstructionType.Normal, volSurface: bidVol, valueDate: valueDate, underlyingCode: req.UnderlyingCode, exerciseDate: bidMaturityDate, strike: req.Strike, isBuy: true, isCall: req.OptionType == "Call", spotPrice: req.SpotPrice, isMoneynessOption: false); var bidResult = ValueCalculator.CalculateTradeValue(qdpMarketId, trade, underlying, parameter); trade.MaturityDate = askMaturityDate; trade.ExerciseDate = askMaturityDate; parameter.Volatility = VolatilityHelper.GetInterpolatedVol( volConstructionType: PS.Config.ErpElement.SkewMapVolConstruction ? VolConstructionType.SkewMap : VolConstructionType.Normal, volSurface: askVol, valueDate: valueDate, underlyingCode: req.UnderlyingCode, exerciseDate: askMaturityDate, strike: req.Strike, isBuy: false, isCall: req.OptionType == "Call", spotPrice: req.SpotPrice, isMoneynessOption: false); var askResult = ValueCalculator.CalculateTradeValue(qdpMarketId, trade, underlying, parameter); var valueResult = new ClientOptionQuoteResult() { BuyQuote = Math.Abs(bidResult.Pv) / req.Notional, BuyVol = bidResult.Vol, BuyPv = Math.Abs(bidResult.Pv), BuyPercentageQuote = Math.Abs(bidResult.Pv) / req.Notional / req.SpotPrice, SellQuote = Math.Abs(askResult.Pv) / req.Notional, SellVol = askResult.Vol, SellPv = Math.Abs(askResult.Pv), SellPercentageQuote = Math.Abs(askResult.Pv) / req.Notional / req.SpotPrice, SellMargin = CommodityFutureOptionMargin( Math.Abs(askResult.Pv), req.Notional, req.Strike, req.SpotPrice, variety.Margin ?? 0.0, //避免品种预付金未设置而导致计算出错 req.OptionType) }; QdpMarketManager.Instance.RemovePrebuiltMarketProxy(qdpMarketId); return valueResult; } else { LogFactory.GetLogger().Error($"ValueOptionV2: 未找到标的{req.UnderlyingCode}或其波动率为空!"); return null; } } catch (Exception ex) { LogFactory.GetLogger().Error("ValueOptionV2", ex); return null; } } /// /// 期货期权卖方交易预付金的收取标准为下列两者中较大者: ///(1)权利金(期权合约结算价×标的期货合约交易单位)+标的期货合约交易预付金-期权合约虚值额的一半 ///(2)权利金(期权合约结算价×标的期货合约交易单位)+标的期货合约交易预付金的一半 ///看涨虚值额 = max(期权合约执行价格 - 标的期货合约当日结算价,0)*合约乘数; ///看跌虚值额=max(标的期货合约当日结算价 - 期权合约执行价格,0)*合约乘数。 ///豆粕期权的合约乘数是10、标的期货合约的交易单位是10. ///所以以上2个公式可以结合成为一个公式: ///预付金=权利金+MAX(期货预付金-1/2虚值额,1/2期货预付金) /// /// 权利金 /// 交易量 /// 行权价 /// 现价 /// 期货预付金率 /// 看涨看跌 /// private static double CommodityFutureOptionMargin(double premium, double amount, double strike, double spotPrice, double futureMarginRatio, string optionType = "Call") { var futureMargin = futureMarginRatio * amount * spotPrice; var outTheMoney = (optionType.ToUpper() == "PUT") ? Math.Max(spotPrice - strike, 0) * amount //看跌虚值额 : Math.Max(strike - spotPrice, 0) * amount; //看涨虚值额 return premium + Math.Max(futureMargin - outTheMoney * 0.5, futureMargin * 0.5); } } }