using BaseOUDAL; using Microsoft.Extensions.Caching.Memory; using Qdp.Pricing.Base.Utilities; using YLErp.Abstract; using YLErp.BLL; using YLErp.BLL.Eod; using YLErp.Configuration; using YLErp.DBModels.Enums; using YLErp.Models; using YLErp.Modules.CalculationModule; using YLErp.Modules.CalculationModule.Abstract; using YLErp.Modules.TradeRiskCalcModule.Abstract; using YLErp.Modules.SwapModule; using YLErp.Modules.TradeRiskCalcModule.TaskRunner; using YLErp.Modules.VolatilityModule; using YLErp.QdpModule; namespace YLErp.Modules.TradeRiskCalcModule { /// /// 交易风险计算业务服务 /// public class TradeRiskCalcService { static readonly IYcLogger _logger; static readonly MemoryCache _cache; static TradeRiskCalcService() { _cache = new MemoryCache(new MemoryCacheOptions()); _logger = LogFactory.GetLogger(nameof(TradeRiskCalcService)); } readonly ITradeRiskCalcContext _context; readonly IOptionCalcDataProvider _dataProvider; HashSet _underlyingCodeFilterSet; readonly string _skipTradeTypes; public TradeRiskCalcService(ITradeRiskCalcContext context, string skipTradeTypes) { _context = context ?? throw new ArgumentNullException(nameof(context)); _dataProvider = CalcCheckHelper.CheckOptionCalcDataProvider(context.OptionCalcDataProvider); _skipTradeTypes = skipTradeTypes; } public TradeRiskCalcService SetUnderlyingFilter(params string[] underlyingCodes) { if (underlyingCodes != null && underlyingCodes.Any(n => !string.IsNullOrEmpty(n))) { var linq = underlyingCodes.Where(n => !string.IsNullOrEmpty(n)); _underlyingCodeFilterSet = new HashSet(linq, StringComparer.OrdinalIgnoreCase); } return this; } /// /// 计算 /// public IEnumerable Calculate(ITradeDataSource tradeDataSource, IDataSource manualRiskProvider) { if (tradeDataSource == null) { throw new ArgumentNullException(nameof(tradeDataSource)); } IOtcTradeValueCalcContext optionContext = null; try { _logger?.Debug($"[{_context.VolType}]开始计算当前风险>>>>>>>>>"); //构建计算上下文 var sysRiskFreeRate = valuedateBLL.RiskFreeRate * 0.01; optionContext = _context.CreateOptionValueCalcContext(sysRiskFreeRate); //场外交易处理 var tradeRiskList = CalcOtcRisks(tradeDataSource, optionContext, manualRiskProvider); var swapTradeRiskList = CalcSwapRisks(tradeDataSource, optionContext); tradeRiskList.AddRange(swapTradeRiskList); var hedgeOptionContext = PS.Config.Is润和 ? new InnerOptionValueCalcContext(DateTime.Today, optionContext) : optionContext; //对冲交易处理 var hedgingRisk = CalcHedgeTradeRisks(tradeDataSource, hedgeOptionContext); tradeRiskList.AddRange(hedgingRisk); //计算每笔期权的盈亏 TradeRiskHelper.CalculateOptionTradePnlWithHedge(tradeRiskList); _logger?.Debug($"[{_context.VolType}]当前风险计算完毕<<<<<<<<<<<"); return tradeRiskList; } finally { optionContext?.Dispose(); } } #region----场外交易处理---- //场外交易风险和盈亏计算(除收益互换) private List CalcOtcRisks(ITradeDataSource tradeDataSource , IOtcTradeValueCalcContext optionContext, IDataSource manualRiskProvider) { var volType = _context.VolType; //准备计算数据 var tradeRiskList = new List(); var otcTrades = tradeDataSource.GetOtcTrades() ?? Enumerable.Empty(); otcTrades = otcTrades.Where(x => x.TradeType != "收益互换"); if (_underlyingCodeFilterSet != null) { otcTrades = otcTrades.Where(n => _underlyingCodeFilterSet.Contains(n.UnderlyingCode)).ToArray(); } _logger?.Debug($"[{volType}]参与计算的场外交易数量:" + otcTrades.Count()); var set = new HashSet(otcTrades.Select(t => t.id)); var tradeCashLookup = _context.GetTrade_Cashes(set).ToLookup(n => n.TradeId); var ydContexts = GetYdOptionCalculateContexts(_context, optionContext); DateTime valueDate, preValueDate; //执行风险计算 foreach (var trad in otcTrades) { SetTradeLots(trad); valueDate = _context.ValueDate; preValueDate = _context.PreValueDate; void getTradeCashList(out IEnumerable tradeCashList, out IEnumerable tdTradeCashUnwindList) { tradeCashList = tradeCashLookup.FirstOrDefault(a => a.Key == trad.id)?.AsEnumerable() ?? Enumerable.Empty(); tdTradeCashUnwindList = tradeCashList.Where(tc => tc.ValidState != ConsGlobal.InValid && !tc.IsDeleted && ClientCashInCashOut.PROFIT_ACTION.Contains(tc.Action) && tc.ValueDate <= valueDate && tc.ValueDate > preValueDate); } if (!string.IsNullOrEmpty(_skipTradeTypes) && _skipTradeTypes.Contains(trad.TradeType)) { var resultRisk = UseManualRisk(trad, volType, manualRiskProvider, (calcRisk, lastPv) => { getTradeCashList(out var tradeCashList, out var tdTradeCashUnwindList); UpdateOTCPnl(trad, calcRisk, lastPv, tradeCashList: tradeCashList, tdTradeCashUnwindList: tdTradeCashUnwindList); }); if (resultRisk != null) { tradeRiskList.Add(resultRisk); } } else { TradingRiskParameter firstRisk = null; IOtcTradeValueCalcContext ydContext = null; try { var tdOtionContext = optionContext; ydContext = ydContexts[(int)trad.SettlementType]; if (PS.Config.Is润和) { tdOtionContext = new InnerOptionValueCalcContext(DateTime.Today, optionContext); ydContext = new InnerOptionValueCalcContext(DateTime.Today.AddDays(-1), optionContext); } ydContext.UserGroup = tdOtionContext.UserGroup = UserBLL.GetUserGroup(trad.TraderId); if (volType == "交易曲面分红率0") { trad.DividendRate = 0; } var result = TradeRiskCalcUtil.CalcTradeRisk(trad, tdOtionContext, out var underlyings); if (result == null) { continue; } result.TradeId = trad.id; if (underlyings == null) { continue; } var index = 0; foreach (var underlying in underlyings) { var trp = TransformOneUnderlying(underlying, trad, new List { result }, index++); tradeRiskList.Add(trp); if (index == 1) { firstRisk = trp; } } } catch (Exception ex) { var key = "today_otcTrade_calc_" + trad.id; if (_cache.Get(key) == null) { _cache.Set(key, this, DateTimeOffset.Now.AddMinutes(10)); _logger?.Error(ex, $"[{volType}-{trad.id}-{trad.TradeNumber}]当前风险计算出错"); } } if (firstRisk != null) { double? lastPv = null; getTradeCashList(out var tradeCashList, out var tdTradeCashUnwindList); if (firstRisk.TradeType == "远期") { firstRisk.TotalPnl = 0; } if (trad.TradeDate < valueDate) { var yp = _context.YdEodPositionDataProvider.GetOtcTradePositionData(trad.id, _context.VolType); if (yp != null) { lastPv = yp.Position.Pv; if (firstRisk.TradeType == "远期") { firstRisk.TotalPnl = yp.Position.TotalPnL; } } else { lastPv = CalcYdOtcValue(trad, ydContext, tdTradeCashUnwindList, volType, out var spotPrice, out var vol); if (firstRisk.Debug != null) { firstRisk.Debug.Add(spotPrice); firstRisk.Debug.Add(vol); } } } UpdateOTCPnl(trad, firstRisk, lastPv, tradeCashList: tradeCashList, tdTradeCashUnwindList: tdTradeCashUnwindList); } } } AppManager.SetSysInfo(string.Concat("实时风险-", volType, "-计算结果"), tradeRiskList.Count + "条"); _logger?.Debug($"[{volType}]场外交易风险计算完毕"); return tradeRiskList; } //场外收益互换交易风险和盈亏计算 private List CalcSwapRisks(ITradeDataSource tradeDataSource, IOtcTradeValueCalcContext optionContext) { var volType = _context.VolType; //厦门象屿 DateTime? nextDateXmxy = null; if (PS.Config.Company == Configuration.CompanyEnum.厦门象屿) { var date = SpecialModule.XiaMenXiangYuHelper.GetRefernceValueDate(_context.ValueDate); if (date > _context.ValueDate) { nextDateXmxy = date; } } //准备计算数据 var tradeRiskList = new List(); var otcTrades = tradeDataSource.GetOtcTrades() ?? Enumerable.Empty(); otcTrades = otcTrades.Where(x => x.TradeType == "收益互换"); var tradeIds = otcTrades.Select(t => t.id).ToList(); var swapTradeService = new SwapTradeService(OptUserInfo.SystemUser); var swapEodPositionService = new SwapEodPositionService(OptUserInfo.SystemUser); var tradePositions = swapTradeService.GetSwapPositions(tradeIds, _underlyingCodeFilterSet); var tradePositionSwapIds = tradePositions.Select(s => s.SwapTradeId).Distinct().ToList(); otcTrades = otcTrades.Where(x => tradePositionSwapIds.Contains(x.id)); _logger?.Debug($"[{volType}]参与计算的场外互换交易数量:" + otcTrades.Count()); var ydContexts = GetYdOptionCalculateContexts(_context, optionContext); DateTime valueDate = _context.ValueDate; DateTime preValueDate = _context.PreValueDate; var swapEodPositions = swapEodPositionService.GetEodPositions(tradePositionSwapIds, valueDate, preValueDate); var eodSwaps = swapEodPositionService.GetEodSwaps(tradePositionSwapIds, valueDate, preValueDate); //风险计算 foreach (var trad in otcTrades) { var lastSwapEodPositions = swapEodPositions.Where(x => x.SwapTradeId == trad.id).OrderByDescending(o => o.ValueDate).ToList();//可能没有 var lastPositionIds = lastSwapEodPositions.Select(x => x.PositionId).Distinct(); var eodSwap = eodSwaps.Where(x => x.SwapTradeId == trad.id).OrderByDescending(o => o.ValueDate).FirstOrDefault(); var positions = tradePositions.Where(x=>x.SwapTradeId==trad.id).ToList(); if (lastPositionIds.Any()) { foreach (var item in lastPositionIds) { var todayPosition = lastSwapEodPositions.FirstOrDefault(x => x.PositionId == item && x.ValueDate == valueDate); var lastPosition = lastSwapEodPositions.FirstOrDefault(x => x.PositionId == item && x.ValueDate == preValueDate); var currentPosition = todayPosition == null ? lastPosition : todayPosition; var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(currentPosition.UnderlyingCode); var position = tradePositions.FirstOrDefault(f => f.id == item); var result = new TradeValueResult(); var spotPrice = swapEodPositionService.UnderlyingCodePrice(currentPosition.UnderlyingCode, valueDate,out decimal vobp); int shortRatio = currentPosition.PositionType == (int)PositionTypeFlag.Long ? 1 : -1; int directionRatio = currentPosition.PosiDirection == (int)SwapDirectionEnum.收取 ? 1 : -1; result.Pv =Convert.ToDouble( currentPosition.UnderlyingMarketValue); result.Delta = directionRatio * shortRatio * Convert.ToDouble(currentPosition.PosiQuantity) * Convert.ToDouble(currentPosition.CountRatio); result.DeltaCash = result.Delta * Convert.ToDouble(spotPrice); result.Gamma = 0; result.GammaCash = 0; result.Vega = 0; result.VegaCash = 0; result.TradingDayTheta = 0; result.CalendarDayTheta = 0; result.Rho = 0; var resultRisk = SwapTransformOneUnderlying(underlying, trad, new List { result }, currentPosition, position); if (resultRisk != null) { tradeRiskList.Add(resultRisk); } } } } AppManager.SetSysInfo(string.Concat("实时风险-", volType, "-计算结果"), tradeRiskList.Count + "条"); _logger?.Debug($"[{volType}]互换交易风险计算完毕"); return tradeRiskList; } //使用自定义交易风险,注意:应该屏蔽远期 private TradingRiskParameter UseManualRisk(trade trad, string volType, IDataSource manualRiskProvider, Action UpdateOTCPnl) { var usePrePve = false; var result = new TradeValueResult(); if (!ConsTrade.TradeCompleteStatus.Contains(trad.TradeStatus)) { var risk = manualRiskProvider?.GetData(trad.id); if (risk == null) { var yp = _context.YdEodPositionDataProvider.GetOtcTradePositionData(trad.id, _context.VolType); if (yp != null) { var rate = trad.Notional / yp.Position.Amount; result.Pv = yp.Position.Pv * rate; var ypRisk = yp.Risk; if (ypRisk != null) { result.Delta = ypRisk.Delta * rate; result.DeltaCash = ypRisk.DeltaCash * rate; result.Gamma = ypRisk.Gamma * rate; result.GammaCash = ypRisk.GammaCash * rate; result.Vega = ypRisk.Vega * rate; result.VegaCash = ypRisk.VegaCash * rate; result.TradingDayTheta = ypRisk.Theta * rate; result.CalendarDayTheta = ypRisk.Theta * rate; result.Rho = ypRisk.Rho * rate; } } } else { result.Pv = risk.Pv ?? 0; result.Delta = risk.Delta ?? 0; result.Gamma = risk.Gamma ?? 0; result.Vega = risk.Vega ?? 0; result.TradingDayTheta = risk.Theta ?? 0; result.CalendarDayTheta = risk.Theta ?? 0; result.Rho = risk.Rho ?? 0; if (_dataProvider.UnderlyingPriceProvider.TryGetPrice(trad.UnderlyingCode, out var price)) { result.DeltaCash = result.Delta * price; result.GammaCash = result.Gamma * Math.Pow(price, 2) / 100; result.VegaCash = result.Vega * price; } usePrePve = true; } } var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(trad.UnderlyingCode); var resultRisk = TransformOneUnderlying(underlying, trad, new List { result }, 0); if (usePrePve) { double? lastPv = null; var yp = _context.YdEodPositionDataProvider.GetOtcTradePositionData(trad.id, _context.VolType); if (yp != null) { lastPv = yp.Position.Pv; } UpdateOTCPnl(resultRisk, lastPv); } return resultRisk; } //计算场外交易上个交易日市值 private double? CalcYdOtcValue(trade trad, IOtcTradeValueCalcContext ydContext, IEnumerable tdTradeCashUnwindList, string volType , out double? spotPrice, out double? vol) { vol = null; spotPrice = null; try { if (ConsTrade.TradeCompleteStatus.Contains(trad.TradeStatus)) { trad.Notional = 0; } //计算昨日pv时需要还原昨日的持仓数量 trad.Notional += tdTradeCashUnwindList.Sum(tc => tc.UnwindNotional ?? 0); var result = TradeRiskCalcUtil.CalcTradeRisk(trad, ydContext, out var underlyings); if (result != null) { vol = result.Vol; spotPrice = result.SpotPrice; } return result?.Pv; } catch (Exception ex) { var key = "yeday_otcTrade_calc_" + trad.id; if (_cache.Get(key) == null) { _cache.Set(key, this, DateTimeOffset.Now.AddMinutes(10)); _logger?.Error(ex, $"[{volType}-{trad.id}-{trad.TradeNumber}]昨日场外期权交易PV计算出错"); } return null; } } //获取三种结算方式的昨日交易估值上下文(因为昨日交易有日终价格) private static YdOptionCalculateContext[] GetYdOptionCalculateContexts(ITradeRiskCalcContext riskContext, IOtcTradeValueCalcContext optionContext) { var ydContexts = new YdOptionCalculateContext[3]; ydContexts[(int)SettlementTypeEnum.ClosePrice] = new YdOptionCalculateContext(riskContext.PreValueDate, optionContext, riskContext.YdEodPriceProvider, riskContext.YdTradeHisDataProvider, SettlementTypeEnum.ClosePrice) { CalcScenario = Enums.CalcScenarioEnum.RealtimeRisk }; ydContexts[(int)SettlementTypeEnum.SettlePrice] = new YdOptionCalculateContext(riskContext.PreValueDate, optionContext, riskContext.YdEodPriceProvider, riskContext.YdTradeHisDataProvider, SettlementTypeEnum.SettlePrice) { CalcScenario = Enums.CalcScenarioEnum.RealtimeRisk }; ydContexts[(int)SettlementTypeEnum.ReferencePrice] = new YdOptionCalculateContext(riskContext.PreValueDate, optionContext, riskContext.YdEodPriceProvider, riskContext.YdTradeHisDataProvider, SettlementTypeEnum.ReferencePrice) { CalcScenario = Enums.CalcScenarioEnum.RealtimeRisk }; return ydContexts; } /// /// 设置交易记录手数值 /// private void SetTradeLots(trade trad) { //成交手数 trad.Lots = TradeLotsCalc.GetLots(trad.UnderlyingCode, trad.OriginalNotional ?? 0); trad.LotsNewInfo = TradeLotsCalc.GetLots(trad.UnderlyingCode, trad.Notional); } #endregion #region----对冲交易处理---- /// /// 更新对冲交易盈亏数据 /// private IEnumerable CalcHedgeTradeRisks(ITradeDataSource tradeDataSource, IOtcTradeValueCalcContext optionCalcContext) { _logger?.Debug($"[{_context.VolType}]对冲交易PNL处理开始"); //获取所有未结算过的对冲交易 var HedgeTradeList = tradeDataSource.GetExchangeTrades() ?? Enumerable.Empty(); if (_underlyingCodeFilterSet != null) { HedgeTradeList = HedgeTradeList.Where(n => _underlyingCodeFilterSet.Contains(n.UnderlyingCode)).ToArray(); } //股票 商品期货临时tradeId var tempTradeId = -1; var togetherUnOptionList = new List(); var hedgePnlContext = _context.CreateHedgePnlCalcContext(optionCalcContext); //昨日持仓信息 var predicat = PredicateBuilder.True(); if (_underlyingCodeFilterSet != null) { predicat = predicat.And(t => _underlyingCodeFilterSet.Contains(t.UnderlyingCode)); } var lastEodPositions = _context.YdEodPositionDataProvider .GetExchangeTradePositionList(_context.VolType).Where(predicat.Compile()).ToArray(); //计算昨日持仓+新增加交易合计的PV var hedgePnlList = new HedgePnlCalc(hedgePnlContext).Calculate(HedgeTradeList, lastEodPositions); //根据是否展示累积盈亏过滤前一交易日已清仓的持仓为0的持仓信息 if (!PS.Config.ShowAccruedTotalPnL) { hedgePnlList = hedgePnlList.Where(t => t.HasNewTrade || Math.Abs(t.Notional) != 0).ToList(); } var calcDeltaT1 = TradeRiskHelper.IsCalcDeltaT1(); foreach (var pnl in hedgePnlList) { var um = _dataProvider.UnderlyingDataProvider.GetUnderlying(pnl.UnderlyingCode); if (um == null) { continue; } um.Price = pnl.SettlePrice; var variety = _dataProvider.UnderlyingDataProvider.GetVariety(um.UnderlyingTypeId); if (variety == null) { optionCalcContext.ErrorHandler?.AddError($"{um.UnderlyingCode}没有找到品种信息"); variety = new Variety(); } var TradeUnitValue = variety?.TradeUnitValue; var tempRisk = new TradingRiskParameter { VarietyCode = variety.VarietyCode?.ToUpperInvariant(), IsOption = false, TradeId = tempTradeId--, UnderlyingId = um.id, UnderlyingCode = pnl.UnderlyingCode, UnderlyingName = um?.UnderlyingName ?? string.Empty, SpotPriceChangePercent = um.GetPriceChangePercent(), TradeType = pnl.TradeType, BookId = pnl.BookId, BuySell = pnl.BuySell, Notional = pnl.Notional, Pv = pnl.Pv, Tv = pnl.TimeValue, TradePrice = pnl.Cost, OriginalPrincipalSum = 0, DailyPnl = pnl.DailyPnL, TotalPnl = pnl.TotalPnl, ExercisePnl = pnl.TotalPnl, SpotPrice = um.Price, TradeStatus = ConsTrade.确认成交, InstrumentType = um.UnderlyingInstrumentType, IsSynthetic = um.IsSynthetic(), ExchangeOptionCode = pnl.ExchangeOptionCode, PositionType = pnl.PositionType, CallPut = pnl.CallPut, Strike = pnl.Strike, //风险参数 Delta = pnl.Delta, SA_Delta = pnl.SA_Delta, DeltaCash = pnl.DeltaCash, Gamma = pnl.Gamma, Vega = pnl.Vega, GammaCash = pnl.GammaCash, DeltaInLots = pnl.DeltaInLots, GammaInLots = pnl.GammaInLots, Theta = pnl.Theta, Rho = pnl.Rho, DdeltaDt = pnl.DdeltaDt, DdeltaDvol = pnl.DdeltaDvol, DvegaDt = pnl.DvegaDt, DvegaDvol = pnl.DvegaDvol, Lots = pnl.Lots, LotsNewInfo = pnl.Lots, Cost = pnl.Cost, AccruedTotalPnl = pnl.TotalPnl, ExerciseDate = pnl.ExerciseDate, Vol = pnl.Vol, TradeSavedVol = pnl.Vol, ExOptionPrice = pnl.ExOptionPrice }; if (calcDeltaT1) { tempRisk.DeltaT1 = pnl.TradeType == "场内期权" ? pnl.DeltaT1 : pnl.Delta; } if (PS.Config.Is渤海) { tempRisk.ExercisePnl = 0.0; } tempRisk.OriginalNotionalV = tempRisk.OriginalNotional = tempRisk.Notional; tempRisk.TradeFlag = (int)EnumTradeTypeUtil.GetTradeFlag(tempRisk.TradeType); if (Math.Abs(tempRisk.Notional) > 1e-7) { tempRisk.Premium = tempRisk.TradePrice / tempRisk.Notional; tempRisk.StockEqvNotional = Math.Abs(tempRisk.Pv); tempRisk.BuySell = tempRisk.Notional > 0 ? "买入" : "卖出"; } else { tempRisk.BuySell = string.Empty; } if (PS.Config.Company == CompanyEnum.红塔众鑫) { var day = QdpCalendarHelper.GetNonHolidayDaysBetween(valuedateBLL.ValueDate ,pnl.ExerciseDate.GetValueOrDefault()); //剩余天数 tempRisk.ThetaNet = Math.Round(pnl.Theta,2) * day; } togetherUnOptionList.Add(tempRisk); } if (togetherUnOptionList.Any()) { //更新行权盈亏 foreach (var t in togetherUnOptionList) { if (t.TradeType == "场内期权") { var pnl = (t.CallPut == "Call" ? 1 : -1) * ((t.SpotPrice ?? 0.0) - (t.Strike ?? 0.0)) * Math.Abs(t.Notional); pnl = pnl < 0 ? 0 : pnl * EodOperationBase.GetSign(t.BuySell); t.ExercisePnl = pnl - t.Cost; } else { t.ExercisePnl = t.Pv - t.Cost; } } } _logger?.Debug($"[{_context.VolType}]对冲交易PNL计算完毕"); return togetherUnOptionList; } #endregion #region-----tradingRiskParameter----- //衍生品交易计算结果转换(未考虑多标的期权--待明确业务后重写) private TradingRiskParameter TransformOneUnderlying(underlying_manager um, trade tradeObj, List optionValueResult, int index) { var contractSize = 0d; var variety = um == null ? null : _dataProvider.UnderlyingDataProvider.GetVariety(um.UnderlyingCode, out contractSize) ?? new Variety(); var sign = EodOperationBase.GetSign(tradeObj.BuySell); var trp = new TradingRiskParameter { IsOption = true, UnderlyingCode = um?.UnderlyingCode, UnderlyingId = um?.id, UnderlyingName = um?.UnderlyingName, InstrumentType = um?.UnderlyingInstrumentType, SpotPrice = um?.Price, SpotPriceChangePercent = um?.GetPriceChangePercent(), IsGroup = tradeObj.IsGroup, TradeId = tradeObj.id, TradeNumber = tradeObj.TradeNumber, TradeType = tradeObj.TradeType, StructureType = tradeObj.StructureType, TradeStatus = tradeObj.TradeStatus, TradeDate = tradeObj.TradeDate, TradePrice = (tradeObj.TradePrice ?? 0) * sign, OriginalPrincipalSum = (tradeObj.OriginalPrincipalSum ?? 0) * sign, ClientId = tradeObj.ClientId, ClientName = tradeObj.ClientName, CallPut = tradeObj.CallPut, BuySell = tradeObj.BuySell, BookId = tradeObj.AssetId, ExerciseMode = tradeObj.ExerciseMode, ExerciseDate = tradeObj.ExerciseDate, InitSpotPrice = tradeObj.SpotPrice, Notional = tradeObj.Notional, OriginalNotional = tradeObj.OriginalNotional ?? tradeObj.Notional, StockEqvNotional = tradeObj.StockEqvNotional, IsMoneynessOption = tradeObj.IsMoneynessOption, IsPremiumRate = tradeObj.IsUsePremiumRate ?? false, DividendRate = tradeObj.DividendRate, NoRiskRate = tradeObj.NoRiskRate, OpenVol = tradeObj.TradeOpenVolatility ?? 0, Premium = tradeObj.IsUsePremiumRate == true ? (tradeObj.PremiumRate ?? 0) : (tradeObj.TradeSinglePrice ?? 0), Lots = tradeObj.Lots, LotsNewInfo = tradeObj.LotsNewInfo, TradeSavedVol = tradeObj.TradeSavedVol, Pv = optionValueResult.Sum(t => t.Pv), NPv = optionValueResult.Sum(t => t.NPv), Tv = optionValueResult.Sum(t => t.TimeValue), Delta = optionValueResult.Sum(t => t.GetDelta(index)), DeltaT1 = optionValueResult.FirstOrDefault()?.DeltaT1, SA_Delta = optionValueResult.Sum(t => t.SA_Delta), Gamma = optionValueResult.Sum(t => t.GetGamma(index)), Vega = optionValueResult.Sum(t => t.GetVega(index)), DeltaCash = optionValueResult.Sum(t => t.GetDeltaCash(index)), GammaCash = optionValueResult.Sum(t => t.GetGammaCash(index)), TradeFlag = (int)EnumTradeTypeUtil.GetTradeFlag(tradeObj.TradeType, tradeObj.Comments), AccruedTotalPnl = 0, CompanyObj = null, Cost = 0, DailyPnl = 0, GammaInLots = 0, Strike = tradeObj.Strike, Debug = new List { optionValueResult[0].SpotPrice, optionValueResult[0].Vol } }; if (tradeObj.TradeType == "远期") { trp.TradePrice = tradeObj.TradePrice ?? 0; } var list = DbContextFactory.GetYLDbContext().dividendrate_record.Where(x => x.TradeType.Contains(tradeObj.TradeType) && valuedateBLL.ValueDate >= x.ValueDate && (x.OptionType == tradeObj.OptionType || x.OptionType == "全部")).ToList(); var record = list.Where(x => x.UnderlyingCode.Split(',').Any(code => code == tradeObj.UnderlyingCode)).OrderByDescending(x => x.OptDate).OrderByDescending(x => x.ValueDate); if (record.Any()) { trp.DividendRate = record.FirstOrDefault()?.DividendRate; } if (!trp.DividendRate.HasValue) { trp.DividendRate = trp.NoRiskRate; } //成交份额(countRatio传入1算出来的是成交份额) trp.OriginalNotionalV = TradeCalcHelper.GetTradeAmountV(tradeObj, tradeObj.OriginalNotional ?? tradeObj.Notional, 1); //组合标的设置DeltaInLots为0 if (um != null) { if (um.IsSynthetic()) { trp.IsSynthetic = true; trp.VarietyCode = "组合标的"; trp.DeltaInLots = optionValueResult.Sum(t => t.GetDelta(index)); if (um.ContractSize > 0) { trp.DeltaInLots /= um.ContractSize; } } else { trp.VarietyCode = variety?.VarietyCode?.ToUpperInvariant(); trp.DeltaInLots = optionValueResult.Sum(t => t.GetDelta(index)) / contractSize; } } else if (string.IsNullOrEmpty(trp.UnderlyingCode)) { if (trp.TradeType == ConsGlobal.TradeType.CashFlow) { trp.VarietyCode = trp.UnderlyingCode = "现金流"; } else { trp.VarietyCode = trp.UnderlyingCode = "未知"; } } trp.GammaInLots = optionValueResult.Sum(t => t.GetGamma(index)) / contractSize; trp.Theta = optionValueResult.Sum(t => t.Theta); if (PS.Config.Company == CompanyEnum.红塔众鑫) { trp.ThetaNet = optionValueResult.Sum(t => { var day = QdpCalendarHelper.GetNonHolidayDaysBetween( valuedateBLL.ValueDate,tradeObj.ExerciseDate.GetValueOrDefault()); //剩余天数 return Math.Round(t.Theta, 2) * day; }); } trp.Rho = optionValueResult.Sum(t => t.Rho) * 100; trp.DdeltaDt = optionValueResult.Sum(t => t.DDeltaDt); trp.DdeltaDvol = optionValueResult.Sum(t => t.DDeltaDVol); trp.DvegaDt = optionValueResult.Sum(t => t.DVegaDt); trp.DvegaDvol = optionValueResult.Sum(t => t.DVegaDVol); if (tradeObj.TradeType == "场内期权") { trp.PositionType = sign > 0 ? "long" : "short"; trp.ExchangeOptionCode = tradeObj.ExchangeOptionCode; } else if (tradeObj.TradeType == "结构化交易") { trp.TradeType = tradeObj.StructureType ?? string.Empty; trp.StructureType = "结构化交易"; } //目前optionValueResult中只有一个标的的Vol值 //对于彩虹期权、价差期权等多标的的期权交易,并不能获取各标的的Vol,因此暂时使用第一个Vol //TODO: 当optionValueResult支持多个Vol之后,需要做相应修改 trp.Vol = optionValueResult[0].Vol; trp.PricingT = optionValueResult[0].PricingT; //场外期权行权盈亏 if (trp.TradeFlag > (int)EnumTradeType.ExchangeOption) { trp.ExercisePnl = GetExercisePnl(tradeObj, um?.Price ?? 0.0, out var actualStrike); trp.Strike = actualStrike; } if (PS.Config.Is润和) { var unVol = VolatilityHelper.GetVol(DateTime.Today, "交易", tradeObj.UnderlyingCode, DataCacheProvider.GetAssetUnitDataSource().GetData(tradeObj.AssetId)?.UserGroup); trp.TradeSavedVol = VolatilityHelper.GetInterpolatedVol( volConstructionType: PS.Config.ErpElement.SkewMapVolConstruction ? VolConstructionType.SkewMap : VolConstructionType.Normal, volSurface: unVol, valueDate: DateTime.Today, underlyingCode: tradeObj.UnderlyingCode, exerciseDate: tradeObj.ExerciseDate.Value, strike: tradeObj.Strike ?? 0, isBuy: tradeObj.BuySell == "买入", isCall: tradeObj.CallPut == "Call", spotPrice: tradeObj.SpotPrice ?? 0, isMoneynessOption: tradeObj.IsMoneynessOption == "是"); } trp.QuoteUnit = variety?.QuoteUnit; if (optionValueResult != null && optionValueResult.Count() > 0 && optionValueResult[0] != null && optionValueResult[0].IsKnockOut) { trp.IsKnockOut = true; trp.KnockOutPayoff = optionValueResult.Sum(t => t.KnockOutPayoff); } return trp; } //衍生品互换交易计算结果转换(未考虑多标的期权--待明确业务后重写) private TradingRiskParameter SwapTransformOneUnderlying(underlying_manager um, trade tradeObj, List optionValueResult, eod_swap_position eodPosition, swap_position position) { var contractSize = 0d; var variety = um == null ? null : _dataProvider.UnderlyingDataProvider.GetVariety(um.UnderlyingCode, out contractSize) ?? new Variety(); int shortRatio = position.PositionType == (int)PositionTypeFlag.Long ? 1 : -1; int directionRatio = position.PosiDirection == (int)SwapDirectionEnum.收取 ? 1 : -1; var trp = new TradingRiskParameter { IsOption = true, UnderlyingCode = um?.UnderlyingCode, UnderlyingId = um?.id, UnderlyingName = um?.UnderlyingName, InstrumentType = um?.UnderlyingInstrumentType, SpotPrice = um?.Price, SpotPriceChangePercent = um?.GetPriceChangePercent(), IsGroup = tradeObj.IsGroup, TradeId = tradeObj.id, TradeNumber = tradeObj.TradeNumber, TradeType = tradeObj.TradeType, StructureType = tradeObj.StructureType, TradeStatus = tradeObj.TradeStatus, TradeDate = tradeObj.TradeDate, OriginalPrincipalSum = 0, ClientId = tradeObj.ClientId, ClientName = tradeObj.ClientName, CallPut = shortRatio == 1 ? "Call" : "Put", BuySell = directionRatio == 1 ? "卖出" : "买入", BookId = tradeObj.AssetId, ExerciseMode = tradeObj.ExerciseMode, ExerciseDate = tradeObj.ExerciseDate, InitSpotPrice = Convert.ToDouble(position.PosiNetPrice), OriginalNotional = Convert.ToDouble(position.PosiQuantity * position.CountRatio), IsMoneynessOption = tradeObj.IsMoneynessOption, IsPremiumRate = false, DividendRate = 0, NoRiskRate = 0, OpenVol = 0, Premium = Convert.ToDouble(position.PosiNetPrice), Lots = Convert.ToDouble(position.PosiQuantity * position.CountRatio / position.ContractSize), TradeSavedVol = tradeObj.TradeSavedVol, Pv = optionValueResult.Sum(t => t.Pv), Tv = optionValueResult.Sum(t => t.TimeValue), Delta = optionValueResult.Sum(t => t.GetDelta(0)), DeltaT1 = optionValueResult.FirstOrDefault()?.DeltaT1, SA_Delta = optionValueResult.Sum(t => t.SA_Delta), Gamma = optionValueResult.Sum(t => t.GetGamma(0)), Vega = optionValueResult.Sum(t => t.GetVega(0)), DeltaCash = optionValueResult.Sum(t => t.GetDeltaCash(0)), GammaCash = optionValueResult.Sum(t => t.GetGammaCash(0)), TradeFlag = (int)EnumTradeType.SwapOption, CompanyObj = null, Cost = 0, DailyPnl = 0, GammaInLots = 0, Strike = tradeObj.Strike, Debug = new List { optionValueResult[0].SpotPrice, optionValueResult[0].Vol } }; if (eodPosition!=null) { trp.AccruedTotalPnl = Convert.ToDouble(eodPosition.RealizedMtmPnL); trp.LotsNewInfo = Convert.ToDouble(eodPosition.PosiQuantity * eodPosition.CountRatio / eodPosition.ContractSize); trp.TradePrice = Convert.ToDouble(eodPosition.PosiNetPrice * shortRatio); trp.Notional = Convert.ToDouble(eodPosition.PosiQuantity * eodPosition.CountRatio); trp.StockEqvNotional = Convert.ToDouble(eodPosition.PosiNotionalValue); } else { trp.LotsNewInfo = trp.Lots; trp.TradePrice = Convert.ToDouble(position.PosiNetPrice * shortRatio); trp.Notional = Convert.ToDouble(position.PosiQuantity * position.CountRatio); trp.StockEqvNotional = Convert.ToDouble(position.PosiNotionalValue); } //成交份额(countRatio传入1算出来的是成交份额) trp.OriginalNotionalV = trp.OriginalNotional; trp.GammaInLots = optionValueResult.Sum(t => t.GetGamma(0)) / contractSize; trp.Theta = optionValueResult.Sum(t => t.Theta); trp.Rho = optionValueResult.Sum(t => t.Rho) * 100; trp.DdeltaDt = optionValueResult.Sum(t => t.DDeltaDt); trp.DdeltaDvol = optionValueResult.Sum(t => t.DDeltaDVol); trp.DvegaDt = optionValueResult.Sum(t => t.DVegaDt); trp.DvegaDvol = optionValueResult.Sum(t => t.DVegaDVol); trp.Vol = optionValueResult[0].Vol; trp.PricingT = optionValueResult[0].PricingT; if (PS.Config.Is润和) { var unVol = VolatilityHelper.GetVol(DateTime.Today, "交易", um.UnderlyingCode, DataCacheProvider.GetAssetUnitDataSource().GetData(tradeObj.AssetId)?.UserGroup); trp.TradeSavedVol = VolatilityHelper.GetInterpolatedVol( volConstructionType: PS.Config.ErpElement.SkewMapVolConstruction ? VolConstructionType.SkewMap : VolConstructionType.Normal, volSurface: unVol, valueDate: DateTime.Today, underlyingCode: um.UnderlyingCode, exerciseDate: tradeObj.ExerciseDate.Value, strike: Convert.ToDouble(eodPosition.PosiNetPrice), isBuy: trp.BuySell == "买入", isCall: trp.CallPut == "Call", spotPrice: tradeObj.SpotPrice ?? 0, isMoneynessOption: tradeObj.IsMoneynessOption == "是"); } trp.QuoteUnit = variety?.QuoteUnit; return trp; } //更新实时盈亏 private static void UpdateOTCPnl(trade td, TradingRiskParameter para, double? lastPv , IEnumerable tradeCashList, IEnumerable tdTradeCashUnwindList) { var hasLastPv = lastPv.HasValue; var hasNotional = Math.Abs(para.Notional) > 1e-6; var completed = ConsTrade.TradeCompleteStatus.Contains(para.TradeStatus); var pendingForAudit = !completed && para.TradeStatus == ConsTrade.平仓待复核 || para.TradeStatus == ConsTrade.行权待复核; //当日实现收益 double RealizedProfit = 0; if (hasLastPv) { RealizedProfit = tdTradeCashUnwindList.Sum(tc => tc.Amount); } else { var lastTc = tradeCashList.Where(tc => tc.ValidState != ConsGlobal.InValid && !tc.IsDeleted && tc.Action == ClientCashInCashOut.系统操作_期权费); if (lastTc.Any()) { lastPv = -lastTc.Sum(tc => tc.Amount - (tc.ExtraAmount ?? 0)); } else if (para.TradeType == "远期") { lastPv = -para.TradePrice; //远期交易金额没有买卖方向区分,转换为pv直接取符号即可 } else { var sign = TradeCalcHelper.GetBuySellSign(para.BuySell); lastPv = para.TradePrice - para.OriginalPrincipalSum * sign; } if (td.TradeType == "雪球期权" && td.trade_snowball.PrepaymentUsed) { lastPv += (td.trade_snowball.PrepaymentRatio ?? 0) * (td.OriginalStockEqvNotional ?? 0) * (td.BuySell == "卖出" ? -1 : 1); } RealizedProfit = tradeCashList.Where(tc => tc.ValidState != ConsGlobal.InValid && !tc.IsDeleted && ClientCashInCashOut.PROFIT_ACTION.Contains(tc.Action)).Sum(tc => tc.Amount); } //已平仓 已到期 已行权 if (completed) { para.DailyPnl = RealizedProfit - lastPv.Value; para.TotalPnl = tradeCashList.Where(tc => tc.ValidState != ConsGlobal.InValid && !tc.IsDeleted).Sum(tc => tc.Amount); para.AccruedTotalPnl = para.TotalPnl; para.ExercisePnl = 0; para.Notional = 0; para.TradePrice = 0; para.OriginalPrincipalSum = 0; para.Pv = 0; para.Delta = 0; para.DeltaT1 = 0; para.DeltaT1Lots = 0; para.SA_Delta = 0; para.DeltaCash = 0; para.Gamma = 0; para.Vega = 0; para.GammaCash = 0; para.DeltaInLots = 0; para.GammaInLots = 0; para.Theta = 0; para.Rho = 0; para.DdeltaDt = 0; para.DdeltaDvol = 0; para.DvegaDt = 0; para.DvegaDvol = 0; para.StockEqvNotional = 0; para.KnockOutPayoff = 0; } else if (pendingForAudit) { //平仓待复核 行权待复核 var tradeCashTemp = tradeCashList.Where(tc => tc.ValidState == ConsGlobal.InValid && !tc.IsDeleted && tc.Action != ClientCashInCashOut.系统操作_期权费).OrderByDescending(a => a.id).FirstOrDefault(); //剩余持仓 var holdNotional = para.Notional - (tradeCashTemp == null ? 0 : ConsTrade.平仓待复核.Equals(para.TradeStatus) ? (tradeCashTemp.UnwindNotional ?? 0) : para.Notional); //剩余持仓百分比 var holdRatio = hasNotional ? holdNotional / para.Notional : 0; var newPv = para.Pv * holdRatio + (tradeCashTemp?.Amount ?? 0); para.Notional = holdNotional; para.DailyPnl = newPv + RealizedProfit - lastPv.Value; para.AccruedTotalPnl = para.TotalPnl = tradeCashList.Where(tc => tc.ValidState != ConsGlobal.InValid && !tc.IsDeleted).Sum(t => t.Amount) + newPv; if (Math.Abs(1 - holdRatio) > 1e-5) { para.ExercisePnl *= holdRatio; para.Pv *= holdRatio; para.Tv *= holdRatio; para.TradePrice *= holdRatio; para.OriginalPrincipalSum *= holdRatio; para.Delta *= holdRatio; para.SA_Delta *= holdRatio; para.DeltaCash *= holdRatio; para.Gamma *= holdRatio; para.Vega *= holdRatio; para.GammaCash *= holdRatio; para.DeltaInLots *= holdRatio; para.GammaInLots *= holdRatio; para.Theta *= holdRatio; para.Rho *= holdRatio; para.DdeltaDt *= holdRatio; para.DdeltaDvol *= holdRatio; para.DvegaDt *= holdRatio; para.DvegaDvol *= holdRatio; if (para.IsKnockOut) { para.KnockOutPayoff *= holdRatio; } } } else if (para.TradeType == "远期") { para.DailyPnl = para.Pv + RealizedProfit - lastPv.Value; if (hasLastPv) { para.TotalPnl += para.DailyPnl; } else { para.TotalPnl = para.DailyPnl; } para.AccruedTotalPnl = para.TotalPnl; } else if (ConsTrade.TradeStatusBeforConfirmed.Contains(para.TradeStatus)) { para.DailyPnl = para.Pv - para.TradePrice; if (td.TradeType == "雪球期权" && td.trade_snowball.PrepaymentUsed) { para.DailyPnl -= (td.trade_snowball.PrepaymentRatio ?? 0) * td.StockEqvNotional * (td.BuySell == "卖出" ? -1 : 1); } para.TotalPnl = para.DailyPnl; para.AccruedTotalPnl = para.DailyPnl; } else { para.DailyPnl = para.Pv + RealizedProfit - lastPv.Value; para.TotalPnl = para.Pv + tradeCashList.Where(tc => tc.ValidState != ConsGlobal.InValid && !tc.IsDeleted).Sum(t => t.Amount); if (td.TradeType == "雪球期权" && td.trade_snowball.PrepaymentUsed) { //只关注持仓的部分 para.TotalPnl -= (td.trade_snowball.PrepaymentRatio ?? 0) * para.Notional * (td.SpotPrice ?? 0) * (td.BuySell == "卖出" ? -1 : 1); } para.AccruedTotalPnl = para.TotalPnl; } //用于计算错误时查看 para.LastPv = lastPv; } //获取行权盈亏 private double GetExercisePnl(OtcTradeBase tradeObj, double spotPrice, out double? actualStrike) { actualStrike = null; if (tradeObj == null) { return 0; } if ("亚式期权".Equals(tradeObj.TradeType)) { double? avgPrice = null; var trade_asian_option = _dataProvider.TradeExtendDataProvider.GetTrade_Asian_Option(tradeObj.id); if (trade_asian_option != null) { var fixings = AsiaOptionProvider.Default.GetFixingString(_context.ValueDate, tradeObj, trade_asian_option); avgPrice = GetAsianAveragePrice(fixings, trade_asian_option.PayoffType); if (avgPrice != null && "Floating".Equals(trade_asian_option.StrikeType)) { actualStrike = avgPrice; } } if (actualStrike == null) { actualStrike = tradeObj.IsMoneynessOptionData ? (tradeObj.SpotPrice * tradeObj.Strike) : tradeObj.Strike; } if (trade_asian_option == null) { return 0.0; } if ("Fixed".Equals(trade_asian_option?.StrikeType)) { return (Math.Max((tradeObj.CallPut == "Call" ? 1 : -1) * ((avgPrice ?? spotPrice) - (actualStrike ?? 0.0)) * tradeObj.Notional, 0.0) - (tradeObj.TradePrice ?? 0.0)) * EodOperationBase.GetSign(tradeObj.BuySell); } if (avgPrice == null) { return 0.0; } return (Math.Max((tradeObj.CallPut == "Call" ? 1 : -1) * (spotPrice - avgPrice.Value) * tradeObj.Notional, 0.0) - (tradeObj.TradePrice ?? 0.0)) * EodOperationBase.GetSign(tradeObj.BuySell); } else if ("结构化交易".Equals(tradeObj.TradeType)) { throw new ServiceException($"不支持'结构化交易'主交易的计算,tradeId:{tradeObj.id},tradeNumber:{tradeObj.TradeNumber}"); } else { actualStrike = tradeObj.IsMoneynessOptionData ? (tradeObj.SpotPrice * tradeObj.Strike) : tradeObj.Strike; return (Math.Max((tradeObj.CallPut == "Call" ? 1 : -1) * (spotPrice - (actualStrike ?? 0.0)) * tradeObj.Notional, 0.0) - (tradeObj.TradePrice ?? 0.0)) * EodOperationBase.GetSign(tradeObj.BuySell); } } //获取亚式期权均价起算日之后的均价,未到均价起算日则返回Null private static double? GetAsianAveragePrice(string fixings, string PayoffType) { if (string.IsNullOrWhiteSpace(fixings)) { return null; } var arr = fixings.Split(QdpConsts.Semilicon); var fixingValues = arr.Select(x => DataConvert.ConvertCommaValueToDouble(x, 1) ?? 0).ToArray(); if ("GeometricAverage".Equals(PayoffType)) { var n = fixingValues.Length; return Math.Pow(fixingValues.Aggregate(func: (result, item) => result * item), 1.0 / n); } if ("ArithmeticAverage".Equals(PayoffType) || "DiscreteArithmeticAverage".Equals(PayoffType)) { return fixingValues.Average(); } return null; } #endregion } }