759 lines
38 KiB
C#
759 lines
38 KiB
C#
using NPOI.Util;
|
|
using Qdp.Pricing.Base.Implementations;
|
|
using YLErp.Abstract;
|
|
using YLErp.Abstract.DataProviders;
|
|
using YLErp.BLL;
|
|
using YLErp.BLL.Calculation;
|
|
using YLErp.DBModels.Enums;
|
|
using YLErp.Enums;
|
|
using YLErp.Modules.CalculationModule.Abstract;
|
|
using YLErp.Modules.VolatilityModule;
|
|
using YLErp.QdpModule;
|
|
|
|
namespace YLErp.Modules.CalculationModule
|
|
{
|
|
/// <summary>
|
|
/// 期权价值计算
|
|
/// </summary>
|
|
public class TradeRiskValueCalc
|
|
{
|
|
readonly OtcTradeBase _trade;
|
|
readonly IOtcTradeValueCalcContext _context;
|
|
readonly IPriceProvider _underlyingPriceProvider;
|
|
|
|
readonly int _maturityShift;
|
|
|
|
underlying_manager _underlying;
|
|
underlying_manager[] _spreadUnderlyings;
|
|
|
|
readonly IOptionCalcDataProvider _dataProvider;
|
|
readonly IUnderlyingDataProvider _underlyingDataProvider;
|
|
readonly ITradeExtendDataProvider _tradeExtendDataProvider;
|
|
readonly ITradeKnockOutPayoffCalcService _tradeKnockOutPayoffCalcService;
|
|
|
|
public TradeRiskValueCalc(IOtcTradeValueCalcContext context, OtcTradeBase trade)
|
|
{
|
|
_trade = trade ?? throw new ArgumentNullException(nameof(trade));
|
|
_context = context ?? throw new ArgumentNullException(nameof(context));
|
|
|
|
_dataProvider = CalcCheckHelper.CheckOptionCalcDataProvider(context.DataProvider);
|
|
_underlyingDataProvider = _dataProvider.UnderlyingDataProvider;
|
|
_tradeExtendDataProvider = _dataProvider.TradeExtendDataProvider;
|
|
_underlyingPriceProvider = _dataProvider.UnderlyingPriceProvider;
|
|
|
|
_maturityShift = 0;
|
|
_tradeKnockOutPayoffCalcService = new TradeKnockOutPayoffCalcService(_tradeExtendDataProvider, _context.ValueDate);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算衍生品价值
|
|
/// </summary>
|
|
public TradeValueResult GetTradeValue(out underlying_manager[] underlyingArr)
|
|
{
|
|
TradeValueResult result;
|
|
|
|
if (_trade.TradeType == "现金流交易")
|
|
{
|
|
result = InnerGetCashFlowTradeValue();
|
|
|
|
if (_context.CalcDeltaT1)
|
|
{
|
|
result.DeltaT1 = result.Delta;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result = InnerGetTradeValue(null);
|
|
}
|
|
|
|
underlyingArr = _spreadUnderlyings ?? Array.Empty<underlying_manager>();
|
|
Array.Resize(ref underlyingArr, underlyingArr.Length + 1);
|
|
underlyingArr[0] = _underlying;
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算衍生品价值
|
|
/// </summary>
|
|
private TradeValueResult InnerGetTradeValue(underlying_manager unly)
|
|
{
|
|
//underlying
|
|
_underlying = unly;
|
|
|
|
if (_underlying == null)
|
|
{
|
|
_underlying = _underlyingDataProvider.GetUnderlying(_trade.UnderlyingCode);
|
|
if (_underlying == null)
|
|
{
|
|
throw new TradeCalcExpception(_trade.id, $"未获取到标的数据:{_trade.UnderlyingCode}");
|
|
}
|
|
_underlying.QuotationDate = _context.ValueDate;
|
|
}
|
|
|
|
//结构化交易
|
|
if (_trade.TradeType == "结构化交易")
|
|
{
|
|
throw new ServiceException($"不支持'结构化交易'主交易的计算,tradeId:{_trade.id},tradeNumber:{_trade.TradeNumber}");
|
|
}
|
|
|
|
//准备计算
|
|
_spreadUnderlyings = null;
|
|
|
|
//stock MaturityDate
|
|
if (_underlying.UnderlyingInstrumentType == "Stock")
|
|
{
|
|
//股票默认到期日为行权日
|
|
if (_trade.ExerciseDate != null)
|
|
{
|
|
_trade.MaturityDate = _trade.ExerciseDate;
|
|
}
|
|
}
|
|
|
|
//标的即期价格
|
|
var blGetPrice = _underlyingPriceProvider.TryGetPrice(_trade.UnderlyingCode, out var spotPrice);
|
|
|
|
_underlying.Price = spotPrice;
|
|
|
|
//如果当天有维护过风险值,则直接获取风险值即可,未维护的风险值则通过系统计算
|
|
var result = TradeRiskCalcUtil.GetManualOptionValue(_context.ValueDate, (trade)_trade, spotPrice, 0, false, _context.CalcScenario, volType: _context.VolType, isSettle: _context.CalcScenario == Enums.CalcScenarioEnum.EodSettlement);
|
|
var optionValue = result.manual != null ? result.optionValue : null;
|
|
//没有自定义风险维护值 或者 自定风险维护没有涵盖PV和所有希腊值(部分维护场景)
|
|
//部分维护场景下,需要系统计算出未赋值的属性,进行合并返还
|
|
if (result.manual == null || result.manual != null && !result.manual.IsPVAndAllGreek)
|
|
{
|
|
//开始计算
|
|
switch (_trade.TradeType)
|
|
{
|
|
case "远期":
|
|
if (PS.Config.ErpElement.ForwardTradePriceModel == Configuration.Enums.ForwardTradePriceModel.STANDARD && !string.IsNullOrWhiteSpace(_trade.BasisUnderlyingCode))
|
|
{
|
|
_underlyingPriceProvider.TryGetPrice(_trade.BasisUnderlyingCode, out var BasiseodPrice);
|
|
spotPrice -= BasiseodPrice;
|
|
}
|
|
optionValue = ForwardradeCalcService.CalcValue(_trade, spotPrice);
|
|
break;
|
|
case "信用债":
|
|
case "商品期货":
|
|
case "股票":
|
|
{
|
|
var pv = spotPrice * _trade.Notional;
|
|
optionValue = new TradeValueResult { Pv = pv, Delta = _trade.Notional, DeltaCash = pv };
|
|
}
|
|
break;
|
|
case "自定义交易":
|
|
{
|
|
var positionVol = VolatilityHelper.GetTradeVol((trade)_trade, _context.ValueDate, _context.CalcScenario == CalcScenarioEnum.EodSettlement);
|
|
optionValue = TradeRiskCalcUtil.GetManualOptionValue(_context.ValueDate, (trade)_trade, spotPrice, positionVol, true, _context.CalcScenario, isSettle: _context.CalcScenario == CalcScenarioEnum.EodSettlement).optionValue;
|
|
}
|
|
break;
|
|
case "收益互换":
|
|
{
|
|
var trade_swap = _tradeExtendDataProvider.GetTrade_Swap(_trade.id);
|
|
optionValue = PayoffSwapCalcService.CalcValue(_trade, _context.ValueDate, _underlyingPriceProvider, false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
//计算期权估值时包含了DeltaT1,所以不需要再次赋值
|
|
if (optionValue == null)
|
|
{
|
|
if (!blGetPrice)
|
|
{
|
|
if (_trade.TradeType == "场内期权")
|
|
{
|
|
throw new ServiceException($"场内期权计算失败,期权代码:{_trade.ExchangeOptionCode},错误信息:期权标的'{_trade.UnderlyingCode}'缺少价格");
|
|
}
|
|
else
|
|
{
|
|
throw new ServiceException($"{_trade.TradeType}计算失败,交易编号:{_trade.TradeNumber},标的:{_trade.UnderlyingCode},错误信息:缺少标的价格");
|
|
}
|
|
}
|
|
optionValue = InnerGetOptionValue(spotPrice);
|
|
}
|
|
else if (_context.CalcDeltaT1)
|
|
{
|
|
optionValue.DeltaT1 = optionValue.Delta;
|
|
}
|
|
}
|
|
|
|
//系统计算结果和手动维护值合并
|
|
TradeRiskCalcUtil.GetOptionValueWithManual(result.manual, optionValue);
|
|
|
|
if (optionValue != null)
|
|
{
|
|
optionValue.Strike = _trade.Strike ?? 0;
|
|
optionValue.SpotPrice = spotPrice;
|
|
optionValue.VegaCash = optionValue.VegaCash.IsNormalize() ? optionValue.VegaCash : (optionValue.Vega * optionValue.SpotPrice).Normalize();
|
|
|
|
optionValue.NPv = optionValue.Pv;
|
|
optionValue.NRoundedPv = optionValue.RoundedPv;
|
|
if (PS.Config.ErpElement.IsPVIncludePrincipal)
|
|
{
|
|
optionValue.Pv += _trade.PrincipalSum() * (_trade.BuySell == "卖出" ? -1 : 1);
|
|
optionValue.RoundedPv += _trade.PrincipalSum() * (_trade.BuySell == "卖出" ? -1 : 1);
|
|
}
|
|
}
|
|
|
|
return optionValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取场外期权价值计算结果
|
|
/// </summary>
|
|
private TradeValueResult InnerGetOptionValue(double spotPrice)
|
|
{
|
|
var pricingRequest = _context.GetPricingRequest(_trade);
|
|
if (pricingRequest == PricingRequest.None)
|
|
{
|
|
return new TradeValueResult
|
|
{
|
|
TradeId = _trade.id,
|
|
UnderlyingCode = _trade.UnderlyingCode,
|
|
BuySell = _trade.BuySell,
|
|
UnderlyingId = _trade.UnderlyingId,
|
|
ErrorMessage = "PricingRequest.None"
|
|
};
|
|
}
|
|
|
|
var qdpTradeId = _context.MarketProxy.NextRequestId() + "_" +
|
|
(_trade.id > 0 ? _trade.id.ToString() : string.IsNullOrWhiteSpace(_trade.ExchangeOptionCode) ? _trade.UnderlyingCode : _trade.ExchangeOptionCode);
|
|
|
|
//准备波动率
|
|
if (!_context.PrepareVolatility(qdpTradeId, _trade, spotPrice, out var volsurfaceNames))
|
|
{
|
|
return new TradeValueResult(false)
|
|
{
|
|
UnderlyingCode = _underlying.UnderlyingCode,
|
|
ErrorMessage = "没有波动率",
|
|
FailReason = TradeValueFailReason.missingVol
|
|
};
|
|
}
|
|
|
|
TradeValueResult result = null;
|
|
var tpRequest = new OptionTradeParamRequest(_context.SysRiskFreeRate)
|
|
{
|
|
tradeId = qdpTradeId,
|
|
preciseTimeMode = _context.IsPreciseTimeMode,
|
|
fixings = null,
|
|
maturityShift = _maturityShift,
|
|
volSurfaceNames = volsurfaceNames,
|
|
hasNightMarket = false,
|
|
isEodCalc = _context.IsEodCalc,
|
|
timeToMaturityDays = double.NaN,
|
|
ParamOverride = p =>
|
|
{
|
|
p.riskFreeRate = _context.GetRiskFreeRate(_trade);
|
|
p.dividendRate = _context.GetDividendRate(_trade);
|
|
}
|
|
};
|
|
var nextDay = QdpCalendarHelper.BizDayShift(valuedateBLL.ValueDate, 1);
|
|
//象屿最后一个交易日实时计算时TTM需要和平仓时算法一致
|
|
if (PS.Config.Is厦门象屿
|
|
&& (_context.CalcScenario == CalcScenarioEnum.RealtimePosition
|
|
|| _context.CalcScenario == CalcScenarioEnum.RealtimeRisk)
|
|
&& _trade.SettlementType == SettlementTypeEnum.ReferencePrice)
|
|
{
|
|
tpRequest.timeToMaturityDays = TradeCalcHelper.CalculateTTMDays(valuedateBLL.ValueDate, _trade.ExerciseDate.Value, _underlying.UnderlyingTypeId, false);
|
|
}
|
|
|
|
var variety = new Variety();
|
|
if (_underlying.IsFutures())
|
|
{
|
|
variety = DataCacheProvider.GetVarietyDataSource().GetData(_underlying.UnderlyingTypeId);
|
|
tpRequest.hasNightMarket = variety != null && variety.HasNightMarket;
|
|
}
|
|
|
|
OptionCalcParam<T> getOptionCalcParam<T>(T tradeParam, double[] spotPrices = null) where T : OptionTradeParamBase
|
|
{
|
|
return new OptionCalcParam<T>(tradeParam)
|
|
{
|
|
engineName = null,
|
|
pricingRequest = pricingRequest,
|
|
spotPrices = spotPrices ?? new[] { spotPrice },
|
|
calcScenario = _context.CalcScenario,
|
|
CalcDeltaT1 = _context.CalcDeltaT1
|
|
};
|
|
}
|
|
|
|
var marketProxy = _context.MarketProxy;
|
|
|
|
//东证润和是精确时间模式参与计算,收盘的话,刚好是整数天,所以不需要特殊处理
|
|
if (PS.Config.Is润和 && !tpRequest.isEodCalc && double.IsNaN(tpRequest.timeToMaturityDays))
|
|
{
|
|
tpRequest.timeToMaturityDays = TradeCalcHelper.CalculateTTMDays(marketProxy.ValueDate, _trade.ExerciseDate.Value, 0, false);
|
|
}
|
|
|
|
//开始计算
|
|
switch (_trade.TradeType)
|
|
{
|
|
case "香草期权":
|
|
{
|
|
var tradeParam = QdpTradeBuilder.GetVanillaOptionTradeParam(_trade, tpRequest, false);
|
|
result = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
|
}
|
|
break;
|
|
case "Risky期权":
|
|
{
|
|
var riskyOption = _tradeExtendDataProvider.GetTrade_Risky_Option(_trade.id);
|
|
result = GetOptionRisky(_trade, riskyOption, tpRequest, _underlying, marketProxy, pricingRequest, spotPrice, _context.CalcScenario, _context.CalcDeltaT1);
|
|
}
|
|
break;
|
|
case "彩虹期权":
|
|
{
|
|
var tradeParam = GetRainbowOptionTradeParam(tpRequest, out var spotPrices);
|
|
if (tradeParam == null)
|
|
{
|
|
return new TradeValueResult { UnderlyingCode = _underlying.UnderlyingCode };
|
|
}
|
|
result = TradeRiskCalcUtil.GetRainbowOptionValue(marketProxy, getOptionCalcParam(tradeParam, spotPrices));
|
|
}
|
|
break;
|
|
case "亚式期权":
|
|
{
|
|
if (!PS.Config.Is润和 && !tpRequest.isEodCalc && double.IsNaN(tpRequest.timeToMaturityDays))
|
|
{
|
|
tpRequest.timeToMaturityDays = TradeCalcHelper.CalculateTTMDays(marketProxy.ValueDate, _trade.ExerciseDate.Value, variety.id, false);
|
|
}
|
|
var tradeParam = GetAsianOptionCalcParam(tpRequest, spotPrice);
|
|
result = TradeRiskCalcUtil.GetAsianOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
|
}
|
|
break;
|
|
case "障碍期权":
|
|
{
|
|
var barrierOption = _tradeExtendDataProvider.GetTrade_Barrier_Option(_trade.id)
|
|
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的障碍期权数据");
|
|
var tradeParam = QdpTradeBuilder.GetBarrierOptionTradeParam(_trade, barrierOption, tpRequest);
|
|
result = TradeRiskCalcUtil.GetBarrierOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
|
}
|
|
break;
|
|
case "二元期权":
|
|
{
|
|
var binaryOption = _tradeExtendDataProvider.GetTrade_Binary_Option(_trade.id)
|
|
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的二元期权数据");
|
|
var tradeParam = QdpTradeBuilder.GetBinaryOptionTradeParam(_trade, binaryOption, tpRequest);
|
|
result = TradeRiskCalcUtil.GetBinaryOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
|
}
|
|
break;
|
|
case "价差期权":
|
|
{
|
|
var tradeParam = GetSpreadOptionCalcParam(tpRequest, out var spotPrices);
|
|
if (tradeParam == null)
|
|
{
|
|
return new TradeValueResult { UnderlyingCode = _underlying.UnderlyingCode };
|
|
}
|
|
result = TradeRiskCalcUtil.GetSpreadOptionValue(marketProxy, getOptionCalcParam(tradeParam, spotPrices));
|
|
}
|
|
break;
|
|
case "场内期权":
|
|
{
|
|
var tradeParam = QdpTradeBuilder.GetVanillaOptionTradeParam(_trade, tpRequest, true);
|
|
result = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
|
}
|
|
break;
|
|
case "合成价差期权":
|
|
{
|
|
var tradeParam = QdpTradeBuilder.GetSSpreadOptionTradeParam(_trade, tpRequest);
|
|
result = TradeRiskCalcUtil.GetSSpreadOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
|
}
|
|
break;
|
|
case "亚式合成价差期权":
|
|
{
|
|
var tradeParam = GetAsianOptionCalcParam(tpRequest, spotPrice);
|
|
result = TradeRiskCalcUtil.GetAsianSSpreadOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
|
}
|
|
break;
|
|
case "双鲨期权":
|
|
{
|
|
var dbsharkfinOption = _tradeExtendDataProvider.GetTrade_Double_SharkFin_Option(_trade.id)
|
|
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的双鲨期权数据");
|
|
var tradeParam = QdpTradeBuilder.GetDoubleSharkFinOptionTradeParam(_trade, dbsharkfinOption, tpRequest);
|
|
result = TradeRiskCalcUtil.GetDoubleSharkFinOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
|
}
|
|
break;
|
|
case "凤凰期权":
|
|
{
|
|
var autocallOption = _tradeExtendDataProvider.GetTrade_Autocall_Option(_trade.id)
|
|
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的凤凰期权数据");
|
|
autocallOption.HappenedObservations = _tradeExtendDataProvider.GetTrade_HappenedObservations(_trade.id);
|
|
var tradeParam = QdpTradeBuilder.GetAutocallOptionTradeParam(_trade, autocallOption, tpRequest);
|
|
try
|
|
{
|
|
result = TradeRiskCalcUtil.GetAutocallOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (string.IsNullOrEmpty(_trade.TradeNumber))
|
|
{
|
|
throw;
|
|
}
|
|
throw new Exception($"凤凰期权'{_trade.TradeNumber}'计算出错:{ex.Message}");
|
|
}
|
|
}
|
|
break;
|
|
case "雪球期权":
|
|
{
|
|
var snowball = _tradeExtendDataProvider.GetTrade_Snowball_Option(_trade.id)
|
|
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的雪球期权数据");
|
|
|
|
if (snowball?.PrepaymentUsed ?? false)
|
|
{
|
|
var snowballSpecialistOptionCalculator = new SnowballSpecialistOptionCalculator();
|
|
var vol = marketProxy.QdpMarket.VolSurfaces[qdpTradeId].ValueOnGrids[0, 0];
|
|
var trade = (trade)_trade;
|
|
trade.trade_snowball = snowball;
|
|
|
|
if (snowball.PrepaymentRatio > 0)
|
|
{
|
|
var specialSnowballTrade = snowballSpecialistOptionCalculator.GetSpecialTrade(trade);
|
|
var specialSnowballResult = snowballSpecialistOptionCalculator.CalcOptionValue(marketProxy.ValueDate, spotPrice, vol, _context.CalcScenario, specialSnowballTrade);
|
|
var breakevenSnowballTrade = snowballSpecialistOptionCalculator.GetBreakevenTrade(trade);
|
|
var tradeParam = QdpTradeBuilder.GetSnowballTradeParam(breakevenSnowballTrade, breakevenSnowballTrade.trade_snowball, tpRequest);
|
|
//因为构建tradeParam的时候,会用全局变量_trade的NoRiskRate,所以这边需要覆盖一下
|
|
tradeParam.riskFreeRate = breakevenSnowballTrade.NoRiskRate ?? 0;
|
|
var breakevenSnowballResult = TradeRiskCalcUtil.GetSnowballOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
|
result = snowballSpecialistOptionCalculator.MergeTradeValueResult(specialSnowballResult, breakevenSnowballResult);
|
|
}
|
|
else
|
|
{
|
|
result = snowballSpecialistOptionCalculator.CalcOptionValue(marketProxy.ValueDate, spotPrice, vol, _context.CalcScenario, trade);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var tradeParam = QdpTradeBuilder.GetSnowballTradeParam(_trade, snowball, tpRequest);
|
|
result = TradeRiskCalcUtil.GetSnowballOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
|
}
|
|
}
|
|
break;
|
|
case "气囊结构":
|
|
{
|
|
var airbag = _tradeExtendDataProvider.GetTrade_Airbag(_trade.id)
|
|
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的气囊结构期权数据");
|
|
var tradeParam = QdpTradeBuilder.GetAirbagOptionTradeParam(_trade, airbag, tpRequest);
|
|
result = TradeRiskCalcUtil.GetAirbagOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
|
}
|
|
break;
|
|
case "收益增强结构":
|
|
{
|
|
var underlyingEnhance = _tradeExtendDataProvider.GetTrade_UnderlyingEnhance(_trade.id)
|
|
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的收益增强结构期权数据");
|
|
var tradeParam = QdpTradeBuilder.GetUnderlyingEnhanceTradeParam(_trade, underlyingEnhance, tpRequest);
|
|
result = TradeRiskCalcUtil.GetUnderlyingEnhanceValue(marketProxy, getOptionCalcParam(tradeParam));
|
|
}
|
|
break;
|
|
case "区间累积期权":
|
|
{
|
|
var rangeAccrual = _tradeExtendDataProvider.GetTrade_RangeAccrual(_trade.id)
|
|
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的区间累积期权数据");
|
|
//有些时候会传入大于估值日期的票息(比如实时风险算昨日pv的时候)
|
|
rangeAccrual.HappenedObservations = _tradeExtendDataProvider.GetTrade_HappenedObservations(_trade.id)
|
|
?.Where(n => n.EndDate < _context.ValueDate)?.ToList();
|
|
var tradeParam = QdpTradeBuilder.GetRangeAccrualTradeParam(_trade, rangeAccrual, tpRequest);
|
|
result = TradeRiskCalcUtil.GetRangeAccrualValue(marketProxy, getOptionCalcParam(tradeParam));
|
|
}
|
|
break;
|
|
case "累计期权":
|
|
{
|
|
var accumulator = _tradeExtendDataProvider.GetTrade_Accumulator_Option(_trade.id)
|
|
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的累计期权数据");
|
|
var tradeParam = QdpTradeBuilder.GetAccumulatorOptionTradeParam(_trade, accumulator, tpRequest);
|
|
result = TradeRiskCalcUtil.GetAccumulatorOptionValue(marketProxy, getOptionCalcParam(tradeParam), _trade.Notional);
|
|
}
|
|
break;
|
|
default:
|
|
result = new TradeValueResult(false) { ErrorMessage = "未知的期权结构" };
|
|
break;
|
|
}
|
|
if (result != null)
|
|
{
|
|
// 通过当前价格计算出来的交易是否敲出以及敲出payoff
|
|
var knockOutPayoffResult = _tradeKnockOutPayoffCalcService.GetKnockOutPayoff((trade)_trade, spotPrice);
|
|
if (knockOutPayoffResult != null)
|
|
{
|
|
result.IsKnockOut = knockOutPayoffResult.IsKnockOut;
|
|
result.KnockOutPayoff = knockOutPayoffResult.Payoff;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取现金流价值计算结果
|
|
/// </summary>
|
|
private TradeValueResult InnerGetCashFlowTradeValue()
|
|
{
|
|
var qdpTradeId = _context.MarketProxy.NextRequestId() + "_" + (_trade.id > 0 ? _trade.id.ToString() : string.IsNullOrWhiteSpace(_trade.ExchangeOptionCode) ? _trade.UnderlyingCode : _trade.ExchangeOptionCode);
|
|
|
|
_context.MarketProxy.SetVolSurface(qdpTradeId, QdpModule.QdpVolHelper.GetDefaultVolatility(0.3));
|
|
|
|
var tpRequest = new OptionTradeParamRequest(_context.SysRiskFreeRate)
|
|
{
|
|
tradeId = qdpTradeId,
|
|
preciseTimeMode = _context.IsPreciseTimeMode,
|
|
fixings = null,
|
|
maturityShift = _maturityShift,
|
|
volSurfaceNames = new[] { qdpTradeId },
|
|
hasNightMarket = false,
|
|
timeToMaturityDays = double.NaN,
|
|
ParamOverride = p =>
|
|
{
|
|
p.riskFreeRate = _context.GetRiskFreeRate(_trade);
|
|
p.dividendRate = _context.GetDividendRate(_trade);
|
|
}
|
|
};
|
|
|
|
var rangeAccrual = _tradeExtendDataProvider.GetTrade_CashFlow(_trade.id) ?? throw new TradeCalcExpception(_trade.id, "未获取到对应的现金流交易数据");
|
|
var tradeParam = QdpTradeBuilder.GetCashFlowTradeParam(_trade, rangeAccrual, tpRequest);
|
|
|
|
var calcParam = new OptionCalcParam<CashFlowTradeParam>(tradeParam)
|
|
{
|
|
engineName = null,
|
|
pricingRequest = _context.GetPricingRequest(_trade),
|
|
calcScenario = _context.CalcScenario
|
|
};
|
|
|
|
return TradeRiskCalcUtil.GetCashFlowValue(_context.MarketProxy, calcParam, _trade.StockEqvNotional);
|
|
}
|
|
|
|
|
|
private static TradeValueResult GetOptionRisky(
|
|
OtcTradeBase td,
|
|
trade_risky_option risky_Option,
|
|
OptionTradeParamRequest request,
|
|
underlying_manager underlyings,
|
|
MarketProxy marketProxy,
|
|
PricingRequest pricingRequest,
|
|
double spotPrice,
|
|
CalcScenarioEnum CalcScenario,
|
|
bool calcDeltaT)
|
|
{
|
|
OptionCalcParam<T> getOptionCalcParam<T>(T tradeParam, double[] spotPrices = null) where T : OptionTradeParamBase
|
|
{
|
|
return new OptionCalcParam<T>(tradeParam)
|
|
{
|
|
engineName = null,
|
|
pricingRequest = pricingRequest,
|
|
spotPrices = spotPrices ?? new[] { spotPrice },
|
|
calcScenario = CalcScenario,
|
|
CalcDeltaT1 = calcDeltaT
|
|
};
|
|
}
|
|
|
|
var result = new TradeValueResult();
|
|
|
|
var tradeclone = td.Copy();
|
|
tradeclone.TradeAmount = tradeclone.TradeAmount = TradeCalcHelper.GetTradeAmountV(td, td.TradeAmount, 1);
|
|
tradeclone.Notional = tradeclone.Notional = TradeCalcHelper.GetTradeAmountV(td, td.Notional, underlyings.CountRatio);
|
|
|
|
var td1 = tradeclone.Copy();
|
|
var td2 = tradeclone.Copy();
|
|
if (risky_Option.ParticipationRate2 != 0)
|
|
{
|
|
td2.Strike = risky_Option.Strike2;
|
|
td2.ParticipationRate = risky_Option.ParticipationRate2;
|
|
td2.TradeAmount = TradeCalcHelper.GetTradeAmount(td2, td2.TradeAmount, 1);
|
|
td2.Notional = TradeCalcHelper.GetTradeAmount(td2, td2.Notional, underlyings.CountRatio);
|
|
var tradeParam2 = QdpTradeBuilder.GetVanillaOptionTradeParam(td2, request, false);
|
|
result = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, getOptionCalcParam(tradeParam2));
|
|
}
|
|
|
|
if (risky_Option.ParticipationRate1 != 0)
|
|
{
|
|
td1.Strike = risky_Option.Strike1;
|
|
td1.ParticipationRate = risky_Option.ParticipationRate1;
|
|
td1.TradeAmount = TradeCalcHelper.GetTradeAmount(td1, td1.TradeAmount, 1);
|
|
td1.Notional = TradeCalcHelper.GetTradeAmount(td1, td1.Notional, underlyings.CountRatio);
|
|
td1.OptionType = "看跌";
|
|
var tradeParam1 = QdpTradeBuilder.GetVanillaOptionTradeParam(td1, request, false);
|
|
var singleresult1 = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, getOptionCalcParam(tradeParam1));
|
|
result.Pv -= singleresult1.Pv;
|
|
result.Delta -= singleresult1.Delta;
|
|
result.Gamma -= singleresult1.Gamma;
|
|
result.Vega -= singleresult1.Vega;
|
|
result.CalendarDayTheta -= singleresult1.CalendarDayTheta;
|
|
result.TradingDayTheta -= singleresult1.TradingDayTheta;
|
|
result.Rho -= singleresult1.Rho;
|
|
result.DeltaInLots -= singleresult1.DeltaInLots;
|
|
result.DeltaCash -= singleresult1.DeltaCash;
|
|
result.GammaCash -= singleresult1.GammaCash;
|
|
result.VegaCash -= singleresult1.VegaCash;
|
|
result.RoundedPv -= singleresult1.RoundedPv;
|
|
result.DDeltaDVol -= singleresult1.DDeltaDVol;
|
|
result.DDeltaDt -= singleresult1.DDeltaDt;
|
|
result.DVegaDVol -= singleresult1.DVegaDVol;
|
|
result.DVegaDt -= singleresult1.DVegaDt;
|
|
result.DeltaT1 -= singleresult1.DeltaT1;
|
|
result.ErrorMessage += singleresult1.ErrorMessage;
|
|
|
|
result.Vol = singleresult1.Vol;
|
|
result.Succeeded = singleresult1.Succeeded && result.Succeeded;
|
|
}
|
|
|
|
var td3 = tradeclone.Copy();
|
|
td3.Strike = risky_Option.Strike3;
|
|
//decimal 为了解决精度问题: 0.2-0.3=0.0999999999
|
|
var participationRate3 = (decimal)risky_Option.ParticipationRate3 - (decimal)risky_Option.ParticipationRate2;
|
|
if (participationRate3 != 0)
|
|
{
|
|
td3.ParticipationRate = (double?)Math.Abs(participationRate3);
|
|
td3.TradeAmount = TradeCalcHelper.GetTradeAmount(td3, td3.TradeAmount, 1);
|
|
td3.Notional = TradeCalcHelper.GetTradeAmount(td3, td3.Notional, underlyings.CountRatio);
|
|
var tradeParam3 = QdpTradeBuilder.GetVanillaOptionTradeParam(td3, request, false);
|
|
var singleresult3 = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, getOptionCalcParam(tradeParam3));
|
|
if (participationRate3 < 0)
|
|
{
|
|
result.Pv -= singleresult3.Pv;
|
|
result.Delta -= singleresult3.Delta;
|
|
result.Gamma -= singleresult3.Gamma;
|
|
result.Vega -= singleresult3.Vega;
|
|
result.CalendarDayTheta -= singleresult3.CalendarDayTheta;
|
|
result.TradingDayTheta -= singleresult3.TradingDayTheta;
|
|
result.Rho -= singleresult3.Rho;
|
|
result.DeltaInLots -= singleresult3.DeltaInLots;
|
|
result.DeltaCash -= singleresult3.DeltaCash;
|
|
result.GammaCash -= singleresult3.GammaCash;
|
|
result.VegaCash -= singleresult3.VegaCash;
|
|
result.RoundedPv -= singleresult3.RoundedPv;
|
|
result.DDeltaDVol -= singleresult3.DDeltaDVol;
|
|
result.DDeltaDt -= singleresult3.DDeltaDt;
|
|
result.DVegaDVol -= singleresult3.DVegaDVol;
|
|
result.DVegaDt -= singleresult3.DVegaDt;
|
|
result.DeltaT1 -= singleresult3.DeltaT1;
|
|
result.ErrorMessage += singleresult3.ErrorMessage;
|
|
|
|
result.Vol = singleresult3.Vol;
|
|
}
|
|
else
|
|
{
|
|
result.Pv += singleresult3.Pv;
|
|
result.Delta += singleresult3.Delta;
|
|
result.Gamma += singleresult3.Gamma;
|
|
result.Vega += singleresult3.Vega;
|
|
result.CalendarDayTheta += singleresult3.CalendarDayTheta;
|
|
result.TradingDayTheta += singleresult3.TradingDayTheta;
|
|
result.Rho += singleresult3.Rho;
|
|
result.DeltaInLots += singleresult3.DeltaInLots;
|
|
result.DeltaCash += singleresult3.DeltaCash;
|
|
result.GammaCash += singleresult3.GammaCash;
|
|
result.VegaCash += singleresult3.VegaCash;
|
|
result.RoundedPv += singleresult3.RoundedPv;
|
|
result.DDeltaDVol += singleresult3.DDeltaDVol;
|
|
result.DDeltaDt += singleresult3.DDeltaDt;
|
|
result.DVegaDVol += singleresult3.DVegaDVol;
|
|
result.DVegaDt += singleresult3.DVegaDt;
|
|
result.DeltaT1 += singleresult3.DeltaT1;
|
|
result.ErrorMessage += singleresult3.ErrorMessage;
|
|
|
|
result.Vol = singleresult3.Vol;
|
|
}
|
|
result.Succeeded = singleresult3.Succeeded && result.Succeeded;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#region----期权计算参数----
|
|
|
|
/// <summary>
|
|
/// 彩虹期权(如果准备波动率失败,返回null)
|
|
/// </summary>
|
|
private RainbowOptionTradeParam GetRainbowOptionTradeParam(OptionTradeParamRequest request, out double[] spotPrices)
|
|
{
|
|
var rainbowOption = _tradeExtendDataProvider.GetTrade_Rainbow_Option(_trade.id)
|
|
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的彩虹期权数据");
|
|
|
|
_underlyingPriceProvider.TryGetPrice(rainbowOption.UnderlyingAssetCode, out var price1);
|
|
_underlyingPriceProvider.TryGetPrice(rainbowOption.UnderlyingAssetCode2, out var price2);
|
|
|
|
spotPrices = new double[2] { price1, price2 };
|
|
|
|
//彩虹期权,需要两个标的的初始化
|
|
|
|
return QdpTradeBuilder.GetRainbowOptionTradeParam(_trade, rainbowOption, request);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 亚式期权
|
|
/// </summary>
|
|
private AsianOptionTradeParam GetAsianOptionCalcParam(OptionTradeParamRequest request, double spotPrice)
|
|
{
|
|
var asianOption = _tradeExtendDataProvider.GetTrade_Asian_Option(_trade.id)
|
|
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的亚式期权数据");
|
|
//主要是某些计算场景(比如实时)的fixing用了缓存提升性能所以才从context中获取
|
|
request.fixings = _context.GetFixingString(_trade, asianOption, spotPrice);
|
|
request.fixings = AsianOptionFixingService.CheckAsiaFixings(_trade, asianOption, request.fixings, spotPrice);
|
|
return QdpTradeBuilder.GetAsianOptionTradeParam(_trade, asianOption, request);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 价差期权
|
|
/// </summary>
|
|
private SpreadOptionTradeParam GetSpreadOptionCalcParam(OptionTradeParamRequest request, out double[] spotPrices)
|
|
{
|
|
var spreadOption = _tradeExtendDataProvider.GetTrade_Spread_Option(_trade.id)
|
|
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的价差期权数据");
|
|
|
|
var spreadOptionInput = GetMarketInputForSpreadOption(spreadOption);
|
|
|
|
spotPrices = spreadOptionInput.SpotPrices.ToArray();
|
|
|
|
var correlations = spreadOptionInput.Correlations.ToArray();
|
|
|
|
_spreadUnderlyings = spreadOptionInput.Underlyings;
|
|
|
|
return QdpTradeBuilder.GetSpreadOptionTradeParam(_trade, spreadOption, request, correlations);
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// 为价差期权的多个标的获取现价和相关性数据
|
|
/// </summary>
|
|
private SpreadOptionPricingInput GetMarketInputForSpreadOption(trade_spread_option spreadOption)
|
|
{
|
|
if (spreadOption == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(spreadOption));
|
|
}
|
|
var unlyArr = new underlying_manager[4];
|
|
unlyArr[0] = _underlyingDataProvider.GetUnderlying(_trade.UnderlyingCode);
|
|
unlyArr[1] = _underlyingDataProvider.GetUnderlying(spreadOption.UnderlyingAssetCode2);
|
|
if (!string.IsNullOrEmpty(spreadOption.UnderlyingAssetCode3))
|
|
{
|
|
unlyArr[2] = _underlyingDataProvider.GetUnderlying(spreadOption.UnderlyingAssetCode3);
|
|
if (!string.IsNullOrEmpty(spreadOption.UnderlyingAssetCode4))
|
|
{
|
|
unlyArr[3] = _underlyingDataProvider.GetUnderlying(spreadOption.UnderlyingAssetCode4);
|
|
}
|
|
}
|
|
unlyArr = unlyArr.Where(n => n != null).ToArray();
|
|
|
|
var spotPrices = new List<double>();
|
|
var correlations = new List<double>();
|
|
|
|
for (var i = 0; i < unlyArr.Length; i++)
|
|
{
|
|
var unly = unlyArr[i];
|
|
if (unly == null)
|
|
{
|
|
break;
|
|
}
|
|
for (var j = 0; j < i; j++)
|
|
{
|
|
correlations.Add(_context.GetCorrelation(unlyArr[j].id, unly.id));
|
|
}
|
|
_underlyingPriceProvider.TryGetPrice(unly.UnderlyingCode, out var price);
|
|
spotPrices.Add(price);
|
|
unly.Price = price;
|
|
}
|
|
|
|
return new SpreadOptionPricingInput()
|
|
{
|
|
Underlyings = unlyArr,
|
|
SpotPrices = spotPrices.ToArray(),
|
|
Correlations = correlations.ToArray()
|
|
};
|
|
}
|
|
}
|
|
}
|