Files
zszq-trs/YLErpDAL/Modules/CalculationModule/OptionCalculatorV2.cs
2024-05-09 14:06:26 +08:00

579 lines
29 KiB
C#

using YLErp.BLL;
using YLErp.Enums;
using YLErp.Modules.CalculationModule.Abstract;
using YLErp.QdpModule;
using YLErp.ThirdParty.CaculatePrice.DongZheng;
using YLErp.ThirdParty.CaculatePrice.DongZheng.Dto;
namespace YLErp.Modules.CalculationModule
{
/// <summary>
/// 只用于计算期权PV
/// </summary>
public class OptionCalculatorV2
{
private static readonly IYcLogger log = LogFactory.GetLogger(nameof(OptionCalculatorV2));
/// <summary>
/// 只用于计算期权PV
/// </summary>
public static TradeValueResult GetOptionValueResult(DateTime valueDate, trade td, OptionValueCalcRequest request, out underlying_manager[] underlyings)
{
if (td is null)
{
throw new ArgumentNullException(nameof(td));
}
if (request is null)
{
throw new ArgumentNullException(nameof(request));
}
using var marketProxy = new MarketProxy(valueDate, request.sysRiskFreeRate);
return GetOptionValueResult(marketProxy, td, request, out underlyings);
}
/// <summary>
/// 只用于计算期权PV
/// </summary>
public static TradeValueResult GetOptionValueResult(MarketProxy marketProxy, trade td, OptionValueCalcRequest request, out underlying_manager[] underlyings)
{
if (marketProxy is null)
{
throw new ArgumentNullException(nameof(marketProxy));
}
if (td is null)
{
throw new ArgumentNullException(nameof(td));
}
if (request is null)
{
throw new ArgumentNullException(nameof(request));
}
if (request.spotPrices == null || !request.spotPrices.Any())
{
throw new Exception("期权计算缺少标的现价");
}
if (string.IsNullOrEmpty(td.UnderlyingCode) && td.HasUnderlying())
{
throw new Exception("缺少标的代码");
}
TradeValueResult result;
//象屿最后一个交易日实时计算时TTM需要和平仓时算法一致
if (PS.Config.Is厦门象屿
&& (request.calcScenario == CalcScenarioEnum.RealtimePosition || request.calcScenario == CalcScenarioEnum.RealtimeRisk)
&& td.SettlementType == SettlementTypeEnum.ReferencePrice)
{
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
if (underlying != null)
{
request.timeToMaturityDays = TradeCalcHelper.CalculateTTMDaysForXiangYu(valuedateBLL.ValueDate, td.ExerciseDate.Value, underlying.UnderlyingTypeId, false);
//20210706:支持厦门象屿参考价相关交易(这类交易不需要支持精确模式)
request.preciseTimeMode = false;
}
}
//东证润和是精确时间模式参与计算,收盘的话,刚好是整数天,所以不需要特殊处理
if (PS.Config.Is润和 && !request.isEodCalc && (request.timeToMaturityDays == null || double.IsNaN(request.timeToMaturityDays.Value)))
{
request.timeToMaturityDays = TradeCalcHelper.CalculateTTMDays(marketProxy.ValueDate, td.ExerciseDate.Value, 0, false);
}
var tpReq = PrepareCalc(marketProxy, td, request, out underlyings, out var getAsianFixings);
switch (td.TradeType)
{
case "香草期权":
{
var tradeParam = QdpTradeBuilder.GetVanillaOptionTradeParam(td, tpReq, false);
result = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
}
break;
case "Risky期权":
{
result = GetOptionRisky(td, request, underlyings[0], tpReq, marketProxy);
}
break;
case "场内期权":
{
var tradeParam = QdpTradeBuilder.GetVanillaOptionTradeParam(td, tpReq, true);
result = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
}
break;
case "合成价差期权":
{
var tradeParam = QdpTradeBuilder.GetSSpreadOptionTradeParam(td, tpReq);
result = TradeRiskCalcUtil.GetSSpreadOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
}
break;
case "亚式期权":
{
if (!PS.Config.Is润和 && !request.isEodCalc && (request.timeToMaturityDays == null || double.IsNaN(request.timeToMaturityDays.Value)))
{
tpReq.timeToMaturityDays = TradeCalcHelper.CalculateTTMDays(marketProxy.ValueDate, td.ExerciseDate.Value, 0, false);
}
tpReq.fixings = getAsianFixings();
var tradeParam = QdpTradeBuilder.GetAsianOptionTradeParam(td, td.trade_asian_option, tpReq);
result = TradeRiskCalcUtil.GetAsianOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
}
break;
case "亚式合成价差期权":
{
tpReq.fixings = getAsianFixings();
var tradeParam = QdpTradeBuilder.GetAsianOptionTradeParam(td, td.trade_asian_option, tpReq);
result = TradeRiskCalcUtil.GetAsianSSpreadOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
}
break;
case "障碍期权":
{
var tradeParam = QdpTradeBuilder.GetBarrierOptionTradeParam(td, td.trade_barrier_option, tpReq);
result = TradeRiskCalcUtil.GetBarrierOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
}
break;
case "二元期权":
{
var tradeParam = QdpTradeBuilder.GetBinaryOptionTradeParam(td, td.trade_binary_option, tpReq);
result = TradeRiskCalcUtil.GetBinaryOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
}
break;
case "双鲨期权":
{
var tradeParam = QdpTradeBuilder.GetDoubleSharkFinOptionTradeParam(td, td.trade_double_sharkfin_option, tpReq);
result = TradeRiskCalcUtil.GetDoubleSharkFinOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
}
break;
case "凤凰期权":
{
if (td.trade_autocall == null)
{
throw new ServiceException("缺少奇异期权数据,交易编号:" + td.TradeNumber);
}
if (td.trade_autocall.HappenedObservations == null && td.id > 0)
{
td.trade_autocall.HappenedObservations = ObservationDataService.QueryDatas(td.id, marketProxy.ValueDate);
}
var tradeParam = QdpTradeBuilder.GetAutocallOptionTradeParam(td, td.trade_autocall, tpReq);
try
{
result = TradeRiskCalcUtil.GetAutocallOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
}
catch (Exception ex)
{
if (string.IsNullOrEmpty(td.TradeNumber))
{
throw;
}
throw new Exception($"凤凰期权'{td.TradeNumber}'计算出错:{ex.Message}");
}
}
break;
case "雪球期权":
{
if (td.IsSnowballSpecialist())
{
var snowballSpecialistOptionCalculator = new SnowballSpecialistOptionCalculator();
if (td.trade_snowball.PrepaymentRatio > 0)
{
var specialSnowballTrade = snowballSpecialistOptionCalculator.GetSpecialTrade(td);
var specialSnowballResult = snowballSpecialistOptionCalculator.CalcOptionValue(marketProxy.ValueDate, request.spotPrices[0], request.vols[0], request.calcScenario, specialSnowballTrade);
var breakevenSnowballTrade = snowballSpecialistOptionCalculator.GetBreakevenTrade(td);
var tradeParam = QdpTradeBuilder.GetSnowballTradeParam(breakevenSnowballTrade, breakevenSnowballTrade.trade_snowball, tpReq);
tradeParam.riskFreeRate = breakevenSnowballTrade.NoRiskRate ?? 0;
var breakevenSnowballResult = TradeRiskCalcUtil.GetSnowballOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
result = snowballSpecialistOptionCalculator.MergeTradeValueResult(specialSnowballResult, breakevenSnowballResult);
}
else
{
result = snowballSpecialistOptionCalculator.CalcOptionValue(marketProxy.ValueDate, request.spotPrices[0], request.vols[0], request.calcScenario, td);
}
}
else
{
// 普通雪球
var tradeParam = QdpTradeBuilder.GetSnowballTradeParam(td, td.trade_snowball, tpReq);
result = TradeRiskCalcUtil.GetSnowballOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
}
}
break;
case "区间累积期权":
{
if (td.trade_rangeaccrual == null)
{
throw new ServiceException("缺少奇异期权数据,交易编号:" + td.TradeNumber);
}
if (td.trade_rangeaccrual.HappenedObservations == null && td.id > 0)
{
td.trade_rangeaccrual.HappenedObservations = ObservationDataService.QueryDatas(td.id, marketProxy.ValueDate, includeValueDate: false);
}
tpReq.fixings = request.fixings;
if (string.IsNullOrWhiteSpace(tpReq.fixings)
&& (td.trade_rangeaccrual.HappenedObservations == null || td.trade_rangeaccrual.HappenedObservations.Count == 0))
{
//非日终时不将当天的价格加入fixing中
var valueDate = request.calcScenario == CalcScenarioEnum.EodMargin
|| request.calcScenario == CalcScenarioEnum.EodSettlement
|| request.calcScenario == CalcScenarioEnum.ScenarioCalc
? marketProxy.ValueDate : marketProxy.ValueDate.AddDays(-1);
tpReq.fixings = FixingService.GetFixingString(valueDate: valueDate, otcTrade: td, startDate: td.StartDate ?? td.TradeDate.Value, observationDates: td.trade_rangeaccrual.ObservationDates);
}
var tradeParam = QdpTradeBuilder.GetRangeAccrualTradeParam(td, td.trade_rangeaccrual, tpReq);
result = TradeRiskCalcUtil.GetRangeAccrualValue(marketProxy, GetOptionCalcParam(tradeParam, request));
}
break;
case "气囊结构":
{
var tradeParam = QdpTradeBuilder.GetAirbagOptionTradeParam(td, td.trade_airbag, tpReq);
result = TradeRiskCalcUtil.GetAirbagOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
}
break;
case "收益增强结构":
{
var tradeParam = QdpTradeBuilder.GetUnderlyingEnhanceTradeParam(td, td.trade_underlying_enhance, tpReq);
result = TradeRiskCalcUtil.GetUnderlyingEnhanceValue(marketProxy, GetOptionCalcParam(tradeParam, request));
}
break;
case "累计期权":
{
var tradeParam = QdpTradeBuilder.GetAccumulatorOptionTradeParam(td, td.trade_accumulator_option, tpReq);
result = TradeRiskCalcUtil.GetAccumulatorOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request), td.Notional);
}
break;
case "现金流交易":
{
var tradeParam = QdpTradeBuilder.GetCashFlowTradeParam(td, td.trade_cashflow, tpReq);
result = TradeRiskCalcUtil.GetCashFlowValue(marketProxy, GetOptionCalcParam(tradeParam, request), td.StockEqvNotional);
}
break;
case "结构化产品":
var structProductParm = QdpTradeBuilder.GetStructProductTradeParam(request, marketProxy, td);
StructureResult spResult;
log.Info("定价请求信息:" + JsonHelper.Serialize(structProductParm.Request));
if (DongZhengPriceApi.StructureProduct(structProductParm.Request, out spResult, structProductParm.VolSurface))
{
result = new TradeValueResult
{
Pv = spResult.pv,
Delta = spResult.delta,
Gamma = spResult.gamma,
Vega = spResult.vegaPercentage,
TradingDayTheta = spResult.thetaPerDay,
Rho = spResult.rhoPercentage
};
}
else
{
throw new Exception("东证结构化产品定价计算失败" + spResult.message);
}
log.Info("定价请求结果:" + JsonHelper.Serialize(spResult));
break;
default:
throw new Exception("不支持这种类型交易的期权计算:" + td.TradeType);
}
if (result != null)
{
result.Strike = td.Strike ?? 0;
result.SpotPrice = request.spotPrices[0];
result.VegaCash = result.VegaCash.IsNormalize() ? result.VegaCash : (result.Vega * result.SpotPrice).Normalize();
}
return result;
}
private static TradeValueResult GetOptionRisky(
trade td,
OptionValueCalcRequest request,
underlying_manager underlyings,
OptionTradeParamRequest tpReq,
MarketProxy marketProxy)
{
var result = new TradeValueResult();
var tradeclone = td.Clone();
tradeclone.TradeAmount = tradeclone.TradeAmount = TradeCalcHelper.GetTradeAmountV(td, td.TradeAmount, 1);
tradeclone.Notional = tradeclone.Notional = TradeCalcHelper.GetTradeAmountV(td, td.Notional, underlyings.CountRatio);
var td1 = tradeclone.Clone();
var td2 = tradeclone.Clone();
if (td.trade_risky_option.ParticipationRate2 != 0)
{
td2.Strike = td.trade_risky_option.Strike2;
td2.ParticipationRate = td.trade_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, tpReq, false);
result = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, GetOptionCalcParam(tradeParam2, request));
}
if (td.trade_risky_option.ParticipationRate1 != 0)
{
td1.Strike = td.trade_risky_option.Strike1;
td1.ParticipationRate = td.trade_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, tpReq, false);
var singleresult1 = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, GetOptionCalcParam(tradeParam1, request));
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.Clone();
td3.Strike = td.trade_risky_option.Strike3;
//decimal 为了解决精度问题: 0.2-0.3=0.0999999999
var participationRate3 = (decimal)td.trade_risky_option.ParticipationRate3 - (decimal)td.trade_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, tpReq, false);
var singleresult3 = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, GetOptionCalcParam(tradeParam3, request));
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;
}
/// <summary>
/// 获取期权计算参数
/// </summary>
private static OptionCalcParam<T> GetOptionCalcParam<T>(T tradeParam, OptionValueCalcRequest request) where T : OptionTradeParamBase
{
return new OptionCalcParam<T>(tradeParam)
{
engineName = request.engineName,
pricingRequest = request.pricingRequest,
spotPrices = request.spotPrices,
calcScenario = request.calcScenario,
quadratureFastMode = request.quadratureFastMode,
CalcDeltaT1 = request.calcDeltaT1
};
}
/// <summary>
/// 准备计算
/// </summary>
private static OptionTradeParamRequest PrepareCalc(MarketProxy marketProxy, trade td
, OptionValueCalcRequest request, out underlying_manager[] underlyings, out Func<string> getAsianFixings)
{
getAsianFixings = new Func<string>(() =>
{
if (!string.IsNullOrEmpty(request.fixings))
{
return request.fixings;
}
var fixings = AsianOptionFixingService.GetFixingString(marketProxy.ValueDate, td);
fixings = FixingService.AddOrReplaceLastDateSpotPrice(fixings, marketProxy.ValueDate, request.spotPrices[0]);
if (PS.Config.Is润和 && DateTime.Now.Hour < 15)
{
var index = fixings.IndexOf(marketProxy.ValueDate.ToString("yyyy-MM-dd"));
if (index >= 0)
{
fixings = fixings.Remove(index).TrimEnd(';');
}
}
return AsianOptionFixingService.CheckAsiaFixings(td, td.trade_asian_option, fixings, request.spotPrices[0]);
});
var tpReq = new OptionTradeParamRequest(request.sysRiskFreeRate)
{
hasNightMarket = false,
maturityShift = request.maturityShift,
ParamOverride = request.ParamOverride,
preciseTimeMode = request.preciseTimeMode,
timeToMaturityDays = request.timeToMaturityDays ?? double.NaN,
tradeId = marketProxy.NextRequestId() + "_",
volSurfaceNames = null,
fixings = null
};
tradeBLL.SetFieldsByTradeType(td);
switch (td.TradeType)
{
case "彩虹期权":
//var underlyingCodes = new[] { trade.UnderlyingCode, trade.trade_rainbow_option.UnderlyingAssetCode2 };
//request.volSurfaceNames = PrepareVols(marketProxy, tradeId.ToString(), underlyingCodes, request.vols);
throw new Exception("不支持彩虹期权计算");
case "价差期权":
//var underlyingCodes = trade.trade_spread_option.UnderlyingAssetCodes();
//request.volSurfaceNames = PrepareVols(marketProxy, tradeId.ToString(), underlyingCodes, request.vols);
throw new Exception("不支持价差期权计算");
case ConsGlobal.TradeType.CashFlow:
break;
default:
tpReq.volSurfaceNames = PrepareVols(marketProxy, tpReq.tradeId, new string[] { td.UnderlyingCode }, request.vols);
if (td.StructureType == "亚式熊市价差")
{
tpReq.fixings = getAsianFixings();
}
break;
}
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
if (underlying == null)
{
if (td.HasUnderlying())
{
throw new TradeCalcExpception(td.id, "没有找到标的数据:" + td.UnderlyingCode);
}
}
else if (underlying.IsFutures())
{
var variety = DataCacheProvider.GetVarietyDataSource().GetData(underlying.UnderlyingTypeId);
tpReq.hasNightMarket = variety != null && variety.HasNightMarket;
}
underlyings = new[] { underlying ?? new underlying_manager() };
tpReq.tradeId += td.id > 0 ? td.id.ToString() : string.IsNullOrWhiteSpace(td.ExchangeOptionCode) ? td.UnderlyingCode : td.ExchangeOptionCode;
return tpReq;
}
/// <summary>
/// 准备波动率
/// </summary>
private static string[] PrepareVols(MarketProxy marketProxy, string tradeId, string[] underlyingCodes, double[] vols)
{
if (string.IsNullOrEmpty(tradeId))
{
throw new ArgumentException($"“{nameof(tradeId)}”不能是 Null 或为空。", nameof(tradeId));
}
if (underlyingCodes == null || !underlyingCodes.Any())
{
throw new Exception("缺少标的代码");
}
if (vols == null || !vols.Any() || vols.Length < underlyingCodes.Length)
{
throw new Exception("缺少波动率");
}
var volSurfaceNames = new string[underlyingCodes.Length];
for (var i = 0; i < underlyingCodes.Length; i++)
{
var volatility = QdpVolHelper.GetDefaultVolatility(vols[i]);
volSurfaceNames[i] = QdpVolHelper.GetVolSurfaceName(tradeId, i > 0 ? underlyingCodes[i] : null);
marketProxy.SetVolSurface(volSurfaceNames[i], volatility);
}
return volSurfaceNames;
}
/// <summary>
/// 计算交叉GAMMA
/// </summary>
public static double[] CalcSSpreadCrossGammas(DateTime valueDate, trade td, OptionValueCalcRequest request, double[] coefficients)
{
if (td is null)
{
throw new ArgumentNullException(nameof(td));
}
if (request is null)
{
throw new ArgumentNullException(nameof(request));
}
if (request.spotPrices == null || !request.spotPrices.Any())
{
throw new Exception("期权计算缺少标的现价");
}
if (string.IsNullOrEmpty(td.UnderlyingCode) && td.HasUnderlying())
{
throw new Exception("缺少标的代码");
}
using var marketProxy = new MarketProxy(valueDate, request.sysRiskFreeRate);
var tpReq = PrepareCalc(marketProxy, td, request, out _, out _);
var tradeParam = QdpTradeBuilder.GetSSpreadOptionTradeParam(td, tpReq, coefficients);
return TradeRiskCalcUtil.CalcSSpreadCrossGammas(marketProxy, GetOptionCalcParam(tradeParam, request), coefficients);
}
}
}