using YLErp.Abstract.DataProviders; using YLErp.BLL.Eod; using YLErp.BLL.Hedge; using YLErp.DBModels.Helpers; using YLErp.Modules.CalculationModule.Abstract; using YLErp.Modules.VolatilityModule; namespace YLErp.Modules.CalculationModule { /// /// 对冲交易盈亏计算 /// public class HedgePnlCalc { readonly IHedgePnlCalcContext _context; readonly IUnderlyingDataProvider _unDataProvider; readonly IPriceProvider _unPriceProvider; readonly IPriceProvider _exchangeOptionPriceProvider; readonly IPriceProvider _unSettlePriceProvider; public HedgePnlCalc(IHedgePnlCalcContext context) { _context = CalcCheckHelper.CheckHedgePnlCalcContext(context); _unDataProvider = context.UnderlyingDataProvider; _unPriceProvider = context.UnderlyingPriceProvider; _unSettlePriceProvider = context.UnderlyingSettlePriceProvider; _exchangeOptionPriceProvider = context.ExchangeOptionPriceProvider; } /// /// 计算对冲交易Pnl信息 /// /// 当日对冲交易数据 /// 上日持仓数据 public IEnumerable Calculate(IEnumerable newHedgeTrades, IEnumerable eodPositions) { if (newHedgeTrades == null && eodPositions == null) { return Enumerable.Empty(); } var pnlResults = new List(); //----------------------------------------- // 处理昨日持仓盈亏 //----------------------------------------- if (eodPositions != null) { foreach (var eodPosition in eodPositions) { ExchangeListOption exchangeOption = null; if (eodPosition.TradeType == "场内期权") { exchangeOption = _unDataProvider.GetExchange_List_Option(eodPosition.ExchangeOptionCode); //剔除已到期场内期权持仓 if (exchangeOption == null || exchangeOption.MaturityDate < _context.ValueDate) { continue; } } else if (!(ConsTrade.TradeTypesForHedge.Contains(eodPosition.TradeType)||ConsTrade.BondTypeList.Contains(eodPosition.TradeType))) { continue; } var eodPnl = ProcessEodPositionHegePnl(eodPosition, exchangeOption); pnlResults.Add(eodPnl); } } //----------------------------------------- // 处理当日对冲交易盈亏 //----------------------------------------- if (newHedgeTrades != null && newHedgeTrades.Any()) { //对冲交易手续费计算 var tradeCommissionDict = _context.CommissionCalc.GetTradeCommission(newHedgeTrades); foreach (var newTrade in newHedgeTrades) { //手续费 var commission = tradeCommissionDict.GetTradeCommission(newTrade.id); //标的价格 _unPriceProvider.TryGetPrice(newTrade.UnderlyingCode, out var underlyingPrice); //计算 ProcessNewTradeHegePnlV2(pnlResults, newTrade, commission, underlyingPrice); } } //----------------------------------------- // 场内期权对冲盈利 //----------------------------------------- var tempTradeId = 0; foreach (var pnl in pnlResults) { var underlying = _unDataProvider.GetUnderlying(pnl.UnderlyingCode, out var contractSize); if (underlying == null) { continue; } if (pnl.TradeType == "场内期权") { tempTradeId--; ProcessExchangeOptionPnl(pnl, underlying, tempTradeId); } else { pnl.Delta = pnl.Notional; pnl.DeltaCash = pnl.Pv; pnl.DeltaInLots = pnl.Delta / contractSize; pnl.Lots = pnl.Notional / contractSize; } } return pnlResults; } /// /// 昨日持仓对冲盈利 /// private HedgePnl ProcessEodPositionHegePnl(EodTradePosition eodPosition, ExchangeListOption exchangeOption) { double lastPv = 0, pv = 0, dailyPnl = 0, realizedPnL = 0, cost = 0; _unPriceProvider.TryGetPrice(eodPosition.UnderlyingCode, out var SettlePrice); var notional = eodPosition.Amount; if (Math.Abs(notional) > 0) { lastPv = eodPosition.Pv; pv = SettlePrice * eodPosition.Amount; dailyPnl = pv - lastPv; realizedPnL = eodPosition.ClosedPnL; cost = eodPosition.Cost; } else { notional = 0; } var uniqueCode = GetHedgeUniqueCode(eodPosition.BookId, eodPosition.TradeType, eodPosition.PositionType, eodPosition.UnderlyingCode, eodPosition.ExchangeOptionCode); var lastTotalPnl = eodPosition.TotalPnL; var eodPnl = new HedgePnl { BookId = eodPosition.BookId, ValueDate = _context.ValueDate, TradeType = eodPosition.TradeType, PositionType = eodPosition.PositionType, CallPut = TradeHelper.GetCallPut(exchangeOption?.OptionType), BuySell = eodPosition.BuySell, UnderlyingId = eodPosition.UnderlyingId, UnderlyingCode = eodPosition.UnderlyingCode, HedgeUniqueCode = uniqueCode, Notional = notional, LastPv = lastPv, Pv = pv, DailyPnL = dailyPnl, RealizedPnL = realizedPnL, TotalPnl = lastTotalPnl + dailyPnl, LastTotalPnl = lastTotalPnl, Cost = cost, Commission = eodPosition.Commission, Strike = exchangeOption?.Strike ?? 0, ExchangeOptionCode = eodPosition.ExchangeOptionCode, SettlePrice = SettlePrice, PositionPnl = eodPosition.PositionPnL, ExerciseDate = exchangeOption?.MaturityDate }; if (Math.Abs(eodPnl.Strike) < 1e-7 && exchangeOption != null) { eodPnl.Strike = exchangeOption.Strike; eodPnl.BuySell = "long".Equals(eodPosition.PositionType) ? "买入" : "卖出"; } return eodPnl; } /// /// 当日交易对冲盈利V2 /// private HedgePnl ProcessNewTradeHegePnlV2(List pnlResults, ExchangeTrade newTrade, double newTradeCommission, double underlyingPrice) { if (newTrade.Notional < 0) { throw new ServiceFaultException($"[场内交易数据,id:{newTrade.id},成交份额:{newTrade.Notional}]不允许成交份额小于0的场内交易数据存在!"); } //持仓类型 多头 空头分割 var positionType = GetHedgeLongShort(newTrade.TradeType, newTrade.TradeSide); //对冲唯一编码(簿记账户ID_结构类型_持仓类型_合约代码) var uniqueCode = GetHedgeUniqueCode(newTrade.AssetBookId, newTrade.TradeType, positionType, newTrade.UnderlyingCode, newTrade.OptionCode); //获取是否存在对应uniqueCode的对冲信息 var eodPnl = pnlResults.FirstOrDefault(t => t.HedgeUniqueCode == uniqueCode); //新的pnl(如果eodPnl存在则使用eodPnl) var newPnl = eodPnl; if (eodPnl == null) { newPnl = new HedgePnl { BookId = newTrade.AssetBookId, ValueDate = _context.ValueDate, TradeType = newTrade.TradeType, PositionType = positionType, CallPut = TradeHelper.GetCallPut(newTrade.OptionType), BuySell = positionType == "long" ? "买入" : "卖出", UnderlyingId = newTrade.UnderlyingId, UnderlyingCode = newTrade.UnderlyingCode, HedgeUniqueCode = uniqueCode, Strike = newTrade.OptionStrike ?? 0, ExchangeOptionCode = newTrade.OptionCode, SettlePrice = underlyingPrice, ExerciseDate = newTrade.MaturityDate }; pnlResults.Add(newPnl); if (newTrade.TradeType == "场内期权") { //场内期权的buysell不影响qdp计算 newPnl.BuySell = newTrade.TradeSide; } } else { newPnl.LastPv = eodPnl.LastPv; //昨市值 } //持仓符号和新交易符号 var posSign = newPnl.Notional < 0 ? -1 : 1; var newSign = EodOperationBase.GetSign(newTrade.TradeSide); double newNotional = newTrade.Notional, openNotional = 0d; //平仓处理 if (newSign != posSign) { var closeNotional = Math.Abs(newPnl.Notional); if (newNotional > closeNotional) { //平仓超出部分需要变成开仓 openNotional = newNotional - closeNotional; } else { closeNotional = newNotional; } if (closeNotional > 0) { //开仓金额(带符号) var openAmount = closeNotional * posSign * newPnl.Cost / newPnl.Notional; //平仓金额(带符号) var closeAmount = closeNotional * newSign * newTrade.TradeSinglePrice; //平仓盈亏 var closeProfit = -(openAmount + closeAmount); //已实现盈亏 newPnl.RealizedPnL += closeProfit; //减去持仓成本 newPnl.Cost -= newTrade.TradeAmount* newTrade.TradeSinglePrice; //减去持仓份额 newPnl.Notional -= closeNotional * posSign; } } else { openNotional = newNotional; } //开仓处理 if (openNotional > 0) { //转换为带符号的值 openNotional *= newSign; //累加开仓份额 newPnl.Notional += openNotional; //累加开仓成本 newPnl.Cost += openNotional * newTrade.TradeSinglePrice; } var newNotional_s = newNotional * newSign; var newCost = (newTrade.TradeSinglePrice * newNotional_s) + newTradeCommission; var newPv = newNotional_s * underlyingPrice; var newDailyPnl = newPv - newCost; newPnl.Cost += newTradeCommission; //成本加上手续费 newPnl.Commission += newTradeCommission; //累加总手续费 newPnl.DailyPnL += newDailyPnl; //当日盈亏 newPnl.TotalPnl += newDailyPnl; //总盈亏 newPnl.Pv += newPv; //总市值 newPnl.TdCost += newCost; //当日成本--主要用于下方的场内期权盈亏计算 return newPnl; } #region----场内期权对冲盈利---- private void ProcessExchangeOptionPnl(HedgePnl pnl, underlying_manager underlying, int tempTradeId) { TradeValueResult optionResult = null; //场内期权合约信息 var exchangeOption = (_unDataProvider.GetExchange_List_Option(pnl.ExchangeOptionCode)?.Clone()) ?? throw new HedgePnlCalcException($"场内期权合约[{pnl.ExchangeOptionCode}]在场内期权合约信息表中不存在!"); if (exchangeOption.MaturityDate < _context.ValueDate) { throw new HedgePnlCalcException($"场内期权合约'{pnl.ExchangeOptionCode}'已过期,合约到期日:{exchangeOption.MaturityDate:yyyy-MM-dd},估值日期:{_context.ValueDate:yyyy-MM-dd}"); } if (exchangeOption.UnderlyingCode.StartsWith("IO")) { exchangeOption.UnderlyingCode = "000300.SH"; } //pnl callput pnl.CallPut = TradeHelper.GetCallPut(exchangeOption.OptionType); //场内期权 合约乘数 var contractSize = underlying.ContractSize; if (exchangeOption.ContractSize > 1e-6) { contractSize = exchangeOption.ContractSize; } //pnl持仓手数 pnl.Lots = pnl.Notional / contractSize; //更新场内期权市场价格 pnl.ExOptionPrice = _exchangeOptionPriceProvider.TryGetPrice(pnl.ExchangeOptionCode, out var price) ? price : null; //是否有持仓 var hasPosition = Math.Abs(pnl.Notional) > 1e-10; if (hasPosition) { //使用曲面波动率或者隐含波动率计算期权风险 optionResult = InnerCalcExchangeOptionRisks(tempTradeId, pnl, underlying, exchangeOption); } if (optionResult != null && optionResult.Succeeded) { pnl.Vol = optionResult.Vol; pnl.Delta = optionResult.Delta; pnl.DeltaT1 = optionResult.DeltaT1; pnl.SA_Delta = optionResult.SA_Delta; pnl.Gamma = optionResult.Gamma; pnl.Vega = optionResult.Vega; pnl.DeltaCash = optionResult.DeltaCash; pnl.GammaCash = optionResult.GammaCash; pnl.DeltaInLots = optionResult.Delta / contractSize; pnl.GammaInLots = optionResult.Gamma / contractSize; pnl.Theta = optionResult.Theta; pnl.Rho = optionResult.Rho * 100; pnl.DdeltaDt = optionResult.DDeltaDt; pnl.DdeltaDvol = optionResult.DDeltaDVol; pnl.DvegaDt = optionResult.DVegaDt; pnl.DvegaDvol = optionResult.DVegaDVol; pnl.Pv = optionResult.Pv; pnl.TimeValue = optionResult.TimeValue; } else { pnl.Pv = 0; } var flag = _context.ExchangeOptionPriceUseFlag; if (PS.Config.Is光大光子) { flag = flag == ExchangeOptionPriceUseFlag.TrialCalclMode ? ExchangeOptionPriceUseFlag.SetExOptionPrice : ExchangeOptionPriceUseFlag.CalcPv; } if (flag == ExchangeOptionPriceUseFlag.CalcPv && hasPosition) { pnl.Pv = (pnl.ExOptionPrice * pnl.Notional) ?? 0; var intrinsicValue = ConsGlobal.CallPut.IsCall(pnl.CallPut) ? pnl.SettlePrice - pnl.Strike : pnl.Strike - pnl.SettlePrice; pnl.TimeValue = pnl.Pv - Math.Max(0, intrinsicValue) * pnl.Notional; } pnl.DailyPnL = pnl.Pv - pnl.LastPv - pnl.TdCost; pnl.TotalPnl = pnl.LastTotalPnl + pnl.DailyPnL; } //场内期权估值计算,pnl在此方法中会对CallPut、Lots、ExOptionPrice赋值处理 private TradeValueResult InnerCalcExchangeOptionRisks(int tempTradeId, HedgePnl pnl, underlying_manager underlying, ExchangeListOption exchangeOption) { var tempTrade = new trade { TradeType = pnl.TradeType, UnderlyingCode = pnl.UnderlyingCode, UnderlyingId = pnl.UnderlyingId, TradeDate = _context.ValueDate, BuySell = pnl.BuySell, StartDate = _context.ValueDate, ExerciseDate = exchangeOption.MaturityDate, MaturityDate = underlying.MaturityDate, TradePrice = Math.Abs(pnl.Cost), TradeStatus = "确认成交", ExerciseMode = exchangeOption.ExerciseMode, OptionType = exchangeOption.OptionType, Strike = pnl.Strike, Notional = pnl.Notional, UnderlyingInstrumentType = underlying.UnderlyingInstrumentType, ExchangeOptionCode = pnl.ExchangeOptionCode, AssetId = pnl.BookId, id = tempTradeId, UnderlyingAssetClass = underlying.UnderlyingType, //用于反算隐含波动率 StructureType = "场内期权", TradeSinglePrice = pnl.ExOptionPrice }; if (PS.Config.ErpElement.ExchangeOptionVolType == Configuration.Enums.ExchangeOptionVolType.ImpliedVol) { _unSettlePriceProvider.TryGetPrice(pnl.UnderlyingCode, out var price); tempTrade.Vol = VolatilityHelper.GetImpliedVol(_context.ValueDate, tempTrade, null, price.Normalize(), _context.IsEodCalc); } var optionCalcContext = _context.CreateOptionCalculateContext(); var optionResult = TradeRiskCalcUtil.CalcTradeRisk(tempTrade, optionCalcContext, out _); if (optionResult != null) { if (optionResult.FailReason == TradeValueFailReason.missingVol) { if (PS.Config.Is光大光子 && _context.CalcScenario == Enums.CalcScenarioEnum.EodSettlement && _context.VolType == "对冲") { var errmsg = $"[光子对冲收盘 {_context.ValueDate:yyyy-MM-dd}]场内期权'{pnl.ExchangeOptionCode}' 找不到波动率!"; throw new HedgePnlCalcException(errmsg); } } if (!optionResult.Succeeded) { var errmsg = $"{_context.ValueDate:yyyy-MM-dd},场内期权'{pnl.ExchangeOptionCode}' 计算失败,{optionResult.ErrorMessage},fail reason:{optionResult.FailReason}"; if (_context.CalcScenario == Enums.CalcScenarioEnum.EodSettlement) { LogFactory.GetLogger(nameof(HedgePnlCalc)).Error(errmsg); } else { //其他场景可能会产生大量重复日志,为了避免这种情况使用debug方式输出 LogFactory.GetLogger(nameof(HedgePnlCalc)).Debug(errmsg); } } } else { var errmsg = $"{_context.ValueDate:yyyy-MM-dd},场内期权'{pnl.ExchangeOptionCode}' 获取不到计算结果"; if (_context.CalcScenario == Enums.CalcScenarioEnum.EodSettlement) { LogFactory.GetLogger(nameof(HedgePnlCalc)).Error(errmsg); } else { LogFactory.GetLogger(nameof(HedgePnlCalc)).Debug(errmsg); } } return optionResult; } #endregion /// /// 根据对冲账号 簿记账户I 结构类型 买卖方向 标的代码 场内期权代码 编制对冲唯一编码 /// public static string GetHedgeUniqueCode(int BookId, string TradeType, string LongShort, string UnderlyingCode, string ExchangeOptionCode = null) { List tradeTypes=new List() { "利率债", "信用债", "其它债券" }; if (tradeTypes.Contains(TradeType)) { return $"{BookId}_{TradeType}_{UnderlyingCode}".ToUpperInvariant(); } return $"{BookId}_{TradeType}_{LongShort}_{("场内期权".Equals(TradeType) ? ExchangeOptionCode : UnderlyingCode)}".ToUpperInvariant(); } /// /// 对冲交易 根据结构类型 买卖方向 看涨看跌 获取持仓long short /// public static string GetHedgeLongShort(string TradeType, string BuySell) { switch (TradeType) { case "商品期货": case "商品现货": case "场内期权": case "利率债": case "信用债": case "其它债券": return BuySell.Contains("多头") ? "long" : "short"; case "股票": default: return "long"; } } } /// /// 对冲交易计算异常 /// public class HedgePnlCalcException : Exception { public HedgePnlCalcException(string message) : base(message) { } public HedgePnlCalcException(string message, Exception innerException) : base(message, innerException) { } } }