1599 lines
70 KiB
C#
1599 lines
70 KiB
C#
using CxxCalcLib;
|
|
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.Base.Utilities;
|
|
using Qdp.Pricing.Ecosystem.Market;
|
|
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;
|
|
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;
|
|
using Qdp.Pricing.Library.Options.Products.PayoffEnhance.Engines.Analytical;
|
|
using Qdp.Pricing.Library.Options.Products.RangeAccrual;
|
|
using Qdp.Pricing.Library.Options.Products.Vanilla;
|
|
using YLErp.BLL.Calculation;
|
|
using YLErp.BLL.Calculation.Engine;
|
|
using YLErp.BLL.Calculation.V2;
|
|
using YLErp.Commons;
|
|
using YLErp.Enums;
|
|
using YLErp.Modules.ApiModule;
|
|
using YLErp.Modules.CalculationLogModule;
|
|
using YLErp.Modules.CalculationModule.Abstract;
|
|
using YLErp.QdpModule;
|
|
|
|
namespace YLErp.Modules.CalculationModule
|
|
{
|
|
/// <summary>
|
|
/// 交易风险计算(这个类中的MarketProxy应该是已经准备好波动率和价格的对象)
|
|
/// </summary>
|
|
public static class TradeRiskCalcUtil
|
|
{
|
|
/// <summary>
|
|
/// 获取Qdp数据
|
|
/// </summary>
|
|
public static TradeValueResult CalcTradeRisk(OtcTradeBase tradeObj, IOtcTradeValueCalcContext context,
|
|
out underlying_manager[] underlyings)
|
|
{
|
|
if (tradeObj == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(tradeObj));
|
|
}
|
|
|
|
if (context == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(context));
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(tradeObj.UnderlyingCode) && tradeObj.TradeType != "现金流交易")
|
|
{
|
|
throw new TradeCalcExpception(tradeObj.id, "UnderlyingCode为空");
|
|
}
|
|
|
|
if (context.MarketProxy == null)
|
|
{
|
|
throw new ArgumentNullException("context.Market");
|
|
}
|
|
|
|
return new TradeRiskValueCalc(context, tradeObj).GetTradeValue(out underlyings);
|
|
}
|
|
|
|
#region----CalcHelper----
|
|
|
|
class InnerCalcHelper : IDisposable
|
|
{
|
|
protected readonly MarketProxy _marketProxy;
|
|
|
|
protected string _dividendCurveName;
|
|
|
|
public InnerCalcHelper(MarketProxy marketProxy)
|
|
{
|
|
_marketProxy = marketProxy;
|
|
}
|
|
|
|
public virtual void Dispose()
|
|
{
|
|
if (!string.IsNullOrEmpty(_dividendCurveName))
|
|
{
|
|
_marketProxy.RemoveYieldCurve(_dividendCurveName);
|
|
_dividendCurveName = null;
|
|
}
|
|
}
|
|
|
|
protected void PrepareBase<T>(OptionCalcParam<T> calcParam) where T : OptionTradeParamBase
|
|
{
|
|
_dividendCurveName = null;
|
|
|
|
//分红率曲线
|
|
if (!calcParam.TradeParam.isForwardTrade)
|
|
{
|
|
var dividendRate = calcParam.TradeParam.dividendRate;
|
|
if (dividendRate > 0)
|
|
{
|
|
_dividendCurveName = Guid.NewGuid().ToString();
|
|
var dividendCurve = CalculatorHelper.CreateConstantRiskFreeCurve(_dividendCurveName, dividendRate);
|
|
_marketProxy.SetYieldCurve(_dividendCurveName, dividendCurve);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class SingleUnderlyingCalcHelper : InnerCalcHelper
|
|
{
|
|
string volSurfaceName;
|
|
|
|
double spotPrice;
|
|
|
|
public string underlyingTicker { get; private set; }
|
|
|
|
public SingleUnderlyingCalcHelper(MarketProxy marketProxy) : base(marketProxy)
|
|
{
|
|
|
|
}
|
|
|
|
public SingleUnderlyingCalcHelper Prepare<T>(OptionCalcParam<T> calcParam, bool noUnderlying = false) where T : OptionTradeParamBase
|
|
{
|
|
if (calcParam is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(calcParam));
|
|
}
|
|
|
|
if (!noUnderlying)
|
|
{
|
|
if (calcParam.spotPrices == null || !calcParam.spotPrices.Any())
|
|
{
|
|
throw new Exception("期权计算缺少标的现价");
|
|
}
|
|
|
|
if (calcParam.underlyingTickers == null || !calcParam.underlyingTickers.Any())
|
|
{
|
|
throw new Exception("期权计算缺少标的");
|
|
}
|
|
|
|
spotPrice = calcParam.spotPrices[0];
|
|
underlyingTicker = calcParam.underlyingTickers[0];
|
|
|
|
OptimizeSpotPrice(ref spotPrice, calcParam.TradeParam.GetAbsStrike());
|
|
|
|
//设置标的价格
|
|
_marketProxy.SetStockPrice(underlyingTicker, spotPrice);
|
|
}
|
|
|
|
if (calcParam.volSurfaceNames == null || !calcParam.volSurfaceNames.Any())
|
|
{
|
|
throw new Exception("期权计算缺少波动率");
|
|
}
|
|
|
|
volSurfaceName = calcParam.volSurfaceNames[0];
|
|
|
|
PrepareBase(calcParam);
|
|
|
|
return this;
|
|
}
|
|
|
|
public OptionValuationParameters GetValuationParameters()
|
|
{
|
|
return new OptionValuationParameters(_marketProxy.DiscountCurveName ?? MarketProxy.ConstantZeroCurve,
|
|
_dividendCurveName ?? MarketProxy.ConstantZeroCurve, volSurfaceName, underlyingTicker);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算T+1日Delta
|
|
/// </summary>
|
|
static double? CalcDeltaT1(TradeBase optionTrade, PrebuiltQdpMarket baseMarket, IEngine engine, OptionValuationParameters parameters)
|
|
{
|
|
var variety = DataCacheProvider.GetVariety(parameters.UnderlyingId);
|
|
var referenceDateAddOne = baseMarket.ReferenceDate.AddDays(1);
|
|
var nextDay = QdpCalendarHelper.GetNonHoliday(referenceDateAddOne);
|
|
|
|
if (nextDay > optionTrade.MaturityDate.DateTime)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var market = new PrebuiltQdpMarket(nextDay.ToString("yyyy-MM-dd"), nextDay)
|
|
{
|
|
YieldCurves = baseMarket.YieldCurves,
|
|
StockPrices = baseMarket.StockPrices,
|
|
VolSurfaces = baseMarket.VolSurfaces,
|
|
CorrSurfaces = baseMarket.CorrSurfaces
|
|
};
|
|
|
|
var isAsianOption = false;
|
|
if (optionTrade.Instrument is AsianOption asianOption && asianOption.Fixings != null)
|
|
{
|
|
isAsianOption = true;
|
|
if (asianOption.Fixings.TryGetValue(baseMarket.ReferenceDate, out var dd))
|
|
{
|
|
asianOption.Fixings[nextDay] = dd;
|
|
}
|
|
else if (baseMarket.StockPrices != null && asianOption.UnderlyingTickers != null
|
|
&& asianOption.UnderlyingTickers.Length > 0
|
|
&& baseMarket.StockPrices.TryGetValue(asianOption.UnderlyingTickers[0], out dd))
|
|
{
|
|
asianOption.Fixings[baseMarket.ReferenceDate] = dd;
|
|
if (!PS.Config.Is润和)
|
|
{
|
|
asianOption.Fixings[nextDay] = dd;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (optionTrade.Instrument is Qdp.Pricing.Library.Common.Base.OptionBase optionBase)
|
|
{
|
|
if (PS.Config.Is润和)
|
|
{
|
|
var optionDayCount = CalculatorHelper.GetTradeDayCount();
|
|
var timeToMaturityDays = double.NaN;
|
|
if (isAsianOption)
|
|
{
|
|
timeToMaturityDays = TradeCalcHelper.CalculateTTMDays(nextDay, optionTrade.MaturityDate.DateTime, 0, false, serverDateTime: nextDay.AddHours(15));
|
|
}
|
|
else
|
|
{
|
|
//当天有夜盘,并且当天还没有进入夜盘(以晚上9点为判断标准),DeltaT+1按照当天晚上9点来计算,否则按照下一个工作日早晨9点来计算
|
|
//如果当天时节假日,则按照下一个工作日早晨9点来计算
|
|
//ToDo: 关于长假,虽然品种有夜盘,但是放假前一天晚上是没有夜盘,DeltaT+1需要用下一个工作日的早晨9点来计算的逻辑还未实现
|
|
if (variety.HasNightMarket && DateTime.Now.Hour < 21 && !QdpCalendarHelper.IsHoliday(baseMarket.ReferenceDate))
|
|
{
|
|
timeToMaturityDays = TradeCalcHelper.CalculateTTMDays(baseMarket.ReferenceDate.DateTime, optionTrade.MaturityDate.DateTime, 0, false, serverDateTime: baseMarket.ReferenceDate.DateTime.AddHours(21));
|
|
}
|
|
else
|
|
{
|
|
timeToMaturityDays = TradeCalcHelper.CalculateTTMDays(nextDay, optionTrade.MaturityDate.DateTime, 0, false, serverDateTime: nextDay.AddHours(9));
|
|
}
|
|
}
|
|
|
|
optionBase.PricingToverride = QdpCalendarHelper.CalculateTFromDays(timeToMaturityDays, optionDayCount, optionTrade.StartDate);
|
|
}
|
|
else
|
|
{
|
|
optionBase.PricingToverride = double.NaN;
|
|
}
|
|
|
|
optionBase.CommodityFuturesPreciseTimeMode = false;
|
|
}
|
|
|
|
var result = optionTrade.CalculateRisks(market, PricingRequest.Delta, engine, parameters);
|
|
|
|
if (result.Succeeded)
|
|
{
|
|
if (PS.Config.Company == Configuration.CompanyEnum.国元固收)
|
|
{
|
|
return result.Delta / 10000;
|
|
}
|
|
else
|
|
{
|
|
return result.Delta;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 现价和执行价非常接近时QDP计算雪球凤凰时会非常慢而且吃内存,故而需要优化
|
|
/// !!!注意不要传入相对行权价
|
|
/// </summary>
|
|
static void OptimizeSpotPrice(ref double spotPrice, double strike)
|
|
{
|
|
if (spotPrice == 0)
|
|
{
|
|
if (Math.Abs(strike) < 1e-5)
|
|
{
|
|
spotPrice = strike;
|
|
}
|
|
}
|
|
else if (Math.Abs(1 - (strike / spotPrice)) < 1e-5)
|
|
{
|
|
spotPrice = strike;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region----香草期权----
|
|
|
|
/// <summary>
|
|
/// 计算香草期权的估值与风险
|
|
/// </summary>
|
|
public static TradeValueResult GetVanillaOptionValue(MarketProxy marketProxy, OptionCalcParam<VanillaOptionTradeParam> calcParam)
|
|
{
|
|
if (marketProxy is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(marketProxy));
|
|
}
|
|
|
|
if (calcParam is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(calcParam));
|
|
}
|
|
|
|
using var helper = new SingleUnderlyingCalcHelper(marketProxy).Prepare(calcParam);
|
|
var optionTrade = QdpTradeBuilder.GetVanillaOptionTrade(calcParam.TradeParam);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcParam.calcScenario, calcParam?.TradeParam?.tradeNumber);
|
|
|
|
var parameters = helper.GetValuationParameters();
|
|
|
|
var option = optionTrade.Instrument as VanillaOption;
|
|
var factory = OptionEngineFactory.GetEngineFactory("VanillaOption");
|
|
var engine = factory.GetEngine(calcParam.engineName, option.Exercise);
|
|
|
|
marketProxy.Trace?.WriteData(optionTrade, "QDP 香草");
|
|
|
|
var result = optionTrade.CalculateRisks(marketProxy.QdpMarket, calcParam.pricingRequest, engine, parameters);
|
|
|
|
if (result.Succeeded)
|
|
{
|
|
var tr = new TradeValueResult(result)
|
|
{
|
|
UnderlyingCode = helper.underlyingTicker,
|
|
RoundedPv = OtcFormatHelper.FormatValue(result.Pv / optionTrade.Notional, 2) * optionTrade.Notional
|
|
};
|
|
|
|
if (calcParam.CalcDeltaT1)
|
|
{
|
|
tr.DeltaT1 = CalcDeltaT1(optionTrade, marketProxy.QdpMarket, engine, parameters);
|
|
}
|
|
if (PS.Config.Company == Configuration.CompanyEnum.天风)//?不记得这个算法调整为啥有些地方加了,有些地方没加了,所以不要把这个逻辑移动到 ConvertTradeValueResultOfCompany 里
|
|
{
|
|
tr.GammaCash = 0.5 * result.Gamma * Math.Pow(calcParam.spotPrices[0] * 0.01, 2);
|
|
}
|
|
|
|
ValueCalculator.ConvertTradeValueResultOfCompany(tr, calcParam.TradeParam.tradeType);
|
|
|
|
return tr;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region----彩虹期权----
|
|
|
|
/// <summary>
|
|
/// 彩虹期权
|
|
/// </summary>
|
|
public static TradeValueResult GetRainbowOptionValue(MarketProxy marketProxy, OptionCalcParam<RainbowOptionTradeParam> calcParam)
|
|
{
|
|
if (marketProxy is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(marketProxy));
|
|
}
|
|
|
|
if (calcParam is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(calcParam));
|
|
}
|
|
|
|
var tp = calcParam.TradeParam;
|
|
|
|
//这里需要优化
|
|
if (calcParam.volSurfaceNames == null || calcParam.volSurfaceNames.Length != 2)
|
|
{
|
|
throw new Exception("缺少波动率");
|
|
}
|
|
|
|
var optionTrade = QdpTradeBuilder.GetRainbowOptionTrade(tp);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcParam.calcScenario, calcParam?.TradeParam?.tradeNumber);
|
|
|
|
//设置标的价格
|
|
var count = Math.Min(calcParam.underlyingTickers.Length, calcParam.spotPrices.Length);
|
|
for (var i = 0; i < count; ++i)
|
|
{
|
|
marketProxy.SetStockPrice(calcParam.underlyingTickers[i], calcParam.spotPrices[i]);
|
|
}
|
|
|
|
//设置correlation
|
|
marketProxy.SetCorrelation(calcParam.underlyingTickers, tp.correlation);
|
|
|
|
string dividendCurveName = null;
|
|
OptionValuationParameters parameters;
|
|
if (ConsGlobal.InstrumentType.CalcTypeIsStock(tp.underlyingInstrumentType))
|
|
{
|
|
//设置DividendCurve
|
|
dividendCurveName = Guid.NewGuid().ToString();
|
|
var dividendCurve = CalculatorHelper.CreateConstantRiskFreeCurve(dividendCurveName, tp.dividendRate);
|
|
marketProxy.SetYieldCurve(dividendCurveName, dividendCurve);
|
|
parameters = new OptionValuationParameters(
|
|
marketProxy.DiscountCurveName,
|
|
new string[] { dividendCurveName, dividendCurveName }, calcParam.volSurfaceNames,
|
|
new string[] { calcParam.underlyingTickers[0] + "_" + calcParam.underlyingTickers[1] }, null);
|
|
}
|
|
else
|
|
{
|
|
parameters = new OptionValuationParameters(
|
|
marketProxy.DiscountCurveName,
|
|
new string[] { MarketProxy.ConstantZeroCurve, MarketProxy.ConstantZeroCurve },
|
|
calcParam.volSurfaceNames,
|
|
new string[] { calcParam.underlyingTickers[0] + "_" + calcParam.underlyingTickers[1] }, null);
|
|
}
|
|
|
|
marketProxy.Trace?.WriteData(optionTrade, "QDP 彩虹");
|
|
|
|
var riskResult = optionTrade.CalculateRisks(marketProxy.QdpMarket, calcParam.pricingRequest, null, parameters);
|
|
|
|
if (!string.IsNullOrEmpty(dividendCurveName))
|
|
{
|
|
marketProxy.RemoveYieldCurve(dividendCurveName);
|
|
}
|
|
|
|
if (riskResult.Succeeded)
|
|
{
|
|
var ret = new TradeValueResult
|
|
{
|
|
UnderlyingCode = calcParam.underlyingTickers[0],
|
|
Pv = riskResult.Pv,
|
|
Delta = riskResult.asset1Delta,
|
|
Gamma = riskResult.asset1Gamma,
|
|
Vega = riskResult.asset1Vega,
|
|
CalendarDayTheta = riskResult.Theta,
|
|
TradingDayTheta = riskResult.ThetaPnL,
|
|
Rho = riskResult.Rho,
|
|
DeltaCash = riskResult.DeltaCash,
|
|
GammaCash = riskResult.GammaCash,
|
|
Vol = riskResult.PricingVol,
|
|
|
|
UnderlyingCode2 = calcParam.underlyingTickers[1],
|
|
Delta2 = riskResult.asset2Delta,
|
|
Gamma2 = riskResult.asset2Gamma,
|
|
DeltaCash2 = riskResult.asset2DeltaCash,
|
|
GammaCash2 = riskResult.asset2GammaCash,
|
|
Vega2 = riskResult.asset2Vega,
|
|
CrossGamma = riskResult.crossGamma,
|
|
CrossVogga = riskResult.crossVomma,
|
|
CorrVega = riskResult.correlationVega,
|
|
RoundedPv = OtcFormatHelper.FormatValue(riskResult.Pv / optionTrade.Notional, 2) * optionTrade.Notional,
|
|
TimeValue = riskResult.TimeValue
|
|
};
|
|
ValueCalculator.ConvertTradeValueResultOfCompany(ret, calcParam?.TradeParam?.tradeType);
|
|
return ret;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region----亚式期权----
|
|
|
|
/// <summary>
|
|
/// 亚式期权
|
|
/// </summary>
|
|
public static TradeValueResult GetAsianOptionValue(MarketProxy marketProxy, OptionCalcParam<AsianOptionTradeParam> calcParam)
|
|
{
|
|
using var helper = new SingleUnderlyingCalcHelper(marketProxy).Prepare(calcParam);
|
|
var optionTrade = QdpTradeBuilder.GetAsianOptionTrade(calcParam.TradeParam);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcParam.calcScenario, calcParam?.TradeParam?.tradeNumber);
|
|
var parameters = helper.GetValuationParameters();
|
|
|
|
var option = optionTrade.Instrument as AsianOption;
|
|
var factory = OptionEngineFactory.GetEngineFactory("AsianOption");
|
|
var engine = factory.GetEngine(calcParam.engineName, option.Exercise, calcParam.TradeParam.payoffType, option, calcParam.TradeParam.strikeStyle);
|
|
var pricingRequest = calcParam.pricingRequest;
|
|
if (option.AsianType == AsianType.EnhancedArithmeticAverage)
|
|
{
|
|
pricingRequest = QdpPricingRequest.UnionBaiscGreeks(calcParam.pricingRequest);
|
|
|
|
if ((calcParam.pricingRequest & PricingRequest.TimeValue) == PricingRequest.TimeValue)
|
|
{
|
|
pricingRequest |= PricingRequest.TimeValue;
|
|
}
|
|
}
|
|
|
|
marketProxy.Trace?.WriteData(optionTrade, "QDP 亚式");
|
|
|
|
var result = optionTrade.CalculateRisks(marketProxy.QdpMarket, pricingRequest, engine, parameters);
|
|
|
|
if (result.Succeeded)
|
|
{
|
|
var tr = new TradeValueResult(result)
|
|
{
|
|
UnderlyingCode = helper.underlyingTicker,
|
|
RoundedPv = OtcFormatHelper.FormatValue(result.Pv / optionTrade.Notional, 2) * optionTrade.Notional,
|
|
};
|
|
|
|
if (calcParam.CalcDeltaT1)
|
|
{
|
|
tr.DeltaT1 = CalcDeltaT1(optionTrade, marketProxy.QdpMarket, engine, parameters);
|
|
}
|
|
if (PS.Config.Company == Configuration.CompanyEnum.天风)
|
|
{
|
|
tr.GammaCash = 0.5 * result.Gamma * Math.Pow(calcParam.spotPrices[0] * 0.01, 2);
|
|
}
|
|
ValueCalculator.ConvertTradeValueResultOfCompany(tr, calcParam?.TradeParam?.tradeType);
|
|
|
|
return tr;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 亚式合成价差期权
|
|
/// </summary>
|
|
public static TradeValueResult GetAsianSSpreadOptionValue(MarketProxy marketProxy, OptionCalcParam<AsianOptionTradeParam> calcParam)
|
|
{
|
|
using var helper = new SingleUnderlyingCalcHelper(marketProxy).Prepare(calcParam);
|
|
var optionTrade = QdpTradeBuilder.GetAsianSSpreadOptionTrade(calcParam.TradeParam);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcParam.calcScenario, calcParam?.TradeParam?.tradeNumber);
|
|
var parameters = helper.GetValuationParameters();
|
|
var engine = new AnalyticalAsianSyntheticNormalSpreadOptionEngine();
|
|
|
|
marketProxy.Trace?.WriteData(optionTrade, "QDP 亚式合成价差");
|
|
|
|
var result = optionTrade.CalculateRisks(marketProxy.QdpMarket, calcParam.pricingRequest, engine, parameters);
|
|
|
|
if (result.Succeeded)
|
|
{
|
|
var tr = new TradeValueResult(result)
|
|
{
|
|
UnderlyingCode = helper.underlyingTicker,
|
|
RoundedPv = OtcFormatHelper.FormatValue(result.Pv / optionTrade.Notional, 2) * optionTrade.Notional
|
|
};
|
|
|
|
if (calcParam.CalcDeltaT1)
|
|
{
|
|
tr.DeltaT1 = CalcDeltaT1(optionTrade, marketProxy.QdpMarket, engine, parameters);
|
|
}
|
|
if (PS.Config.Company == Configuration.CompanyEnum.天风)
|
|
{
|
|
tr.GammaCash = 0.5 * result.Gamma * Math.Pow(calcParam.spotPrices[0] * 0.01, 2);
|
|
}
|
|
ValueCalculator.ConvertTradeValueResultOfCompany(tr, calcParam?.TradeParam?.tradeType);
|
|
|
|
return tr;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region----障碍期权----
|
|
|
|
/// <summary>
|
|
/// 障碍期权
|
|
/// </summary>
|
|
public static TradeValueResult GetBarrierOptionValue(MarketProxy marketProxy, OptionCalcParam<BarrierOptionTradeParam> calcParam)
|
|
{
|
|
using var helper = new SingleUnderlyingCalcHelper(marketProxy).Prepare(calcParam);
|
|
var tp = calcParam.TradeParam;
|
|
|
|
//如果用户自定义的观察日就是每天观察,则忽略自定义观察日
|
|
var observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(tp.startDate, tp.exerciseDate)
|
|
.Union(new[] { tp.exerciseDate }).ToArray();
|
|
var barrierObservationDates = QdpHelper.ParseObservationDate(tp.observationDateStr);
|
|
if (barrierObservationDates != null &&
|
|
(barrierObservationDates.Count(d => d >= observationDates.First() && d <= observationDates.Last()) == observationDates.Length))
|
|
{
|
|
tp.observationDateStr = null;
|
|
}
|
|
|
|
var pricingRequest = calcParam.pricingRequest;
|
|
if (!string.IsNullOrWhiteSpace(calcParam.TradeParam.observationDateStr))
|
|
{
|
|
pricingRequest = QdpPricingRequest.UnionBaiscGreeks(calcParam.pricingRequest);
|
|
}
|
|
|
|
var optionTrade = QdpTradeBuilder.GetBarrierOptionTrade(tp);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcParam.calcScenario, calcParam?.TradeParam?.tradeNumber);
|
|
var parameters = helper.GetValuationParameters();
|
|
var option = optionTrade.Instrument as BarrierOption;
|
|
var factory = OptionEngineFactory.GetEngineFactory("BarrierOption");
|
|
var engine = factory.GetEngine(calcParam.engineName, option.Exercise, option,
|
|
!string.IsNullOrWhiteSpace(tp.observationDateStr), calcParam.quadratureFastMode);
|
|
|
|
marketProxy.Trace?.WriteData(optionTrade, "QDP 障碍");
|
|
|
|
var result = optionTrade.CalculateRisks(marketProxy.QdpMarket, pricingRequest, engine, parameters);
|
|
|
|
if (result.Succeeded)
|
|
{
|
|
var tr = new TradeValueResult(result)
|
|
{
|
|
UnderlyingCode = helper.underlyingTicker,
|
|
RoundedPv = OtcFormatHelper.FormatValue(result.Pv / optionTrade.Notional, 2) * optionTrade.Notional,
|
|
};
|
|
|
|
if (calcParam.CalcDeltaT1)
|
|
{
|
|
tr.DeltaT1 = CalcDeltaT1(optionTrade, marketProxy.QdpMarket, engine, parameters);
|
|
}
|
|
if (PS.Config.Company == Configuration.CompanyEnum.天风)
|
|
{
|
|
tr.GammaCash = 0.5 * result.Gamma * Math.Pow(calcParam.spotPrices[0] * 0.01, 2);
|
|
}
|
|
|
|
ValueCalculator.ConvertTradeValueResultOfCompany(tr, calcParam?.TradeParam?.tradeType);
|
|
|
|
return tr;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region----二元期权----
|
|
|
|
private static BinaryOptionReplicationStrategy ConvertReplicationStrategy(string replicationStrategy)
|
|
{
|
|
switch (replicationStrategy)
|
|
{
|
|
case "Down":
|
|
return BinaryOptionReplicationStrategy.Down;
|
|
case "Middle":
|
|
return BinaryOptionReplicationStrategy.Middle;
|
|
default:
|
|
return BinaryOptionReplicationStrategy.None;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 二元期权
|
|
/// </summary>
|
|
public static TradeValueResult GetBinaryOptionValue(MarketProxy marketProxy, OptionCalcParam<BinaryOptionTradeParam> calcParam)
|
|
{
|
|
if (marketProxy is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(marketProxy));
|
|
}
|
|
|
|
if (calcParam is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(calcParam));
|
|
}
|
|
|
|
using var helper = new SingleUnderlyingCalcHelper(marketProxy).Prepare(calcParam);
|
|
var tp = calcParam.TradeParam;
|
|
|
|
var optionTrade = QdpTradeBuilder.GetBinaryOptionTrade(tp);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcParam.calcScenario, calcParam?.TradeParam?.tradeNumber);
|
|
var replicationStrategy = ConvertReplicationStrategy(tp.binaryOptionReplicationStrategy);
|
|
var parameters = helper.GetValuationParameters();
|
|
|
|
var option = optionTrade.Instrument as BinaryOption;
|
|
var factory = OptionEngineFactory.GetEngineFactory("BinaryOption");
|
|
var engine = factory.GetEngine(calcParam.engineName, option.Exercise,
|
|
(tp.replicationShiftSize <= 0) ? option.Strike * 0.01 : tp.replicationShiftSize, replicationStrategy,
|
|
!string.IsNullOrWhiteSpace(tp.observationDateStr), option.RebateAnnualizedAtKO);
|
|
|
|
marketProxy.Trace?.WriteData(optionTrade, "QDP 二元");
|
|
|
|
var result = optionTrade.CalculateRisks(marketProxy.QdpMarket, calcParam.pricingRequest, engine, parameters);
|
|
|
|
if (result.Succeeded)
|
|
{
|
|
var tr = new TradeValueResult(result)
|
|
{
|
|
UnderlyingCode = helper.underlyingTicker,
|
|
RoundedPv = OtcFormatHelper.FormatValue(result.Pv / optionTrade.Notional, 2) * optionTrade.Notional,
|
|
};
|
|
|
|
if (calcParam.CalcDeltaT1)
|
|
{
|
|
tr.DeltaT1 = CalcDeltaT1(optionTrade, marketProxy.QdpMarket, engine, parameters);
|
|
}
|
|
if (PS.Config.Company == Configuration.CompanyEnum.天风)
|
|
{
|
|
tr.GammaCash = 0.5 * result.Gamma * Math.Pow(calcParam.spotPrices[0] * 0.01, 2);
|
|
}
|
|
|
|
ValueCalculator.ConvertTradeValueResultOfCompany(tr, calcParam?.TradeParam?.tradeType);
|
|
|
|
return tr;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region----价差期权----
|
|
|
|
/// <summary>
|
|
/// 价差期权
|
|
/// </summary>
|
|
public static TradeValueResult GetSpreadOptionValue(MarketProxy marketProxy, OptionCalcParam<SpreadOptionTradeParam> calcParam)
|
|
{
|
|
var tp = calcParam.TradeParam;
|
|
|
|
if (calcParam.volSurfaceNames == null)
|
|
{
|
|
tp.volSurfaceNames = calcParam.underlyingTickers.Select(x => x.GenerateVolSurfaceKey()).ToArray();
|
|
}
|
|
|
|
var optionTrade = QdpTradeBuilder.GetSpreadOptionTrade(tp);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcParam.calcScenario, calcParam?.TradeParam?.tradeNumber);
|
|
|
|
//设置标的价格
|
|
var count = Math.Min(calcParam.underlyingTickers.Length, calcParam.spotPrices.Length);
|
|
for (var i = 0; i < count; ++i)
|
|
{
|
|
marketProxy.SetStockPrice(calcParam.underlyingTickers[i], calcParam.spotPrices[i]);
|
|
}
|
|
|
|
//设置correlation
|
|
marketProxy.SetCorrelation(new string[] { calcParam.underlyingTickers[0], calcParam.underlyingTickers[1] }, tp.correlations[0]);
|
|
if (calcParam.underlyingTickers.Length >= 3)
|
|
{
|
|
marketProxy.SetCorrelation(new string[] { calcParam.underlyingTickers[0], calcParam.underlyingTickers[2] }, tp.correlations[1]);
|
|
marketProxy.SetCorrelation(new string[] { calcParam.underlyingTickers[1], calcParam.underlyingTickers[2] }, tp.correlations[2]);
|
|
if (calcParam.underlyingTickers.Length >= 4)
|
|
{
|
|
marketProxy.SetCorrelation(new string[] { calcParam.underlyingTickers[0], calcParam.underlyingTickers[3] }, tp.correlations[3]);
|
|
marketProxy.SetCorrelation(new string[] { calcParam.underlyingTickers[1], calcParam.underlyingTickers[3] }, tp.correlations[4]);
|
|
marketProxy.SetCorrelation(new string[] { calcParam.underlyingTickers[2], calcParam.underlyingTickers[3] }, tp.correlations[5]);
|
|
}
|
|
}
|
|
|
|
var parameters = QdpTradeBuilder.GetParametersForSpreadOption(calcParam.underlyingTickers, marketProxy.DiscountCurveName, calcParam.volSurfaceNames);
|
|
|
|
var factory = OptionEngineFactory.GetEngineFactory("SpreadOption");
|
|
var engine = factory.GetEngine(calcParam.engineName);
|
|
|
|
marketProxy.Trace?.WriteData(optionTrade, "QDP 价差");
|
|
|
|
var riskResult = optionTrade.CalculateRisks(marketProxy.QdpMarket, calcParam.pricingRequest, engine, parameters);
|
|
|
|
if (riskResult.Succeeded)
|
|
{
|
|
var valueResult = new TradeValueResult
|
|
{
|
|
Pv = riskResult.Pv,
|
|
Delta = riskResult.asset1Delta,
|
|
Gamma = riskResult.asset1Gamma,
|
|
Vega = riskResult.asset1Vega,
|
|
CalendarDayTheta = riskResult.Theta,
|
|
TradingDayTheta = riskResult.ThetaPnL,
|
|
Rho = riskResult.Rho,
|
|
DeltaCash = riskResult.DeltaCash,
|
|
GammaCash = riskResult.GammaCash,
|
|
Vol = riskResult.PricingVol,
|
|
|
|
Delta2 = riskResult.asset2Delta,
|
|
Gamma2 = riskResult.asset2Gamma,
|
|
DeltaCash2 = riskResult.asset2DeltaCash,
|
|
GammaCash2 = riskResult.asset2GammaCash,
|
|
Vega2 = riskResult.asset2Vega,
|
|
|
|
Delta3 = riskResult.asset3Delta,
|
|
Gamma3 = riskResult.asset3Gamma,
|
|
DeltaCash3 = riskResult.asset3DeltaCash,
|
|
GammaCash3 = riskResult.asset3GammaCash,
|
|
Vega3 = riskResult.asset3Vega,
|
|
|
|
Delta4 = riskResult.asset4Delta,
|
|
Gamma4 = riskResult.asset4Gamma,
|
|
DeltaCash4 = riskResult.asset4DeltaCash,
|
|
GammaCash4 = riskResult.asset4GammaCash,
|
|
Vega4 = riskResult.asset4Vega,
|
|
|
|
CrossGamma = riskResult.crossGamma,
|
|
CrossVogga = riskResult.crossVomma,
|
|
CorrVega = riskResult.correlationVega,
|
|
|
|
RoundedPv = OtcFormatHelper.FormatValue(riskResult.Pv / optionTrade.Notional, 2) * optionTrade.Notional,
|
|
TimeValue = riskResult.TimeValue,
|
|
UnderlyingCode = calcParam.underlyingTickers[0],
|
|
UnderlyingCode2 = calcParam.underlyingTickers[1]
|
|
};
|
|
if (calcParam.underlyingTickers.Length > 2)
|
|
{
|
|
valueResult.UnderlyingCode3 = calcParam.underlyingTickers[2];
|
|
}
|
|
if (calcParam.underlyingTickers.Length > 3)
|
|
{
|
|
valueResult.UnderlyingCode4 = calcParam.underlyingTickers[3];
|
|
}
|
|
|
|
ValueCalculator.ConvertTradeValueResultOfCompany(valueResult, calcParam?.TradeParam?.tradeType);
|
|
return valueResult;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region----合成价差期权----
|
|
|
|
/// <summary>
|
|
/// 合成价差期权
|
|
/// </summary>
|
|
public static TradeValueResult GetSSpreadOptionValue(MarketProxy marketProxy, OptionCalcParam<SSpreadOptionTradeParam> calcParam)
|
|
{
|
|
using var helper = new SingleUnderlyingCalcHelper(marketProxy).Prepare(calcParam);
|
|
var optionTrade = QdpTradeBuilder.GetSSpreadOptionTrade(calcParam.TradeParam);
|
|
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcParam.calcScenario, calcParam?.TradeParam?.tradeNumber);
|
|
|
|
var parameters = helper.GetValuationParameters();
|
|
var option = optionTrade.Instrument as VanillaOption;
|
|
var factory = OptionEngineFactory.GetEngineFactory("SynthticNormalSpreadOption");
|
|
var engine = factory.GetEngine(calcParam.engineName, option.Exercise);
|
|
|
|
marketProxy.Trace?.WriteData(optionTrade, "QDP 合成价差");
|
|
|
|
var result = optionTrade.CalculateRisks(marketProxy.QdpMarket, calcParam.pricingRequest, engine, parameters);
|
|
|
|
if (result.Succeeded)
|
|
{
|
|
var tr = new TradeValueResult(result)
|
|
{
|
|
UnderlyingCode = helper.underlyingTicker,
|
|
RoundedPv = OtcFormatHelper.FormatValue(result.Pv / optionTrade.Notional, 2) * optionTrade.Notional,
|
|
};
|
|
|
|
if (calcParam.CalcDeltaT1)
|
|
{
|
|
tr.DeltaT1 = CalcDeltaT1(optionTrade, marketProxy.QdpMarket, engine, parameters);
|
|
}
|
|
if (PS.Config.Company == Configuration.CompanyEnum.天风)
|
|
{
|
|
tr.GammaCash = 0.5 * result.Gamma * Math.Pow(calcParam.spotPrices[0] * 0.01, 2);
|
|
}
|
|
ValueCalculator.ConvertTradeValueResultOfCompany(tr, calcParam?.TradeParam?.tradeType);
|
|
|
|
return tr;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 合成价差期权
|
|
/// </summary>
|
|
public static double[] CalcSSpreadCrossGammas(MarketProxy marketProxy, OptionCalcParam<SSpreadOptionTradeParam> calcParam, double[] coefficients)
|
|
{
|
|
using var helper = new SingleUnderlyingCalcHelper(marketProxy).Prepare(calcParam);
|
|
var optionTrade = QdpTradeBuilder.GetSSpreadOptionTrade(calcParam.TradeParam);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcParam.calcScenario, calcParam?.TradeParam?.tradeNumber);
|
|
var parameters = helper.GetValuationParameters();
|
|
marketProxy.Trace?.WriteData(optionTrade, "QDP 合成价差 CrossGammas");
|
|
var result = optionTrade.CalcCrossGammas(marketProxy.QdpMarket, parameters);
|
|
if (result == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var crossGammas = new List<double>();
|
|
|
|
// 先获取返回的结果矩阵中的对角线上的元素,对应的是每个标的的Gamma
|
|
for (var i = 0; i < coefficients.Length; ++i)
|
|
{
|
|
crossGammas.Add(result[i, i]);
|
|
}
|
|
|
|
// 再获取两两对应的Cross Gamma
|
|
for (var i = 0; i < coefficients.Length - 1; ++i)
|
|
{
|
|
for (var j = i + 1; j < coefficients.Length; ++j)
|
|
{
|
|
crossGammas.Add(result[i, j]);
|
|
}
|
|
}
|
|
|
|
return crossGammas.ToArray();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----双鲨期权----
|
|
|
|
public static TradeValueResult GetDoubleSharkFinOptionValue(MarketProxy marketProxy, OptionCalcParam<DoubleSharkFinOptionTradeParam> calcParam)
|
|
{
|
|
using var helper = new SingleUnderlyingCalcHelper(marketProxy).Prepare(calcParam);
|
|
var tp = calcParam.TradeParam;
|
|
|
|
//如果用户自定义的观察日就是每天观察,则忽略自定义观察日
|
|
var observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(tp.startDate, tp.exerciseDate)
|
|
.Union(new[] { tp.exerciseDate }).ToArray();
|
|
var barrierObservationDates = QdpHelper.ParseObservationDate(tp.observationDateStr);
|
|
if (barrierObservationDates != null &&
|
|
(barrierObservationDates.Count(d => d >= observationDates.First() && d <= observationDates.Last()) == observationDates.Length))
|
|
{
|
|
tp.observationDateStr = null;
|
|
}
|
|
|
|
var optionTrade = QdpTradeBuilder.GetDoubleSharkFinOptionTrade(tp);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcParam.calcScenario, calcParam?.TradeParam?.tradeNumber);
|
|
var parameters = helper.GetValuationParameters();
|
|
var option = optionTrade.Instrument as DoubleSharkFinOption;
|
|
var factory = OptionEngineFactory.GetEngineFactory("DoubleSharkFinOption");
|
|
var engine = factory.GetEngine(calcParam.engineName, option.Exercise, option, false
|
|
, !string.IsNullOrWhiteSpace(tp.observationDateStr), calcParam.quadratureFastMode);
|
|
marketProxy.Trace?.WriteData(optionTrade, "QDP 双鲨");
|
|
var result = optionTrade.CalculateRisks(marketProxy.QdpMarket, calcParam.pricingRequest, engine, parameters);
|
|
if (result.Succeeded)
|
|
{
|
|
var tr = new TradeValueResult(result)
|
|
{
|
|
UnderlyingCode = helper.underlyingTicker,
|
|
RoundedPv = OtcFormatHelper.FormatValue(result.Pv / optionTrade.Notional, 2) * optionTrade.Notional,
|
|
};
|
|
|
|
if (calcParam.CalcDeltaT1)
|
|
{
|
|
tr.DeltaT1 = CalcDeltaT1(optionTrade, marketProxy.QdpMarket, engine, parameters);
|
|
}
|
|
if (PS.Config.Company == Configuration.CompanyEnum.天风)
|
|
{
|
|
tr.GammaCash = 0.5 * result.Gamma * Math.Pow(calcParam.spotPrices[0] * 0.01, 2);
|
|
}
|
|
ValueCalculator.ConvertTradeValueResultOfCompany(tr, calcParam?.TradeParam?.tradeType);
|
|
|
|
return tr;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----凤凰期权----
|
|
|
|
public static TradeValueResult GetAutocallOptionValue(MarketProxy marketProxy, OptionCalcParam<AutocallOptionTradeParam> calcParam)
|
|
{
|
|
using var helper = new SingleUnderlyingCalcHelper(marketProxy).Prepare(calcParam);
|
|
var optionTrade = QdpTradeBuilder.GetAutocallOptionTrade(calcParam.TradeParam);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcParam.calcScenario, calcParam?.TradeParam?.tradeNumber);
|
|
var parameters = helper.GetValuationParameters();
|
|
var option = optionTrade.Instrument as AutoCall;
|
|
var factory = OptionEngineFactory.GetEngineFactory("AutoCall");
|
|
var engine = factory.GetEngine(calcParam.engineName, option.Exercise, calcParam.quadratureFastMode);
|
|
|
|
marketProxy.Trace?.WriteData(optionTrade, "QDP 凤凰");
|
|
|
|
try
|
|
{
|
|
var result = optionTrade.CalculateRisks(marketProxy.QdpMarket, calcParam.pricingRequest, engine, parameters);
|
|
|
|
if (result.Succeeded)
|
|
{
|
|
var tr = new TradeValueResult(result)
|
|
{
|
|
UnderlyingCode = helper.underlyingTicker,
|
|
RoundedPv = OtcFormatHelper.FormatValue(result.Pv / optionTrade.Notional, 2) * optionTrade.Notional
|
|
};
|
|
|
|
if (calcParam.CalcDeltaT1)
|
|
{
|
|
tr.DeltaT1 = CalcDeltaT1(optionTrade, marketProxy.QdpMarket, engine, parameters);
|
|
}
|
|
if (PS.Config.Company == Configuration.CompanyEnum.天风)
|
|
{
|
|
tr.GammaCash = 0.5 * result.Gamma * Math.Pow(calcParam.spotPrices[0] * 0.01, 2);
|
|
}
|
|
ValueCalculator.ConvertTradeValueResultOfCompany(tr, calcParam?.TradeParam?.tradeType);
|
|
|
|
return tr;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
using var db = DbContextFactory.GetYLDbContext();
|
|
if (int.TryParse(calcParam.TradeParam.tradeId.Split('_')[1], out var t))
|
|
{
|
|
var tradeNumber = db.trade.FirstOrDefault(l => l.id == t)?.TradeNumber;
|
|
throw new Exception($"交易编号:{tradeNumber},错误信息:{ex.Message}");
|
|
}
|
|
else
|
|
{
|
|
//todo
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----雪球期权----
|
|
|
|
public static TradeValueResult GetSnowballOptionValue(MarketProxy marketProxy, OptionCalcParam<SnowballOptionTradeParam> calcParam)
|
|
{
|
|
SimpleSnowball option;
|
|
AnnualizedPremiumSnowball annualOption = null;
|
|
|
|
using var helper = new SingleUnderlyingCalcHelper(marketProxy).Prepare(calcParam);
|
|
var optionTrade = QdpTradeBuilder.GetSnowballOptionTrade(calcParam.TradeParam);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcParam.calcScenario, calcParam?.TradeParam?.tradeNumber);
|
|
if (optionTrade.Instrument is AnnualizedPremiumSnowball snowball)
|
|
{
|
|
option = annualOption = snowball;
|
|
annualOption.AnnualizedPremium = calcParam.TradeParam.annualizedPremiumRate ?? 0;
|
|
}
|
|
else
|
|
{
|
|
option = optionTrade.Instrument as SimpleSnowball;
|
|
}
|
|
|
|
var parameters = helper.GetValuationParameters();
|
|
var factory = OptionEngineFactory.GetEngineFactory("SnowBall");
|
|
var engine = factory.GetEngine(calcParam.engineName, option.Exercise, calcParam.quadratureFastMode);
|
|
|
|
marketProxy.Trace?.WriteData(optionTrade, "QDP 雪球");
|
|
|
|
IPricingResult result;
|
|
|
|
if (annualOption == null)
|
|
{
|
|
//加速方法定价的结果没有考虑deltacash和gammacash,所以只有定价可以使用
|
|
if (calcParam.calcScenario == Enums.CalcScenarioEnum.Pricing && engine is QuadAutoCallEngine engine1)
|
|
{
|
|
//普通雪球期权可以采用加速方法定价
|
|
var market = SnowballTrade.ConvertToMarketCondition(optionTrade.TradeId, marketProxy.QdpMarket, parameters);
|
|
var gridSnapshot = new QuadSnowballGridSnapshot(option, market, engine1);
|
|
result = gridSnapshot.Calculate(market.SpotPrices.Value.Values.First()
|
|
, option.BarrierStatus == BarrierStatus.KnockedIn, calcParam.pricingRequest, rebuildGrid: false);
|
|
}
|
|
else
|
|
{
|
|
result = optionTrade.CalculateRisks(marketProxy.QdpMarket, calcParam.pricingRequest, engine, parameters);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result = optionTrade.CalculateRisks(marketProxy.QdpMarket, calcParam.pricingRequest, engine, parameters);
|
|
}
|
|
|
|
if (result != null && result.Succeeded)
|
|
{
|
|
var tr = new TradeValueResult(result)
|
|
{
|
|
UnderlyingCode = helper.underlyingTicker,
|
|
RoundedPv = OtcFormatHelper.FormatValue(result.Pv / optionTrade.Notional, 2) * optionTrade.Notional
|
|
};
|
|
|
|
if (calcParam.CalcDeltaT1)
|
|
{
|
|
tr.DeltaT1 = CalcDeltaT1(optionTrade, marketProxy.QdpMarket, engine, parameters);
|
|
}
|
|
if (PS.Config.Company == Configuration.CompanyEnum.天风)
|
|
{
|
|
tr.GammaCash = 0.5 * result.Gamma * Math.Pow(calcParam.spotPrices[0] * 0.01, 2);
|
|
}
|
|
|
|
ValueCalculator.ConvertTradeValueResultOfCompany(tr, calcParam?.TradeParam?.tradeType);
|
|
|
|
return tr;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 专业版雪球
|
|
/// </summary>
|
|
/// <param name="marketProxy"></param>
|
|
/// <param name="calcParam"></param>
|
|
/// <returns></returns>
|
|
public static TradeValueResult GetSnowballSpecialistOptionValue(MarketProxy marketProxy, OptionCalcParam<SnowballSpecialistOptionTradeParam> calcParam, OptionValueCalcRequest request)
|
|
{
|
|
return new TradeValueResult { Succeeded = false, ErrorMessage = "不支持专业版雪球" };
|
|
|
|
//var tradeParams = calcParam.TradeParam;
|
|
|
|
//using var wrapper = new CxxCalcApi();
|
|
|
|
//// 敲入观察日
|
|
//var koParams = tradeParams.koObservationDateStr.Split(";");
|
|
//var koObsDateStr = koParams[0];
|
|
//var koObsDates = koObsDateStr.Split(",").Select(g => new Date(DateTime.Parse(g))).ToArray();
|
|
//var koBarriers = koParams[1].Split(",").Select(g => double.Parse(g)).ToArray();
|
|
//var koCoupons = koParams[2].Split(",").Select(g => double.Parse(g)).ToArray();
|
|
|
|
//var kiBarrier = tradeParams.kiBarrier;
|
|
//var strike = tradeParams.strike;
|
|
//if (!tradeParams.isMoneynessOption)
|
|
//{
|
|
// //转为相对价格比例
|
|
// koBarriers = koBarriers.Select(g => g / tradeParams.initialSpotPrice).ToArray();
|
|
// kiBarrier /= tradeParams.initialSpotPrice;
|
|
// strike /= tradeParams.initialSpotPrice;
|
|
//}
|
|
|
|
//// 计算票息年化系数(外部有不同的计算方式)
|
|
//var dayCount = tradeParams.couponDayCount.ToDayCountImpl();
|
|
//var koFractions = koObsDateStr.Split(",").Select(g =>
|
|
//{
|
|
// var koObsDate = DateTime.Parse(g);
|
|
// var koFraction = YLErp.QdpModule.QdpHelper.AnnualizeFactor(tradeParams.startDate, koObsDate, dayCount);
|
|
// return koFraction;
|
|
//}).ToArray();
|
|
|
|
//// 计算入参
|
|
//var calcIn = new CalcIn()
|
|
//{
|
|
// // 市场(日历)名称,如China
|
|
// market = "chn",
|
|
// // 开始日期
|
|
// startDate = tradeParams.startDate.ToString(),
|
|
// // 估值日期
|
|
// valueDate = marketProxy.ValueDate.ToString("yyyy-MM-dd"),
|
|
// // 5是向上敲出的雪球,6是向下敲出的雪球??
|
|
// barrierType = tradeParams.optionType == OptionType.Call ? CxxCalcLib.BarrierType.UP_OUT_DOWN_IN : CxxCalcLib.BarrierType.UP_OUT_DOWN_IN,
|
|
// // 期初价格
|
|
// initialSpot = tradeParams.initialSpotPrice,
|
|
// // 敲出观察日列表,以逗号分隔
|
|
// koObsDateStr = koObsDateStr,
|
|
// // 敲出障碍价格列表,与敲出观察日对应
|
|
// koBarriers = koBarriers,
|
|
// // 敲出票息列表??
|
|
// koCoupons = koCoupons,
|
|
// // 敲出票息的年化系数列表
|
|
// koFractions = koFractions,
|
|
// // 剩余未观察的敲出观察日个数
|
|
// activeKoObsCount = koObsDates.Where(g => g > tradeParams.startDate).Count(),
|
|
// // 敲出增强收益的参与率,无增强收益填0??
|
|
// koPayoffParticipation = 0,
|
|
// // 敲入障碍价格
|
|
// kiBarrier = kiBarrier,
|
|
// // 敲入后的行权价
|
|
// kiStrike = strike,
|
|
// // 是否仅在到期日进行敲入观察,否则为每日观察
|
|
// kiObsOnlyAtMaturity = tradeParams.KIObservationType == KIObservationType.OnlyEndDate,
|
|
// // 敲入后的期权参与率??
|
|
// kiParticipationRate = tradeParams.KIParticipationRate,
|
|
// // 红利票息
|
|
// maturityCoupon = tradeParams.coupon,
|
|
// // 保本比例,如0.8表示敲入后亏损封顶20%
|
|
// protectionRatio = tradeParams.PrincipalProtectionRate,
|
|
// // 初始预付金比例
|
|
// initialMarginRatio = tradeParams.PrepaymentRatio,
|
|
// // 预付金利率
|
|
// marginInterestRate = tradeParams.PrepaymentInterestRate,
|
|
// // 数量??
|
|
// amount = tradeParams.notional,
|
|
// // 是否已敲入
|
|
// isKnockedIn = false,
|
|
// // 一组标的资产价格,每个价格都是一个情景
|
|
// spots = new double[] { tradeParams.initialSpotPrice },
|
|
// // 情景个数??
|
|
// scenarioCount = 1,
|
|
// // 无风险利率
|
|
// r = tradeParams.riskFreeRate,
|
|
// // 分红率
|
|
// q = tradeParams.dividendRate,
|
|
// // 波动率
|
|
// v = request.vols[0],
|
|
// // 计算指标,逗号隔开
|
|
// greekType = "PV,DELTA,GAMMA,VEGA,RHO,THETA"
|
|
//};
|
|
//var result1 = wrapper.CalcPlatform_SnowballScenario(calcIn);
|
|
//var result = result1.Values.FirstOrDefault();
|
|
//return new TradeValueResult(true)
|
|
//{
|
|
// Pv = result.PV,
|
|
// Delta = result.DELTA,
|
|
// Gamma = result.GAMMA,
|
|
// Vega = result.VEGA,
|
|
// Rho = result.RHO,
|
|
// CalendarDayTheta = result.THETA,
|
|
// TradingDayTheta = result.THETA,
|
|
//};
|
|
}
|
|
#endregion
|
|
|
|
#region ----气囊结构----
|
|
|
|
/// <summary>
|
|
/// 气囊结构
|
|
/// </summary>
|
|
public static TradeValueResult GetAirbagOptionValue(MarketProxy marketProxy, OptionCalcParam<AirBagOptionTradeParam> calcParam)
|
|
{
|
|
using var helper = new SingleUnderlyingCalcHelper(marketProxy).Prepare(calcParam);
|
|
var optionTrade = QdpTradeBuilder.GetAirbagOptionTrade(calcParam.TradeParam);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcParam.calcScenario, calcParam?.TradeParam?.tradeNumber);
|
|
var parameters = helper.GetValuationParameters();
|
|
var option = optionTrade.Instrument as Airbag;
|
|
var engine = new AnalyticalAirbagReplicationEngine();
|
|
|
|
marketProxy.Trace?.WriteData(optionTrade, "QDP 气囊");
|
|
|
|
var result = optionTrade.CalculateRisks(marketProxy.QdpMarket, calcParam.pricingRequest, engine, parameters);
|
|
if (result.Succeeded)
|
|
{
|
|
var tr = new TradeValueResult(result)
|
|
{
|
|
UnderlyingCode = helper.underlyingTicker,
|
|
RoundedPv = OtcFormatHelper.FormatValue(result.Pv / optionTrade.Notional, 2) * optionTrade.Notional
|
|
};
|
|
|
|
if (calcParam.CalcDeltaT1)
|
|
{
|
|
tr.DeltaT1 = CalcDeltaT1(optionTrade, marketProxy.QdpMarket, engine, parameters);
|
|
}
|
|
if (PS.Config.Company == Configuration.CompanyEnum.天风)
|
|
{
|
|
tr.GammaCash = 0.5 * result.Gamma * Math.Pow(calcParam.spotPrices[0] * 0.01, 2);
|
|
}
|
|
ValueCalculator.ConvertTradeValueResultOfCompany(tr, calcParam?.TradeParam?.tradeType);
|
|
|
|
return tr;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----收益增强结构----
|
|
|
|
public static TradeValueResult GetUnderlyingEnhanceValue(MarketProxy marketProxy, OptionCalcParam<UnderlyingEnhanceTradeParam> calcParam)
|
|
{
|
|
using var helper = new SingleUnderlyingCalcHelper(marketProxy).Prepare(calcParam);
|
|
var optionTrade = QdpTradeBuilder.GetUnderlyingEnhanceTrade(calcParam.TradeParam);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcParam.calcScenario, calcParam?.TradeParam?.tradeNumber);
|
|
var parameters = helper.GetValuationParameters();
|
|
var option = optionTrade.Instrument as UnderlyingPayoffEnhance;
|
|
var engine = new AnalyticalUnderlyingPayoffEnhanceEngine(calcAsOptions: true);
|
|
|
|
marketProxy.Trace?.WriteData(optionTrade, "QDP 收益增强");
|
|
|
|
var result = optionTrade.CalculateRisks(marketProxy.QdpMarket, calcParam.pricingRequest, engine, parameters);
|
|
|
|
if (result.Succeeded)
|
|
{
|
|
var tr = new TradeValueResult(result)
|
|
{
|
|
UnderlyingCode = helper.underlyingTicker,
|
|
RoundedPv = OtcFormatHelper.FormatValue(result.Pv / optionTrade.Notional, 2) * optionTrade.Notional
|
|
};
|
|
|
|
if (calcParam.CalcDeltaT1)
|
|
{
|
|
tr.DeltaT1 = CalcDeltaT1(optionTrade, marketProxy.QdpMarket, engine, parameters);
|
|
}
|
|
if (PS.Config.Company == Configuration.CompanyEnum.天风)
|
|
{
|
|
tr.GammaCash = 0.5 * result.Gamma * Math.Pow(calcParam.spotPrices[0] * 0.01, 2);
|
|
}
|
|
ValueCalculator.ConvertTradeValueResultOfCompany(tr, calcParam?.TradeParam?.tradeType);
|
|
|
|
return tr;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region----区间累积----
|
|
|
|
public static TradeValueResult GetRangeAccrualValue(MarketProxy marketProxy, OptionCalcParam<RangeAccrualTradeParam> calcParam)
|
|
{
|
|
using var helper = new SingleUnderlyingCalcHelper(marketProxy).Prepare(calcParam);
|
|
var optionTrade = QdpTradeBuilder.GetRangeAccrualTrade(calcParam.TradeParam);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcParam.calcScenario, calcParam?.TradeParam?.tradeNumber);
|
|
var parameters = helper.GetValuationParameters();
|
|
var option = optionTrade.Instrument as RangeAccrual;
|
|
var factory = OptionEngineFactory.GetEngineFactory("RangeAccrual");
|
|
var engine = factory.GetEngine(calcParam.engineName, option.Exercise);
|
|
|
|
marketProxy.Trace?.WriteData(optionTrade, "QDP 区间累积");
|
|
|
|
var result = optionTrade.CalculateRisks(marketProxy.QdpMarket, calcParam.pricingRequest, engine, parameters);
|
|
|
|
if (result.Succeeded)
|
|
{
|
|
var tr = new TradeValueResult(result)
|
|
{
|
|
UnderlyingCode = helper.underlyingTicker,
|
|
RoundedPv = OtcFormatHelper.FormatValue(result.Pv / optionTrade.Notional, 2) * optionTrade.Notional,
|
|
};
|
|
|
|
if (calcParam.CalcDeltaT1)
|
|
{
|
|
tr.DeltaT1 = CalcDeltaT1(optionTrade, marketProxy.QdpMarket, engine, parameters);
|
|
}
|
|
if (PS.Config.Company == Configuration.CompanyEnum.天风)
|
|
{
|
|
tr.GammaCash = 0.5 * result.Gamma * Math.Pow(calcParam.spotPrices[0] * 0.01, 2);
|
|
}
|
|
ValueCalculator.ConvertTradeValueResultOfCompany(tr, calcParam?.TradeParam?.tradeType);
|
|
|
|
return tr;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----累计期权----
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="marketProxy"></param>
|
|
/// <param name="calcParam"></param>
|
|
/// <param name="notional">累计期权交易的notional,和param里面的notional不同,param里面的notional代表的是沽购数量</param>
|
|
/// <returns></returns>
|
|
public static TradeValueResult GetAccumulatorOptionValue(MarketProxy marketProxy, OptionCalcParam<AccumulatorOptionTradeParam> calcParam, double notional)
|
|
{
|
|
using var helper = new SingleUnderlyingCalcHelper(marketProxy).Prepare(calcParam);
|
|
//当到期日和定价日一样,并且当前时间大于3点,ttm传0;qdp里面 ttm等于0和不等于0逻辑处理不一样。
|
|
if (calcParam.TradeParam.exerciseDate.DateTime == marketProxy.ValueDate && DateTime.Now.Hour >= 15)
|
|
{
|
|
calcParam.TradeParam.timeToMaturityDays = 0;
|
|
}
|
|
var optionTrade = QdpTradeBuilder.GetAccumulatorOptionTrade(calcParam.TradeParam);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcParam.calcScenario, calcParam?.TradeParam?.tradeNumber);
|
|
var parameters = helper.GetValuationParameters();
|
|
IEngine engine;
|
|
if (optionTrade is SegmentedAccumulatorOptionTrade)
|
|
{
|
|
engine = new AnalyticalSegmentedAccumulatorOptionEngine();
|
|
}
|
|
else
|
|
{
|
|
if (calcParam.TradeParam.EarlyTerminate)
|
|
{
|
|
engine = new QuadAccumulatorOptionEngine();
|
|
}
|
|
else
|
|
{
|
|
engine = new AnalyticalAccumulatorOptionEngine();
|
|
}
|
|
}
|
|
|
|
calcParam.pricingRequest = PricingRequest.Pv | PricingRequest.Delta | PricingRequest.Gamma | PricingRequest.Vega | PricingRequest.Rho | PricingRequest.Theta;
|
|
|
|
marketProxy.Trace?.WriteData(optionTrade, "QDP 累计期权");
|
|
|
|
var result = optionTrade.CalculateRisks(marketProxy.QdpMarket, calcParam.pricingRequest, engine, parameters);
|
|
|
|
if (result.Succeeded)
|
|
{
|
|
var tr = new TradeValueResult(result)
|
|
{
|
|
UnderlyingCode = helper.underlyingTicker,
|
|
RoundedPv = OtcFormatHelper.FormatValue(result.Pv / notional, 2) * notional
|
|
};
|
|
|
|
if (calcParam.CalcDeltaT1)
|
|
{
|
|
tr.DeltaT1 = CalcDeltaT1(optionTrade, marketProxy.QdpMarket, engine, parameters);
|
|
}
|
|
if (PS.Config.Company == Configuration.CompanyEnum.天风)
|
|
{
|
|
tr.GammaCash = 0.5 * result.Gamma * Math.Pow(calcParam.spotPrices[0] * 0.01, 2);
|
|
}
|
|
|
|
ValueCalculator.ConvertTradeValueResultOfCompany(tr, calcParam?.TradeParam?.tradeType);
|
|
|
|
return tr;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----现金流----
|
|
|
|
public static TradeValueResult GetCashFlowValue(MarketProxy marketProxy, OptionCalcParam<CashFlowTradeParam> calcParam, double stockEqvNotional)
|
|
{
|
|
if (marketProxy is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(marketProxy));
|
|
}
|
|
|
|
if (calcParam is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(calcParam));
|
|
}
|
|
|
|
calcParam.TradeParam.volSurfaceNames = new string[] { "cashflow_0" };
|
|
|
|
//现金流交易在Qdp里的对应的类(Deposit)不接受常量无风险利率作为输入,只能以折现曲线的形式传入定价
|
|
//现金流交易的无风险利率使用market中设置的全局值
|
|
marketProxy.SetYieldCurve(marketProxy.DiscountCurveName,
|
|
QdpHelper.CreateRiskFreeCurve(marketProxy.DiscountCurveName, calcParam.TradeParam.riskFreeRate, marketProxy.CurveDayCount));
|
|
|
|
using var helper = new SingleUnderlyingCalcHelper(marketProxy).Prepare(calcParam, true);
|
|
var optionTrade = QdpTradeBuilder.GetCashFlowTrade(calcParam.TradeParam, stockEqvNotional);
|
|
// 添加计算日志
|
|
AddCalculationLog(optionTrade, calcParam.calcScenario, calcParam?.TradeParam?.tradeNumber);
|
|
var discountCurveName = marketProxy.DiscountCurveName ?? MarketProxy.ConstantZeroCurve;
|
|
var parameters = new SimpleCfValuationParameters(discountCurveName, discountCurveName, discountCurveName);
|
|
IEngine engine = new DepositEngine();
|
|
var result = optionTrade.CalculateRisks(marketProxy.QdpMarket, calcParam.pricingRequest, engine, parameters);
|
|
TradeValueResult tradeValue = null;
|
|
if (result.Succeeded)
|
|
{
|
|
tradeValue = new TradeValueResult(result);// { RoundedPv = result.Pv };
|
|
|
|
if (calcParam.TradeParam.DepositType == CashflowDepositTypeEnum.资金收益)
|
|
{
|
|
tradeValue.Pv += stockEqvNotional * calcParam.TradeParam.PrepayRatio * (calcParam.TradeParam.buysell == TradeType.Buy.ToString() ? 1 : -1);
|
|
}
|
|
tradeValue.RoundedPv = tradeValue.Pv;
|
|
ValueCalculator.ConvertTradeValueResultOfCompany(tradeValue, calcParam?.TradeParam?.tradeType);
|
|
}
|
|
return tradeValue;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region----自定义交易----
|
|
/// <summary>
|
|
/// 计算自定义交易期权价值
|
|
/// <para>默认取维护的风险值</para>
|
|
/// <para>没维护过风险值且ExternalAPIEnable功能开启时,会调用外部接口计算</para>
|
|
/// </summary>
|
|
/// <param name="valueDate">估值日期</param>
|
|
/// <param name="td">交易</param>
|
|
/// <param name="spotPrice">现价</param>
|
|
/// <param name="isCustomerTrade">是否为自定义交易</param>
|
|
/// <param name="volType">调用外部接口计算时,使用的波动率类型,默认为交易波动率</param>
|
|
/// <param name="volValue">调用外部接口计算时,使用的波动率,该值忽略时使用波动率类型取值</param>
|
|
/// <returns></returns>
|
|
public static (eod_trade_risk_manual manual, TradeValueResult optionValue) GetManualOptionValue(
|
|
DateTime valueDate, trade td, double spotPrice, double volValue, bool isCustomerTrade, CalcScenarioEnum calcScenario, string volType = "持仓", SettlementTypeEnum settlementType = SettlementTypeEnum.ClosePrice, double overrideVol = double.NaN, bool isSettle = false)
|
|
{
|
|
using var db = DbContextFactory.GetYLDbContext();
|
|
if (volType == "交易")
|
|
{
|
|
volType = "持仓";
|
|
}
|
|
var tradeId = td.id;
|
|
//分类维护的风险数据优先级高于普通的风险数据
|
|
var manual = db.eod_trade_risk_manual.Where(x => (x.ValueDate <= valueDate && isCustomerTrade || x.ValueDate == valueDate) && x.TradeId == tradeId && x.VolType == volType && x.SettlementType == settlementType)
|
|
.OrderByDescending(x => x.ValueDate).FirstOrDefault();
|
|
if (manual == null)
|
|
{
|
|
manual = db.eod_trade_risk_manual.Where(x => (x.ValueDate <= valueDate && isCustomerTrade || x.ValueDate == valueDate) && x.TradeId == tradeId && string.IsNullOrEmpty(x.VolType))
|
|
.OrderByDescending(x => x.ValueDate).FirstOrDefault();
|
|
}
|
|
else
|
|
{
|
|
//如果分类维护的数据不全,通过单一数据补充
|
|
var singleManual = db.eod_trade_risk_manual.Where(x => (x.ValueDate <= valueDate && isCustomerTrade || x.ValueDate == valueDate) && x.TradeId == tradeId && string.IsNullOrEmpty(x.VolType))
|
|
.OrderByDescending(x => x.ValueDate).FirstOrDefault();
|
|
if (singleManual != null)
|
|
{
|
|
manual.Pv ??= singleManual.Pv;
|
|
manual.Delta ??= singleManual.Delta;
|
|
manual.DeltaCash ??= singleManual.DeltaCash;
|
|
manual.Gamma ??= singleManual.Gamma;
|
|
manual.GammaCash ??= singleManual.GammaCash;
|
|
manual.Theta ??= singleManual.Theta;
|
|
manual.Vega ??= singleManual.Vega;
|
|
manual.VegaCash ??= singleManual.VegaCash;
|
|
manual.Rho ??= singleManual.Rho;
|
|
}
|
|
}
|
|
|
|
TradeValueResult optionValue = null;
|
|
|
|
if (manual == null && isCustomerTrade && PS.Config.ErpElement.ExternalAPIForCustomCalcEnable)
|
|
{
|
|
var apiValue = TradeCalcApiHelper.CalculateCustomizedTrade(valueDate, td, spotPrice, volValue, isSettle, calcScenario, TradeCalcApiHelper.PV, TradeCalcApiHelper.DELTA, TradeCalcApiHelper.GAMMA, TradeCalcApiHelper.VEGA, TradeCalcApiHelper.THETA, TradeCalcApiHelper.RHO);
|
|
if (apiValue.Success)
|
|
{
|
|
manual = new eod_trade_risk_manual()
|
|
{
|
|
ValueDate = valueDate,
|
|
TradeId = td.id,
|
|
TradeNumber = td.TradeNumber,
|
|
Pv = apiValue.Content.Pv,
|
|
Delta = apiValue.Content.Delta,
|
|
Gamma = apiValue.Content.Gamma,
|
|
Vega = apiValue.Content.Vega,
|
|
Theta = apiValue.Content.Theta,
|
|
Rho = apiValue.Content.Rho,
|
|
DeltaCash = apiValue.Content.Delta * spotPrice,
|
|
GammaCash = apiValue.Content.Gamma * Math.Pow(spotPrice, 2) / 100,
|
|
VegaCash = apiValue.Content.Vega * spotPrice,
|
|
Vol = td.Vol,
|
|
};
|
|
optionValue = apiValue.Content;
|
|
optionValue.FromManual = false;
|
|
}
|
|
//抛出异常信息返回前端
|
|
else
|
|
{
|
|
throw new Exception(apiValue.Msg);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
optionValue = new TradeValueResult { FromManual = true };
|
|
if (manual != null)
|
|
{
|
|
manual.DeltaCash ??= (manual.Delta * spotPrice);
|
|
manual.GammaCash ??= (manual.Gamma * Math.Pow(spotPrice, 2) / 100);
|
|
manual.VegaCash ??= (manual.Vega * spotPrice);
|
|
|
|
optionValue = new TradeValueResult
|
|
{
|
|
Pv = manual.Pv ?? 0,
|
|
RoundedPv = manual.Pv ?? 0,
|
|
Delta = manual.Delta ?? 0,
|
|
Gamma = manual.Gamma ?? 0,
|
|
Vega = manual.Vega ?? 0,
|
|
TradingDayTheta = manual.Theta ?? 0,
|
|
CalendarDayTheta = manual.Theta ?? 0,
|
|
Rho = manual.Rho ?? 0,
|
|
DeltaCash = manual.DeltaCash ?? 0,
|
|
GammaCash = manual.GammaCash ?? 0,
|
|
VegaCash = manual.VegaCash ?? 0,
|
|
FromManual = true,
|
|
Vol = manual.Vol ?? 0
|
|
};
|
|
}
|
|
}
|
|
|
|
return (manual, optionValue);
|
|
}
|
|
|
|
//如果当天有维护过部分风险值,则直接获取风险值,未维护的风险值部分则通过系统计算
|
|
//系统计算结果和手动维护值合并
|
|
public static void GetOptionValueWithManual(eod_trade_risk_manual manual, TradeValueResult result)
|
|
{
|
|
if (manual != null)
|
|
{
|
|
result.Pv = manual.Pv ?? result.Pv;
|
|
result.RoundedPv = manual.Pv ?? result.RoundedPv;
|
|
result.Delta = manual.Delta ?? result.Delta;
|
|
result.DeltaCash = manual.DeltaCash ?? result.DeltaCash;
|
|
result.Gamma = manual.Gamma ?? result.Gamma;
|
|
result.GammaCash = manual.GammaCash ?? result.GammaCash;
|
|
result.Vega = manual.Vega ?? result.Vega;
|
|
result.TradingDayTheta = manual.Theta ?? result.TradingDayTheta;
|
|
result.CalendarDayTheta = manual.Theta ?? result.CalendarDayTheta;
|
|
result.Rho = manual.Rho ?? result.Rho;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
private static void AddCalculationLog(TradeBase trade, CalcScenarioEnum scenarioEnum, string tradeNumber)
|
|
{
|
|
try
|
|
{
|
|
var option = trade.Instrument as OptionBase;
|
|
|
|
// 添加计算日志
|
|
var 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);
|
|
}
|
|
}
|
|
}
|
|
}
|