2323 lines
111 KiB
C#
2323 lines
111 KiB
C#
using Qdp.ComputeService.Data.CommonModels.ValuationParams.Equity;
|
|
using Qdp.ComputeService.Data.CommonModels.ValuationParams.FixedIncome;
|
|
using Qdp.ComputeServiceV2.Data.CommonModels.TradeInfos;
|
|
using Qdp.ComputeServiceV2.Data.CommonModels.TradeInfos.Options;
|
|
using Qdp.Foundation.Implementations;
|
|
using Qdp.Pricing.Base.Enums;
|
|
using Qdp.Pricing.Base.Implementations;
|
|
using Qdp.Pricing.Base.Interfaces;
|
|
using Qdp.Pricing.Library.Common.Base;
|
|
using Qdp.Pricing.Library.Common.Interfaces;
|
|
using Qdp.Pricing.Library.Common.Products.Rates.Engines;
|
|
using Qdp.Pricing.Library.Options.Products.Accumulator.Engines.Analytical;
|
|
using Qdp.Pricing.Library.Options.Products.Accumulator.Engines.Numerical;
|
|
using Qdp.Pricing.Library.Options.Products.Airbag.Engines.Analytical;
|
|
using Qdp.Pricing.Library.Options.Products.Asian;
|
|
using Qdp.Pricing.Library.Options.Products.AsianSyntheticSpread.Engines.Analytical;
|
|
using Qdp.Pricing.Library.Options.Products.Autocall.Phoenix;
|
|
using Qdp.Pricing.Library.Options.Products.Autocall.Phoenix.Numerical;
|
|
using Qdp.Pricing.Library.Options.Products.Autocall.Snowball;
|
|
using Qdp.Pricing.Library.Options.Products.Barrier;
|
|
using Qdp.Pricing.Library.Options.Products.Binary;
|
|
using Qdp.Pricing.Library.Options.Products.DoubleSharkFin;
|
|
using Qdp.Pricing.Library.Options.Products.PayoffEnhance.Engines.Analytical;
|
|
using Qdp.Pricing.Library.Options.Products.Rainbow;
|
|
using Qdp.Pricing.Library.Options.Products.RangeAccrual;
|
|
using Qdp.Pricing.Library.Options.Products.Spread;
|
|
using Qdp.Pricing.Library.Options.Products.Vanilla;
|
|
using YLErp.BLL.Calculation.Engine;
|
|
using YLErp.BLL.Calculation.V2.Parameter;
|
|
using YLErp.Commons;
|
|
using YLErp.Configuration;
|
|
using YLErp.Enums;
|
|
using YLErp.Modules;
|
|
using YLErp.Modules.CalculationLogModule;
|
|
using YLErp.Modules.CalculationModule;
|
|
using YLErp.QdpModule;
|
|
using YLErp.QdpModule.Constants;
|
|
|
|
namespace YLErp.BLL.Calculation.V2
|
|
{
|
|
public class ValueCalculator
|
|
{
|
|
/// <summary>
|
|
/// 计算金融产品及衍生品的价值
|
|
/// </summary>
|
|
/// <param name="userId">QdpMarketId</param>
|
|
/// <param name="trade">交易</param>
|
|
/// <param name="underlying">标的资产</param>
|
|
/// <param name="parameter">计算参数</param>
|
|
/// <returns></returns>
|
|
public static TradeValueResult CalculateTradeValue(
|
|
string userId,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
ParameterBase parameter,
|
|
PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default,
|
|
bool quadratureFastMode = false)
|
|
{
|
|
underlying = underlying.Clone();
|
|
|
|
underlying.UnderlyingInstrumentType = ConsGlobal.InstrumentType.ConvertCalcType(underlying.UnderlyingInstrumentType);
|
|
|
|
switch (trade.TradeType)
|
|
{
|
|
case "香草期权":
|
|
case "场内期权":
|
|
return CalculateVanillaOption(userId, trade, underlying, parameter as VanillaOptionParameter, pricingRequest, calcScenario);
|
|
case "Risky期权":
|
|
return CalculateRiskyOption(userId, trade, underlying, parameter as VanillaOptionParameter, pricingRequest, calcScenario);
|
|
case "障碍期权":
|
|
return CalculateBarrierOption(userId, trade, underlying, parameter as BarrierOptionParameter, pricingRequest, calcScenario, quadratureFastMode: quadratureFastMode);
|
|
case "亚式期权":
|
|
return CalculateAsianOption(userId, trade, underlying, parameter as AsianOptionParameter, pricingRequest, calcScenario);
|
|
case "二元期权":
|
|
return CalculateBinaryOption(userId, trade, underlying, parameter as BinaryOptionParameter, pricingRequest, calcScenario);
|
|
case "彩虹期权":
|
|
return CalculateRainbowOption(userId, trade, underlying, parameter as RainbowOptionParameter, pricingRequest, calcScenario);
|
|
case "价差期权":
|
|
return CalculateSpreadOption(userId, trade, underlying, parameter as SpreadOptionParameter, pricingRequest, calcScenario);
|
|
case "合成价差期权":
|
|
return CalculateSyntheticNormalSpreadOption(userId, trade, underlying, parameter as SyntheticNormalSpreadOptionParameter, pricingRequest, calcScenario);
|
|
case "亚式合成价差期权":
|
|
return CalculateAsianSyntheticNormalSpreadOption(userId, trade, underlying, parameter as AsianSyntheticNormalSpreadOptionParameter, pricingRequest, calcScenario);
|
|
case "双鲨期权":
|
|
return CalculateDoubleSharkFinOption(userId, trade, underlying, parameter as DoubleSharkFinOptionParameter, pricingRequest, calcScenario, quadratureFastMode: quadratureFastMode);
|
|
case "凤凰期权":
|
|
return CalculateAutocall(userId, trade, underlying, parameter as AutocallParameter, pricingRequest, calcScenario, quadratureFastMode: quadratureFastMode);
|
|
case "雪球期权":
|
|
if (trade.IsSnowballSpecialist())
|
|
{
|
|
SnowballSpecialistOptionCalculator snowballSpecialistOptionCalculator = new SnowballSpecialistOptionCalculator();
|
|
if (parameter?.SpotPrices == null || !parameter.SpotPrices.TryGetValue(underlying.UnderlyingCode, out var spotPrice))
|
|
{
|
|
throw new Exception("[计算雪球期权交易]缺少SpotPrice");
|
|
}
|
|
|
|
|
|
if (trade.trade_snowball.PrepaymentRatio > 0)
|
|
{
|
|
var specialSnowballTrade = snowballSpecialistOptionCalculator.GetSpecialTrade(trade);
|
|
var specialSnowballResult = snowballSpecialistOptionCalculator.CalcOptionValue(parameter.ValueDate, spotPrice, (parameter as SnowballParameter).Volatility ?? 0, calcScenario, specialSnowballTrade);
|
|
|
|
var breakevenSnowballTrade = snowballSpecialistOptionCalculator.GetBreakevenTrade(trade);
|
|
parameter.RiskFreeRate = breakevenSnowballTrade.NoRiskRate;
|
|
var breakevenSnowballResult = CalculateSnowball(userId, breakevenSnowballTrade, underlying, parameter as SnowballParameter, pricingRequest, calcScenario, quadratureFastMode: quadratureFastMode);
|
|
|
|
return snowballSpecialistOptionCalculator.MergeTradeValueResult(specialSnowballResult, breakevenSnowballResult);
|
|
}
|
|
else
|
|
{
|
|
return snowballSpecialistOptionCalculator.CalcOptionValue(parameter.ValueDate, spotPrice, (parameter as SnowballParameter).Volatility ?? 0, calcScenario, trade);
|
|
}
|
|
|
|
}
|
|
return CalculateSnowball(userId, trade, underlying, parameter as SnowballParameter, pricingRequest, calcScenario, quadratureFastMode: quadratureFastMode);
|
|
case "区间累积期权":
|
|
return CalculateRangeAccrual(userId, trade, underlying, parameter as RangeAccrualParameter, pricingRequest, calcScenario);
|
|
case "气囊结构":
|
|
return CalculateAirbag(userId, trade, underlying, parameter as AirbagParameter, pricingRequest, calcScenario);
|
|
case "收益增强结构":
|
|
return CalculateUnderlyingEnhance(userId, trade, underlying, parameter as UnderlyingEnhanceParameter, pricingRequest, calcScenario);
|
|
case "累计期权":
|
|
return CalculateAccumulatorOption(userId, trade, underlying, parameter as AccumulatorOptionParameter, pricingRequest, calcScenario);
|
|
case "现金流交易":
|
|
return CalculateCashFlow(userId, trade, underlying, parameter as CashFlowTradeParameter, pricingRequest, calcScenario);
|
|
case "远期":
|
|
{
|
|
if (parameter?.SpotPrices == null || !parameter.SpotPrices.TryGetValue(underlying.UnderlyingCode, out var spotPrice))
|
|
{
|
|
throw new Exception("[计算远期交易]缺少SpotPrice");
|
|
}
|
|
return CalculateForward(trade.Strike ?? 0.0, spotPrice, trade.Notional, trade.CallPut);
|
|
}
|
|
case "信用债":
|
|
case "商品期货":
|
|
case "商品现货":
|
|
case "股票":
|
|
{
|
|
if (parameter?.SpotPrices == null || !parameter.SpotPrices.TryGetValue(underlying.UnderlyingCode, out var spotPrice))
|
|
{
|
|
throw new Exception($"[计算{trade.TradeType}交易]缺少SpotPrice");
|
|
}
|
|
var pv = spotPrice * trade.Notional;
|
|
return new TradeValueResult { Pv = pv, RoundedPv = pv, Delta = trade.Notional, DeltaCash = pv };
|
|
}
|
|
case "结构化产品":
|
|
var para = parameter as VanillaOptionParameter;
|
|
var isPreciseTimeMode = underlying.QuotationDate.Value.Date == valuedateBLL.ValueDate.Date;
|
|
var request = new OptionValueCalcRequest(valuedateBLL.SysRiskFreeRate())
|
|
{
|
|
vols = new[] { (double)para.Volatility },
|
|
spotPrices = new[] { parameter.SpotPrices[trade.UnderlyingCode] },
|
|
engineName = para.EngineName,
|
|
preciseTimeMode = isPreciseTimeMode,
|
|
pricingRequest = QdpPricingRequest.PRICE_GREEKS,
|
|
quadratureFastMode = true,
|
|
calcScenario = CalcScenarioEnum.Pricing,
|
|
fixings = null,
|
|
timeToMaturityDays = trade.TTMDays
|
|
};
|
|
return OptionCalculatorV2.GetOptionValueResult(para.ValueDate, trade, request, out _);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
throw new Exception($"不支持的交易结构{trade.TradeType}的计算");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 原OptionCalculator中的GetOptionValueResult,仅用于定价计算
|
|
/// </summary>
|
|
public static TradeValueResult GetOptionValueResultV2(
|
|
string userId,
|
|
underlying_manager underlying,
|
|
trade trade,
|
|
double[] vols,
|
|
double[] spotPrices = null,
|
|
string fixing = null,
|
|
bool preciseTimeMode = false,
|
|
double[] correlations = null,
|
|
string discountCurveName = null,
|
|
int maturityShift = 0,
|
|
double timeToMaturityDays = double.NaN,
|
|
string engineName = null,
|
|
PricingRequest request = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default,
|
|
bool quadratureFastMode = false)
|
|
{
|
|
if (spotPrices is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(spotPrices));
|
|
}
|
|
|
|
if (trade.TTMDays != null && timeToMaturityDays.Equals(double.NaN))
|
|
{
|
|
timeToMaturityDays = trade.TTMDays.Value;
|
|
}
|
|
|
|
//股票默认到期日为行权日
|
|
if (underlying.UnderlyingInstrumentType == "Stock" && trade.ExerciseDate != null)
|
|
{
|
|
trade.MaturityDate = trade.ExerciseDate;
|
|
}
|
|
|
|
var variety = DataCacheProvider.GetVarietyDataSource().GetData(underlying.UnderlyingTypeId);
|
|
|
|
var parameter = ParameterHelper.CreatePricingParameter(
|
|
underlying: underlying,
|
|
variety: variety,
|
|
trade: trade,
|
|
vols: vols,
|
|
spotPrices: spotPrices,
|
|
fixing: fixing,
|
|
preciseTimeMode: preciseTimeMode,
|
|
correlations: correlations ?? new double[0],
|
|
discountCurveName: discountCurveName,
|
|
timeToMaturityDays: timeToMaturityDays,
|
|
engineName: engineName);
|
|
parameter.maturityShift = maturityShift;
|
|
try
|
|
{
|
|
var result = CalculateTradeValue(userId, trade, underlying, parameter, request, calcScenario, quadratureFastMode: quadratureFastMode);
|
|
|
|
if (result != null)
|
|
{
|
|
result.Strike = trade.Strike ?? 0;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new ServiceException($"计算交易{trade.TradeNumber}估值报错,{ex.Message}", ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算保本雪球的年化期权费率
|
|
/// </summary>
|
|
public static double CalculateSnowballAnnualPremium(
|
|
string userId,
|
|
underlying_manager underlying,
|
|
trade trade,
|
|
double[] vols,
|
|
double[] spotPrices = null,
|
|
bool preciseTimeMode = false,
|
|
string discountCurveName = null,
|
|
int maturityShift = 0,
|
|
double timeToMaturityDays = double.NaN,
|
|
string engineName = null,
|
|
bool quadratureFastMode = false)
|
|
{
|
|
if (spotPrices is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(spotPrices));
|
|
}
|
|
|
|
if (trade.TTMDays != null && timeToMaturityDays.Equals(double.NaN))
|
|
{
|
|
timeToMaturityDays = trade.TTMDays.Value;
|
|
}
|
|
|
|
//股票默认到期日为行权日
|
|
if (underlying.UnderlyingInstrumentType == "Stock" && trade.ExerciseDate != null)
|
|
{
|
|
trade.MaturityDate = trade.ExerciseDate;
|
|
}
|
|
|
|
var variety = DataCacheProvider.GetVarietyDataSource().GetData(underlying.UnderlyingTypeId);
|
|
|
|
var parameter = ParameterHelper.CreatePricingParameter(
|
|
underlying,
|
|
variety,
|
|
trade,
|
|
vols,
|
|
spotPrices,
|
|
null,
|
|
preciseTimeMode,
|
|
null,
|
|
discountCurveName,
|
|
timeToMaturityDays,
|
|
engineName);
|
|
|
|
parameter.maturityShift = maturityShift;
|
|
|
|
return CalculateSnowballAnnualPremium(userId, trade, underlying, parameter as SnowballParameter, quadratureFastMode);
|
|
}
|
|
|
|
private static double CalculateSnowballAnnualPremium(string userId, trade trade, underlying_manager underlying, SnowballParameter parameter, bool quadratureFastMode)
|
|
{
|
|
var snowball = trade.trade_snowball;
|
|
|
|
snowball.AnnualizedPremiumRate = null;
|
|
|
|
var buySidePv = CalculateSnowball(userId, trade, underlying, parameter, PricingRequest.Pv, quadratureFastMode: quadratureFastMode).Pv;
|
|
buySidePv /= trade.Notional;
|
|
buySidePv = Math.Abs(buySidePv);
|
|
|
|
var sellSideProduct = QdpTradeBuilder.GetSnowballOptionTrade(new SnowballOptionTradeParam
|
|
{
|
|
tradeId = null,
|
|
volSurfaceNames = null,
|
|
tradeDate = parameter.ValueDate,
|
|
underlyingTickers = new[] { underlying.UnderlyingCode },
|
|
underlyingInstrumentType = underlying.UnderlyingInstrumentType,
|
|
startDate = trade.StartDate.Value,
|
|
endDate = trade.ExerciseDate.Value,
|
|
initialSpotPrice = trade.SpotPrice ?? 0.0,
|
|
isMoneynessOption = trade.IsMoneynessOptionData,
|
|
koBarrier = snowball.KOBarrier,
|
|
kiBarrier = snowball.KIBarrier,
|
|
koObservationDateStr = snowball.KOObservationDates,
|
|
couponPaymentDateStr = snowball.CouponUsePaymentDate == true ? snowball.KOObservationSettleDates : null,
|
|
observationDateStr = snowball.ObservationDates,
|
|
coupon = snowball.Coupon,
|
|
participationRate = trade.ParticipationRate ?? 1.0,
|
|
principalRate = trade.PrincipalRate ?? 0.0,
|
|
notional = 1.0,
|
|
buysell = "Buy",
|
|
exerciseDate = trade.ExerciseDate.Value,
|
|
annualizedFactor = snowball.AnnualizeFactor2 ?? 1.0,
|
|
callput = QdpConverter.ConvertOptionType(trade.CallPut),
|
|
strike = trade.Strike ?? 0.0,
|
|
spreadStrikeAtKO = -1,
|
|
spreadStrikeAtMaturity = -1,
|
|
isFixedCoupon = false,
|
|
koRebate = double.NaN,
|
|
koBarrierAdjustStep = snowball.KOBarrierAdjustStep,
|
|
//敲出转期权
|
|
useOptionPayoffAtKO = false,
|
|
//未敲出转期权
|
|
useOptionPayoffAtMaturity = false,
|
|
annualizedOptionPayoff = snowball.IsAnnualized2,
|
|
couponDayCount = snowball.CouponDayCount,
|
|
timeToMaturityDays = parameter.OverrideTTM ?? double.NaN,
|
|
riskFreeRate = parameter.RiskFreeRate ?? double.NaN,
|
|
dividendRate = parameter.DividendRate ?? double.NaN,
|
|
}, SnowBallOptionMode.SimpleSnowball);
|
|
|
|
var option = sellSideProduct.Instrument as SimpleSnowball;
|
|
var engine = new QuadAutoCallEngine();
|
|
return engine.CalcSnowballCouponRate(option, parameter.SpotPrices.Values.First(), parameter.RiskFreeRate.Value, parameter.DividendRate.Value, parameter.Volatility.Value, hasNkoNkiRebate: true, targetPv: buySidePv);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 反算雪球票息
|
|
/// </summary>
|
|
public static double CalculateSnowballKORebateV2(
|
|
OtcTradeBase trade,
|
|
trade_snowball snowball,
|
|
DateTime valueDate,
|
|
double[] vols,
|
|
double[] spotPrices = null,
|
|
bool preciseTimeMode = false,
|
|
double timeToMaturityDays = double.NaN,
|
|
double initialMarginRate = 0.0)
|
|
{
|
|
if (spotPrices is null || !spotPrices.Any())
|
|
{
|
|
throw new ArgumentException("不能为空", nameof(spotPrices));
|
|
}
|
|
|
|
if (vols is null || !vols.Any())
|
|
{
|
|
throw new ArgumentException("不能为空", nameof(vols));
|
|
}
|
|
|
|
//股票默认到期日为行权日
|
|
if (ConsGlobal.InstrumentType.CalcTypeIsStock(trade.UnderlyingInstrumentType) && trade.ExerciseDate != null)
|
|
{
|
|
trade.MaturityDate = trade.ExerciseDate;
|
|
}
|
|
|
|
var targetPv = trade.TradeSinglePrice ?? 0.0;
|
|
|
|
snowball.AnnualizedPremiumRate = null;
|
|
|
|
var para = QdpTradeBuilder.GetSnowballTradeParam(trade, snowball, new OptionTradeParamRequest(valuedateBLL.SysRiskFreeRate()));
|
|
|
|
para.tradeId = null;
|
|
para.volSurfaceNames = null;
|
|
para.annualizedPremiumRate = null;
|
|
para.preciseTimeMode = preciseTimeMode;
|
|
para.tradeDate = valueDate;
|
|
para.notional = 1.0;
|
|
para.buysell = "Buy";
|
|
para.spreadStrikeAtKO = -1;
|
|
para.koRebate = double.NaN;
|
|
//敲出转期权
|
|
para.useOptionPayoffAtKO = false;
|
|
//未敲出转期权
|
|
para.timeToMaturityDays = timeToMaturityDays;
|
|
para.barrierStatus = null;
|
|
|
|
var optionTrade = QdpTradeBuilder.GetSnowballOptionTrade(para, SnowBallOptionMode.SimpleSnowball);
|
|
|
|
var option = optionTrade.Instrument as SimpleSnowball;
|
|
var engine = new QuadAutoCallEngine();
|
|
|
|
if (initialMarginRate <= 0.0) //无预付预付金,按一笔正常雪球反算票息
|
|
{
|
|
return engine.CalcSnowballCouponRate(
|
|
option,
|
|
spotPrices.First(),
|
|
para.riskFreeRate,
|
|
para.dividendRate,
|
|
vols[0],
|
|
hasNkoNkiRebate: snowball.Coupon > 0,
|
|
targetPv: targetPv);
|
|
}
|
|
else //有预付预付金
|
|
{
|
|
//计算方法:
|
|
//将有预付预付金的雪球看作两笔雪球A和B的组合,A是没有预付预付金的原雪球,B是与A期限、敲出观察、敲出障碍都一样的一笔保本雪球
|
|
//1. 如果预付预付金比例为100%,则B的票息就是无风险利率,直接计算pv
|
|
//2. 如果预付预付金比例小于100%(假设30%),则应该计算名义本金为30、票息为无风险利率的保本雪球pv
|
|
//
|
|
//最后用A和B的pv之和反算A的票息,即为所得结果
|
|
|
|
var paraB = QdpTradeBuilder.GetSnowballTradeParam(trade, snowball, new OptionTradeParamRequest(valuedateBLL.SysRiskFreeRate()));
|
|
paraB.notional = initialMarginRate; //部分预付金
|
|
paraB.useOptionPayoffAtMaturity = false; //保本雪球
|
|
paraB.koRebate = para.riskFreeRate;
|
|
paraB.coupon = para.riskFreeRate;
|
|
|
|
var optionTradeB = QdpTradeBuilder.GetSnowballOptionTrade(paraB, SnowBallOptionMode.SimpleSnowball);
|
|
var optionB = optionTradeB.Instrument as SimpleSnowball;
|
|
var pvB = engine.PriceSnowball(optionB, new Date(valueDate), spotPrices.First(), vols[0], paraB.dividendRate, paraB.riskFreeRate);
|
|
|
|
//因为A的pv为0,用B的pv反算A的票息
|
|
var koRebateA = engine.CalcSnowballCouponRate(
|
|
option,
|
|
spotPrices.First(),
|
|
para.riskFreeRate,
|
|
para.dividendRate,
|
|
vols[0],
|
|
hasNkoNkiRebate: snowball.Coupon > 0,
|
|
targetPv: pvB);
|
|
|
|
return koRebateA;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 先保留这个方法,不一定适用于客户真实情况
|
|
/// 这个方法是假设带初始预付金的雪球的初始pv也为0
|
|
/// </summary>
|
|
private static double CalculateSnowballCouponRateWithInitialMargin(
|
|
SnowballOptionTradeParam para,
|
|
OtcTradeBase trade,
|
|
trade_snowball snowball,
|
|
SimpleSnowball option,
|
|
DateTime valueDate,
|
|
double[] vols,
|
|
double[] spotPrices = null,
|
|
double initialMarginRate = 0.0)
|
|
{
|
|
var engine = new QuadAutoCallEngine();
|
|
double koRebateB = 0.0, pvB = 0.0;
|
|
if (initialMarginRate == 1.0)
|
|
{
|
|
koRebateB = para.riskFreeRate;
|
|
|
|
var paraB = QdpTradeBuilder.GetSnowballTradeParam(trade, snowball, new OptionTradeParamRequest(valuedateBLL.SysRiskFreeRate()));
|
|
paraB.notional = 1.0;
|
|
paraB.useOptionPayoffAtMaturity = false; //保本雪球
|
|
paraB.koRebate = koRebateB;
|
|
paraB.coupon = koRebateB;
|
|
|
|
var optionTradeB = QdpTradeBuilder.GetSnowballOptionTrade(paraB, SnowBallOptionMode.SimpleSnowball);
|
|
var optionB = optionTradeB.Instrument as SimpleSnowball;
|
|
pvB = engine.PriceSnowball(optionB, new Date(valueDate), spotPrices.First(), vols[0], paraB.dividendRate, paraB.riskFreeRate);
|
|
}
|
|
else
|
|
{
|
|
var paraB = QdpTradeBuilder.GetSnowballTradeParam(trade, snowball, new OptionTradeParamRequest(valuedateBLL.SysRiskFreeRate()));
|
|
paraB.notional = initialMarginRate; //部分预付金
|
|
paraB.useOptionPayoffAtMaturity = false; //保本雪球
|
|
paraB.koRebate = para.riskFreeRate;
|
|
paraB.coupon = para.riskFreeRate;
|
|
|
|
var optionTradeB = QdpTradeBuilder.GetSnowballOptionTrade(paraB, SnowBallOptionMode.SimpleSnowball);
|
|
var optionB = optionTradeB.Instrument as SimpleSnowball;
|
|
pvB = engine.PriceSnowball(optionB, new Date(valueDate), spotPrices.First(), vols[0], paraB.dividendRate, paraB.riskFreeRate);
|
|
|
|
//再按全名义本金计算
|
|
paraB.notional = 1.0;
|
|
optionTradeB = QdpTradeBuilder.GetSnowballOptionTrade(paraB, SnowBallOptionMode.SimpleSnowball);
|
|
optionB = optionTradeB.Instrument as SimpleSnowball;
|
|
koRebateB = engine.CalcSnowballCouponRate(
|
|
optionB,
|
|
spotPrices.First(),
|
|
para.riskFreeRate,
|
|
para.dividendRate,
|
|
vols[0],
|
|
hasNkoNkiRebate: snowball.Coupon > 0,
|
|
targetPv: pvB);
|
|
}
|
|
|
|
//使A+B为0,用-pvB反算A的票息,再相加
|
|
var koRebateA = engine.CalcSnowballCouponRate(
|
|
option,
|
|
spotPrices.First(),
|
|
para.riskFreeRate,
|
|
para.dividendRate,
|
|
vols[0],
|
|
hasNkoNkiRebate: snowball.Coupon > 0,
|
|
targetPv: -pvB);
|
|
|
|
return koRebateA + koRebateB;
|
|
}
|
|
|
|
public static double CalculatePhoenixCouponRate(
|
|
underlying_manager underlying,
|
|
trade trade,
|
|
double[] vols,
|
|
double[] spotPrices = null,
|
|
bool preciseTimeMode = false,
|
|
string discountCurveName = null,
|
|
int maturityShift = 0,
|
|
double timeToMaturityDays = double.NaN,
|
|
string engineName = null)
|
|
{
|
|
if (spotPrices is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(spotPrices));
|
|
}
|
|
|
|
if (trade.TTMDays != null && timeToMaturityDays.Equals(double.NaN))
|
|
{
|
|
timeToMaturityDays = trade.TTMDays.Value;
|
|
}
|
|
|
|
//股票默认到期日为行权日
|
|
if (underlying.UnderlyingInstrumentType == "Stock" && trade.ExerciseDate != null)
|
|
{
|
|
trade.MaturityDate = trade.ExerciseDate;
|
|
}
|
|
|
|
var variety = DataCacheProvider.GetVarietyDataSource().GetData(underlying.UnderlyingTypeId);
|
|
|
|
var parameter = ParameterHelper.CreatePricingParameter(
|
|
underlying,
|
|
variety,
|
|
trade,
|
|
vols,
|
|
spotPrices,
|
|
null,
|
|
preciseTimeMode,
|
|
null,
|
|
discountCurveName,
|
|
timeToMaturityDays,
|
|
engineName);
|
|
|
|
parameter.maturityShift = maturityShift;
|
|
|
|
return CalculatePhoenixCouponRate(trade, underlying, parameter as AutocallParameter);
|
|
}
|
|
|
|
private static double CalculatePhoenixCouponRate(trade trade, underlying_manager underlying, AutocallParameter parameter)
|
|
{
|
|
var targetPv = trade.TradeSinglePrice ?? 0.0;
|
|
var phoenix = trade.trade_autocall;
|
|
|
|
var optionTrade = QdpTradeBuilder.GetAutocallOptionTrade(new AutocallOptionTradeParam
|
|
{
|
|
tradeId = null,
|
|
volSurfaceNames = null,
|
|
tradeDate = parameter.ValueDate,
|
|
underlyingTickers = new[] { underlying.UnderlyingCode },
|
|
underlyingInstrumentType = underlying.UnderlyingInstrumentType,
|
|
startDate = trade.StartDate.Value,
|
|
endDate = trade.ExerciseDate.Value,
|
|
initialSpotPrice = trade.SpotPrice ?? 0.0,
|
|
isMoneynessOption = trade.IsMoneynessOptionData,
|
|
couponBarrier = phoenix.CouponBarrier,
|
|
koBarrier = phoenix.KOBarrier,
|
|
kiBarrier = phoenix.KIBarrier,
|
|
koObservationDateStr = phoenix.KOObservationDates,
|
|
observationDateStr = phoenix.ObservationDates,
|
|
coupon = phoenix.Coupon > 0 ? phoenix.Coupon : double.NaN,
|
|
participationRate = trade.ParticipationRate ?? 1.0,
|
|
principalRate = trade.PrincipalRate ?? 0.0,
|
|
notional = 1.0,
|
|
buysell = "Buy",
|
|
exerciseDate = trade.ExerciseDate.Value,
|
|
annualizedFactor = phoenix.AnnualizeFactor2 ?? 1.0,
|
|
callput = QdpConverter.ConvertCallPut(trade.CallPut),
|
|
kiOptionType = QdpConverter.ConvertOptionType(phoenix.KIPayoffType).ToString(),
|
|
strike = trade.Strike ?? 0.0,
|
|
spreadStrike = phoenix.SpreadStrike ?? -1,
|
|
isFixedCoupon = phoenix.IsFixedCoupon,
|
|
couponPayAtMaturity = phoenix.CouponPayType != CouponPayTypeEnum.AtCreated,
|
|
includeCouponAfterKI = phoenix.IncludeCouponAfterKI,
|
|
annualizedOptionPayoff = phoenix.IsAnnualized2,
|
|
couponDayCount = phoenix.CouponDayCount,
|
|
timeToMaturityDays = parameter.OverrideTTM ?? double.NaN,
|
|
riskFreeRate = parameter.RiskFreeRate ?? double.NaN,
|
|
dividendRate = parameter.DividendRate ?? double.NaN,
|
|
});
|
|
|
|
var option = optionTrade.Instrument as AutoCall;
|
|
var engine = new QuadAutoCallEngine();
|
|
return engine.CalcPhoenixCouponRate(
|
|
option,
|
|
parameter.SpotPrices.Values.First(),
|
|
parameter.RiskFreeRate.Value,
|
|
parameter.DividendRate.Value,
|
|
parameter.Volatility.Value,
|
|
targetPv: targetPv);
|
|
}
|
|
|
|
private static OptionTradeParamRequest GetOptionTradeParamRequest(ParameterBase parameter, params string[] volSurfaceNames)
|
|
{
|
|
return new OptionTradeParamRequest(valuedateBLL.SysRiskFreeRate())
|
|
{
|
|
volSurfaceNames = volSurfaceNames,
|
|
dividends = parameter.Dividends,
|
|
hasNightMarket = parameter.HasNightMarket,
|
|
preciseTimeMode = parameter.PreciseTimeMode,
|
|
timeToMaturityDays = parameter.OverrideTTM ?? double.NaN,
|
|
maturityShift = parameter.maturityShift,
|
|
|
|
ParamOverride = p =>
|
|
{
|
|
//这段逻辑应该不需要,先注释掉,OTC-13391
|
|
//p.tradeDate = valueDateStr;
|
|
//p.startDate = valueDateStr;
|
|
p.riskFreeRate = parameter.RiskFreeRate ?? double.NaN;
|
|
},
|
|
|
|
tradeId = null,
|
|
fixings = null,
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算香草期权
|
|
/// </summary>
|
|
private static TradeValueResult CalculateVanillaOption(
|
|
string userId,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
VanillaOptionParameter parameter,
|
|
PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default)
|
|
{
|
|
var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd");
|
|
var marketProxy = CreateMarketProxy(userId, valueDateStr);
|
|
|
|
var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter);
|
|
|
|
var valuationParameter = CreateQdpOptionValuationParameters(
|
|
underlying.UnderlyingInstrumentType, marketObjectName,
|
|
underlying.UnderlyingCode, parameter.IsForwardTrade);
|
|
|
|
var optionTrade = QdpTradeBuilder.GetVanillaOptionTrade(trade, GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName), false);
|
|
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber);
|
|
|
|
var option = optionTrade.Instrument as VanillaOption;
|
|
var factory = OptionEngineFactory.GetEngineFactory("VanillaOption");
|
|
var engine = factory.GetEngine(parameter.EngineName, option.Exercise);
|
|
var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter);
|
|
|
|
CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName);
|
|
|
|
return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算Risky期权
|
|
/// </summary>
|
|
private static TradeValueResult CalculateRiskyOption(
|
|
string userId,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
VanillaOptionParameter parameter,
|
|
PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default)
|
|
{
|
|
TradeValueResult result = new TradeValueResult();
|
|
|
|
var tradeclone = trade.Clone();
|
|
tradeclone.TradeAmount = tradeclone.TradeAmount = TradeCalcHelper.GetTradeAmountV(trade, trade.TradeAmount, 1);
|
|
tradeclone.Notional = tradeclone.Notional = TradeCalcHelper.GetTradeAmountV(trade, trade.Notional, underlying.CountRatio);
|
|
|
|
var td1 = tradeclone.Clone();
|
|
var td2 = tradeclone.Clone();
|
|
|
|
if (trade.trade_risky_option.ParticipationRate2 != 0)
|
|
{
|
|
td2.Strike = trade.trade_risky_option.Strike2;
|
|
td2.ParticipationRate = trade.trade_risky_option.ParticipationRate2;
|
|
td2.TradeAmount = TradeCalcHelper.GetTradeAmount(td2, td2.TradeAmount, 1);
|
|
td2.Notional = TradeCalcHelper.GetTradeAmount(td2, td2.Notional, underlying.CountRatio);
|
|
result = CalculateVanillaOption(userId, td2, underlying, parameter, pricingRequest, calcScenario);
|
|
}
|
|
|
|
if (trade.trade_risky_option.ParticipationRate1 != 0)
|
|
{
|
|
td1.Strike = trade.trade_risky_option.Strike1;
|
|
td1.ParticipationRate = trade.trade_risky_option.ParticipationRate1;
|
|
td1.TradeAmount = TradeCalcHelper.GetTradeAmount(td1, td1.TradeAmount, 1);
|
|
td1.Notional = TradeCalcHelper.GetTradeAmount(td1, td1.Notional, underlying.CountRatio);
|
|
td1.OptionType = "看跌";
|
|
var singleresult1 = CalculateVanillaOption(userId, td1, underlying, parameter, pricingRequest, calcScenario);
|
|
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.Vol = singleresult1.Vol;
|
|
}
|
|
|
|
var td3 = tradeclone.Clone();
|
|
td3.Strike = trade.trade_risky_option.Strike3;
|
|
//decimal 为了解决精度问题: 0.2-0.3=0.0999999999
|
|
var participationRate3 = (decimal)trade.trade_risky_option.ParticipationRate3 - (decimal)trade.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, underlying.CountRatio);
|
|
var singleresult3 = CalculateVanillaOption(userId, td3, underlying, parameter, pricingRequest, calcScenario);
|
|
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.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.Vol = singleresult3.Vol;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算障碍期权
|
|
/// </summary>
|
|
private static TradeValueResult CalculateBarrierOption(
|
|
string userId,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
BarrierOptionParameter parameter,
|
|
PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default,
|
|
bool quadratureFastMode = false)
|
|
{
|
|
var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd");
|
|
var marketProxy = CreateMarketProxy(userId, valueDateStr);
|
|
|
|
var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter);
|
|
|
|
var valuationParameter = CreateQdpOptionValuationParameters(underlying.UnderlyingInstrumentType, marketObjectName, underlying.UnderlyingCode);
|
|
|
|
//如果用户自定义的观察日就是每天观察,则忽略自定义观察日
|
|
var observationDateStr = trade.trade_barrier_option.ObservationDates;
|
|
var observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(new Date(trade.StartDate.Value), new Date(trade.ExerciseDate.Value)).Union(new[] { new Date(trade.ExerciseDate.Value) }).ToArray();
|
|
var barrierObservationDates = QdpHelper.ParseObservationDate(observationDateStr);
|
|
if (barrierObservationDates != null && (barrierObservationDates.Count(d => d >= observationDates.First() && d <= observationDates.Last()) == observationDates.Length))
|
|
{
|
|
observationDateStr = null;
|
|
}
|
|
|
|
var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName);
|
|
var tParam = QdpTradeBuilder.GetBarrierOptionTradeParam(trade, trade.trade_barrier_option, tpReq);
|
|
tParam.observationDateStr = observationDateStr;
|
|
var optionTrade = QdpTradeBuilder.GetBarrierOptionTrade(tParam, valueDateStr);
|
|
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber);
|
|
|
|
var option = optionTrade.Instrument as BarrierOption;
|
|
var factory = OptionEngineFactory.GetEngineFactory("BarrierOption");
|
|
var engine = factory.GetEngine(
|
|
parameter.EngineName,
|
|
option.Exercise,
|
|
option,
|
|
!string.IsNullOrWhiteSpace(observationDateStr),
|
|
quadratureFastMode);
|
|
var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter);
|
|
|
|
CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName);
|
|
|
|
return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算亚式期权
|
|
/// </summary>
|
|
private static TradeValueResult CalculateAsianOption(
|
|
string userId,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
AsianOptionParameter parameter,
|
|
PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default)
|
|
{
|
|
var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd");
|
|
var marketProxy = CreateMarketProxy(userId, valueDateStr);
|
|
|
|
var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter);
|
|
|
|
var valuationParameter = CreateQdpOptionValuationParameters(
|
|
underlying.UnderlyingInstrumentType,
|
|
marketObjectName,
|
|
underlying.UnderlyingCode);
|
|
|
|
trade.TradeDate = trade.StartDate = parameter.ValueDate;
|
|
var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName);
|
|
tpReq.fixings = AsianOptionFixingService.CheckAsiaFixings(trade, trade.trade_asian_option, parameter.Fixings, parameter.SpotPrices[underlying.UnderlyingCode]);
|
|
var optionTrade = QdpTradeBuilder.GetAsianOptionTrade(trade, trade.trade_asian_option, tpReq);
|
|
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber);
|
|
|
|
var option = optionTrade.Instrument as AsianOption;
|
|
var factory = OptionEngineFactory.GetEngineFactory("AsianOption");
|
|
var engine = factory.GetEngine(parameter.EngineName, option.Exercise, trade.trade_asian_option.PayoffType, option, trade.trade_asian_option.StrikeType);
|
|
var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter);
|
|
|
|
CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName);
|
|
|
|
return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算二元期权
|
|
/// </summary>
|
|
private static TradeValueResult CalculateBinaryOption(
|
|
string userId,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
BinaryOptionParameter parameter,
|
|
PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default)
|
|
{
|
|
var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd");
|
|
var marketProxy = CreateMarketProxy(userId, valueDateStr);
|
|
|
|
var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter);
|
|
|
|
var valuationParameter = CreateQdpOptionValuationParameters(
|
|
underlying.UnderlyingInstrumentType,
|
|
marketObjectName,
|
|
underlying.UnderlyingCode);
|
|
|
|
var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName);
|
|
var optionTrade = QdpTradeBuilder.GetBinaryOptionTrade(trade, trade.trade_binary_option, tpReq);
|
|
var option = optionTrade.Instrument as BinaryOption;
|
|
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber);
|
|
|
|
//如果是美式二元,则要判断是否有自定义观察日
|
|
//如果用户自定义的观察日就是每天观察,则忽略自定义观察日
|
|
string observationDateStr = null;
|
|
if (option.Exercise == OptionExercise.American)
|
|
{
|
|
observationDateStr = trade.trade_binary_option.ObservationDates;
|
|
var observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(new Date(trade.StartDate.Value), new Date(trade.ExerciseDate.Value)).Union(new[] { new Date(trade.ExerciseDate.Value) }).ToArray();
|
|
var barrierObservationDates = QdpHelper.ParseObservationDate(observationDateStr);
|
|
if (barrierObservationDates != null && (barrierObservationDates.Count(d => d >= observationDates.First() && d <= observationDates.Last()) == observationDates.Length))
|
|
{
|
|
observationDateStr = null;
|
|
}
|
|
}
|
|
|
|
var factory = OptionEngineFactory.GetEngineFactory("BinaryOption");
|
|
var engine = factory.GetEngine(
|
|
parameter.EngineName,
|
|
option.Exercise,
|
|
(parameter.ReplicationShiftSize <= 0.0) ? option.Strike * 0.01 : parameter.ReplicationShiftSize,
|
|
QdpConverter.ConvertReplicationStrategy(parameter.BinaryReplicationStrategy),
|
|
!string.IsNullOrWhiteSpace(observationDateStr),
|
|
option.RebateAnnualizedAtKO);
|
|
|
|
var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter);
|
|
|
|
CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName);
|
|
|
|
return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算彩虹期权
|
|
/// </summary>
|
|
private static TradeValueResult CalculateRainbowOption(
|
|
string userId,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
RainbowOptionParameter parameter,
|
|
PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default)
|
|
{
|
|
var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd");
|
|
var marketProxy = CreateMarketProxy(userId, valueDateStr);
|
|
|
|
var marketObjectName = PrepareRainbowOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter);
|
|
|
|
var valuationParameter = CreateQdpRainbowOptionValuationParameters(
|
|
underlying.UnderlyingInstrumentType,
|
|
marketObjectName,
|
|
trade.trade_rainbow_option.UnderlyingCodes);
|
|
|
|
var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceNames);
|
|
var optionTrade = QdpTradeBuilder.GetRainbowOptionTrade(trade, trade.trade_rainbow_option, tpReq);
|
|
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber);
|
|
|
|
var option = optionTrade.Instrument as RainbowOption;
|
|
var factory = OptionEngineFactory.GetEngineFactory("RainbowOption");
|
|
var engine = factory.GetEngine(parameter.EngineName, option.Exercise);
|
|
|
|
var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter);
|
|
|
|
CleanupRainbowOptionMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName);
|
|
|
|
return ConvertRainbowOptionValueResult(results, trade);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算价差期权
|
|
/// </summary>
|
|
private static TradeValueResult CalculateSpreadOption(
|
|
string userId,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
SpreadOptionParameter parameter,
|
|
PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default)
|
|
{
|
|
var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd");
|
|
var marketProxy = CreateMarketProxy(userId, valueDateStr);
|
|
|
|
var marketObjectName = PrepareSpreadOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter);
|
|
|
|
var valuationParameter = CreateQdpSpreadOptionValuationParameters(
|
|
trade.trade_spread_option.UnderlyingAssetCodes(),
|
|
marketObjectName.DiscountCurveName,
|
|
marketObjectName.VolSurfaceNames);
|
|
|
|
var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceNames);
|
|
var optionTrade = QdpTradeBuilder.GetSpreadOptionTrade(trade, trade.trade_spread_option, tpReq, correlations: parameter.Correlations);
|
|
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber);
|
|
|
|
var option = optionTrade.Instrument as SpreadOption;
|
|
var factory = OptionEngineFactory.GetEngineFactory("SpreadOption");
|
|
var engine = factory.GetEngine(parameter.EngineName, option.Exercise);
|
|
var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter);
|
|
|
|
CleanupSpreadOptionMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName);
|
|
|
|
return ConvertSpreadOptionValueResult(results, trade);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算合成价差期权
|
|
/// </summary>
|
|
private static TradeValueResult CalculateSyntheticNormalSpreadOption(
|
|
string userId,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
SyntheticNormalSpreadOptionParameter parameter,
|
|
PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default)
|
|
{
|
|
var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd");
|
|
var marketProxy = CreateMarketProxy(userId, valueDateStr);
|
|
|
|
var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter);
|
|
|
|
var valuationParameter = CreateQdpOptionValuationParameters(
|
|
underlying.UnderlyingInstrumentType,
|
|
marketObjectName,
|
|
underlying.UnderlyingCode,
|
|
parameter.IsForwardTrade);
|
|
|
|
var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName);
|
|
var optionTrade = QdpTradeBuilder.GetSSpreadOptionTrade(trade, tpReq);
|
|
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber);
|
|
|
|
var option = optionTrade.Instrument as VanillaOption;
|
|
var factory = OptionEngineFactory.GetEngineFactory("SynthticNormalSpreadOption");
|
|
var engine = factory.GetEngine(parameter.EngineName, option.Exercise);
|
|
var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter);
|
|
|
|
CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName);
|
|
|
|
return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 计算亚式合成价差期权
|
|
/// </summary>
|
|
private static TradeValueResult CalculateAsianSyntheticNormalSpreadOption(
|
|
string userId,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
AsianSyntheticNormalSpreadOptionParameter parameter,
|
|
PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default)
|
|
{
|
|
var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd");
|
|
var marketProxy = CreateMarketProxy(userId, valueDateStr);
|
|
|
|
var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter);
|
|
|
|
var valuationParameter = CreateQdpOptionValuationParameters(
|
|
underlying.UnderlyingInstrumentType,
|
|
marketObjectName,
|
|
underlying.UnderlyingCode);
|
|
|
|
trade.TradeDate = trade.StartDate = parameter.ValueDate;
|
|
var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName);
|
|
tpReq.fixings = parameter.Fixings;
|
|
var optionTrade = QdpTradeBuilder.GetAsianSSpreadOptionTrade(trade, trade.trade_asian_option, tpReq);
|
|
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber);
|
|
|
|
var engine = new AnalyticalAsianSyntheticNormalSpreadOptionEngine();
|
|
var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter);
|
|
|
|
CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName);
|
|
|
|
return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算双鲨期权
|
|
/// </summary>
|
|
private static TradeValueResult CalculateDoubleSharkFinOption(
|
|
string userId,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
DoubleSharkFinOptionParameter parameter,
|
|
PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default,
|
|
bool quadratureFastMode = false)
|
|
{
|
|
var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd");
|
|
var marketProxy = CreateMarketProxy(userId, valueDateStr);
|
|
|
|
var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter);
|
|
|
|
var valuationParameter = CreateQdpOptionValuationParameters(
|
|
underlying.UnderlyingInstrumentType,
|
|
marketObjectName,
|
|
underlying.UnderlyingCode);
|
|
|
|
var observationDateStr = trade.trade_double_sharkfin_option.ObservationDates;
|
|
//如果用户自定义的观察日就是每天观察,则忽略自定义观察日
|
|
var observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(new Date(trade.StartDate.Value), new Date(trade.ExerciseDate.Value)).Union(new[] { new Date(trade.ExerciseDate.Value) }).ToArray();
|
|
var barrierObservationDates = QdpHelper.ParseObservationDate(observationDateStr);
|
|
if (barrierObservationDates != null &&
|
|
(barrierObservationDates.Count(d => d >= observationDates.First() && d <= observationDates.Last()) == observationDates.Length))
|
|
{
|
|
observationDateStr = null;
|
|
}
|
|
|
|
var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName);
|
|
var tParam = QdpTradeBuilder.GetDoubleSharkFinOptionTradeParam(trade, trade.trade_double_sharkfin_option, tpReq);
|
|
tParam.observationDateStr = observationDateStr;
|
|
var optionTrade = QdpTradeBuilder.GetDoubleSharkFinOptionTrade(tParam);
|
|
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber);
|
|
|
|
var option = optionTrade.Instrument as DoubleSharkFinOption;
|
|
var factory = OptionEngineFactory.GetEngineFactory("DoubleSharkFinOption");
|
|
var engine = factory.GetEngine(
|
|
parameter.EngineName,
|
|
option.Exercise,
|
|
option,
|
|
false,
|
|
!string.IsNullOrWhiteSpace(observationDateStr),
|
|
quadratureFastMode);
|
|
var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter);
|
|
|
|
CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName);
|
|
|
|
return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算远期交易(买方角度)
|
|
/// </summary>
|
|
public static TradeValueResult CalculateForward(double strike, double spotPrice, double notional, string callPut)
|
|
{
|
|
var pv = 0.0;
|
|
if (callPut == "Call")
|
|
{
|
|
pv = (spotPrice - strike) * notional;
|
|
}
|
|
else if (callPut == "Put")
|
|
{
|
|
pv = (strike - spotPrice) * notional;
|
|
}
|
|
|
|
return new TradeValueResult
|
|
{
|
|
Pv = pv,
|
|
Delta = callPut == "Call" ? notional : -notional,
|
|
DeltaCash = callPut == "Call" ? spotPrice * notional : -spotPrice * notional
|
|
};
|
|
}
|
|
|
|
private static TradeValueResult CalculateSnowball(
|
|
string userId,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
SnowballParameter parameter,
|
|
PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default,
|
|
bool quadratureFastMode = false)
|
|
{
|
|
var snowball = trade.trade_snowball;
|
|
|
|
// 年化权利金模式的雪球期权
|
|
if (snowball.AnnualizedPremiumRate.HasValue && snowball.AnnualizedPremiumRate != 0)
|
|
{
|
|
return CalculateAnnualPremiumSnowball(userId, trade, underlying, parameter, pricingRequest, quadratureFastMode: quadratureFastMode);
|
|
}
|
|
|
|
var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd");
|
|
var marketProxy = CreateMarketProxy(userId, valueDateStr);
|
|
|
|
var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter);
|
|
|
|
var valuationParameter = CreateQdpOptionValuationParameters(
|
|
underlying.UnderlyingInstrumentType,
|
|
marketObjectName,
|
|
underlying.UnderlyingCode);
|
|
|
|
var optionTrade = QdpTradeBuilder.GetSnowballOptionTrade(new SnowballOptionTradeParam
|
|
{
|
|
preciseTimeMode = parameter.PreciseTimeMode,
|
|
tradeId = null,
|
|
volSurfaceNames = new[] { marketObjectName.VolSurfaceName },
|
|
tradeDate = trade.TradeDate.Value,
|
|
underlyingTickers = new[] { underlying.UnderlyingCode },
|
|
underlyingInstrumentType = underlying.UnderlyingInstrumentType,
|
|
startDate = trade.StartDate.Value,
|
|
endDate = trade.ExerciseDate.Value,
|
|
initialSpotPrice = trade.SpotPrice ?? 0.0,
|
|
isMoneynessOption = trade.IsMoneynessOptionData,
|
|
koBarrier = snowball.KOBarrier,
|
|
kiBarrier = snowball.KIBarrier,
|
|
koObservationDateStr = snowball.KOObservationDates,
|
|
couponPaymentDateStr = snowball.CouponUsePaymentDate == true ? snowball.KOObservationSettleDates : null,
|
|
observationDateStr = snowball.ObservationDates,
|
|
coupon = snowball.Coupon,
|
|
participationRate = trade.ParticipationRate ?? 1.0,
|
|
principalRate = trade.PrincipalRate ?? 0.0,
|
|
notional = trade.Notional,
|
|
buysell = trade.BuySell,
|
|
exerciseDate = trade.ExerciseDate.Value,
|
|
annualizedFactor = snowball.AnnualizeFactor2 ?? 1.0,
|
|
callput = QdpConverter.ConvertCallPut(trade.CallPut),
|
|
strike = trade.Strike ?? 0.0,
|
|
spreadStrikeAtKO = snowball.SpreadStrikeAtKO ?? -1,
|
|
spreadStrikeAtMaturity = snowball.SpreadStrikeAtMaturity ?? -1,
|
|
isFixedCoupon = snowball.IsFixedCoupon,
|
|
koRebate = snowball.KORebate,
|
|
koBarrierAdjustStep = snowball.KOBarrierAdjustStep,
|
|
//敲出转期权
|
|
useOptionPayoffAtKO = snowball.KOPayoffType != KOPayoffTypeEnum.Rebate,
|
|
//未敲出转期权
|
|
useOptionPayoffAtMaturity = snowball.KIPayoffType != KIPayoffTypeEnum.None,
|
|
annualizedOptionPayoff = snowball.IsAnnualized2,
|
|
couponDayCount = snowball.CouponDayCount,
|
|
timeToMaturityDays = parameter.OverrideTTM ?? double.NaN,
|
|
riskFreeRate = parameter.RiskFreeRate ?? double.NaN,
|
|
dividendRate = parameter.DividendRate ?? double.NaN,
|
|
koStrike = snowball.KOPayoffType != KOPayoffTypeEnum.Rebate
|
|
? snowball.SpreadStrikeAtKO1.Value : double.NaN,
|
|
koOptionType = QdpConverter.ConvertAutocallKOOptionType(snowball.KOPayoffType, QdpConverter.ConvertCallPut(trade.CallPut)),
|
|
kiOptionType = QdpConverter.ConvertOptionType(snowball.KIPayoffType),
|
|
barrierStatus = snowball.KnockInOutStatus,
|
|
hasNightMarket=parameter.HasNightMarket
|
|
}, SnowBallOptionMode.SimpleSnowball);
|
|
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber);
|
|
|
|
var option = optionTrade.Instrument as SimpleSnowball;
|
|
var factory = OptionEngineFactory.GetEngineFactory("SnowBall");
|
|
var engine = factory.GetEngine(parameter.EngineName, option.Exercise, quadratureFastMode);
|
|
|
|
IPricingResult results;
|
|
if (!(engine is QuadAutoCallEngine) || parameter.ValueDate == trade.ExerciseDate)
|
|
{
|
|
results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter);
|
|
}
|
|
else
|
|
{
|
|
//普通雪球期权可以采用加速方法定价
|
|
var market = SnowballTrade.ConvertToMarketCondition(optionTrade.TradeId, marketProxy.GetQdpMarket(valueDateStr), valuationParameter);
|
|
var gridSnapshot = new QuadSnowballGridSnapshot(option, market, (QuadAutoCallEngine)engine);
|
|
results = gridSnapshot.Calculate(
|
|
market.SpotPrices.Value.Values.First(),
|
|
option.BarrierStatus == BarrierStatus.KnockedIn,
|
|
pricingRequest,
|
|
rebuildGrid: false);
|
|
}
|
|
|
|
CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName);
|
|
|
|
return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算年化权利金模式的保本雪球
|
|
/// </summary>
|
|
private static TradeValueResult CalculateAnnualPremiumSnowball(
|
|
string userId,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
SnowballParameter parameter,
|
|
PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default,
|
|
bool quadratureFastMode = false)
|
|
{
|
|
var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd");
|
|
var marketProxy = CreateMarketProxy(userId, valueDateStr);
|
|
|
|
var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter);
|
|
|
|
var valuationParameter = CreateQdpOptionValuationParameters(
|
|
underlying.UnderlyingInstrumentType,
|
|
marketObjectName,
|
|
underlying.UnderlyingCode);
|
|
|
|
var snowball = trade.trade_snowball;
|
|
|
|
var optionTrade = QdpTradeBuilder.GetSnowballOptionTrade(new SnowballOptionTradeParam
|
|
{
|
|
preciseTimeMode = parameter.PreciseTimeMode,
|
|
tradeId = null,
|
|
volSurfaceNames = new[] { marketObjectName.VolSurfaceName },
|
|
tradeDate = trade.TradeDate.Value,
|
|
underlyingTickers = new[] { underlying.UnderlyingCode },
|
|
underlyingInstrumentType = underlying.UnderlyingInstrumentType,
|
|
startDate = trade.StartDate.Value,
|
|
endDate = trade.ExerciseDate.Value,
|
|
initialSpotPrice = trade.SpotPrice ?? 0.0,
|
|
isMoneynessOption = trade.IsMoneynessOptionData,
|
|
koBarrier = snowball.KOBarrier,
|
|
kiBarrier = double.NaN,
|
|
barrierStatus = snowball.KnockInOutStatus,
|
|
koObservationDateStr = snowball.KOObservationDates,
|
|
couponPaymentDateStr = snowball.CouponUsePaymentDate == true ? snowball.KOObservationSettleDates : null,
|
|
observationDateStr = snowball.ObservationDates,
|
|
coupon = snowball.Coupon,
|
|
participationRate = trade.ParticipationRate ?? 1.0,
|
|
principalRate = trade.PrincipalRate ?? 0.0,
|
|
notional = trade.Notional,
|
|
buysell = trade.BuySell,
|
|
exerciseDate = trade.ExerciseDate.Value,
|
|
annualizedFactor = snowball.AnnualizeFactor2 ?? 1.0,
|
|
callput = QdpConverter.ConvertCallPut(trade.CallPut),
|
|
strike = 0,
|
|
spreadStrikeAtKO = -1,
|
|
spreadStrikeAtMaturity = -1,
|
|
isFixedCoupon = snowball.IsFixedCoupon,
|
|
koRebate = snowball.KORebate,
|
|
koBarrierAdjustStep = snowball.KOBarrierAdjustStep,
|
|
useOptionPayoffAtKO = snowball.KOPayoffType != KOPayoffTypeEnum.Rebate,
|
|
useOptionPayoffAtMaturity = false,
|
|
annualizedOptionPayoff = snowball.IsAnnualized2,
|
|
couponDayCount = snowball.CouponDayCount,
|
|
timeToMaturityDays = parameter.OverrideTTM ?? double.NaN,
|
|
riskFreeRate = parameter.RiskFreeRate ?? double.NaN,
|
|
dividendRate = parameter.DividendRate ?? double.NaN,
|
|
}, SnowBallOptionMode.AnnualizedPremiumSnowball);
|
|
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber);
|
|
|
|
var option = optionTrade.Instrument as AnnualizedPremiumSnowball;
|
|
option.AnnualizedPremium = snowball.AnnualizedPremiumRate.Value;
|
|
var factory = OptionEngineFactory.GetEngineFactory("SnowBall");
|
|
var engine = factory.GetEngine(parameter.EngineName, option.Exercise, quadratureFastMode) as QuadAutoCallEngine;
|
|
var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter);
|
|
|
|
CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName);
|
|
|
|
return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]);
|
|
}
|
|
|
|
private static TradeValueResult CalculateRangeAccrual(
|
|
string userId,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
RangeAccrualParameter parameter,
|
|
PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default)
|
|
{
|
|
var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd");
|
|
var marketProxy = CreateMarketProxy(userId, valueDateStr);
|
|
|
|
var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter);
|
|
|
|
var valuationParameter = CreateQdpOptionValuationParameters(
|
|
underlying.UnderlyingInstrumentType,
|
|
marketObjectName,
|
|
underlying.UnderlyingCode);
|
|
|
|
var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName);
|
|
tpReq.fixings = parameter.Fixings;
|
|
var optionTrade = QdpTradeBuilder.GetRangeAccrualTrade(trade, trade.trade_rangeaccrual, tpReq);
|
|
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber);
|
|
|
|
var option = optionTrade.Instrument as RangeAccrual;
|
|
var factory = OptionEngineFactory.GetEngineFactory("RangeAccrual");
|
|
var engine = factory.GetEngine(parameter.EngineName, option.Exercise);
|
|
var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter);
|
|
|
|
CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName);
|
|
|
|
return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]);
|
|
}
|
|
|
|
private static TradeValueResult CalculateAirbag(
|
|
string userId,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
AirbagParameter parameter,
|
|
PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default)
|
|
{
|
|
var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd");
|
|
var marketProxy = CreateMarketProxy(userId, valueDateStr);
|
|
|
|
var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter);
|
|
|
|
var valuationParameter = CreateQdpOptionValuationParameters(
|
|
underlying.UnderlyingInstrumentType,
|
|
marketObjectName,
|
|
underlying.UnderlyingCode);
|
|
|
|
var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName);
|
|
var optionTrade = QdpTradeBuilder.GetAirbagOptionTrade(trade, trade.trade_airbag, tpReq);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber);
|
|
|
|
var engine = new AnalyticalAirbagReplicationEngine();
|
|
var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter);
|
|
|
|
CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName);
|
|
|
|
return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]);
|
|
}
|
|
|
|
private static TradeValueResult CalculateUnderlyingEnhance(
|
|
string userId,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
UnderlyingEnhanceParameter parameter,
|
|
PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default)
|
|
{
|
|
var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd");
|
|
var marketProxy = CreateMarketProxy(userId, valueDateStr);
|
|
|
|
var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter);
|
|
|
|
var valuationParameter = CreateQdpOptionValuationParameters(
|
|
underlying.UnderlyingInstrumentType,
|
|
marketObjectName,
|
|
underlying.UnderlyingCode);
|
|
|
|
var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName);
|
|
var optionTrade = QdpTradeBuilder.GetUnderlyingEnhanceTrade(trade, trade.trade_underlying_enhance, tpReq);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber);
|
|
|
|
var engine = new AnalyticalUnderlyingPayoffEnhanceEngine(calcAsOptions: true);
|
|
var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter);
|
|
|
|
CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName);
|
|
|
|
return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]);
|
|
}
|
|
|
|
private static TradeValueResult CalculateAutocall(
|
|
string userId,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
AutocallParameter parameter,
|
|
PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default,
|
|
bool quadratureFastMode = false)
|
|
{
|
|
var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd");
|
|
var marketProxy = CreateMarketProxy(userId, valueDateStr);
|
|
|
|
var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter);
|
|
|
|
var valuationParameter = CreateQdpOptionValuationParameters(
|
|
underlying.UnderlyingInstrumentType,
|
|
marketObjectName,
|
|
underlying.UnderlyingCode);
|
|
|
|
var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName);
|
|
var optionTrade = QdpTradeBuilder.GetAutocallOptionTrade(trade, trade.trade_autocall, tpReq);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber);
|
|
|
|
var option = optionTrade.Instrument as AutoCall;
|
|
var factory = OptionEngineFactory.GetEngineFactory("AutoCall");
|
|
var engine = factory.GetEngine(parameter.EngineName, option.Exercise, quadratureFastMode);
|
|
var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter);
|
|
|
|
CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName);
|
|
|
|
return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]);
|
|
}
|
|
|
|
private static TradeValueResult CalculateAccumulatorOption(
|
|
string userId,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
AccumulatorOptionParameter parameter,
|
|
PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default)
|
|
{
|
|
var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd");
|
|
var marketProxy = CreateMarketProxy(userId, valueDateStr);
|
|
|
|
var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter);
|
|
|
|
var valuationParameter = CreateQdpOptionValuationParameters(underlying.UnderlyingInstrumentType, marketObjectName, underlying.UnderlyingCode);
|
|
|
|
//如果用户自定义的观察日就是每天观察,则忽略自定义观察日
|
|
var observationDateStr = trade.trade_accumulator_option.KOObservationDates;
|
|
var observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(new Date(trade.StartDate.Value), new Date(trade.ExerciseDate.Value)).Union(new[] { new Date(trade.ExerciseDate.Value) }).ToArray();
|
|
var barrierObservationDates = QdpHelper.ParseObservationDate(observationDateStr);
|
|
if (barrierObservationDates != null && (barrierObservationDates.Count(d => d >= observationDates.First() && d <= observationDates.Last()) == observationDates.Length))
|
|
{
|
|
observationDateStr = null;
|
|
}
|
|
|
|
var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName);
|
|
var tParam = QdpTradeBuilder.GetAccumulatorOptionTradeParam(trade, trade.trade_accumulator_option, tpReq);
|
|
tParam.KOObservationDates = observationDateStr;
|
|
//当到期日和定价日一样,并且当前时间大于等于3点,ttm传0;qdp里面 ttm等于0和不等于0逻辑处理不一样。
|
|
if (trade.ExerciseDate == parameter.ValueDate && DateTime.Now.Hour >= 15)
|
|
{
|
|
tParam.timeToMaturityDays = 0;
|
|
}
|
|
var optionTrade = QdpTradeBuilder.GetAccumulatorOptionTrade(tParam);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber);
|
|
|
|
IEngine engine;
|
|
if (optionTrade is SegmentedAccumulatorOptionTrade)
|
|
{
|
|
engine = new AnalyticalSegmentedAccumulatorOptionEngine();
|
|
}
|
|
else
|
|
{
|
|
if (tParam.EarlyTerminate)
|
|
{
|
|
engine = new QuadAccumulatorOptionEngine();
|
|
}
|
|
else
|
|
{
|
|
engine = new AnalyticalAccumulatorOptionEngine();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter);
|
|
|
|
CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName);
|
|
|
|
return ConvertValueResult(results, trade, underlying, parameter.SpotPrices[underlying.UnderlyingCode]);
|
|
}
|
|
|
|
private static TradeValueResult CalculateCashFlow(
|
|
string userId,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
CashFlowTradeParameter parameter,
|
|
PricingRequest pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
|
CalcScenarioEnum calcScenario = CalcScenarioEnum.Default)
|
|
{
|
|
var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd");
|
|
var marketProxy = CreateMarketProxy(userId, valueDateStr);
|
|
|
|
var marketObjectName = PrepareOptionMarketObjects(marketProxy, valueDateStr, trade, underlying, parameter);
|
|
|
|
var valuationParameter = new SimpleCfValuationParameters(
|
|
marketObjectName.DiscountCurveName,
|
|
marketObjectName.DiscountCurveName,
|
|
marketObjectName.DiscountCurveName);
|
|
|
|
var tpReq = GetOptionTradeParamRequest(parameter, marketObjectName.VolSurfaceName);
|
|
var tParam = QdpTradeBuilder.GetCashFlowTradeParam(trade, trade.trade_cashflow, tpReq);
|
|
var optionTrade = QdpTradeBuilder.GetCashFlowTrade(tParam, trade.StockEqvNotional);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcScenario, trade.TradeNumber);
|
|
|
|
IEngine engine = new DepositEngine();
|
|
var results = optionTrade.CalculateRisks(marketProxy.GetQdpMarket(valueDateStr), pricingRequest, engine, valuationParameter);
|
|
|
|
CleanupMarketObjects(parameter, marketProxy, valueDateStr, marketObjectName);
|
|
|
|
return ConvertValueResult(results, trade, underlying, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 构造MarketProxy
|
|
/// </summary>
|
|
/// <param name="userId"></param>
|
|
/// <param name="valueDateStr"></param>
|
|
/// <returns></returns>
|
|
private static IQdpMarketProxy CreateMarketProxy(string userId, string valueDateStr)
|
|
{
|
|
var marketProxy = QdpMarketManager.Instance.GetPrebuiltMarketProxy(userId);
|
|
marketProxy.CreateMarket(valueDateStr);
|
|
return marketProxy;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 准备标的价格数据
|
|
/// </summary>
|
|
/// <param name="marketProxy"></param>
|
|
/// <param name="valueDateStr"></param>
|
|
/// <param name="prices"></param>
|
|
/// <returns></returns>
|
|
private static bool PrepareSpotPrices(IQdpMarketProxy marketProxy, string valueDateStr, Dictionary<string, double> prices)
|
|
{
|
|
if (prices == null || prices.Count == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var kv in prices)
|
|
{
|
|
marketProxy.AddStockPrice(kv.Key, valueDateStr, kv.Value);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 准备波动率曲面
|
|
/// </summary>
|
|
/// <param name="marketProxy"></param>
|
|
/// <param name="valueDateStr"></param>
|
|
/// <param name="volSurfaceName"></param>
|
|
/// <param name="overrideVol"></param>
|
|
/// <returns></returns>
|
|
private static string PrepareVolSurface(IQdpMarketProxy marketProxy, string valueDateStr, string volSurfaceName, double? overrideVol)
|
|
{
|
|
if (overrideVol.HasValue)
|
|
{
|
|
volSurfaceName = CreateVolSurfaceFromVolValue(marketProxy, valueDateStr, overrideVol.Value);
|
|
}
|
|
|
|
return volSurfaceName;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 准备折现曲线
|
|
/// </summary>
|
|
/// <param name="marketProxy"></param>
|
|
/// <param name="valueDateStr"></param>
|
|
/// <param name="discountCurveName"></param>
|
|
/// <param name="riskFreeRate"></param>
|
|
/// <returns></returns>
|
|
private static string PrepareDiscountCurve(IQdpMarketProxy marketProxy, string valueDateStr, string discountCurveName, double? riskFreeRate)
|
|
{
|
|
if (riskFreeRate.HasValue)
|
|
{
|
|
discountCurveName = Guid.NewGuid().ToString();
|
|
var discountCurve = CalculatorHelper.CreateConstantRiskFreeCurve(discountCurveName, riskFreeRate.Value);
|
|
marketProxy.AddYieldCurve(discountCurveName, valueDateStr, discountCurve);
|
|
}
|
|
return discountCurveName;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 准备分红曲线
|
|
/// </summary>
|
|
/// <param name="marketProxy"></param>
|
|
/// <param name="valueDateStr"></param>
|
|
/// <param name="dividendRate"></param>
|
|
/// <returns></returns>
|
|
private static string PrepareDividendCurve(IQdpMarketProxy marketProxy, string valueDateStr, double? dividendRate)
|
|
{
|
|
if (dividendRate.HasValue)
|
|
{
|
|
var dividendCurveName = Guid.NewGuid().ToString();
|
|
var dividendCurve = CalculatorHelper.CreateConstantRiskFreeCurve(dividendCurveName, dividendRate.Value);
|
|
marketProxy.AddYieldCurve(dividendCurveName, valueDateStr, dividendCurve);
|
|
return dividendCurveName;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 为期权计算准备市场数据
|
|
/// </summary>
|
|
/// <param name="marketProxy"></param>
|
|
/// <param name="valueDateStr"></param>
|
|
/// <param name="trade"></param>
|
|
/// <param name="underlying"></param>
|
|
/// <param name="parameter"></param>
|
|
/// <returns></returns>
|
|
private static OptionMarketObjectName PrepareOptionMarketObjects(
|
|
IQdpMarketProxy marketProxy,
|
|
string valueDateStr,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
BaseOptionParameter parameter)
|
|
{
|
|
if (!PrepareSpotPrices(marketProxy, valueDateStr, parameter.SpotPrices) && trade.TradeType != "现金流交易")
|
|
{
|
|
throw new Exception($"交易计算未设置标的价格: {trade.id} {trade.TradeNumber} {underlying.UnderlyingCode}");
|
|
}
|
|
|
|
var volSurfaceName = PrepareVolSurface(marketProxy, valueDateStr, parameter.VolSurfaceName, parameter.Volatility);
|
|
if (string.IsNullOrWhiteSpace(volSurfaceName) && trade.TradeType != "现金流交易")
|
|
{
|
|
throw new Exception($"交易计算未设置波动率: {trade.id} {trade.TradeNumber} {underlying.UnderlyingCode}");
|
|
}
|
|
|
|
var discountCurveName = PrepareDiscountCurve(marketProxy, valueDateStr, parameter.DiscountCurveName, parameter.RiskFreeRate);
|
|
if (string.IsNullOrWhiteSpace(discountCurveName))
|
|
{
|
|
throw new Exception($"交易计算未设置无风险利率: {trade.id} {trade.TradeNumber}");
|
|
}
|
|
|
|
string dividendCurveName = null;
|
|
if (underlying.UnderlyingInstrumentType == "Stock")
|
|
{
|
|
dividendCurveName = PrepareDividendCurve(marketProxy, valueDateStr, parameter.DividendRate ?? 0);
|
|
if (string.IsNullOrWhiteSpace(dividendCurveName))
|
|
{
|
|
throw new Exception($"交易计算未设置分红率: {trade.id} {trade.TradeNumber}");
|
|
}
|
|
}
|
|
|
|
return new OptionMarketObjectName()
|
|
{
|
|
DiscountCurveName = discountCurveName,
|
|
DividendCurveName = dividendCurveName,
|
|
VolSurfaceName = volSurfaceName
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 为彩虹期权准备市场数据
|
|
/// </summary>
|
|
private static RainbowOptionMarketObjectName PrepareRainbowOptionMarketObjects(
|
|
IQdpMarketProxy marketProxy,
|
|
string valueDateStr,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
RainbowOptionParameter parameter)
|
|
{
|
|
if (!PrepareSpotPrices(marketProxy, valueDateStr, parameter.SpotPrices))
|
|
{
|
|
throw new Exception($"交易计算未设置标的价格: {trade.id} {trade.TradeNumber} {underlying.UnderlyingCode}");
|
|
}
|
|
|
|
foreach (var code in trade.trade_rainbow_option.UnderlyingCodes)
|
|
{
|
|
if (!parameter.SpotPrices.ContainsKey(code))
|
|
{
|
|
throw new Exception($"交易计算未设置标的价格: {trade.id} {trade.TradeNumber} {code}");
|
|
}
|
|
}
|
|
|
|
var volSurfaceName1 = PrepareVolSurface(marketProxy, valueDateStr, parameter.VolSurfaceName, parameter.Volatility);
|
|
if (string.IsNullOrWhiteSpace(volSurfaceName1))
|
|
{
|
|
throw new Exception($"交易计算未设置波动率: {trade.id} {trade.TradeNumber} {trade.trade_rainbow_option.UnderlyingCodes[0]}");
|
|
}
|
|
|
|
var volSurfaceName2 = PrepareVolSurface(marketProxy, valueDateStr, parameter.VolSurfaceName2, parameter.Volatility2);
|
|
if (string.IsNullOrWhiteSpace(volSurfaceName2))
|
|
{
|
|
throw new Exception($"交易计算未设置波动率: {trade.id} {trade.TradeNumber} {trade.trade_rainbow_option.UnderlyingCodes[1]}");
|
|
}
|
|
|
|
var discountCurveName = PrepareDiscountCurve(marketProxy, valueDateStr, parameter.DiscountCurveName, parameter.RiskFreeRate);
|
|
if (string.IsNullOrWhiteSpace(discountCurveName))
|
|
{
|
|
throw new Exception($"交易计算未设置无风险利率: {trade.id} {trade.TradeNumber}");
|
|
}
|
|
|
|
string dividendCurveName = null;
|
|
if (underlying.UnderlyingInstrumentType == "Stock")
|
|
{
|
|
dividendCurveName = PrepareDividendCurve(marketProxy, valueDateStr, parameter.DividendRate ?? 0);
|
|
if (string.IsNullOrWhiteSpace(dividendCurveName))
|
|
{
|
|
throw new Exception($"交易计算未设置分红率: {trade.id} {trade.TradeNumber}");
|
|
}
|
|
}
|
|
|
|
marketProxy.SaveCorrelationSurface(valueDateStr, trade.trade_rainbow_option.UnderlyingCodes, parameter.Correlation ?? 0);
|
|
|
|
return new RainbowOptionMarketObjectName()
|
|
{
|
|
DiscountCurveName = discountCurveName,
|
|
DividendCurveName = dividendCurveName,
|
|
VolSurfaceNames = new string[] { volSurfaceName1, volSurfaceName2 }
|
|
};
|
|
}
|
|
|
|
private static SpreadOptionMarketObjectName PrepareSpreadOptionMarketObjects(
|
|
IQdpMarketProxy marketProxy,
|
|
string valueDateStr,
|
|
trade trade,
|
|
underlying_manager underlying,
|
|
SpreadOptionParameter parameter)
|
|
{
|
|
if (!PrepareSpotPrices(marketProxy, valueDateStr, parameter.SpotPrices))
|
|
{
|
|
throw new Exception($"交易计算未设置标的价格: {trade.id} {trade.TradeNumber} {underlying.UnderlyingCode}");
|
|
}
|
|
|
|
var underlyingCodes = trade.trade_spread_option.UnderlyingAssetCodes();
|
|
if (parameter.VolSurfaceNames == null && (parameter.Volatilities == null || parameter.Volatilities.Length != underlyingCodes.Length)
|
|
|| parameter.Volatilities == null && (parameter.VolSurfaceNames == null || parameter.VolSurfaceNames.Length != underlyingCodes.Length))
|
|
{
|
|
throw new Exception($"交易计算未设置波动率: {trade.id} {trade.TradeNumber}");
|
|
}
|
|
|
|
var volSurfaceNames = new List<string>();
|
|
for (var i = 0; i < underlyingCodes.Length; ++i)
|
|
{
|
|
if (!parameter.SpotPrices.ContainsKey(underlyingCodes[i]))
|
|
{
|
|
throw new Exception($"交易计算未设置标的价格: {trade.id} {trade.TradeNumber} {underlyingCodes[i]}");
|
|
}
|
|
|
|
var volSurfaceName =
|
|
PrepareVolSurface(
|
|
marketProxy,
|
|
valueDateStr,
|
|
(parameter.VolSurfaceNames != null && parameter.VolSurfaceNames.Length > i) ? parameter.VolSurfaceNames[i] : null,
|
|
(parameter.Volatilities != null && parameter.Volatilities.Length > i) ? parameter.Volatilities[i] : null);
|
|
|
|
volSurfaceNames.Add(volSurfaceName);
|
|
}
|
|
|
|
var discountCurveName = PrepareDiscountCurve(marketProxy, valueDateStr, parameter.DiscountCurveName, parameter.RiskFreeRate);
|
|
if (string.IsNullOrWhiteSpace(discountCurveName))
|
|
{
|
|
throw new Exception($"交易计算未设置无风险利率: {trade.id} {trade.TradeNumber}");
|
|
}
|
|
|
|
marketProxy.SaveCorrelationSurface(valueDateStr, new string[] { underlyingCodes[0], underlyingCodes[1] }, parameter.Correlations[0]);
|
|
if (underlyingCodes.Length >= 3)
|
|
{
|
|
marketProxy.SaveCorrelationSurface(valueDateStr, new string[] { underlyingCodes[0], underlyingCodes[2] }, parameter.Correlations[1]);
|
|
marketProxy.SaveCorrelationSurface(valueDateStr, new string[] { underlyingCodes[1], underlyingCodes[2] }, parameter.Correlations[2]);
|
|
if (underlyingCodes.Length >= 4)
|
|
{
|
|
marketProxy.SaveCorrelationSurface(valueDateStr, new string[] { underlyingCodes[0], underlyingCodes[3] }, parameter.Correlations[3]);
|
|
marketProxy.SaveCorrelationSurface(valueDateStr, new string[] { underlyingCodes[2], underlyingCodes[3] }, parameter.Correlations[5]);
|
|
}
|
|
}
|
|
|
|
return new SpreadOptionMarketObjectName()
|
|
{
|
|
DiscountCurveName = discountCurveName,
|
|
VolSurfaceNames = volSurfaceNames.ToArray()
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 构造Qdp估值参数
|
|
/// </summary>
|
|
private static OptionValuationParameters CreateQdpOptionValuationParameters(
|
|
string underlyingInstrumentType,
|
|
OptionMarketObjectName marketObjectName,
|
|
string underlyingCode,
|
|
bool isForwardTrade = false)
|
|
{
|
|
return new OptionValuationParameters(
|
|
isForwardTrade ? MarketProxy.ConstantZeroCurve : marketObjectName.DiscountCurveName,
|
|
underlyingInstrumentType == "Stock" ? marketObjectName.DividendCurveName : MarketProxy.ConstantZeroCurve,
|
|
marketObjectName.VolSurfaceName,
|
|
underlyingCode);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 构造彩虹期权的Qdp估值参数
|
|
/// </summary>
|
|
private static OptionValuationParameters CreateQdpRainbowOptionValuationParameters(
|
|
string underlyingInstrumentType,
|
|
RainbowOptionMarketObjectName marketObjectName,
|
|
string[] underlyingCodes)
|
|
{
|
|
return new OptionValuationParameters(
|
|
marketObjectName.DiscountCurveName,
|
|
(underlyingInstrumentType == "Stock" ?
|
|
new string[] { marketObjectName.DividendCurveName, marketObjectName.DividendCurveName } :
|
|
new string[] { MarketProxy.ConstantZeroCurve, MarketProxy.ConstantZeroCurve }),
|
|
marketObjectName.VolSurfaceNames,
|
|
new string[] { underlyingCodes[0] + "_" + underlyingCodes[1] },
|
|
null);
|
|
}
|
|
|
|
private static OptionValuationParameters CreateQdpSpreadOptionValuationParameters(
|
|
string[] underlyingTickers, string discountCurveName, string[] volSurfaceNames)
|
|
{
|
|
var dividendCurveNames = new string[] { MarketProxy.ConstantZeroCurve, MarketProxy.ConstantZeroCurve };
|
|
var corrSurfNames = new string[] { underlyingTickers[0] + "_" + underlyingTickers[1] };
|
|
|
|
// construct correlation surface names
|
|
// see SpreadOptionTrade.GenerateMarketConditionFromPrebuilt for the order
|
|
if (underlyingTickers.Length == 3)
|
|
{
|
|
dividendCurveNames = new string[] { MarketProxy.ConstantZeroCurve, MarketProxy.ConstantZeroCurve, MarketProxy.ConstantZeroCurve };
|
|
corrSurfNames = new string[] {
|
|
underlyingTickers[0] + "_" + underlyingTickers[1],
|
|
underlyingTickers[0] + "_" + underlyingTickers[2],
|
|
underlyingTickers[1] + "_" + underlyingTickers[2]
|
|
};
|
|
}
|
|
else if (underlyingTickers.Length == 4)
|
|
{
|
|
dividendCurveNames = new string[] { MarketProxy.ConstantZeroCurve, MarketProxy.ConstantZeroCurve, MarketProxy.ConstantZeroCurve, MarketProxy.ConstantZeroCurve };
|
|
corrSurfNames = new string[] {
|
|
underlyingTickers[0] + "_" + underlyingTickers[1],
|
|
underlyingTickers[0] + "_" + underlyingTickers[2],
|
|
underlyingTickers[1] + "_" + underlyingTickers[2],
|
|
underlyingTickers[0] + "_" + underlyingTickers[3],
|
|
underlyingTickers[1] + "_" + underlyingTickers[3],
|
|
underlyingTickers[2] + "_" + underlyingTickers[3],
|
|
};
|
|
}
|
|
|
|
return new OptionValuationParameters(
|
|
discountCurveName,
|
|
dividendCurveNames,
|
|
volSurfaceNames,
|
|
corrSurfNames,
|
|
null);
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// 释放QdpMarket中的数据
|
|
/// </summary>
|
|
private static void CleanupMarketObjects(
|
|
BaseOptionParameter parameter,
|
|
IQdpMarketProxy marketProxy,
|
|
string valueDateStr,
|
|
OptionMarketObjectName marketObjectName)
|
|
{
|
|
if (parameter.Volatility.HasValue && !string.IsNullOrWhiteSpace(marketObjectName.VolSurfaceName))
|
|
{
|
|
marketProxy.RemoveVolSurface(valueDateStr, marketObjectName.VolSurfaceName);
|
|
}
|
|
if (parameter.RiskFreeRate.HasValue && !string.IsNullOrWhiteSpace(marketObjectName.DiscountCurveName) && marketObjectName.DiscountCurveName != MarketProxy.ConstantZeroCurve)
|
|
{
|
|
marketProxy.RemoveYieldCurve(marketObjectName.DiscountCurveName, valueDateStr);
|
|
}
|
|
if (!string.IsNullOrWhiteSpace(marketObjectName.DividendCurveName) && marketObjectName.DividendCurveName != MarketProxy.ConstantZeroCurve)
|
|
{
|
|
marketProxy.RemoveYieldCurve(marketObjectName.DividendCurveName, valueDateStr);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 为彩虹期权释放QdpMarket中的数据
|
|
/// </summary>
|
|
private static void CleanupRainbowOptionMarketObjects(
|
|
RainbowOptionParameter parameter,
|
|
IQdpMarketProxy marketProxy,
|
|
string valueDateStr,
|
|
RainbowOptionMarketObjectName marketObjectName)
|
|
{
|
|
if (parameter.Volatility.HasValue && !string.IsNullOrWhiteSpace(marketObjectName.VolSurfaceNames[0]))
|
|
{
|
|
marketProxy.RemoveVolSurface(valueDateStr, marketObjectName.VolSurfaceNames[0]);
|
|
}
|
|
if (parameter.Volatility2.HasValue && !string.IsNullOrWhiteSpace(marketObjectName.VolSurfaceNames[1]))
|
|
{
|
|
marketProxy.RemoveVolSurface(valueDateStr, marketObjectName.VolSurfaceNames[1]);
|
|
}
|
|
if (parameter.RiskFreeRate.HasValue && !string.IsNullOrWhiteSpace(marketObjectName.DiscountCurveName) && marketObjectName.DiscountCurveName != MarketProxy.ConstantZeroCurve)
|
|
{
|
|
marketProxy.RemoveYieldCurve(marketObjectName.DiscountCurveName, valueDateStr);
|
|
}
|
|
if (!string.IsNullOrWhiteSpace(marketObjectName.DividendCurveName) && marketObjectName.DividendCurveName != MarketProxy.ConstantZeroCurve)
|
|
{
|
|
marketProxy.RemoveYieldCurve(marketObjectName.DividendCurveName, valueDateStr);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 为价差期权释放QdpMarket中的数据
|
|
/// </summary>
|
|
private static void CleanupSpreadOptionMarketObjects(
|
|
SpreadOptionParameter parameter,
|
|
IQdpMarketProxy marketProxy,
|
|
string valueDateStr,
|
|
SpreadOptionMarketObjectName marketObjectName)
|
|
{
|
|
for (var i = 0; i < parameter.Volatilities.Length; ++i)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(marketObjectName.VolSurfaceNames[i]))
|
|
{
|
|
marketProxy.RemoveVolSurface(valueDateStr, marketObjectName.VolSurfaceNames[i]);
|
|
}
|
|
}
|
|
|
|
if (parameter.RiskFreeRate.HasValue && !string.IsNullOrWhiteSpace(marketObjectName.DiscountCurveName) && marketObjectName.DiscountCurveName != MarketProxy.ConstantZeroCurve)
|
|
{
|
|
marketProxy.RemoveYieldCurve(marketObjectName.DiscountCurveName, valueDateStr);
|
|
}
|
|
}
|
|
|
|
private static TradeValueResult ConvertValueResult(IPricingResult results, trade trade, underlying_manager underlying, double spotPrice)
|
|
{
|
|
if (results.Succeeded)
|
|
{
|
|
var ret = new TradeValueResult
|
|
{
|
|
UnderlyingCode = underlying.UnderlyingCode,
|
|
Pv = results.Pv,
|
|
Delta = results.Delta,
|
|
Gamma = results.Gamma,
|
|
Vega = results.Vega,
|
|
CalendarDayTheta = results.Theta,
|
|
TradingDayTheta = results.ThetaPnL,
|
|
Rho = results.Rho,
|
|
DeltaInLots = TradeLotsCalc.CalcDeltaInLots(results.Delta, null, underlying),
|
|
DeltaCash = results.DeltaCash,
|
|
//目前这个算法等价于 results.GammaCash/2*0.01 之所以没有这样算,是因为下面的算法是天风提供的;
|
|
GammaCash = PS.Config.Company == Configuration.CompanyEnum.天风 ? (0.5 * results.Gamma * Math.Pow(spotPrice * 0.01, 2)) : results.GammaCash,
|
|
VegaCash = results.VegaCash,
|
|
Vol = results.PricingVol,
|
|
//RoundedPv = OtcFormatHelper.FormatValue(results.Pv / trade.Notional, 2) * trade.Notional
|
|
};
|
|
if (trade.TradeType == "现金流交易" && trade.trade_cashflow?.DepositType == CashflowDepositTypeEnum.资金收益)
|
|
{
|
|
ret.Pv += trade.StockEqvNotional * trade.trade_cashflow.PrepayRatio * TradeCalcHelper.GetBuySellSign(trade.BuySell);
|
|
}
|
|
ret.RoundedPv = OtcFormatHelper.FormatValue(results.Pv / trade.Notional, 2) * trade.Notional;
|
|
ConvertTradeValueResultOfCompany(ret, trade.TradeType);
|
|
return ret;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 按照各公司的配置调整估值结果
|
|
/// </summary>
|
|
/// <param name="ret"></param>
|
|
public static void ConvertTradeValueResultOfCompany(TradeValueResult ret, string tradeType)
|
|
{
|
|
switch (PS.Config.Company)
|
|
{
|
|
case Configuration.CompanyEnum.国元固收:
|
|
ret.Delta /= 10000;
|
|
ret.Delta2 /= 10000;
|
|
ret.Delta3 /= 10000;
|
|
ret.Delta4 /= 10000;
|
|
ret.DeltaCash /= 10000;
|
|
ret.DeltaCash2 /= 10000;
|
|
ret.DeltaCash3 /= 10000;
|
|
ret.DeltaCash4 /= 10000;
|
|
ret.DeltaInLots /= 10000;
|
|
ret.DeltaMax /= 10000;
|
|
ret.DeltaT1 /= 10000;
|
|
ret.DDeltaDt /= 10000;
|
|
ret.DDeltaDVol /= 10000;
|
|
ret.SA_Delta /= 10000;
|
|
ret.Gamma /= 100000000;
|
|
ret.Gamma2 /= 100000000;
|
|
ret.Gamma3 /= 100000000;
|
|
ret.Gamma4 /= 100000000;
|
|
ret.GammaCash /= 100000000;
|
|
ret.GammaCash2 /= 100000000;
|
|
ret.GammaCash3 /= 100000000;
|
|
ret.GammaCash4 /= 100000000;
|
|
ret.CrossGamma /= 100000000;
|
|
break;
|
|
case Configuration.CompanyEnum.招证:
|
|
if (tradeType == "现金流交易")
|
|
{
|
|
ret.Rho = 0;
|
|
}
|
|
break;
|
|
case Configuration.CompanyEnum.默认:
|
|
case Configuration.CompanyEnum.国泰君安:
|
|
case Configuration.CompanyEnum.申万:
|
|
case Configuration.CompanyEnum.渤海:
|
|
case Configuration.CompanyEnum.安粮:
|
|
case Configuration.CompanyEnum.上期资本:
|
|
case Configuration.CompanyEnum.格林大华:
|
|
case Configuration.CompanyEnum.方顿:
|
|
case Configuration.CompanyEnum.东兴:
|
|
case Configuration.CompanyEnum.光大光子:
|
|
case Configuration.CompanyEnum.长江:
|
|
case Configuration.CompanyEnum.海通:
|
|
case Configuration.CompanyEnum.茂川资本:
|
|
case Configuration.CompanyEnum.宏源:
|
|
case Configuration.CompanyEnum.伴兴:
|
|
case Configuration.CompanyEnum.中财资本:
|
|
case Configuration.CompanyEnum.瑞达:
|
|
case Configuration.CompanyEnum.兴证:
|
|
case Configuration.CompanyEnum.弘业:
|
|
case Configuration.CompanyEnum.东方财富:
|
|
case Configuration.CompanyEnum.国海:
|
|
case Configuration.CompanyEnum.山西:
|
|
case Configuration.CompanyEnum.天风:
|
|
case Configuration.CompanyEnum.广期资本:
|
|
case Configuration.CompanyEnum.厦门象屿:
|
|
case Configuration.CompanyEnum.润和:
|
|
case Configuration.CompanyEnum.兴业商贸:
|
|
case Configuration.CompanyEnum.天示:
|
|
case Configuration.CompanyEnum.中金:
|
|
case Configuration.CompanyEnum.中粮:
|
|
case Configuration.CompanyEnum.浙期:
|
|
case Configuration.CompanyEnum.中基石化:
|
|
case Configuration.CompanyEnum.国投:
|
|
case Configuration.CompanyEnum.华安:
|
|
case Configuration.CompanyEnum.国信金阳:
|
|
case Configuration.CompanyEnum.国贸启润:
|
|
case Configuration.CompanyEnum.东吴:
|
|
case Configuration.CompanyEnum.广发商贸:
|
|
case Configuration.CompanyEnum.湘财:
|
|
case Configuration.CompanyEnum.华西:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
private static TradeValueResult ConvertRainbowOptionValueResult(IPricingResult results, trade trade)
|
|
{
|
|
if (results.Succeeded)
|
|
{
|
|
//var vol = 0.0;
|
|
var ret = new TradeValueResult
|
|
{
|
|
UnderlyingCode = trade.trade_rainbow_option.UnderlyingAssetCode,
|
|
Pv = results.Pv,
|
|
Delta = results.asset1Delta,
|
|
Gamma = results.asset1Gamma,
|
|
Vega = results.asset1Vega,
|
|
CalendarDayTheta = results.Theta,
|
|
TradingDayTheta = results.ThetaPnL,
|
|
Rho = results.Rho,
|
|
DeltaCash = results.DeltaCash,
|
|
GammaCash = results.GammaCash,
|
|
Vol = results.PricingVol,
|
|
|
|
UnderlyingCode2 = trade.trade_rainbow_option.UnderlyingAssetCode2,
|
|
Delta2 = results.asset2Delta,
|
|
Gamma2 = results.asset2Gamma,
|
|
DeltaCash2 = results.asset2DeltaCash,
|
|
GammaCash2 = results.asset2GammaCash,
|
|
Vega2 = results.asset2Vega,
|
|
CrossGamma = results.crossGamma,
|
|
CrossVogga = results.crossVomma,
|
|
CorrVega = results.correlationVega,
|
|
RoundedPv = OtcFormatHelper.FormatValue(results.Pv / trade.Notional, 2) * trade.Notional
|
|
};
|
|
ConvertTradeValueResultOfCompany(ret, trade.TradeType);
|
|
return ret;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static TradeValueResult ConvertSpreadOptionValueResult(IPricingResult results, trade trade)
|
|
{
|
|
if (results.Succeeded)
|
|
{
|
|
var valueResult = new TradeValueResult
|
|
{
|
|
Pv = results.Pv,
|
|
Delta = results.asset1Delta,
|
|
Gamma = results.asset1Gamma,
|
|
Vega = results.asset1Vega,
|
|
CalendarDayTheta = results.Theta,
|
|
TradingDayTheta = results.ThetaPnL,
|
|
Rho = results.Rho,
|
|
DeltaCash = results.DeltaCash,
|
|
GammaCash = results.GammaCash,
|
|
Vol = results.PricingVol,
|
|
|
|
Delta2 = results.asset2Delta,
|
|
Gamma2 = results.asset2Gamma,
|
|
DeltaCash2 = results.asset2DeltaCash,
|
|
GammaCash2 = results.asset2GammaCash,
|
|
Vega2 = results.asset2Vega,
|
|
|
|
Delta3 = results.asset3Delta,
|
|
Gamma3 = results.asset3Gamma,
|
|
DeltaCash3 = results.asset3DeltaCash,
|
|
GammaCash3 = results.asset3GammaCash,
|
|
Vega3 = results.asset3Vega,
|
|
|
|
Delta4 = results.asset4Delta,
|
|
Gamma4 = results.asset4Gamma,
|
|
DeltaCash4 = results.asset4DeltaCash,
|
|
GammaCash4 = results.asset4GammaCash,
|
|
Vega4 = results.asset4Vega,
|
|
|
|
CrossGamma = results.crossGamma,
|
|
CrossVogga = results.crossVomma,
|
|
CorrVega = results.correlationVega,
|
|
|
|
RoundedPv = OtcFormatHelper.FormatValue(results.Pv / trade.Notional, 2) * trade.Notional
|
|
};
|
|
|
|
var underlyingCodes = trade.trade_spread_option.UnderlyingAssetCodes();
|
|
|
|
valueResult.UnderlyingCode = underlyingCodes[0];
|
|
valueResult.UnderlyingCode2 = underlyingCodes[1];
|
|
if (underlyingCodes.Length > 2)
|
|
{
|
|
valueResult.UnderlyingCode3 = underlyingCodes[2];
|
|
}
|
|
if (underlyingCodes.Length > 3)
|
|
{
|
|
valueResult.UnderlyingCode4 = underlyingCodes[3];
|
|
}
|
|
|
|
ConvertTradeValueResultOfCompany(valueResult, trade.TradeType);
|
|
|
|
return valueResult;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static string CreateVolSurfaceFromVolValue(IQdpMarketProxy marketProxy, string valueDateStr, double vol)
|
|
{
|
|
var volPart = GetDefaultVol(vol);
|
|
volPart.QuotationDate = DateTime.Parse(valueDateStr);
|
|
return SaveVolSurfaceToMarketProxy(marketProxy, valueDateStr, volPart);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取默认波动率
|
|
/// </summary>
|
|
private static VolatilityDefault GetDefaultVol(double vol)
|
|
{
|
|
vol = vol < 0 ? ConsVolInfos.defVol : vol;
|
|
|
|
var result = new VolatilityDefault
|
|
{
|
|
InterpolationMethod = ConsVolInfos.defInterpolationMethod,
|
|
VolSurfaceMode = ConsVolInfos.defVolMode,
|
|
VolType = ConsVolInfos.defVolType,
|
|
QuotationDate = DateTime.MinValue,
|
|
UnderlyingId = null,
|
|
ContractCode = null,
|
|
OptId = 0,
|
|
OptName = ConsVolInfos.defOptName,
|
|
OptDate = DateTime.Now
|
|
};
|
|
result.Data = QdpVolHelper.GenerateFlatSingleVols(vol).ToJson();
|
|
return result;
|
|
}
|
|
|
|
private static string SaveVolSurfaceToMarketProxy(IQdpMarketProxy marketProxy, string valueDateStr, volatility volatility)
|
|
{
|
|
var volSurfaceName = Guid.NewGuid().ToString();
|
|
var volSurfaceWrap = new VolSurfaceBuilder
|
|
{
|
|
volSurfaceName = volSurfaceName,
|
|
volSurfaceType = "MoneynessVol",
|
|
interpolation = "BiLinear"
|
|
}.SetVectors(volatility.VolTable).Build(valueDateStr);
|
|
marketProxy.SaveVolSurface(volSurfaceWrap);
|
|
return volSurfaceName;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算一组相同标的的期权交易,使用同一个波动率曲面
|
|
/// </summary>
|
|
public static Dictionary<int, TradeValueResult> CalculateOptionsWithSharedVolSurface(
|
|
string userId,
|
|
IEnumerable<trade> trades,
|
|
underlying_manager underlying,
|
|
ParameterBase parameter,
|
|
volatility volatility,
|
|
DateTime exerciseDate)
|
|
{
|
|
if (PS.Config.Is润和)
|
|
{
|
|
parameter.OverrideTTM = TradeCalcHelper.CalculateTTMDays(parameter.ValueDate, exerciseDate, underlying?.UnderlyingTypeId ?? 0, false);
|
|
}
|
|
var valueDateStr = parameter.ValueDate.ToString("yyyy-MM-dd");
|
|
var marketProxy = QdpMarketManager.Instance.GetPrebuiltMarketProxy(userId);
|
|
marketProxy.CreateMarket(valueDateStr);
|
|
var volSurfaceName = SaveVolSurfaceToMarketProxy(marketProxy, valueDateStr, volatility);
|
|
var optionParameter = parameter as BaseOptionParameter;
|
|
optionParameter.VolSurfaceName = volSurfaceName;
|
|
|
|
var results = new Dictionary<int, TradeValueResult>();
|
|
foreach (var trade in trades)
|
|
{
|
|
var result = CalculateTradeValue(userId, trade, underlying, parameter);
|
|
results[trade.id] = result;
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
private static void AddCalculationLog(TradeBase trade, CalcScenarioEnum scenarioEnum, string tradeNumber)
|
|
{
|
|
try
|
|
{
|
|
var option = trade.Instrument as OptionBase;
|
|
|
|
// 添加计算日志
|
|
CalculationLog log = new CalculationLog()
|
|
{
|
|
CreateTime = DateTime.Now,
|
|
Scenario = scenarioEnum,
|
|
LogObject = trade,
|
|
TradeNumber = tradeNumber,
|
|
Notional = trade.Notional,
|
|
Exercise = option?.Exercise.ToString(),
|
|
OptionType = option?.OptionType.ToString(),
|
|
TradeDate = trade.TradeDate.ToString(),
|
|
MaturityDate = trade.MaturityDate?.ToString(),
|
|
InitialSpotPrice = option?.InitialSpotPrice.ToString(),
|
|
Strike = option?.Strike.ToString(),
|
|
};
|
|
|
|
ICalculationLogService calcLogService = new CalculationLogService();
|
|
calcLogService.AddLog(log, scenarioEnum);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine(ex.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
class OptionMarketObjectName
|
|
{
|
|
public string DiscountCurveName { get; set; }
|
|
public string DividendCurveName { get; set; }
|
|
public string VolSurfaceName { get; set; }
|
|
}
|
|
|
|
class RainbowOptionMarketObjectName : OptionMarketObjectName
|
|
{
|
|
public string[] VolSurfaceNames { get; set; }
|
|
}
|
|
|
|
class SpreadOptionMarketObjectName : OptionMarketObjectName
|
|
{
|
|
public string[] VolSurfaceNames { get; set; }
|
|
public string[] CorrelationSurfaceNames { get; set; }
|
|
}
|
|
}
|