- 从QdpTradeBuilder构造函数调用中移除updownPriceMode参数 - 注释掉累购期权中不再支持的价格模式设置逻辑 - 更新Excel导出方法调用以移除多余的数据分组参数
2986 lines
134 KiB
C#
2986 lines
134 KiB
C#
using Qdp.ComputeService.Data.CommonModels.ValuationParams.Equity;
|
|
using Qdp.ComputeService.Data.CommonModels.ValuationParams.FixedIncome;
|
|
using Qdp.ComputeServiceV2.Data.CommonModels.TradeInfos.Options;
|
|
using Qdp.ComputeServiceV2.Data.CommonModels.TradeInfos.Rates;
|
|
using Qdp.Foundation.Implementations;
|
|
using Qdp.Pricing.Base.Enums;
|
|
using Qdp.Pricing.Base.Implementations;
|
|
using Qdp.Pricing.Base.Utilities;
|
|
using Qdp.Pricing.Library.Common.Products.Rates;
|
|
using Qdp.Pricing.Library.Options.Products.Accumulator;
|
|
using Qdp.Pricing.Library.Options.Products.Airbag;
|
|
using Qdp.Pricing.Library.Options.Products.Asian;
|
|
using Qdp.Pricing.Library.Options.Products.AsianSyntheticSpread;
|
|
using Qdp.Pricing.Library.Options.Products.Autocall.Phoenix;
|
|
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.Rainbow;
|
|
using Qdp.Pricing.Library.Options.Products.RangeAccrual;
|
|
using Qdp.Pricing.Library.Options.Products.Spread;
|
|
using Qdp.Pricing.Library.Options.Products.SyntheticSpread;
|
|
using Qdp.Pricing.Library.Options.Products.Vanilla;
|
|
using Qdp.Pricing.Library.Options.Utilities;
|
|
using System.Runtime.InteropServices;
|
|
using YLErp.BLL;
|
|
using YLErp.BLL.Calculation;
|
|
using YLErp.Modules.CalculationModule.Abstract;
|
|
using YLErp.Modules.TradeModule.Structure_dz;
|
|
using YLErp.Modules.VolatilityModule;
|
|
using YLErp.QdpModule;
|
|
using YLErp.ThirdParty.CaculatePrice.DongZheng.Dto;
|
|
|
|
namespace YLErp.Modules.CalculationModule
|
|
{
|
|
/// <summary>
|
|
/// 构建QDP交易
|
|
/// </summary>
|
|
public class QdpTradeBuilder
|
|
{
|
|
private static IYcLogger log = LogFactory.GetLogger(nameof(QdpTradeBuilder));
|
|
|
|
#region ----香草期权----
|
|
|
|
/// <summary>
|
|
/// 香草期权
|
|
/// </summary>
|
|
public static VanillaOptionTrade GetVanillaOptionTrade(VanillaOptionTradeParam param)
|
|
{
|
|
CheckOptionTradeParam(param);
|
|
|
|
var exercise = QdpConverter.ConvertExerciseType(param.exerciseType);
|
|
var optionStartDate = param.startDate;
|
|
var underlyingMaturityDate = param.endDate;
|
|
var temp_exerciseDate = param.exerciseDate;
|
|
|
|
if (temp_exerciseDate < optionStartDate)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Date[] exerciseDates, observationDates;
|
|
|
|
if (exercise == OptionExercise.American)
|
|
{
|
|
exerciseDates = CalendarImpl.Get("chn").BizDaysBetweenDatesInclEndDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
}
|
|
else
|
|
{
|
|
exerciseDates = new Date[] { temp_exerciseDate };
|
|
observationDates = new Date[] { temp_exerciseDate };
|
|
}
|
|
|
|
var optionDayCount = CalculatorHelper.GetTradeDayCount();
|
|
var pricingTOverride = QdpCalendarHelper.CalculateTFromDays(param.timeToMaturityDays, optionDayCount, optionStartDate.DateTime);
|
|
|
|
var vanillaOption = new VanillaOption(
|
|
startDate: optionStartDate,
|
|
maturityDate: underlyingMaturityDate,
|
|
exercise: exercise,
|
|
optionType: param.optionType,
|
|
strike: param.strike,
|
|
underlyingInstrumentType: (InstrumentType)Enum.Parse(typeof(InstrumentType), param.underlyingInstrumentType),
|
|
calendar: CalendarImpl.Get("chn"),
|
|
dayCount: optionDayCount.ToDayCountImpl(),
|
|
payoffCcy: CurrencyCode.CNY,
|
|
settlementCcy: CurrencyCode.CNY,
|
|
exerciseDates: exerciseDates,
|
|
observationDates: observationDates,
|
|
notional: param.notional,
|
|
settlementGap: null,
|
|
optionPremiumPaymentDate: null,
|
|
optionPremium: 0.0,
|
|
isMoneynessOption: param.isMoneynessOption,
|
|
initialSpotPrice: param.initialSpotPrice,
|
|
dividends: param.dividends,
|
|
hasNightMarket: param.hasNightMarket,
|
|
commodityFuturesPreciseTimeMode: param.preciseTimeMode,
|
|
pricingToverride: pricingTOverride,
|
|
riskFreeRateOverride: param.riskFreeRate,
|
|
dividendRateOverride: param.dividendRate,
|
|
participationRate: param.participationRate,
|
|
isAnnualized: param.isAnnualized,
|
|
annualizedFactor: param.annualizedFactor)
|
|
{
|
|
UnderlyingTickers = param.underlyingTickers
|
|
};
|
|
|
|
if (string.IsNullOrWhiteSpace(param.tradeId))
|
|
{
|
|
param.tradeId = Guid.NewGuid().ToString();
|
|
}
|
|
|
|
return new VanillaOptionTrade(
|
|
tradeId: param.tradeId,
|
|
tradeDate: param.tradeDate,
|
|
startDate: vanillaOption.StartDate,
|
|
maturityDate: vanillaOption.ExerciseDates.Last(),
|
|
tradeType: QdpConverter.ConvertTradeType(param.buysell),
|
|
notional: param.notional,
|
|
price: 0.0,
|
|
option: vanillaOption)
|
|
{
|
|
ValuationParameters = new OptionValuationParameters(MarketProxy.RiskFreeDiscountCurve, MarketProxy.ConstantZeroCurve,
|
|
param.volSurfaceNames?.FirstOrDefault(), vanillaOption.UnderlyingTickers[0]),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 香草期权
|
|
/// </summary>
|
|
public static VanillaOptionTrade GetVanillaOptionTrade(OtcTradeBase td, OptionTradeParamRequest request, bool isExchangeOption)
|
|
{
|
|
var param = GetVanillaOptionTradeParam(td, request, isExchangeOption);
|
|
return GetVanillaOptionTrade(param);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 香草期权
|
|
/// </summary>
|
|
/// <param name="isExchangeOption">是否场内期权</param>
|
|
public static VanillaOptionTradeParam GetVanillaOptionTradeParam(OtcTradeBase td, OptionTradeParamRequest request, bool isExchangeOption)
|
|
{
|
|
var result = BuildTradeParamBase<VanillaOptionTradeParam>(td, request);
|
|
|
|
if (isExchangeOption)
|
|
{
|
|
result.buysell = "Buy";
|
|
result.isMoneynessOption = false;
|
|
}
|
|
else if (td.Comments?.Trim() == "YQ")
|
|
{
|
|
result.riskFreeRate = 0;
|
|
result.isForwardTrade = true;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----合成价差期权----
|
|
|
|
/// <summary>
|
|
/// 合成价差期权
|
|
/// </summary>
|
|
public static SyntheticNormalSpreadOptionTrade GetSSpreadOptionTrade(SSpreadOptionTradeParam param)
|
|
{
|
|
CheckOptionTradeParam(param);
|
|
|
|
if (param.underlyingInstrumentType == null)
|
|
{
|
|
throw new Exception("标的资产类型不能为空");
|
|
}
|
|
|
|
var exercise = QdpConverter.ConvertExerciseType(param.exerciseType);
|
|
var optionStartDate = param.startDate;
|
|
var temp_exerciseDate = param.exerciseDate;
|
|
|
|
if (temp_exerciseDate < optionStartDate)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Date[] exerciseDates, observationDates;
|
|
if (exercise == OptionExercise.American)
|
|
{
|
|
exerciseDates = CalendarImpl.Get("chn").BizDaysBetweenDatesInclEndDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
}
|
|
else
|
|
{
|
|
exerciseDates = new Date[] { temp_exerciseDate };
|
|
observationDates = new Date[] { temp_exerciseDate };
|
|
}
|
|
|
|
var optionDayCount = CalculatorHelper.GetTradeDayCount();
|
|
var pricingTOverride = QdpCalendarHelper.CalculateTFromDays(param.timeToMaturityDays, optionDayCount, optionStartDate.DateTime);
|
|
|
|
var syntheticNormalSpreadOption = new SyntheticNormalSpreadOption(
|
|
startDate: optionStartDate,
|
|
exercise: exercise,
|
|
optionType: param.optionType,
|
|
strike: param.strike,
|
|
underlyingInstrumentType: (InstrumentType)Enum.Parse(typeof(InstrumentType), param.underlyingInstrumentType),
|
|
calendar: CalendarImpl.Get("chn"),
|
|
dayCount: optionDayCount.ToDayCountImpl(),
|
|
payoffCcy: CurrencyCode.CNY,
|
|
settlementCcy: CurrencyCode.CNY,
|
|
exerciseDates: exerciseDates,
|
|
observationDates: observationDates,
|
|
syntheticCoefficients: param.coefficients,
|
|
notional: param.notional,
|
|
settlementGap: null,
|
|
optionPremiumPaymentDate: null,
|
|
optionPremium: 0.0,
|
|
isMoneynessOption: param.isMoneynessOption,
|
|
initialSpotPrice: param.initialSpotPrice,
|
|
dividends: param.dividends,
|
|
hasNightMarket: param.hasNightMarket,
|
|
commodityFuturesPreciseTimeMode: param.preciseTimeMode,
|
|
pricingToverride: pricingTOverride,
|
|
riskFreeRateOverride: param.riskFreeRate,
|
|
dividendRateOverride: param.dividendRate,
|
|
participationRate: param.participationRate,
|
|
isAnnualized: param.isAnnualized,
|
|
annualizedFactor: param.annualizedFactor)
|
|
{
|
|
UnderlyingTickers = param.underlyingTickers
|
|
};
|
|
|
|
if (string.IsNullOrWhiteSpace(param.tradeId))
|
|
{
|
|
param.tradeId = Guid.NewGuid().ToString();
|
|
}
|
|
return new SyntheticNormalSpreadOptionTrade(
|
|
tradeId: param.tradeId,
|
|
tradeDate: param.tradeDate,
|
|
startDate: syntheticNormalSpreadOption.StartDate,
|
|
maturityDate: syntheticNormalSpreadOption.ExerciseDates.Last(),
|
|
tradeType: QdpConverter.ConvertTradeType(param.buysell),
|
|
notional: param.notional,
|
|
price: 0.0,
|
|
option: syntheticNormalSpreadOption)
|
|
{
|
|
ValuationParameters = new OptionValuationParameters(MarketProxy.RiskFreeDiscountCurve, MarketProxy.ConstantZeroCurve,
|
|
param.volSurfaceNames?.FirstOrDefault(), syntheticNormalSpreadOption.UnderlyingTickers[0]),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 合成价差期权
|
|
/// </summary>
|
|
public static SyntheticNormalSpreadOptionTrade GetSSpreadOptionTrade(OtcTradeBase td, OptionTradeParamRequest request = null)
|
|
{
|
|
var param = GetSSpreadOptionTradeParam(td, request);
|
|
return GetSSpreadOptionTrade(param);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 合成价差期权
|
|
/// </summary>
|
|
public static SSpreadOptionTradeParam GetSSpreadOptionTradeParam(OtcTradeBase td, OptionTradeParamRequest request, double[] coefficients = null)
|
|
{
|
|
var result = BuildTradeParamBase<SSpreadOptionTradeParam>(td, request);
|
|
|
|
if (td.Comments?.Trim() == "YQ")
|
|
{
|
|
result.riskFreeRate = 0;
|
|
result.isForwardTrade = true;
|
|
}
|
|
|
|
result.coefficients = coefficients;
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----亚式期权----
|
|
|
|
/// <summary>
|
|
/// 亚式期权
|
|
/// </summary>
|
|
public static AsianOptionTrade GetAsianOptionTrade(AsianOptionTradeParam param)
|
|
{
|
|
CheckOptionTradeParam(param);
|
|
|
|
var exercise = QdpConverter.ConvertExerciseType(param.exerciseType);
|
|
var optionStartDate = param.startDate;
|
|
var underlyingMaturityDate = param.endDate;
|
|
var temp_exerciseDate = param.exerciseDate;
|
|
|
|
Date[] exerciseDates, observationDates;
|
|
|
|
var calendar = CalendarImpl.Get("chn");
|
|
|
|
if (exercise == OptionExercise.American)
|
|
{
|
|
exerciseDates = calendar.BizDaysBetweenDatesInclEndDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
}
|
|
else
|
|
{
|
|
exerciseDates = new Date[] { temp_exerciseDate };
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(param.observationDateStr))
|
|
{
|
|
observationDates = QdpHelper.ParseObservationDate(param.observationDateStr);
|
|
}
|
|
else
|
|
{
|
|
var observationStartDate = param.averagingPeriodStartDate ?? optionStartDate;
|
|
var observationEndDate = QdpCalendarHelper.GetNonHolidayDefore(param.originalExerciseDate);
|
|
if (PS.Config.ErpElement.AsianOptionObDatesIncludeStartDate)
|
|
{
|
|
observationDates = calendar.BizDaysBetweenDatesInclEndDay(observationStartDate, observationEndDate).ToArray();
|
|
}
|
|
else
|
|
{
|
|
observationDates = calendar.BizDaysBetweenDatesExcluStartDay(observationStartDate, observationEndDate).ToArray();
|
|
}
|
|
}
|
|
|
|
var asianType = (AsianType)Enum.Parse(typeof(AsianType), param.payoffType);
|
|
|
|
if (observationDates.Length <= 1 && asianType != AsianType.EnhancedArithmeticAverage)
|
|
{
|
|
throw new ServiceException("均价起算日不合理");
|
|
}
|
|
|
|
var fixingValues = QdpHelper.ParseFixingsFromString(param.fixings);
|
|
|
|
var optionDayCount = CalculatorHelper.GetTradeDayCount();
|
|
|
|
var pricingTOverride = QdpCalendarHelper.CalculateTFromDays(param.timeToMaturityDays, optionDayCount, optionStartDate.DateTime);
|
|
|
|
var asianOption = new AsianOption(
|
|
startDate: optionStartDate,
|
|
maturityDate: underlyingMaturityDate,
|
|
exercise: (OptionExercise)Enum.Parse(typeof(OptionExercise), param.exerciseType),
|
|
optionType: param.optionType,
|
|
asianType: asianType,
|
|
strikeStyle: (StrikeStyle)Enum.Parse(typeof(StrikeStyle), param.strikeStyle),
|
|
strike: param.strike,
|
|
underlyingInstrumentType: (InstrumentType)Enum.Parse(typeof(InstrumentType), param.underlyingInstrumentType),
|
|
calendar: CalendarImpl.Get("chn"),
|
|
dayCount: optionDayCount.ToDayCountImpl(),
|
|
payoffCcy: CurrencyCode.CNY,
|
|
settlementCcy: CurrencyCode.CNY,
|
|
exerciseDates: exerciseDates,
|
|
observationDates: observationDates,
|
|
fixings: fixingValues,
|
|
strikeGearingFator: param.strikeGearingFactor,
|
|
participationRate: param.participationRate,
|
|
notional: param.notional,
|
|
settlementGap: null,
|
|
optionPremiumPaymentDate: null,
|
|
optionPremium: 0,
|
|
isMoneynessOption: param.isMoneynessOption,
|
|
initialSpotPrice: param.initialSpotPrice,
|
|
dividends: param.dividends,
|
|
hasNightMarket: param.hasNightMarket,
|
|
commodityFuturesPreciseTimeMode: param.preciseTimeMode,
|
|
pricingToverride: pricingTOverride,
|
|
riskFreeRateOverride: param.riskFreeRate,
|
|
dividendRateOverride: param.dividendRate,
|
|
isAnnualized: param.isAnnualized,
|
|
annualizedFactor: param.annualizedFactor,
|
|
enhancedPrice: param.enhancedPrice)
|
|
{
|
|
SettleMode = param.settleMode == "AtEnd" ? SettleMode.AtEnd : SettleMode.AtHit,
|
|
UnderlyingTickers = param.underlyingTickers
|
|
};
|
|
|
|
if (string.IsNullOrWhiteSpace(param.tradeId))
|
|
{
|
|
param.tradeId = Guid.NewGuid().ToString();
|
|
}
|
|
return new AsianOptionTrade(
|
|
tradeId: param.tradeId,
|
|
tradeDate: param.tradeDate,
|
|
startDate: asianOption.StartDate,
|
|
maturityDate: asianOption.ExerciseDates.Last(),
|
|
tradeType: QdpConverter.ConvertTradeType(param.buysell),
|
|
notional: param.notional,
|
|
price: 0.0,
|
|
option: asianOption)
|
|
{
|
|
ValuationParameters = new OptionValuationParameters(MarketProxy.RiskFreeDiscountCurve, MarketProxy.ConstantZeroCurve,
|
|
param.volSurfaceNames?.FirstOrDefault(), asianOption.UnderlyingTickers[0]),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 亚式期权
|
|
/// </summary>
|
|
public static AsianOptionTrade GetAsianOptionTrade(OtcTradeBase td, trade_asian_option asianOption, OptionTradeParamRequest request = null)
|
|
{
|
|
var param = GetAsianOptionTradeParam(td, asianOption, request);
|
|
return GetAsianOptionTrade(param);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 亚式合成价差期权
|
|
/// </summary>
|
|
public static AsianSyntheticNormalSpreadOptionTrade GetAsianSSpreadOptionTrade(AsianOptionTradeParam param)
|
|
{
|
|
CheckOptionTradeParam(param);
|
|
|
|
var exercise = QdpConverter.ConvertExerciseType(param.exerciseType);
|
|
var optionStartDate = param.startDate;
|
|
var temp_exerciseDate = param.exerciseDate;
|
|
|
|
if (temp_exerciseDate < optionStartDate)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Date[] exerciseDates, observationDates;
|
|
var calendar = CalendarImpl.Get("chn");
|
|
if (exercise == OptionExercise.American)
|
|
{
|
|
exerciseDates = calendar.BizDaysBetweenDatesInclEndDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
}
|
|
else
|
|
{
|
|
exerciseDates = new Date[] { temp_exerciseDate };
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(param.observationDateStr))
|
|
{
|
|
observationDates = QdpHelper.ParseObservationDate(param.observationDateStr);
|
|
}
|
|
else
|
|
{
|
|
var observationStartDate = param.averagingPeriodStartDate ?? optionStartDate;
|
|
observationDates = calendar.BizDaysBetweenDatesExcluStartDay(observationStartDate, temp_exerciseDate).Union(new[] { temp_exerciseDate }).ToArray();
|
|
}
|
|
|
|
var fixingValues = QdpHelper.ParseFixingsFromString(param.fixings);
|
|
var optionDayCount = CalculatorHelper.GetTradeDayCount();
|
|
var pricingTOverride = QdpCalendarHelper.CalculateTFromDays(param.timeToMaturityDays, optionDayCount, optionStartDate.DateTime);
|
|
|
|
var asianOption = new AsianSyntheticNormalSpreadOption(
|
|
startDate: optionStartDate,
|
|
exercise: (OptionExercise)Enum.Parse(typeof(OptionExercise), param.exerciseType),
|
|
optionType: param.optionType,
|
|
asianType: (AsianType)Enum.Parse(typeof(AsianType), param.payoffType),
|
|
strikeStyle: (StrikeStyle)Enum.Parse(typeof(StrikeStyle), param.strikeStyle),
|
|
strike: param.strike,
|
|
underlyingInstrumentType: (InstrumentType)Enum.Parse(typeof(InstrumentType), param.underlyingInstrumentType),
|
|
calendar: CalendarImpl.Get("chn"),
|
|
dayCount: optionDayCount.ToDayCountImpl(),
|
|
payoffCcy: CurrencyCode.CNY,
|
|
settlementCcy: CurrencyCode.CNY,
|
|
exerciseDates: exerciseDates,
|
|
observationDates: observationDates,
|
|
fixings: fixingValues,
|
|
strikeGearingFactor: 1.0,
|
|
participationRate: param.participationRate,
|
|
notional: param.notional,
|
|
settlementGap: null,
|
|
optionPremiumPaymentDate: null,
|
|
optionPremium: 0,
|
|
isMoneynessOption: param.isMoneynessOption,
|
|
initialSpotPrice: param.initialSpotPrice,
|
|
dividends: param.dividends,
|
|
hasNightMarket: param.hasNightMarket,
|
|
commodityFuturesPreciseTimeMode: param.preciseTimeMode,
|
|
pricingToverride: pricingTOverride,
|
|
riskFreeRateOverride: param.riskFreeRate,
|
|
dividendRateOverride: param.dividendRate,
|
|
isAnnualized: param.isAnnualized,
|
|
annualizedFactor: param.annualizedFactor)
|
|
{
|
|
SettleMode = param.settleMode == "AtEnd" ? SettleMode.AtEnd : SettleMode.AtHit,
|
|
UnderlyingTickers = param.underlyingTickers
|
|
};
|
|
|
|
if (string.IsNullOrWhiteSpace(param.tradeId))
|
|
{
|
|
param.tradeId = Guid.NewGuid().ToString();
|
|
}
|
|
|
|
return new AsianSyntheticNormalSpreadOptionTrade(
|
|
tradeId: param.tradeId,
|
|
tradeDate: param.tradeDate,
|
|
startDate: asianOption.StartDate,
|
|
maturityDate: asianOption.ExerciseDates.Last(),
|
|
tradeType: QdpConverter.ConvertTradeType(param.buysell),
|
|
notional: param.notional,
|
|
price: 0.0,
|
|
option: asianOption)
|
|
{
|
|
ValuationParameters = new OptionValuationParameters(MarketProxy.RiskFreeDiscountCurve, MarketProxy.ConstantZeroCurve,
|
|
param.volSurfaceNames?.FirstOrDefault(), asianOption.UnderlyingTickers[0])
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 亚式合成价差期权
|
|
/// </summary>
|
|
public static AsianSyntheticNormalSpreadOptionTrade GetAsianSSpreadOptionTrade(OtcTradeBase td, trade_asian_option asianOption, OptionTradeParamRequest request = null)
|
|
{
|
|
var param = GetAsianOptionTradeParam(td, asianOption, request);
|
|
return GetAsianSSpreadOptionTrade(param);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 亚式期权
|
|
/// </summary>
|
|
/// <param name="spotPrice">标的现价</param>
|
|
public static AsianOptionTradeParam GetAsianOptionTradeParam(OtcTradeBase td, trade_asian_option asianOption, OptionTradeParamRequest request)
|
|
{
|
|
if (asianOption == null)
|
|
{
|
|
throw new TradeCalcExpception(td.id, "未获取到对应的亚式期权交易数据");
|
|
}
|
|
|
|
var result = BuildTradeParamBase<AsianOptionTradeParam>(td, request);
|
|
|
|
result.payoffType = asianOption.PayoffType;
|
|
result.settleMode = asianOption.SettleMode;
|
|
result.strikeStyle = asianOption.StrikeType;
|
|
result.strikeGearingFactor = asianOption.StrikeGearingFactor ?? 1;
|
|
|
|
result.fixings = request?.fixings;
|
|
|
|
//在计算的时候判断
|
|
//result.fixings = TradeCalcHelper.CheckAsiaFixings(td,asianOption,request.fixings,spotPrice);
|
|
|
|
result.averagingPeriodStartDate = asianOption.AveragingPeriodStartDate ?? td.TradeDate ?? DateTime.MaxValue;
|
|
result.observationDateStr = asianOption.ObservationDates;
|
|
result.enhancedPrice = asianOption.EnhancedPrice;
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----二元期权----
|
|
|
|
/// <summary>
|
|
/// 二元期权
|
|
/// </summary>
|
|
public static BinaryOptionTrade GetBinaryOptionTrade(BinaryOptionTradeParam param)
|
|
{
|
|
CheckOptionTradeParam(param);
|
|
|
|
var exercise = QdpConverter.ConvertExerciseType(param.exerciseType);
|
|
var optionStartDate = param.startDate;
|
|
var underlyingMaturityDate = param.endDate;
|
|
var rebateType = (string.IsNullOrEmpty(param.binaryRebateType) ? "AtEnd" : param.binaryRebateType).ToBinaryRebateType();
|
|
var temp_exerciseDate = param.exerciseDate;
|
|
|
|
if (temp_exerciseDate < optionStartDate)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Date[] exerciseDates, observationDates;
|
|
exerciseDates = new Date[] { temp_exerciseDate };
|
|
observationDates = new Date[] { temp_exerciseDate };
|
|
|
|
var barrierObservationDates = QdpHelper.ParseObservationDate(param.observationDateStr);
|
|
if (barrierObservationDates != null)
|
|
{
|
|
observationDates = barrierObservationDates;
|
|
}
|
|
|
|
var optionDayCount = CalculatorHelper.GetTradeDayCount();
|
|
var pricingTOverride = QdpCalendarHelper.CalculateTFromDays(param.timeToMaturityDays, optionDayCount, optionStartDate.DateTime);
|
|
|
|
var binaryOption = new BinaryOption(
|
|
startDate: optionStartDate,
|
|
maturityDate: underlyingMaturityDate,
|
|
exercise: exercise,
|
|
optionType: param.optionType,
|
|
strike: param.strike,
|
|
underlyingProductType: (InstrumentType)Enum.Parse(typeof(InstrumentType), param.underlyingInstrumentType),
|
|
binaryOptionPayoffType: (BinaryOptionPayoffType)Enum.Parse(typeof(BinaryOptionPayoffType), param.payoffType),
|
|
cashOrNothingAmount: param.cashOrNothingAmount,
|
|
calendar: CalendarImpl.Get("chn"),
|
|
dayCount: optionDayCount.ToDayCountImpl(),
|
|
payoffCcy: CurrencyCode.CNY,
|
|
settlementCcy: CurrencyCode.CNY,
|
|
exerciseDates: exerciseDates,
|
|
observationDates: observationDates,
|
|
notional: param.notional,
|
|
settlementGap: null,
|
|
optionPremiumPaymentDate: null,
|
|
optionPremium: 0.0,
|
|
isMoneynessOption: param.isMoneynessOption,
|
|
initialSpotPrice: param.initialSpotPrice,
|
|
upperBarrier: param.upperBarrier,
|
|
cashOrNothingAmountHigh: param.cashOrNothingAmountHigh,
|
|
isDiscreteMonitored: param.isDiscreteMonitored,
|
|
rebateAnnualizedAtKO: param.rebateAnnualizedAtKO,
|
|
rebateDayCount: string.IsNullOrWhiteSpace(param.rebateDayCount) ? new Act365() : param.rebateDayCount.ToDayCountImpl(),
|
|
dividends: param.dividends,
|
|
binaryRebateType: rebateType,
|
|
pricingToverride: pricingTOverride,
|
|
hasNightMarket: param.hasNightMarket,
|
|
commodityFuturesPreciseTimeMode: param.preciseTimeMode,
|
|
riskFreeRateOverride: param.riskFreeRate,
|
|
dividendRateOverride: param.dividendRate,
|
|
participationRate: param.participationRate,
|
|
isAnnualized: param.isAnnualized,
|
|
annualizedFactor: param.annualizedFactor)
|
|
{
|
|
UnderlyingTickers = param.underlyingTickers
|
|
};
|
|
|
|
if (string.IsNullOrWhiteSpace(param.tradeId))
|
|
{
|
|
param.tradeId = Guid.NewGuid().ToString();
|
|
}
|
|
return new BinaryOptionTrade(
|
|
tradeId: param.tradeId,
|
|
tradeDate: param.tradeDate,
|
|
startDate: binaryOption.StartDate,
|
|
maturityDate: binaryOption.ExerciseDates.Last(),
|
|
tradeType: QdpConverter.ConvertTradeType(param.buysell),
|
|
notional: param.notional,
|
|
price: 0.0,
|
|
option: binaryOption)
|
|
{
|
|
ValuationParameters = new OptionValuationParameters(MarketProxy.RiskFreeDiscountCurve,
|
|
MarketProxy.ConstantZeroCurve, param.volSurfaceNames?.FirstOrDefault(), binaryOption.UnderlyingTickers[0]),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 二元期权
|
|
/// </summary>
|
|
public static BinaryOptionTrade GetBinaryOptionTrade(OtcTradeBase td, trade_binary_option binaryOption, OptionTradeParamRequest request = null)
|
|
{
|
|
var param = GetBinaryOptionTradeParam(td, binaryOption, request);
|
|
return GetBinaryOptionTrade(param);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 二元期权
|
|
/// </summary>
|
|
public static BinaryOptionTradeParam GetBinaryOptionTradeParam(OtcTradeBase td, trade_binary_option binaryOption, OptionTradeParamRequest request)
|
|
{
|
|
if (binaryOption == null)
|
|
{
|
|
throw new TradeCalcExpception(td.id, "未获取到对应的二元期权交易数据");
|
|
}
|
|
|
|
var result = BuildTradeParamBase<BinaryOptionTradeParam>(td, request);
|
|
|
|
result.payoffType = binaryOption.PayoffType;
|
|
result.upperBarrier = binaryOption.UpperBarrier ?? double.NaN;
|
|
|
|
if (td.IsUsePremiumRate == true)
|
|
{
|
|
result.cashOrNothingAmount = (binaryOption.CashOrNothingAmountRate ?? 0) * (td.SpotPrice ?? 0);
|
|
result.cashOrNothingAmountHigh = (binaryOption.CashOrNothingAmountHighRate ?? 0) * (td.SpotPrice ?? 0);
|
|
}
|
|
else
|
|
{
|
|
result.cashOrNothingAmount = binaryOption.CashOrNothingAmount ?? 0;
|
|
result.cashOrNothingAmountHigh = binaryOption.CashOrNothingAmountHigh ?? 0;
|
|
}
|
|
|
|
result.binaryRebateType = binaryOption.RebateType;
|
|
result.isDiscreteMonitored = binaryOption.IsDiscreteMonitored;
|
|
|
|
result.binaryOptionReplicationStrategy = valuedateBLL.BinaryReplicationStrategy;
|
|
result.replicationShiftSize = binaryOption.Offset ?? (td.Strike ?? 0) * 0.01;
|
|
|
|
result.observationDateStr = binaryOption.ObservationDates;
|
|
|
|
result.rebateDayCount = binaryOption.RebateDayCount;
|
|
result.rebateAnnualizedAtKO = binaryOption.RebateAnnualizedAtKO;
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----障碍期权----
|
|
|
|
/// <summary>
|
|
/// 障碍期权
|
|
/// </summary>
|
|
public static BarrierOptionTrade GetBarrierOptionTrade(BarrierOptionTradeParam param, string valueDateStr = null)
|
|
{
|
|
CheckOptionTradeParam(param);
|
|
|
|
var exercise = QdpConverter.ConvertExerciseType(param.exerciseType);
|
|
//这里不容易理解:为了实现在定价界面的“交易日为过去日期和未来日期的交易,应把交易日当天完整计算在内”的逻辑,
|
|
//交易日期(通常被往前推了一天)有可能小于开始日,这种情况下应该把开始日设置成和估值日一样,否则从交易日到第一个观察日的时间长度就不准确了
|
|
var optionStartDate = param.startDate;
|
|
if (valueDateStr != null)
|
|
{
|
|
var tradeDate = new Date(DateTime.Parse(valueDateStr));
|
|
if (tradeDate < param.startDate)
|
|
{
|
|
optionStartDate = tradeDate;
|
|
}
|
|
}
|
|
|
|
var underlyingMaturityDate = param.endDate;
|
|
var temp_exerciseDate = param.exerciseDate;
|
|
|
|
if (temp_exerciseDate < optionStartDate)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Date[] exerciseDates, observationDates;
|
|
if (exercise == OptionExercise.American)
|
|
{
|
|
exerciseDates = CalendarImpl.Get("chn").BizDaysBetweenDatesInclEndDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
}
|
|
else
|
|
{
|
|
exerciseDates = new Date[] { temp_exerciseDate };
|
|
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).Union(new[] { temp_exerciseDate }).ToArray();
|
|
}
|
|
|
|
var barrierObservationDates = QdpHelper.ParseObservationDate(param.observationDateStr);
|
|
if (barrierObservationDates != null)
|
|
{
|
|
observationDates = barrierObservationDates;
|
|
}
|
|
|
|
var optionDayCount = CalculatorHelper.GetTradeDayCount();
|
|
var pricingTOverride = QdpCalendarHelper.CalculateTFromDays(param.timeToMaturityDays, optionDayCount, optionStartDate.DateTime);
|
|
|
|
var barrierOption = new BarrierOption(
|
|
startDate: optionStartDate,
|
|
maturityDate: underlyingMaturityDate,
|
|
exercise: exercise,
|
|
optionType: param.optionType,
|
|
strike: param.strike,
|
|
rebate: param.rebate,
|
|
coupon: 0.0,
|
|
participationRate: param.participationRate,
|
|
barrierType: (BarrierType)Enum.Parse(typeof(BarrierType), param.barrierType),
|
|
lowerBarrier: param.barrierPrice,
|
|
upperBarrier: param.upperBarrierPrice,
|
|
isDiscreteMonitored: param.isDiscrete,
|
|
underlyingType: (InstrumentType)Enum.Parse(typeof(InstrumentType), param.underlyingInstrumentType),
|
|
calendar: CalendarImpl.Get("chn"),
|
|
dayCount: optionDayCount.ToDayCountImpl(),
|
|
payoffCcy: CurrencyCode.CNY,
|
|
settlementCcy: CurrencyCode.CNY,
|
|
exerciseDates: exerciseDates,
|
|
observationDates: observationDates,
|
|
barrierObservationDates: barrierObservationDates,
|
|
highRebate: param.rebateHigh,
|
|
notional: param.notional,
|
|
settlementGap: null,
|
|
optionPremiumPaymentDate: null,
|
|
optionPremium: 0.0,
|
|
isMoneynessOption: param.isMoneynessOption,
|
|
initialSpotPrice: param.initialSpotPrice,
|
|
dividends: param.dividends,
|
|
position: QdpConverter.ConvertPosition(param.buysell),
|
|
barrierShift: param.barrierShift,
|
|
rebateAnnualizedAtKO: param.rebateAnnualizedAtKO,
|
|
rebateDayCount: string.IsNullOrWhiteSpace(param.rebateDayCount) ? new Act365() : param.rebateDayCount.ToDayCountImpl(),
|
|
pricingToverride: pricingTOverride,
|
|
rebateType: (string.IsNullOrEmpty(param.rebateType) ? "AtEnd" : param.rebateType).ToBinaryRebateType(),
|
|
barrierStatus: QdpConverter.ConvertBarrierStatus(param.barrierStatus),
|
|
hasNightMarket: param.hasNightMarket,
|
|
commodityFuturesPreciseTimeMode: param.preciseTimeMode,
|
|
riskFreeRateOverride: param.riskFreeRate,
|
|
dividendRateOverride: param.dividendRate,
|
|
isAnnualized: param.isAnnualized,
|
|
annualizedFactor: param.annualizedFactor)
|
|
{
|
|
UnderlyingTickers = param.underlyingTickers
|
|
};
|
|
|
|
if (string.IsNullOrWhiteSpace(param.tradeId))
|
|
{
|
|
param.tradeId = Guid.NewGuid().ToString();
|
|
}
|
|
|
|
return new BarrierOptionTrade(
|
|
tradeId: param.tradeId,
|
|
tradeDate: param.tradeDate,
|
|
startDate: barrierOption.StartDate,
|
|
maturityDate: barrierOption.ExerciseDates.Last(),
|
|
tradeType: QdpConverter.ConvertTradeType(param.buysell),
|
|
notional: param.notional,
|
|
price: 0.0,
|
|
option: barrierOption)
|
|
{
|
|
ValuationParameters = new OptionValuationParameters(MarketProxy.RiskFreeDiscountCurve,
|
|
MarketProxy.ConstantZeroCurve, param.volSurfaceNames?.FirstOrDefault(), barrierOption.UnderlyingTickers[0]),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 障碍期权
|
|
/// </summary>
|
|
public static BarrierOptionTrade GetBarrierOptionTrade(OtcTradeBase td, trade_barrier_option barrierOption, OptionTradeParamRequest request = null)
|
|
{
|
|
var param = GetBarrierOptionTradeParam(td, barrierOption, request);
|
|
return GetBarrierOptionTrade(param);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 障碍期权
|
|
/// </summary>
|
|
public static BarrierOptionTradeParam GetBarrierOptionTradeParam(OtcTradeBase td, trade_barrier_option barrierOption, OptionTradeParamRequest request)
|
|
{
|
|
if (barrierOption == null)
|
|
{
|
|
throw new TradeCalcExpception(td.id, "未获取到对应的障碍期权交易数据");
|
|
}
|
|
|
|
var result = BuildTradeParamBase<BarrierOptionTradeParam>(td, request);
|
|
|
|
result.barrierType = barrierOption.BarrierTypeEn;
|
|
result.barrierPrice = barrierOption.BarrierPrice ?? 0;
|
|
result.upperBarrierPrice = barrierOption.UpperBarrierPrice ?? 0;
|
|
|
|
result.isDiscrete = barrierOption.IsDiscrete;
|
|
result.observationDateStr = barrierOption.ObservationDates;
|
|
|
|
if (td.IsUsePremiumRate == true)
|
|
{
|
|
result.rebate = (barrierOption.RebateRate ?? 0) * (td.SpotPrice ?? 0);
|
|
}
|
|
else
|
|
{
|
|
result.rebate = barrierOption.Rebate ?? 0;
|
|
}
|
|
|
|
if (td.IsUsePremiumRate == true)
|
|
{
|
|
result.rebateHigh = (barrierOption.RebateHighRate ?? double.NaN) * (td.SpotPrice ?? 0);
|
|
}
|
|
else
|
|
{
|
|
result.rebateHigh = barrierOption.RebateHigh ?? double.NaN;
|
|
}
|
|
|
|
result.rebateType = barrierOption.RebateType;
|
|
|
|
result.barrierShift = barrierOption.BarrierShift ?? 0.0;
|
|
result.barrierStatus = barrierOption.KnockInOutStatus;
|
|
result.rebateAnnualizedAtKO = barrierOption.RebateAnnualizedAtKO;
|
|
result.rebateDayCount = barrierOption.RebateDayCount;
|
|
|
|
//if (result.rebateAnnualizedAtKO)
|
|
//{
|
|
// var dayCount = string.IsNullOrWhiteSpace(result.rebateDayCount) ? new Act365() : result.rebateDayCount.ToDayCountImpl();
|
|
// result.rebate /= dayCount.CalcDayCountFraction(result.startDate, result.endDate);
|
|
//}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----双鲨期权----
|
|
|
|
/// <summary>
|
|
/// 双鲨期权
|
|
/// </summary>
|
|
public static DoubleSharkFinOptionTrade GetDoubleSharkFinOptionTrade(DoubleSharkFinOptionTradeParam param)
|
|
{
|
|
CheckOptionTradeParam(param);
|
|
|
|
var exercise = QdpConverter.ConvertExerciseType(param.exerciseType);
|
|
var optionStartDate = param.startDate;
|
|
var underlyingMaturityDate = param.endDate;
|
|
var temp_exerciseDate = param.exerciseDate;
|
|
|
|
if (temp_exerciseDate < optionStartDate)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Date[] exerciseDates;
|
|
var observationDates = QdpHelper.ParseObservationDate(param.observationDateStr);
|
|
if (exercise == OptionExercise.American)
|
|
{
|
|
exerciseDates = CalendarImpl.Get("chn").BizDaysBetweenDatesInclEndDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
if (observationDates == null)
|
|
{
|
|
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
exerciseDates = new Date[] { temp_exerciseDate };
|
|
if (observationDates == null)
|
|
{
|
|
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).Union(new[] { temp_exerciseDate }).ToArray();
|
|
}
|
|
}
|
|
|
|
var optionDayCount = CalculatorHelper.GetTradeDayCount();
|
|
var pricingTOverride = QdpCalendarHelper.CalculateTFromDays(param.timeToMaturityDays, optionDayCount, optionStartDate.DateTime);
|
|
var lowRebateValue = double.IsNaN(param.rebate) ? 0.0 : param.rebate;
|
|
var highReateValue = double.IsNaN(param.rebateHigh) ? 0.0 : param.rebateHigh;
|
|
|
|
var doubleSharkFinOption = new DoubleSharkFinOption(
|
|
strikeHigh: param.strikeHigh,
|
|
strikeLow: param.strikeLow,
|
|
barrierHigh: Enumerable.Repeat(param.barrierHigh, observationDates.Length).ToArray(),
|
|
barrierLow: Enumerable.Repeat(param.barrierLow, observationDates.Length).ToArray(),
|
|
rebateLow: Enumerable.Repeat(lowRebateValue, observationDates.Length).ToArray(),
|
|
rebateHigh: Enumerable.Repeat(highReateValue, observationDates.Length).ToArray(),
|
|
rebateType: (string.IsNullOrEmpty(param.rebateType) ? "AtEnd" : param.rebateType).ToBinaryRebateType(),
|
|
observationDates: observationDates,
|
|
startDate: optionStartDate,
|
|
maturityDate: underlyingMaturityDate,
|
|
participationRate: param.participationRate,
|
|
isDiscreteMonitored: param.isDiscrete,
|
|
underlyingInstrumentType: (InstrumentType)Enum.Parse(typeof(InstrumentType), param.underlyingInstrumentType),
|
|
calendar: CalendarImpl.Get("chn"),
|
|
dayCount: optionDayCount.ToDayCountImpl(),
|
|
payoffCcy: CurrencyCode.CNY,
|
|
settlementCcy: CurrencyCode.CNY,
|
|
exerciseDates: exerciseDates,
|
|
callParticipationRate: param.callParticipationRate,
|
|
putParticipationRate: param.putParticipationRate,
|
|
isMoneynessOption: param.isMoneynessOption,
|
|
initialSpotPrice: param.initialSpotPrice,
|
|
notional: param.notional,
|
|
settlementGap: null,
|
|
optionPremiumPaymentDate: null,
|
|
optionPremium: 0,
|
|
pricingToverride: pricingTOverride,
|
|
barrierStatus: QdpConverter.ConvertBarrierStatus(param.barrierStatus),
|
|
hasNightMarket: param.hasNightMarket,
|
|
commodityFuturesPreciseTimeMode: param.preciseTimeMode,
|
|
riskFreeRateOverride: param.riskFreeRate,
|
|
dividendRateOverride: param.dividendRate,
|
|
isAnnualized: param.isAnnualized,
|
|
annualizedFactor: param.annualizedFactor)
|
|
{
|
|
UnderlyingTickers = param.underlyingTickers
|
|
};
|
|
|
|
if (string.IsNullOrWhiteSpace(param.tradeId))
|
|
{
|
|
param.tradeId = Guid.NewGuid().ToString();
|
|
}
|
|
return new DoubleSharkFinOptionTrade(
|
|
tradeId: param.tradeId,
|
|
tradeDate: param.tradeDate,
|
|
startDate: doubleSharkFinOption.StartDate,
|
|
maturityDate: doubleSharkFinOption.ExerciseDates.Last(),
|
|
tradeType: QdpConverter.ConvertTradeType(param.buysell),
|
|
notional: param.notional,
|
|
price: 0.0,
|
|
option: doubleSharkFinOption)
|
|
{
|
|
ValuationParameters = new OptionValuationParameters(MarketProxy.RiskFreeDiscountCurve,
|
|
MarketProxy.ConstantZeroCurve, param.volSurfaceNames?.FirstOrDefault(), doubleSharkFinOption.UnderlyingTickers[0]),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 双鲨期权
|
|
/// </summary>
|
|
public static DoubleSharkFinOptionTrade GetDoubleSharkFinOptionTrade(OtcTradeBase td, trade_double_sharkfin_option dbsharkOption, OptionTradeParamRequest request = null)
|
|
{
|
|
var param = GetDoubleSharkFinOptionTradeParam(td, dbsharkOption, request);
|
|
return GetDoubleSharkFinOptionTrade(param);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 双鲨期权
|
|
/// </summary>
|
|
public static DoubleSharkFinOptionTradeParam GetDoubleSharkFinOptionTradeParam(OtcTradeBase td, trade_double_sharkfin_option dbsharkOption, OptionTradeParamRequest request)
|
|
{
|
|
if (dbsharkOption == null)
|
|
{
|
|
throw new TradeCalcExpception(td.id, "未获取到对应的双鲨期权交易数据");
|
|
}
|
|
|
|
var result = BuildTradeParamBase<DoubleSharkFinOptionTradeParam>(td, request);
|
|
|
|
result.strikeLow = td.Strike ?? 0.0;
|
|
result.strikeHigh = dbsharkOption.StrikeHigh ?? td.Strike ?? 0.0;
|
|
|
|
result.barrierHigh = dbsharkOption.BarrierHigh;
|
|
result.barrierLow = dbsharkOption.BarrierLow;
|
|
|
|
if (td.IsUsePremiumRate == true)
|
|
{
|
|
result.rebate = (dbsharkOption.RebateRate ?? 0) * (td.SpotPrice ?? 0);
|
|
result.rebateHigh = (dbsharkOption.RebateHighRate ?? 0) * (td.SpotPrice ?? 0);
|
|
}
|
|
else
|
|
{
|
|
result.rebate = dbsharkOption.Rebate ?? 0;
|
|
result.rebateHigh = dbsharkOption.RebateHigh ?? 0;
|
|
}
|
|
|
|
result.rebateType = dbsharkOption.RebateType;
|
|
|
|
result.isDiscrete = dbsharkOption.IsDiscrete;
|
|
result.observationDateStr = dbsharkOption.ObservationDates;
|
|
|
|
result.callParticipationRate = dbsharkOption.CallParticipationRate ?? 1.0;
|
|
result.putParticipationRate = dbsharkOption.PutParticipationRate ?? 1.0;
|
|
|
|
result.barrierStatus = dbsharkOption.KnockInOutStatus;
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----雪球期权----
|
|
|
|
/// <summary>
|
|
/// 雪球期权
|
|
/// </summary>
|
|
/// <param name="param">构建参数</param>
|
|
/// <param name="snowballModel">雪球期权类型</param>
|
|
/// <returns></returns>
|
|
public static SnowballTrade GetSnowballOptionTrade(SnowballOptionTradeParam param
|
|
, SnowBallOptionMode snowballMold = SnowBallOptionMode.Auto)
|
|
{
|
|
CheckOptionTradeParam(param);
|
|
|
|
var exercise = QdpConverter.ConvertExerciseType(param.exerciseType);
|
|
var optionStartDate = param.startDate;
|
|
var underlyingMaturityDate = param.endDate;
|
|
var temp_exerciseDate = param.exerciseDate;
|
|
|
|
if (temp_exerciseDate < optionStartDate)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var observationDates = QdpHelper.ParseObservationDate(param.observationDateStr);
|
|
|
|
//敲出观察
|
|
var customizedResults = QdpHelper.ParseAutocallCustomizedInfo(param.koObservationDateStr);
|
|
var koObservationDates = customizedResults.Item1;
|
|
var customizedKOBarriers = customizedResults.Item2;
|
|
var customizedCoupons = customizedResults.Item3;
|
|
|
|
Date[] exerciseDates;
|
|
if (exercise == OptionExercise.American)
|
|
{
|
|
exerciseDates = CalendarImpl.Get("chn").BizDaysBetweenDatesInclEndDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
if (observationDates == null)
|
|
{
|
|
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
exerciseDates = new Date[] { temp_exerciseDate };
|
|
if (observationDates == null)
|
|
{
|
|
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).Union(new[] { temp_exerciseDate }).ToArray();
|
|
}
|
|
}
|
|
|
|
koObservationDates ??= QdpObservationHelper.GetDefaultKoObservationDatesForSnowbalV2(optionStartDate, temp_exerciseDate);
|
|
|
|
//票息日历规则
|
|
if (string.IsNullOrWhiteSpace(param.couponDayCount))
|
|
{
|
|
param.couponDayCount = "Act365";
|
|
}
|
|
|
|
//票息支付日期
|
|
var couponPaymentDates = QdpHelper.ParseObservationDate(param.couponPaymentDateStr);
|
|
|
|
var optionDayCount = CalculatorHelper.GetTradeDayCount();
|
|
var pricingToverride = QdpCalendarHelper.CalculateTFromDays(param.timeToMaturityDays, optionDayCount, optionStartDate.DateTime);
|
|
|
|
SimpleSnowball snowball;
|
|
|
|
if (snowballMold == SnowBallOptionMode.AnnualizedPremiumSnowball
|
|
|| snowballMold == SnowBallOptionMode.Auto && param.annualizedPremiumRate > 0)
|
|
{
|
|
snowball = new AnnualizedPremiumSnowball(
|
|
startDate: optionStartDate,
|
|
maturityDate: underlyingMaturityDate,
|
|
koBarrier: param.koBarrier,
|
|
kiBarrier: double.NaN,
|
|
coupon: param.coupon,
|
|
optionParticipationRate: 1.0, // 在OTC系统增加“期权参与率”字段之前,请保留一直传1.0
|
|
koObsDates: koObservationDates,
|
|
kiObsDates: observationDates,
|
|
observationDates: null,
|
|
notional: param.notional,
|
|
initialSpotPrice: param.initialSpotPrice,
|
|
underlyingType: (InstrumentType)Enum.Parse(typeof(InstrumentType), param.underlyingInstrumentType),
|
|
calendar: CalendarImpl.Get("chn"),
|
|
dayCount: optionDayCount.ToDayCountImpl(),
|
|
payoffCcy: CurrencyCode.CNY,
|
|
settlementCcy: CurrencyCode.CNY,
|
|
exerciseDates: exerciseDates,
|
|
exercise: OptionExercise.European,
|
|
isMoneynessOption: param.isMoneynessOption,
|
|
optionType: param.optionType,
|
|
barrierType: param.optionType == OptionType.Put ? BarrierType.DownAndOut : BarrierType.UpAndOut,
|
|
strike: 0,
|
|
spreadStrikeAtKO: param.spreadStrikeAtKO,
|
|
spreadStrikeAtMaturity: param.spreadStrikeAtMaturity,
|
|
fixedCoupon: param.isFixedCoupon,
|
|
kORebate: param.koRebate,
|
|
kOBarrierAdjustStep: param.koBarrierAdjustStep,
|
|
useOptionPayoffAtKO: param.useOptionPayoffAtKO,
|
|
useOptionPayoffAtMaturity: false,
|
|
annualizedOptionPayoff: param.annualizedOptionPayoff,
|
|
customizedKOBarriers: customizedKOBarriers,
|
|
customizedCoupons: customizedCoupons,
|
|
pricingToverride: pricingToverride,
|
|
barrierStatus: QdpConverter.ConvertBarrierStatus(param.barrierStatus),
|
|
hasNightMarket: param.hasNightMarket,
|
|
commodityFuturesPreciseTimeMode: param.preciseTimeMode,
|
|
riskFreeRateOverride: param.riskFreeRate,
|
|
dividendRateOverride: param.dividendRate,
|
|
annualizedFactor: param.annualizedFactor,
|
|
koStrike: param.koStrike,
|
|
couponDayCount: param.couponDayCount.ToDayCountImpl(),
|
|
koOptionType: param.koOptionType,
|
|
kiOptionType: param.kiOptionType,
|
|
couponPaymentDates: couponPaymentDates);
|
|
|
|
((AnnualizedPremiumSnowball)snowball).AnnualizedPremium = param.annualizedPremiumRate ?? 0;
|
|
}
|
|
else
|
|
{
|
|
snowball = new SimpleSnowball(
|
|
startDate: optionStartDate,
|
|
maturityDate: underlyingMaturityDate,
|
|
koBarrier: param.koBarrier,
|
|
kiBarrier: param.kiBarrier,
|
|
coupon: param.coupon,
|
|
optionParticipationRate: 1.0, // 在OTC系统增加“期权参与率”字段之前,请保留一直传1.0
|
|
koObsDates: koObservationDates,
|
|
kiObsDates: observationDates,
|
|
observationDates: null,
|
|
notional: param.notional,
|
|
initialSpotPrice: param.initialSpotPrice,
|
|
underlyingType: (InstrumentType)Enum.Parse(typeof(InstrumentType), param.underlyingInstrumentType),
|
|
calendar: CalendarImpl.Get("chn"),
|
|
dayCount: optionDayCount.ToDayCountImpl(),
|
|
payoffCcy: CurrencyCode.CNY,
|
|
settlementCcy: CurrencyCode.CNY,
|
|
exerciseDates: exerciseDates,
|
|
exercise: OptionExercise.European,
|
|
isMoneynessOption: param.isMoneynessOption,
|
|
optionType: param.optionType,
|
|
barrierType: param.optionType == OptionType.Put ? BarrierType.DownAndOut : BarrierType.UpAndOut,
|
|
strike: param.strike,
|
|
spreadStrikeAtKO: param.spreadStrikeAtKO,
|
|
spreadStrikeAtMaturity: param.spreadStrikeAtMaturity,
|
|
fixedCoupon: param.isFixedCoupon,
|
|
kORebate: param.koRebate,
|
|
kOBarrierAdjustStep: param.koBarrierAdjustStep,
|
|
useOptionPayoffAtKO: param.useOptionPayoffAtKO,
|
|
useOptionPayoffAtMaturity: param.useOptionPayoffAtMaturity,
|
|
annualizedOptionPayoff: param.annualizedOptionPayoff,
|
|
customizedKOBarriers: customizedKOBarriers,
|
|
customizedCoupons: customizedCoupons,
|
|
pricingToverride: pricingToverride,
|
|
barrierStatus: QdpConverter.ConvertBarrierStatus(param.barrierStatus),
|
|
hasNightMarket: param.hasNightMarket,
|
|
commodityFuturesPreciseTimeMode: param.preciseTimeMode,
|
|
riskFreeRateOverride: param.riskFreeRate,
|
|
dividendRateOverride: param.dividendRate,
|
|
annualizedFactor: param.annualizedFactor,
|
|
koStrike: param.koStrike,
|
|
couponDayCount: param.couponDayCount.ToDayCountImpl(),
|
|
koOptionType: param.koOptionType,
|
|
kiOptionType: param.kiOptionType,
|
|
couponPaymentDates: couponPaymentDates);
|
|
}
|
|
|
|
snowball.UnderlyingTickers = param.underlyingTickers;
|
|
|
|
if (string.IsNullOrWhiteSpace(param.tradeId))
|
|
{
|
|
param.tradeId = Guid.NewGuid().ToString();
|
|
}
|
|
|
|
return new SnowballTrade(
|
|
tradeId: param.tradeId,
|
|
tradeDate: param.tradeDate,
|
|
startDate: snowball.StartDate,
|
|
maturityDate: snowball.ExerciseDates.Last(),
|
|
tradeType: QdpConverter.ConvertTradeType(param.buysell),
|
|
notional: param.notional,
|
|
price: 0.0,
|
|
option: snowball)
|
|
{
|
|
ValuationParameters = new OptionValuationParameters(MarketProxy.RiskFreeDiscountCurve,
|
|
MarketProxy.ConstantZeroCurve, param.volSurfaceNames?.FirstOrDefault(), snowball.UnderlyingTickers[0]),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 雪球期权
|
|
/// </summary>
|
|
public static SnowballTrade GetSnowballOptionTrade(OtcTradeBase td, trade_snowball snowball,
|
|
OptionTradeParamRequest request = null, SnowBallOptionMode snowballMold = SnowBallOptionMode.Auto)
|
|
{
|
|
var tradeParam = GetSnowballTradeParam(td, snowball, request);
|
|
return GetSnowballOptionTrade(tradeParam, snowballMold);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 雪球期权
|
|
/// </summary>
|
|
public static SnowballOptionTradeParam GetSnowballTradeParam(OtcTradeBase td, trade_snowball snowball, OptionTradeParamRequest request)
|
|
{
|
|
if (snowball is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(snowball));
|
|
}
|
|
|
|
var result = BuildTradeParamBase<SnowballOptionTradeParam>(td, request);
|
|
|
|
//敲出设置
|
|
result.koBarrier = snowball.KOBarrier;
|
|
result.koObservationDateStr = snowball.KOObservationDates;
|
|
result.couponPaymentDateStr = snowball.CouponUsePaymentDate == true ? snowball.KOObservationSettleDates : null;
|
|
result.koRebate = snowball.KORebate;
|
|
result.isFixedCoupon = snowball.IsFixedCoupon;
|
|
result.couponDayCount = snowball.CouponDayCount;
|
|
result.useOptionPayoffAtKO = snowball.KOPayoffType != KOPayoffTypeEnum.Rebate;//敲出转期权
|
|
|
|
result.koOptionType = QdpConverter.ConvertAutocallKOOptionType(snowball.KOPayoffType, QdpConverter.ConvertCallPut(td.CallPut));
|
|
result.kiOptionType = QdpConverter.ConvertOptionType(snowball.KIPayoffType);
|
|
|
|
result.koStrike = snowball.KOPayoffType != KOPayoffTypeEnum.Rebate ? (snowball.SpreadStrikeAtKO1 ?? -1) : double.NaN;
|
|
result.spreadStrikeAtKO = snowball.SpreadStrikeAtKO ?? -1;
|
|
result.koBarrierAdjustStep = snowball.KOBarrierAdjustStep;
|
|
|
|
//敲入设置
|
|
result.kiBarrier = snowball.KIBarrier;
|
|
result.observationDateStr = snowball.ObservationDates;
|
|
result.useOptionPayoffAtMaturity = snowball.KIPayoffType != KIPayoffTypeEnum.None;//未敲出转期权
|
|
result.strike = snowball.SpreadStrikeAtMaturity1 ?? -1;
|
|
result.spreadStrikeAtMaturity = snowball.SpreadStrikeAtMaturity ?? -1;
|
|
|
|
//红利票息
|
|
result.coupon = snowball.Coupon > 0 ? snowball.Coupon : 0;
|
|
|
|
//保本雪球
|
|
result.annualizedPremiumRate = snowball.AnnualizedPremiumRate;
|
|
|
|
//其他
|
|
result.barrierStatus = snowball.KnockInOutStatus;
|
|
|
|
//使用自身的年化系数设置
|
|
result.annualizedOptionPayoff = snowball.IsAnnualized2;
|
|
result.annualizedFactor = snowball.AnnualizeFactor2 ?? 1.0;
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 雪球期权
|
|
/// </summary>
|
|
public static SnowballSpecialistOptionTradeParam GetSnowballSpecialistTradeParam(OtcTradeBase td, trade_snowball snowball, OptionTradeParamRequest request)
|
|
{
|
|
if (snowball is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(snowball));
|
|
}
|
|
|
|
var result = BuildTradeParamBase<SnowballSpecialistOptionTradeParam>(td, request);
|
|
|
|
//敲出设置
|
|
result.koBarrier = snowball.KOBarrier;
|
|
result.koObservationDateStr = snowball.KOObservationDates;
|
|
result.couponPaymentDateStr = snowball.CouponUsePaymentDate == true ? snowball.KOObservationSettleDates : null;
|
|
result.koRebate = snowball.KORebate;
|
|
result.isFixedCoupon = snowball.IsFixedCoupon;
|
|
result.couponDayCount = snowball.CouponDayCount;
|
|
result.useOptionPayoffAtKO = snowball.KOPayoffType != KOPayoffTypeEnum.Rebate;//敲出转期权
|
|
|
|
result.koOptionType = QdpConverter.ConvertAutocallKOOptionType(snowball.KOPayoffType, QdpConverter.ConvertCallPut(td.CallPut));
|
|
result.kiOptionType = QdpConverter.ConvertOptionType(snowball.KIPayoffType);
|
|
|
|
result.koStrike = snowball.KOPayoffType != KOPayoffTypeEnum.Rebate ? (snowball.SpreadStrikeAtKO1 ?? -1) : double.NaN;
|
|
result.spreadStrikeAtKO = snowball.SpreadStrikeAtKO ?? -1;
|
|
result.koBarrierAdjustStep = snowball.KOBarrierAdjustStep;
|
|
|
|
//敲入设置
|
|
result.kiBarrier = snowball.KIBarrier;
|
|
result.observationDateStr = snowball.ObservationDates;
|
|
result.useOptionPayoffAtMaturity = snowball.KIPayoffType != KIPayoffTypeEnum.None;//未敲出转期权
|
|
result.strike = snowball.SpreadStrikeAtMaturity1 ?? -1;
|
|
result.spreadStrikeAtMaturity = snowball.SpreadStrikeAtMaturity ?? -1;
|
|
|
|
//红利票息
|
|
result.coupon = snowball.Coupon > 0 ? snowball.Coupon : 0;
|
|
|
|
//保本雪球
|
|
result.annualizedPremiumRate = snowball.AnnualizedPremiumRate;
|
|
|
|
//其他
|
|
result.barrierStatus = snowball.KnockInOutStatus;
|
|
|
|
//使用自身的年化系数设置
|
|
result.annualizedOptionPayoff = snowball.IsAnnualized2;
|
|
result.annualizedFactor = snowball.AnnualizeFactor2 ?? 1.0;
|
|
|
|
|
|
//预付金是否参与定价
|
|
result.PrepaymentUsed = snowball.PrepaymentUsed;
|
|
//预付金比例
|
|
result.PrepaymentRatio = snowball.PrepaymentRatio ?? 0;
|
|
//预付金返息率
|
|
result.PrepaymentInterestRate = snowball.PrepaymentInterestRate ?? 0;
|
|
//预付金折现率
|
|
result.PrepaymentConvertCashRate = snowball?.PrepaymentConvertCashRate ?? 0;
|
|
//终日是否计息
|
|
result.CouponIncludeEndDate = snowball.CouponIncludeEndDate ?? false;
|
|
//增强参与率
|
|
result.EnhancedParticipationRate = snowball.EnhancedParticipationRate ?? 0;
|
|
// 敲入参与率
|
|
result.KIParticipationRate = snowball.KIParticipationRate ?? 0;
|
|
// 保本比率
|
|
result.PrincipalProtectionRate = snowball.PrincipalProtectionRate ?? 0;
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----凤凰期权----
|
|
|
|
/// <summary>
|
|
/// 凤凰期权
|
|
/// </summary>
|
|
public static AutocallTrade GetAutocallOptionTrade(AutocallOptionTradeParam param)
|
|
{
|
|
CheckOptionTradeParam(param);
|
|
|
|
var exercise = QdpConverter.ConvertExerciseType(param.exerciseType);
|
|
var optionStartDate = param.startDate;
|
|
var underlyingMaturityDate = param.endDate;
|
|
var temp_exerciseDate = param.exerciseDate;
|
|
|
|
if (temp_exerciseDate < optionStartDate)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Date[] exerciseDates = null;
|
|
var observationDates = QdpHelper.ParseObservationDate(param.observationDateStr);
|
|
|
|
var customizedResults = QdpHelper.ParseAutocallCustomizedInfoV2(param.koObservationDateStr, param.callput, param.initialSpotPrice, param.isMoneynessOption);
|
|
var koObservationDates = customizedResults.Item1;
|
|
var customizedKOBarriers = customizedResults.Item2;
|
|
var customizedCoupons = customizedResults.Item3;
|
|
|
|
//Date[] koObservationDates = QDPHelper.ParseObservationDate(koObservationDateStr);
|
|
if (exercise == OptionExercise.American)
|
|
{
|
|
exerciseDates = CalendarImpl.Get("chn").BizDaysBetweenDatesInclEndDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
if (observationDates == null)
|
|
{
|
|
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
exerciseDates = new Date[] { temp_exerciseDate };
|
|
if (observationDates == null)
|
|
{
|
|
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).Union(new[] { temp_exerciseDate }).ToArray();
|
|
}
|
|
}
|
|
|
|
if (koObservationDates == null)
|
|
{
|
|
koObservationDates = QdpObservationHelper.GetDatesWithFixedTerm(optionStartDate, temp_exerciseDate, "1M", bdc: BusinessDayConvention.Following, alignEnd: true)
|
|
.Select(n => (Date)n).ToArray();
|
|
}
|
|
|
|
var optionDayCount = CalculatorHelper.GetTradeDayCount();
|
|
var pricingTOverride = QdpCalendarHelper.CalculateTFromDays(param.timeToMaturityDays, optionDayCount, optionStartDate.DateTime);
|
|
|
|
var autocall = new AutoCall(
|
|
startDate: optionStartDate,
|
|
maturityDate: underlyingMaturityDate,
|
|
koBarrier: param.koBarrier,
|
|
kiBarrier: param.kiBarrier,
|
|
couponBarrier: param.couponBarrier,
|
|
coupon: param.coupon,
|
|
couponPayAtMaturity: param.couponPayAtMaturity,
|
|
includeCouponAfterKI: param.includeCouponAfterKI,
|
|
optionParticipationRate: 1.0, // 在OTC系统增加“期权参与率”字段之前,请保留一直传1.0
|
|
koObsDates: koObservationDates,
|
|
kiObsDates: observationDates,
|
|
observationDates: null,
|
|
notional: param.notional,
|
|
initialSpotPrice: param.initialSpotPrice,
|
|
underlyingType: (InstrumentType)Enum.Parse(typeof(InstrumentType), param.underlyingInstrumentType),
|
|
calendar: CalendarImpl.Get("chn"),
|
|
dayCount: optionDayCount.ToDayCountImpl(),
|
|
couponDayCount: (param.couponDayCount.TrimToNull() ?? "Act365").ToDayCountImpl(),
|
|
payoffCcy: CurrencyCode.CNY,
|
|
settlementCcy: CurrencyCode.CNY,
|
|
exerciseDates: exerciseDates,
|
|
barrierType: param.optionType == OptionType.Put ? BarrierType.DownAndOut : BarrierType.UpAndOut,//20220415
|
|
strike: param.strike,
|
|
spreadStrike: param.spreadStrike,
|
|
fixedCoupon: param.isFixedCoupon,
|
|
annualizedOptionPayoff: param.annualizedOptionPayoff,
|
|
kiOptionType: param.kiOptionType.ToOptionType(),
|
|
customizedKOBarriers: customizedKOBarriers,
|
|
customizedCoupons: customizedCoupons,
|
|
isMoneynessOption: param.isMoneynessOption,
|
|
happenedObservations: param.happenedObservations?.Where(o => o != null).Select(o => new ObservationPayment()
|
|
{
|
|
StartDate = new Date(o.StartDate),
|
|
EndDate = new Date(o.EndDate),
|
|
CouponRate = o.CouponRate,
|
|
Notional = o.StockEqvNotional,
|
|
PaymentAmount = o.PaymentAmount,
|
|
PaymentDate = new Date(o.PaymentDate)
|
|
}).ToList(),
|
|
pricingToverride: pricingTOverride,
|
|
barrierStatus: QdpConverter.ConvertBarrierStatus(param.barrierStatus),
|
|
hasNightMarket: param.hasNightMarket,
|
|
commodityFuturesPreciseTimeMode: param.preciseTimeMode,
|
|
riskFreeRateOverride: param.riskFreeRate,
|
|
dividendRateOverride: param.dividendRate,
|
|
annualizedFactor: param.annualizedFactor)
|
|
{
|
|
UnderlyingTickers = param.underlyingTickers
|
|
};
|
|
|
|
if (string.IsNullOrWhiteSpace(param.tradeId))
|
|
{
|
|
param.tradeId = Guid.NewGuid().ToString();
|
|
}
|
|
return new AutocallTrade(
|
|
tradeId: param.tradeId,
|
|
tradeDate: param.tradeDate,
|
|
startDate: autocall.StartDate,
|
|
maturityDate: autocall.ExerciseDates.Last(),
|
|
tradeType: QdpConverter.ConvertTradeType(param.buysell),
|
|
notional: param.notional,//autocall.Notional
|
|
price: 0.0,
|
|
option: autocall)
|
|
{
|
|
ValuationParameters = new OptionValuationParameters(MarketProxy.RiskFreeDiscountCurve,
|
|
MarketProxy.ConstantZeroCurve, param.volSurfaceNames?.FirstOrDefault(), autocall.UnderlyingTickers[0]),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 凤凰期权
|
|
/// </summary>
|
|
public static AutocallTrade GetAutocallOptionTrade(OtcTradeBase td, trade_autocall autocall, OptionTradeParamRequest request = null)
|
|
{
|
|
var tradeParam = GetAutocallOptionTradeParam(td, autocall, request);
|
|
return GetAutocallOptionTrade(tradeParam);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 凤凰期权
|
|
/// </summary>
|
|
public static AutocallOptionTradeParam GetAutocallOptionTradeParam(OtcTradeBase td, trade_autocall autocall, OptionTradeParamRequest request)
|
|
{
|
|
if (autocall == null)
|
|
{
|
|
throw new TradeCalcExpception(td.id, "未获取到对应的凤凰期权交易数据");
|
|
}
|
|
|
|
var result = BuildTradeParamBase<AutocallOptionTradeParam>(td, request);
|
|
|
|
result.coupon = autocall.Coupon;
|
|
result.isFixedCoupon = autocall.IsFixedCoupon;
|
|
result.couponDayCount = autocall.CouponDayCount;
|
|
result.kiOptionType = QdpConverter.ConvertOptionType(autocall.KIPayoffType).ToString();
|
|
result.couponBarrier = autocall.CouponBarrier;
|
|
result.couponPayAtMaturity = autocall.CouponPayType != CouponPayTypeEnum.AtCreated;
|
|
|
|
result.koBarrier = autocall.KOBarrier;
|
|
result.koObservationDateStr = autocall.KOObservationDates;
|
|
result.kiBarrier = autocall.KIBarrier;
|
|
result.observationDateStr = autocall.ObservationDates;
|
|
|
|
result.includeCouponAfterKI = autocall.IncludeCouponAfterKI;
|
|
|
|
//对于凤凰期权,td.Strike应该等于敲入行权价,特别情况下会出现不符合的情况
|
|
result.strike = autocall.SpreadStrike1 ?? -1;
|
|
result.spreadStrike = autocall.SpreadStrike ?? -1;
|
|
result.happenedObservations = autocall.HappenedObservations;
|
|
result.barrierStatus = autocall.KnockInOutStatus;
|
|
|
|
//使用自己的年化系数配置
|
|
result.annualizedOptionPayoff = autocall.IsAnnualized2;
|
|
result.annualizedFactor = autocall.AnnualizeFactor2 ?? 1.0;
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----收益增强----
|
|
|
|
/// <summary>
|
|
/// 收益增强
|
|
/// </summary>
|
|
public static UnderlyingPayoffEnhanceTrade GetUnderlyingEnhanceTrade(UnderlyingEnhanceTradeParam param)
|
|
{
|
|
CheckOptionTradeParam(param);
|
|
|
|
var exercise = QdpConverter.ConvertExerciseType(param.exerciseType);
|
|
var optionStartDate = param.startDate;
|
|
var underlyingMaturityDate = param.endDate;
|
|
var temp_exerciseDate = param.exerciseDate;
|
|
|
|
if (temp_exerciseDate < optionStartDate)
|
|
{
|
|
throw new Exception("到期日期不能小于起始日");
|
|
}
|
|
|
|
Date[] exerciseDates, observationDates;
|
|
if (exercise == OptionExercise.American)
|
|
{
|
|
exerciseDates = CalendarImpl.Get("chn").BizDaysBetweenDatesInclEndDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
}
|
|
else
|
|
{
|
|
exerciseDates = new Date[] { temp_exerciseDate };
|
|
observationDates = new Date[] { temp_exerciseDate };
|
|
}
|
|
|
|
var optionDayCount = CalculatorHelper.GetTradeDayCount();
|
|
var pricingTOverride = QdpCalendarHelper.CalculateTFromDays(param.timeToMaturityDays, optionDayCount, optionStartDate.DateTime);
|
|
|
|
var underlyingEnhance = new UnderlyingPayoffEnhance(
|
|
startDate: optionStartDate,
|
|
maturityDate: underlyingMaturityDate,
|
|
exercise: exercise,
|
|
optionType: param.optionType,
|
|
strike: param.strike,
|
|
underlyingInstrumentType: (InstrumentType)Enum.Parse(typeof(InstrumentType), param.underlyingInstrumentType),
|
|
calendar: CalendarImpl.Get("chn"),
|
|
dayCount: optionDayCount.ToDayCountImpl(),
|
|
enhancePayoffDayCount: new Act365(),
|
|
payoffCcy: CurrencyCode.CNY,
|
|
settlementCcy: CurrencyCode.CNY,
|
|
exerciseDates: exerciseDates,
|
|
observationDates: observationDates,
|
|
annualizedEnhanceRate: param.annualizedEnhanceRate,
|
|
notional: param.notional,
|
|
settlementGap: null,
|
|
optionPremiumPaymentDate: null,
|
|
optionPremium: 0.0,
|
|
isMoneynessOption: param.isMoneynessOption,
|
|
initialSpotPrice: param.initialSpotPrice,
|
|
dividends: param.dividends,
|
|
hasNightMarket: param.hasNightMarket,
|
|
commodityFuturesPreciseTimeMode: param.preciseTimeMode,
|
|
pricingToverride: pricingTOverride,
|
|
riskFreeRateOverride: param.riskFreeRate,
|
|
dividendRateOverride: param.dividendRate,
|
|
participationRate: param.participationRate,
|
|
isAnnualized: param.isAnnualized,
|
|
annualizedFactor: param.annualizedFactor)
|
|
{
|
|
UnderlyingTickers = param.underlyingTickers
|
|
};
|
|
|
|
if (string.IsNullOrWhiteSpace(param.tradeId))
|
|
{
|
|
param.tradeId = Guid.NewGuid().ToString();
|
|
}
|
|
|
|
return new UnderlyingPayoffEnhanceTrade(
|
|
tradeId: param.tradeId,
|
|
tradeDate: param.tradeDate,
|
|
startDate: underlyingEnhance.StartDate,
|
|
maturityDate: underlyingEnhance.ExerciseDates.Last(),
|
|
tradeType: QdpConverter.ConvertTradeType(param.buysell),
|
|
notional: param.notional,
|
|
price: 0.0,
|
|
option: underlyingEnhance)
|
|
{
|
|
ValuationParameters = new OptionValuationParameters(MarketProxy.RiskFreeDiscountCurve,
|
|
MarketProxy.ConstantZeroCurve, param.volSurfaceNames?.FirstOrDefault(), underlyingEnhance.UnderlyingTickers[0])
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 收益增强
|
|
/// </summary>
|
|
public static UnderlyingPayoffEnhanceTrade GetUnderlyingEnhanceTrade(OtcTradeBase td, trade_underlying_enhance underlyingEnhance, OptionTradeParamRequest request = null)
|
|
{
|
|
var tradeParam = GetUnderlyingEnhanceTradeParam(td, underlyingEnhance, request);
|
|
return GetUnderlyingEnhanceTrade(tradeParam);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 收益增强
|
|
/// </summary>
|
|
public static UnderlyingEnhanceTradeParam GetUnderlyingEnhanceTradeParam(OtcTradeBase td, trade_underlying_enhance underlyingEnhance, OptionTradeParamRequest request)
|
|
{
|
|
if (underlyingEnhance == null)
|
|
{
|
|
throw new TradeCalcExpception(td.id, "未获取到对应的收益增强结构交易数据");
|
|
}
|
|
|
|
var result = BuildTradeParamBase<UnderlyingEnhanceTradeParam>(td, request);
|
|
|
|
result.annualizedEnhanceRate = underlyingEnhance.AnnualizedEnhanceRate ?? 0;
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----气囊结构----
|
|
|
|
/// <summary>
|
|
/// 气囊结构
|
|
/// </summary>
|
|
public static AirbagTrade GetAirbagOptionTrade(AirBagOptionTradeParam param)
|
|
{
|
|
CheckOptionTradeParam(param);
|
|
|
|
var exercise = QdpConverter.ConvertExerciseType(param.exerciseType);
|
|
var optionStartDate = param.startDate;
|
|
var underlyingMaturityDate = param.endDate;
|
|
var temp_exerciseDate = param.exerciseDate;
|
|
|
|
if (temp_exerciseDate < optionStartDate)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Date[] exerciseDates, observationDates;
|
|
if (exercise == OptionExercise.American)
|
|
{
|
|
exerciseDates = CalendarImpl.Get("chn").BizDaysBetweenDatesInclEndDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
}
|
|
else
|
|
{
|
|
exerciseDates = new Date[] { temp_exerciseDate };
|
|
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).Union(new[] { temp_exerciseDate }).ToArray();
|
|
}
|
|
|
|
var optionDayCount = CalculatorHelper.GetTradeDayCount();
|
|
var pricingTOverride = QdpCalendarHelper.CalculateTFromDays(param.timeToMaturityDays, optionDayCount, optionStartDate.DateTime);
|
|
|
|
var airbag = new Airbag(
|
|
startDate: optionStartDate,
|
|
maturityDate: underlyingMaturityDate,
|
|
exercise: exercise,
|
|
optionType: param.optionType,
|
|
lowStrike: param.strike,
|
|
highStrike: param.highStrike,
|
|
barrier: param.barrier,
|
|
participationRate: param.participationRate,
|
|
kiParticipationRate: param.kiParticipationRate,
|
|
isDiscreteMonitored: param.isDiscrete,
|
|
hasPayoffLimit: param.hasPayoffLimit,
|
|
underlyingInstrumentType: (InstrumentType)Enum.Parse(typeof(InstrumentType), param.underlyingInstrumentType),
|
|
calendar: CalendarImpl.Get("chn"),
|
|
dayCount: optionDayCount.ToDayCountImpl(),
|
|
payoffCcy: CurrencyCode.CNY,
|
|
settlementCcy: CurrencyCode.CNY,
|
|
exerciseDates: exerciseDates,
|
|
observationDates: observationDates,
|
|
notional: param.notional,
|
|
settlementGap: null,
|
|
optionPremiumPaymentDate: null,
|
|
optionPremium: 0,
|
|
isMoneynessOption: param.isMoneynessOption,
|
|
initialSpotPrice: param.initialSpotPrice,
|
|
dividends: param.dividends,
|
|
barrierStatus: QdpConverter.ConvertBarrierStatus(param.barrierStatus),
|
|
hasNightMarket: param.hasNightMarket,
|
|
commodityFuturesPreciseTimeMode: param.preciseTimeMode,
|
|
pricingToverride: pricingTOverride,
|
|
riskFreeRateOverride: param.riskFreeRate,
|
|
dividendRateOverride: param.dividendRate,
|
|
isAnnualized: param.isAnnualized,
|
|
annualizedFactor: param.annualizedFactor)
|
|
{
|
|
UnderlyingTickers = param.underlyingTickers
|
|
};
|
|
|
|
if (string.IsNullOrWhiteSpace(param.tradeId))
|
|
{
|
|
param.tradeId = Guid.NewGuid().ToString();
|
|
}
|
|
|
|
return new AirbagTrade(
|
|
tradeId: param.tradeId,
|
|
tradeDate: param.tradeDate,
|
|
startDate: airbag.StartDate,
|
|
maturityDate: airbag.ExerciseDates.Last(),
|
|
tradeType: QdpConverter.ConvertTradeType(param.buysell),
|
|
notional: param.notional,
|
|
price: 0.0,
|
|
option: airbag)
|
|
{
|
|
ValuationParameters = new OptionValuationParameters(MarketProxy.RiskFreeDiscountCurve,
|
|
MarketProxy.ConstantZeroCurve, param.volSurfaceNames?.FirstOrDefault(), airbag.UnderlyingTickers[0]),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 气囊结构
|
|
/// </summary>
|
|
public static AirbagTrade GetAirbagOptionTrade(OtcTradeBase td, trade_airbag airbag, OptionTradeParamRequest request = null)
|
|
{
|
|
var param = GetAirbagOptionTradeParam(td, airbag, request);
|
|
return GetAirbagOptionTrade(param);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 气囊结构
|
|
/// </summary>
|
|
public static AirBagOptionTradeParam GetAirbagOptionTradeParam(OtcTradeBase td, trade_airbag airbag, OptionTradeParamRequest request)
|
|
{
|
|
if (airbag == null)
|
|
{
|
|
throw new TradeCalcExpception(td.id, "未获取到对应的气囊结构交易数据");
|
|
}
|
|
|
|
var result = BuildTradeParamBase<AirBagOptionTradeParam>(td, request);
|
|
|
|
result.barrier = airbag.Barrier;
|
|
result.highStrike = airbag.HighStrike;
|
|
result.kiParticipationRate = airbag.KIParticipationRate;
|
|
result.hasPayoffLimit = airbag.HasPayoffLimit;
|
|
result.isDiscrete = airbag.IsDiscreteMonitored;
|
|
result.observationDateStr = null;
|
|
result.barrierStatus = airbag.KnockInOutStatus;
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----区间累积----
|
|
|
|
/// <summary>
|
|
/// 区间累积
|
|
/// </summary>
|
|
public static RangeAccrualTrade GetRangeAccrualTrade(RangeAccrualTradeParam param)
|
|
{
|
|
CheckOptionTradeParam(param);
|
|
|
|
var exercise = QdpConverter.ConvertExerciseType(param.exerciseType);
|
|
var optionStartDate = param.startDate;
|
|
var underlyingMaturityDate = param.endDate;
|
|
var temp_exerciseDate = param.exerciseDate;
|
|
|
|
if (temp_exerciseDate < optionStartDate)
|
|
{
|
|
throw new Exception("到期日期不能小于起始日");
|
|
}
|
|
|
|
Date[] exerciseDates = null;
|
|
var observationDates = QdpHelper.ParseObservationDate(param.observationDateStr);
|
|
if (exercise == OptionExercise.American)
|
|
{
|
|
exerciseDates = CalendarImpl.Get("chn").BizDaysBetweenDatesInclEndDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesInclEndDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
}
|
|
else
|
|
{
|
|
exerciseDates = new Date[] { temp_exerciseDate };
|
|
if (observationDates == null)
|
|
{
|
|
observationDates = CalendarImpl.Get("chn"). BizDaysBetweenDatesInclEndDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
}
|
|
}
|
|
|
|
var optionDayCount = CalculatorHelper.GetTradeDayCount();
|
|
|
|
var pricingTOverride = double.IsNaN(param.timeToMaturityDays) ? double.NaN : QdpCalendarHelper.CalculateTFromDays(param.timeToMaturityDays, optionDayCount, optionStartDate.DateTime);
|
|
|
|
var fixingValues = QdpHelper.ParseFixingsFromString(param.fixings);
|
|
|
|
var bonusRateSettlementDate = param.settlementDate ?? temp_exerciseDate;
|
|
if (bonusRateSettlementDate < optionStartDate)
|
|
{
|
|
bonusRateSettlementDate = temp_exerciseDate;
|
|
}
|
|
|
|
var rangeAccrual = new RangeAccrual(
|
|
startDate: optionStartDate,
|
|
maturityDate: underlyingMaturityDate,
|
|
exercise: exercise,
|
|
optionType: param.optionType,
|
|
strike: param.strike,
|
|
ranges: new RangeDefinition[]
|
|
{
|
|
new RangeDefinition(param.lowerRange,param.upperRange,param.bonusRate, bonusRateSettlementDate, observationDates)
|
|
},
|
|
underlyingInstrumentType: (InstrumentType)Enum.Parse(typeof(InstrumentType), param.underlyingInstrumentType),
|
|
calendar: CalendarImpl.Get("chn"),
|
|
dayCount: optionDayCount.ToDayCountImpl(),
|
|
payoffCcy: CurrencyCode.CNY,
|
|
settlementCcy: CurrencyCode.CNY,
|
|
exerciseDates: exerciseDates,
|
|
observationDates: observationDates,
|
|
fixings: fixingValues,
|
|
happenedObservations: param.happenedObservations?.Where(o => o != null).Select(o => new ObservationPayment()
|
|
{
|
|
StartDate = new Date(o.StartDate),
|
|
EndDate = new Date(o.EndDate),
|
|
CouponRate = o.CouponRate,
|
|
Notional = o.StockEqvNotional,
|
|
PaymentAmount = o.PaymentAmount,
|
|
PaymentDate = new Date(o.PaymentDate)
|
|
}).ToList(),
|
|
notional: param.notional,
|
|
settlementGap: null,
|
|
optionPremiumPaymentDate: null,
|
|
optionPremium: 0.0,
|
|
isMoneynessOption: param.isMoneynessOption,
|
|
initialSpotPrice: param.initialSpotPrice,
|
|
hasNightMarket: param.hasNightMarket,
|
|
commodityFuturesPreciseTimeMode: param.preciseTimeMode,
|
|
pricingToverride: pricingTOverride,
|
|
riskFreeRateOverride: param.riskFreeRate,
|
|
dividendRateOverride: param.dividendRate,
|
|
participationRate: param.participationRate,
|
|
isAnnualized: param.isAnnualized,
|
|
annualizedFactor: param.annualizedFactor)
|
|
{
|
|
|
|
UnderlyingTickers = param.underlyingTickers
|
|
};
|
|
|
|
if (string.IsNullOrWhiteSpace(param.tradeId))
|
|
{
|
|
param.tradeId = Guid.NewGuid().ToString();
|
|
}
|
|
return new RangeAccrualTrade(
|
|
tradeId: param.tradeId,
|
|
tradeDate: param.startDate,
|
|
startDate: rangeAccrual.StartDate,
|
|
maturityDate: rangeAccrual.ExerciseDates.Last(),
|
|
tradeType: QdpConverter.ConvertTradeType(param.buysell),
|
|
notional: rangeAccrual.Notional, price: 0.0, option: rangeAccrual)
|
|
{
|
|
|
|
ValuationParameters = new OptionValuationParameters(MarketProxy.RiskFreeDiscountCurve,
|
|
MarketProxy.ConstantZeroCurve, param.volSurfaceNames?.FirstOrDefault(), rangeAccrual.UnderlyingTickers[0]),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 区间累积
|
|
/// </summary>
|
|
public static RangeAccrualTrade GetRangeAccrualTrade(OtcTradeBase td, trade_rangeaccrual rangeaccrual, OptionTradeParamRequest request = null)
|
|
{
|
|
var tradeParam = GetRangeAccrualTradeParam(td, rangeaccrual, request);
|
|
return GetRangeAccrualTrade(tradeParam);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 区间累积
|
|
/// </summary>
|
|
public static RangeAccrualTradeParam GetRangeAccrualTradeParam(OtcTradeBase td, trade_rangeaccrual rangeaccrual, OptionTradeParamRequest request)
|
|
{
|
|
if (rangeaccrual == null)
|
|
{
|
|
throw new TradeCalcExpception(td.id, "未获取到对应的区间累积交易数据");
|
|
}
|
|
|
|
var result = BuildTradeParamBase<RangeAccrualTradeParam>(td, request);
|
|
|
|
result.lowerRange = rangeaccrual.LowerRange;
|
|
result.upperRange = rangeaccrual.UpperRange;
|
|
|
|
//注意:在QDP中未处理百分比形式的上下限价格(2021-10-21)
|
|
if (result.isMoneynessOption)
|
|
{
|
|
result.isMoneynessOption = false;
|
|
result.strike *= result.initialSpotPrice;
|
|
result.lowerRange *= result.initialSpotPrice;
|
|
result.upperRange *= result.initialSpotPrice;
|
|
}
|
|
|
|
result.bonusRate = rangeaccrual.BonusRate;
|
|
result.observationDateStr = rangeaccrual.ObservationDates;
|
|
result.happenedObservations = rangeaccrual.HappenedObservations;
|
|
result.fixings = request != null ? request.fixings : "";
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----彩虹期权----
|
|
|
|
/// <summary>
|
|
/// 彩虹期权
|
|
/// </summary>
|
|
public static RainbowOptionTrade GetRainbowOptionTrade(RainbowOptionTradeParam param)
|
|
{
|
|
CheckOptionTradeParam(param);
|
|
|
|
var optionStartDate = param.startDate;
|
|
var underlyingMaturityDate = param.endDate;
|
|
var temp_exerciseDate = param.exerciseDate;
|
|
|
|
if (temp_exerciseDate < optionStartDate)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var exerciseDates = new[] { temp_exerciseDate };
|
|
var calendar = CalendarImpl.Get("chn");
|
|
var observationDates = calendar.BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
|
|
var optionDayCount = CalculatorHelper.GetTradeDayCount();
|
|
|
|
var rainbowOption = new RainbowOption(
|
|
startDate: optionStartDate,
|
|
maturityDate: underlyingMaturityDate,
|
|
exercise: (OptionExercise)Enum.Parse(typeof(OptionExercise), param.exerciseType),
|
|
optionType: param.optionType,
|
|
rainbowType: (RainbowType)Enum.Parse(typeof(RainbowType), param.rainbowType),
|
|
strikes: param.strikes,
|
|
cashAmount: param.cashAmount,
|
|
underlyingInstrumentType: (InstrumentType)Enum.Parse(typeof(InstrumentType), param.underlyingInstrumentType),
|
|
calendar: CalendarImpl.Get("chn"),
|
|
dayCount: optionDayCount.ToDayCountImpl(),
|
|
payoffCcy: CurrencyCode.CNY,
|
|
settlementCcy: CurrencyCode.CNY,
|
|
exerciseDates: exerciseDates,
|
|
observationDates: observationDates,
|
|
underlyingTickers: param.underlyingTickers,
|
|
notional: param.notional,
|
|
settlementGap: null,
|
|
optionPremiumPaymentDate: null,
|
|
optionPremium: 0,
|
|
hasNightMarket: param.hasNightMarket,
|
|
commodityFuturesPreciseTimeMode: param.preciseTimeMode,
|
|
participationRate: param.participationRate,
|
|
isAnnualized: param.isAnnualized,
|
|
annualizedFactor: param.annualizedFactor,
|
|
riskFreeRateOverride: param.riskFreeRate,
|
|
dividendRateOverride: param.dividendRate)
|
|
{
|
|
UnderlyingTickers = param.underlyingTickers
|
|
};
|
|
|
|
if (string.IsNullOrWhiteSpace(param.tradeId))
|
|
{
|
|
param.tradeId = Guid.NewGuid().ToString();
|
|
}
|
|
|
|
return new RainbowOptionTrade(
|
|
tradeId: param.tradeId,
|
|
tradeDate: param.tradeDate,
|
|
startDate: rainbowOption.StartDate,
|
|
maturityDate: rainbowOption.ExerciseDates.Last(),
|
|
tradeType: QdpConverter.ConvertTradeType(param.buysell),
|
|
notional: param.notional,
|
|
price: 0.0,
|
|
option: rainbowOption)
|
|
{
|
|
ValuationParameters = new OptionValuationParameters(
|
|
discountCurveName: MarketProxy.RiskFreeDiscountCurve,
|
|
dividendCurveNames: new string[] { MarketProxy.ConstantZeroCurve, MarketProxy.ConstantZeroCurve },
|
|
volSurfNames: param.volSurfaceNames,
|
|
corrSurfNames: new string[] { param.underlyingTickers[0] + "_" + param.underlyingTickers[1] },
|
|
underlyingId: null),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 彩虹期权
|
|
/// </summary>
|
|
public static RainbowOptionTrade GetRainbowOptionTrade(OtcTradeBase td, trade_rainbow_option rainbowOption, OptionTradeParamRequest request = null)
|
|
{
|
|
var param = GetRainbowOptionTradeParam(td, rainbowOption, request);
|
|
return GetRainbowOptionTrade(param);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 彩虹期权
|
|
/// </summary>
|
|
public static RainbowOptionTradeParam GetRainbowOptionTradeParam(OtcTradeBase td, trade_rainbow_option rainbowOption, OptionTradeParamRequest request)
|
|
{
|
|
if (rainbowOption == null)
|
|
{
|
|
throw new TradeCalcExpception(td.id, "未获取到对应的彩虹期权交易数据");
|
|
}
|
|
|
|
var result = BuildTradeParamBase<RainbowOptionTradeParam>(td, request);
|
|
result.underlyingTickers = rainbowOption.UnderlyingCodes;
|
|
result.strikes = rainbowOption.Strikes;
|
|
result.rainbowType = rainbowOption.RainbowType;
|
|
result.cashAmount = rainbowOption.CashAmount ?? 0;
|
|
result.correlation = rainbowOption.CorRelation ?? 0;
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----价差期权----
|
|
|
|
/// <summary>
|
|
/// 价差期权
|
|
/// </summary>
|
|
public static SpreadOptionTrade GetSpreadOptionTrade(SpreadOptionTradeParam param)
|
|
{
|
|
CheckOptionTradeParam(param);
|
|
|
|
var exercise = QdpConverter.ConvertExerciseType(param.exerciseType);
|
|
var optionStartDate = param.startDate;
|
|
var underlyingMaturityDate = param.endDate;
|
|
var temp_exerciseDate = param.exerciseDate;
|
|
|
|
if (temp_exerciseDate < optionStartDate)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var exerciseDates = new[] { temp_exerciseDate };
|
|
var calendar = CalendarImpl.Get("chn");
|
|
var observationDates = calendar.BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
var normalizedWeight = new double[] { 1.0, 1.0, 1.0, 1.0 };
|
|
for (var i = 0; i < param.weights.Length; i++)
|
|
{
|
|
normalizedWeight[i] = param.weights[i];
|
|
}
|
|
|
|
var optionDayCount = CalculatorHelper.GetTradeDayCount();
|
|
|
|
var spreadOption = new SpreadOption(
|
|
startDate: optionStartDate,
|
|
maturityDate: underlyingMaturityDate,
|
|
exercise: exercise,
|
|
optionType: param.optionType,
|
|
spreadType: param.spreadType,
|
|
weights: normalizedWeight,
|
|
strike: param.strike,
|
|
underlyingInstrumentType: (InstrumentType)Enum.Parse(typeof(InstrumentType), param.underlyingInstrumentType),
|
|
calendar: CalendarImpl.Get("chn"),
|
|
dayCount: optionDayCount.ToDayCountImpl(),
|
|
payoffCcy: CurrencyCode.CNY,
|
|
settlementCcy: CurrencyCode.CNY,
|
|
exerciseDates: exerciseDates,
|
|
observationDates: observationDates,
|
|
underlyingTickers: param.underlyingTickers,
|
|
notional: param.notional,
|
|
settlementGap: null,
|
|
optionPremiumPaymentDate: null,
|
|
optionPremium: 0,
|
|
hasNightMarket: param.hasNightMarket,
|
|
commodityFuturesPreciseTimeMode: param.preciseTimeMode,
|
|
participationRate: param.participationRate,
|
|
isAnnualized: param.isAnnualized,
|
|
annualizedFactor: param.annualizedFactor,
|
|
riskFreeRateOverride: param.riskFreeRate);
|
|
|
|
if (string.IsNullOrWhiteSpace(param.tradeId))
|
|
{
|
|
param.tradeId = Guid.NewGuid().ToString();
|
|
}
|
|
|
|
return new SpreadOptionTrade(
|
|
tradeId: param.tradeId,
|
|
tradeDate: param.tradeDate,
|
|
startDate: spreadOption.StartDate,
|
|
maturityDate: spreadOption.ExerciseDates.Last(),
|
|
tradeType: QdpConverter.ConvertTradeType(param.buysell),
|
|
notional: param.notional,
|
|
price: 0.0,
|
|
option: spreadOption)
|
|
{
|
|
ValuationParameters = GetParametersForSpreadOption(param.underlyingTickers, MarketProxy.RiskFreeDiscountCurve, param.volSurfaceNames)
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 价差期权
|
|
/// </summary>
|
|
public static SpreadOptionTrade GetSpreadOptionTrade(OtcTradeBase td, trade_spread_option spreadOption, OptionTradeParamRequest request, double[] correlations)
|
|
{
|
|
var param = GetSpreadOptionTradeParam(td, spreadOption, request, correlations);
|
|
return GetSpreadOptionTrade(param);
|
|
}
|
|
|
|
public static OptionValuationParameters GetParametersForSpreadOption(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>
|
|
/// 价差期权
|
|
/// </summary>
|
|
public static SpreadOptionTradeParam GetSpreadOptionTradeParam(OtcTradeBase td, trade_spread_option spreadOption, OptionTradeParamRequest request, double[] correlations)
|
|
{
|
|
if (spreadOption == null)
|
|
{
|
|
throw new TradeCalcExpception(td.id, "未获取到对应的价差期权交易数据");
|
|
}
|
|
|
|
var result = BuildTradeParamBase<SpreadOptionTradeParam>(td, request);
|
|
|
|
result.underlyingTickers = spreadOption.UnderlyingAssetCodes();
|
|
|
|
var payoff = new Model.SpreadOptionPayoff(spreadOption.Payoff);
|
|
result.weights = payoff.Weights;
|
|
result.spreadType = payoff.SpreadType;
|
|
result.correlations = correlations;
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----累计期权----
|
|
|
|
public static AccumulatorOptionTrade GetAccumulatorOptionTrade(AccumulatorOptionTradeParam param)
|
|
{
|
|
if (param is SegmentedAccumulatorOptionTradeParam tradeParam)
|
|
{
|
|
return GetSegmentedAccumulatorOptionTrade(tradeParam);
|
|
}
|
|
else
|
|
{
|
|
return GetStandardAccumulatorOptionTrade(param);
|
|
}
|
|
}
|
|
|
|
private static AccumulatorOptionTrade GetStandardAccumulatorOptionTrade(AccumulatorOptionTradeParam param)
|
|
{
|
|
CheckOptionTradeParam(param);
|
|
|
|
var exercise = QdpConverter.ConvertExerciseType(param.exerciseType);
|
|
var optionStartDate = param.startDate;
|
|
var underlyingMaturityDate = param.endDate;
|
|
var temp_exerciseDate = param.exerciseDate;
|
|
|
|
if (temp_exerciseDate < optionStartDate)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Date[] exerciseDates, observationDates;
|
|
if (exercise == OptionExercise.American)
|
|
{
|
|
exerciseDates = CalendarImpl.Get("chn").BizDaysBetweenDatesInclEndDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
}
|
|
else
|
|
{
|
|
exerciseDates = new Date[] { temp_exerciseDate };
|
|
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).Union(new[] { temp_exerciseDate }).ToArray();
|
|
}
|
|
|
|
var customizedResults = QdpHelper.ParseAutocallCustomizedInfo(param.KOObservationDates);
|
|
var koObservationDates = customizedResults.Item1;
|
|
var customizedKOBarriers = customizedResults.Item2;
|
|
var customizedCoupons = customizedResults.Item3;
|
|
|
|
if (koObservationDates == null || koObservationDates.Length == 0)
|
|
{
|
|
koObservationDates = observationDates;
|
|
}
|
|
|
|
if (customizedKOBarriers == null || customizedKOBarriers.Length == 0)
|
|
{
|
|
customizedKOBarriers = Enumerable.Repeat(param.Barrier, koObservationDates.Length).ToArray();
|
|
}
|
|
else if (param.isMoneynessOptionOrigianl)
|
|
{
|
|
customizedKOBarriers = customizedKOBarriers.Select(n => n * param.initialSpotPrice).ToArray();
|
|
}
|
|
|
|
//if (customizedCoupons == null || customizedCoupons.Length == 0)
|
|
//{
|
|
// customizedCoupons = Enumerable.Repeat(param.Coupon, koObservationDates.Length).ToArray();
|
|
//}
|
|
|
|
//票息支付日期
|
|
var couponPaymentDates = QdpHelper.ParseObservationDate(param.couponPaymentDateStr);
|
|
|
|
var optionDayCount = CalculatorHelper.GetTradeDayCount();
|
|
var pricingTOverride = QdpCalendarHelper.CalculateTFromDays(param.timeToMaturityDays, optionDayCount, optionStartDate.DateTime);
|
|
|
|
var settleMode = AccumuSettleMode.CashAtHit;
|
|
|
|
switch (param.SettlementMode)
|
|
{
|
|
case "现金当日":
|
|
case "现金实物合并结算":
|
|
settleMode = AccumuSettleMode.CashAtHit;
|
|
break;
|
|
case "现金期末":
|
|
settleMode = AccumuSettleMode.CashAtEnd;
|
|
break;
|
|
case "实物交割":
|
|
settleMode = AccumuSettleMode.ToFoward;
|
|
break;
|
|
}
|
|
|
|
var happenedObservations = settleMode == AccumuSettleMode.CashAtEnd ? param.happenedObservations?.Where(o => o != null).Select(o => new AccumuObservationSettleData
|
|
{
|
|
SettleDate = new Date(o.EndDate),
|
|
SettleNotional = o.PaymentAmount
|
|
}).ToList() : null;
|
|
|
|
var accumulatorOption = new AccumulatorOption(
|
|
startDate: optionStartDate,
|
|
maturityDate: underlyingMaturityDate,
|
|
optionType: param.optionType,
|
|
strike: param.strike,
|
|
barriers: customizedKOBarriers,
|
|
callMultiplier: param.CallMultiplier,
|
|
putMultiplier: param.PutMultiplier,
|
|
earlyTerminate: param.EarlyTerminate,
|
|
underlyingInstrumentType: (InstrumentType)Enum.Parse(typeof(InstrumentType), param.underlyingInstrumentType),
|
|
calendar: CalendarImpl.Get("chn"),
|
|
dayCount: optionDayCount.ToDayCountImpl(),
|
|
payoffCcy: CurrencyCode.CNY,
|
|
settlementCcy: CurrencyCode.CNY,
|
|
exerciseDates: exerciseDates,
|
|
observationDates: koObservationDates,
|
|
coupon: param.Coupon,
|
|
payCoupon: param.PayCoupon,
|
|
couponPaymentDates: couponPaymentDates,
|
|
couponDayCount: param.couponDayCount.ToDayCountImpl(),
|
|
fixedCoupon: param.isFixedCoupon,
|
|
includeStartDateCoupon: param.includeStartDateCoupon,
|
|
notional: param.notional,
|
|
settlementGap: null,
|
|
optionPremiumPaymentDate: null,
|
|
optionPremium: 0.0,
|
|
isMoneynessOption: param.isMoneynessOption,
|
|
initialSpotPrice: param.initialSpotPrice,
|
|
dividends: param.dividends,
|
|
pricingToverride: pricingTOverride,
|
|
hasNightMarket: param.hasNightMarket,
|
|
commodityFuturesPreciseTimeMode: param.preciseTimeMode,
|
|
riskFreeRateOverride: param.riskFreeRate,
|
|
dividendRateOverride: param.dividendRate,
|
|
participationRate: param.participationRate,
|
|
isAnnualized: param.isAnnualized,
|
|
annualizedFactor: param.annualizedFactor,
|
|
accumuType: param.AccumuType == "子弹" ? AccumuType.bullet : AccumuType.none,
|
|
settleMode: settleMode,
|
|
happenedObservations: happenedObservations)
|
|
{
|
|
UnderlyingTickers = param.underlyingTickers
|
|
};
|
|
|
|
if (string.IsNullOrWhiteSpace(param.tradeId))
|
|
{
|
|
param.tradeId = Guid.NewGuid().ToString();
|
|
}
|
|
|
|
return new AccumulatorOptionTrade(
|
|
tradeId: param.tradeId,
|
|
tradeDate: param.tradeDate,
|
|
startDate: accumulatorOption.StartDate,
|
|
maturityDate: accumulatorOption.ExerciseDates.Last(),
|
|
tradeType: QdpConverter.ConvertTradeType(param.buysell),
|
|
notional: param.notional,
|
|
price: 0.0,
|
|
option: accumulatorOption)
|
|
{
|
|
ValuationParameters = new OptionValuationParameters(MarketProxy.RiskFreeDiscountCurve,
|
|
MarketProxy.ConstantZeroCurve, param.volSurfaceNames?.FirstOrDefault(), accumulatorOption.UnderlyingTickers[0]),
|
|
};
|
|
}
|
|
|
|
private static SegmentedAccumulatorOptionTrade GetSegmentedAccumulatorOptionTrade(SegmentedAccumulatorOptionTradeParam param)
|
|
{
|
|
CheckOptionTradeParam(param);
|
|
|
|
var exercise = QdpConverter.ConvertExerciseType(param.exerciseType);
|
|
var optionStartDate = param.startDate;
|
|
var underlyingMaturityDate = param.endDate;
|
|
var temp_exerciseDate = param.exerciseDate;
|
|
|
|
if (temp_exerciseDate < optionStartDate)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Date[] exerciseDates, observationDates;
|
|
if (exercise == OptionExercise.American)
|
|
{
|
|
exerciseDates = CalendarImpl.Get("chn").BizDaysBetweenDatesInclEndDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).ToArray();
|
|
}
|
|
else
|
|
{
|
|
exerciseDates = new Date[] { temp_exerciseDate };
|
|
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).Union(new[] { temp_exerciseDate }).ToArray();
|
|
}
|
|
|
|
var customizedResults = QdpHelper.ParseAutocallCustomizedInfo(param.KOObservationDates);
|
|
var koObservationDates = customizedResults.Item1;
|
|
var customizedKOBarriers = customizedResults.Item2;
|
|
var customizedCoupons = customizedResults.Item3;
|
|
|
|
if (koObservationDates == null || koObservationDates.Length == 0)
|
|
{
|
|
koObservationDates = observationDates;
|
|
}
|
|
|
|
if (customizedKOBarriers == null || customizedKOBarriers.Length == 0)
|
|
{
|
|
customizedKOBarriers = Enumerable.Repeat(param.Barrier, koObservationDates.Length).ToArray();
|
|
}
|
|
else if (param.isMoneynessOptionOrigianl)
|
|
{
|
|
customizedKOBarriers = customizedKOBarriers.Select(n => n * param.initialSpotPrice).ToArray();
|
|
}
|
|
|
|
//if (customizedCoupons == null || customizedCoupons.Length == 0)
|
|
//{
|
|
// customizedCoupons = Enumerable.Repeat(param.Coupon, koObservationDates.Length).ToArray();
|
|
//}
|
|
|
|
//票息支付日期
|
|
var couponPaymentDates = QdpHelper.ParseObservationDate(param.couponPaymentDateStr);
|
|
|
|
var optionDayCount = CalculatorHelper.GetTradeDayCount();
|
|
var pricingTOverride = QdpCalendarHelper.CalculateTFromDays(param.timeToMaturityDays, optionDayCount, optionStartDate.DateTime);
|
|
|
|
var settleMode = AccumuSettleMode.CashAtHit;
|
|
|
|
switch (param.SettlementMode)
|
|
{
|
|
case "现金当日":
|
|
settleMode = AccumuSettleMode.CashAtHit;
|
|
break;
|
|
case "现金期末":
|
|
settleMode = AccumuSettleMode.CashAtEnd;
|
|
break;
|
|
case "实物交割":
|
|
settleMode = AccumuSettleMode.ToFoward;
|
|
break;
|
|
}
|
|
var settleMode2 = AccumuSettleMode.CashAtHit;
|
|
switch (param.SettleMode2)
|
|
{
|
|
case "现金当日":
|
|
settleMode2 = AccumuSettleMode.CashAtHit;
|
|
break;
|
|
case "实物交割":
|
|
settleMode2 = AccumuSettleMode.ToFoward;
|
|
break;
|
|
}
|
|
var settleMode3 = AccumuSettleMode.CashAtHit;
|
|
switch (param.SettleMode3)
|
|
{
|
|
case "现金当日":
|
|
settleMode3 = AccumuSettleMode.CashAtHit;
|
|
break;
|
|
case "实物交割":
|
|
settleMode3 = AccumuSettleMode.ToFoward;
|
|
break;
|
|
}
|
|
|
|
var happenedObservations = settleMode == AccumuSettleMode.CashAtEnd ? param.happenedObservations?.Where(o => o != null).Select(o => new AccumuObservationSettleData
|
|
{
|
|
SettleDate = new Date(o.EndDate),
|
|
SettleNotional = o.PaymentAmount
|
|
}).ToList() : null;
|
|
|
|
var accumulatorOption = new SegmentedAccumulatorOption(
|
|
startDate: optionStartDate,
|
|
maturityDate: underlyingMaturityDate,
|
|
optionType: param.optionType,
|
|
strike: param.strike,
|
|
barriers: customizedKOBarriers,
|
|
multiplier2: param.Multiplier2,
|
|
multiplier1: param.Multiplier1,
|
|
earlyTerminate: param.EarlyTerminate,
|
|
underlyingInstrumentType: (InstrumentType)Enum.Parse(typeof(InstrumentType), param.underlyingInstrumentType),
|
|
calendar: CalendarImpl.Get("chn"),
|
|
dayCount: optionDayCount.ToDayCountImpl(),
|
|
payoffCcy: CurrencyCode.CNY,
|
|
settlementCcy: CurrencyCode.CNY,
|
|
exerciseDates: exerciseDates,
|
|
observationDates: koObservationDates,
|
|
coupon: param.Coupon,
|
|
payCoupon: param.PayCoupon,
|
|
couponPaymentDates: couponPaymentDates,
|
|
couponDayCount: param.couponDayCount.ToDayCountImpl(),
|
|
fixedCoupon: param.isFixedCoupon,
|
|
includeStartDateCoupon: param.includeStartDateCoupon,
|
|
notional: param.notional,
|
|
settlementGap: null,
|
|
optionPremiumPaymentDate: null,
|
|
optionPremium: 0.0,
|
|
isMoneynessOption: param.isMoneynessOption,
|
|
initialSpotPrice: param.initialSpotPrice,
|
|
dividends: param.dividends,
|
|
pricingToverride: pricingTOverride,
|
|
hasNightMarket: param.hasNightMarket,
|
|
commodityFuturesPreciseTimeMode: param.preciseTimeMode,
|
|
riskFreeRateOverride: param.riskFreeRate,
|
|
dividendRateOverride: param.dividendRate,
|
|
participationRate: param.participationRate,
|
|
isAnnualized: param.isAnnualized,
|
|
annualizedFactor: param.annualizedFactor,
|
|
accumuType: param.AccumuType == "子弹" ? AccumuType.bullet : AccumuType.none,
|
|
settleMode: settleMode,
|
|
settleMode2: settleMode2,
|
|
settleMode3: settleMode3,
|
|
happenedObservations: happenedObservations,
|
|
strike2: param.Strike2,
|
|
strike3: param.Strike3,
|
|
coupon2: param.Coupon2,
|
|
multiplier3: param.Multiplier3)
|
|
{
|
|
UnderlyingTickers = param.underlyingTickers
|
|
};
|
|
|
|
if (string.IsNullOrWhiteSpace(param.tradeId))
|
|
{
|
|
param.tradeId = Guid.NewGuid().ToString();
|
|
}
|
|
|
|
return new SegmentedAccumulatorOptionTrade(
|
|
tradeId: param.tradeId,
|
|
tradeDate: param.tradeDate,
|
|
startDate: accumulatorOption.StartDate,
|
|
maturityDate: accumulatorOption.ExerciseDates.Last(),
|
|
tradeType: QdpConverter.ConvertTradeType(param.buysell),
|
|
notional: param.notional,
|
|
price: 0.0,
|
|
option: accumulatorOption)
|
|
{
|
|
ValuationParameters = new OptionValuationParameters(MarketProxy.RiskFreeDiscountCurve,
|
|
MarketProxy.ConstantZeroCurve, param.volSurfaceNames?.FirstOrDefault(), accumulatorOption.UnderlyingTickers[0]),
|
|
};
|
|
}
|
|
|
|
public static AccumulatorOptionTrade GetAccumulatorOptionTrade(OtcTradeBase td, trade_accumulator_option accumulatorOption
|
|
, OptionTradeParamRequest request = null)
|
|
{
|
|
var param = GetAccumulatorOptionTradeParam(td, accumulatorOption, request);
|
|
return GetAccumulatorOptionTrade(param);
|
|
}
|
|
|
|
public static AccumulatorOptionTradeParam GetAccumulatorOptionTradeParam(OtcTradeBase td, trade_accumulator_option accumulatorOption
|
|
, OptionTradeParamRequest request)
|
|
{
|
|
if (accumulatorOption == null)
|
|
{
|
|
throw new TradeCalcExpception(td.id, "未获取到对应的累计期权交易数据");
|
|
}
|
|
AccumulatorOptionTradeParam result = null;
|
|
if (accumulatorOption.AccumulatorStructureType == AccumulatorStructureTypeEnum.Segmented)
|
|
{
|
|
var segmentedResult = BuildTradeParamBase<SegmentedAccumulatorOptionTradeParam>(td, request);
|
|
segmentedResult.Multiplier1 = (accumulatorOption.Multiplier ?? 0);
|
|
segmentedResult.Multiplier2 = (accumulatorOption.Multiplier2 ?? 0);
|
|
segmentedResult.Multiplier3 = (accumulatorOption.Multiplier3 ?? 0);
|
|
segmentedResult.Coupon2 = accumulatorOption.CouponPercent ? (accumulatorOption.Coupon2 ?? 0) * (td.SpotPrice ?? 0) : (accumulatorOption.Coupon2 ?? 0);
|
|
segmentedResult.SettleMode2 = accumulatorOption.SettlementMode2;
|
|
segmentedResult.SettleMode3 = accumulatorOption.SettlementMode3;
|
|
if (segmentedResult.isMoneynessOption)
|
|
{
|
|
segmentedResult.Strike2 = (accumulatorOption.Strike2 ?? 0) * segmentedResult.initialSpotPrice;
|
|
segmentedResult.Strike3 = (accumulatorOption.Strike3 ?? 0) * segmentedResult.initialSpotPrice;
|
|
}
|
|
else
|
|
{
|
|
segmentedResult.Strike2 = (accumulatorOption.Strike2 ?? 0);
|
|
segmentedResult.Strike3 = (accumulatorOption.Strike3 ?? 0);
|
|
}
|
|
result = segmentedResult;
|
|
result.initialSpotPrice = segmentedResult.initialSpotPrice;
|
|
}
|
|
else
|
|
{
|
|
result = BuildTradeParamBase<AccumulatorOptionTradeParam>(td, request);
|
|
result.PutMultiplier = accumulatorOption.PutMultiplier;
|
|
result.CallMultiplier = accumulatorOption.CallMultiplier;
|
|
}
|
|
|
|
result.EarlyTerminate = accumulatorOption.EarlyTerminate;
|
|
|
|
result.PayCoupon = accumulatorOption.PayoffType == "固定";
|
|
result.couponDayCount = accumulatorOption.CouponDayCount.TrimToNull() ?? "Act365"; ;
|
|
result.couponPaymentDateStr = accumulatorOption.KOObservationSettleDates;
|
|
result.isFixedCoupon = accumulatorOption.IsFixedCoupon;
|
|
result.includeStartDateCoupon = false;
|
|
|
|
result.AccumuType = accumulatorOption.AccumuType;
|
|
result.SettlementMode = accumulatorOption.SettlementMode;
|
|
|
|
//barrier
|
|
result.Barrier = accumulatorOption.KOBarrier.HasValue
|
|
? (result.isMoneynessOption ? accumulatorOption.KOBarrier.Value * result.initialSpotPrice : accumulatorOption.KOBarrier.Value)
|
|
: (result.optionType == OptionType.Call ? 1e10 : -1e10);
|
|
if (result.isMoneynessOption)
|
|
{
|
|
result.strike *= result.initialSpotPrice;
|
|
}
|
|
result.isMoneynessOption = false;
|
|
|
|
result.Coupon = accumulatorOption.CouponPercent ? accumulatorOption.Coupon * (td.SpotPrice ?? 0) : accumulatorOption.Coupon;
|
|
result.KOObservationDates = accumulatorOption.KOObservationDates;
|
|
|
|
// Removed updownPriceMode as it's not supported in QDP AccumulatorOption constructor
|
|
// result.updownPriceMode = GlobalConfig.AccumulatorCriticalMode == "updown";
|
|
result.happenedObservations = request?.happenedObservations;
|
|
|
|
//将累计期权引擎需要传入的notional从原来的观察日总数量更正为沽购数量
|
|
result.notional = accumulatorOption.AccumuTradeAmount * (td.CountRatio ?? 1);
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----现金流交易----
|
|
|
|
public static DepositTrade GetCashFlowTrade(CashFlowTradeParam param, double stockEqvNotional)
|
|
{
|
|
var optionStartDate = param.startDate;
|
|
var underlyingMaturityDate = param.endDate;
|
|
var deposit = new Deposit(
|
|
startDate: optionStartDate,
|
|
maturityDate: underlyingMaturityDate,
|
|
depositRate: param.ProfitRate,
|
|
dayCount: param.ProfitDayCount.ToDayCountImpl(),
|
|
calendar: CalendarImpl.Get("chn"),
|
|
bda: BusinessDayConvention.None,
|
|
currency: CurrencyCode.CNY,
|
|
notional: stockEqvNotional,
|
|
annualizedRate: param.RateType == CashFlowRateTypeEnum.年化利率,
|
|
depositType: param.DepositType == CashflowDepositTypeEnum.成本摊还 ? DepositType.CostRedemption : DepositType.Regular,
|
|
payCouponOnly: true
|
|
);
|
|
return new DepositTrade(
|
|
tradeId: param.tradeId,
|
|
tradeDate: param.tradeDate,
|
|
startDate: param.startDate,
|
|
maturityDate: underlyingMaturityDate,
|
|
tradeType: QdpConverter.ConvertTradeType(param.buysell),
|
|
notional: stockEqvNotional,
|
|
price: 0.0,
|
|
instrument: deposit)
|
|
{
|
|
ValuationParameters = new SimpleCfValuationParameters("RiskFreeDiscountCurve", "RiskFreeDiscountCurve", "RiskFreeDiscountCurve")
|
|
};
|
|
}
|
|
|
|
public static DepositTrade GetCashFlowTrade(OtcTradeBase td, trade_cashflow cashflowTrade, OptionTradeParamRequest request = null)
|
|
{
|
|
var param = GetCashFlowTradeParam(td, cashflowTrade, request);
|
|
return GetCashFlowTrade(param, td.StockEqvNotional);
|
|
}
|
|
|
|
public static CashFlowTradeParam GetCashFlowTradeParam(OtcTradeBase td, trade_cashflow cashflow, OptionTradeParamRequest request)
|
|
{
|
|
if (cashflow == null)
|
|
{
|
|
throw new TradeCalcExpception(td.id, "未获取到对应的现金流交易数据");
|
|
}
|
|
|
|
var result = BuildTradeParamBase<CashFlowTradeParam>(td, request);
|
|
|
|
result.ProfitRate = cashflow.ProfitRate;
|
|
result.RateType = cashflow.RateType;
|
|
result.ProfitDayCount = cashflow.ProfitDayCount;
|
|
result.DepositType = cashflow.DepositType;
|
|
result.PrepayRatio = cashflow.PrepayRatio;
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ----结构化产品----
|
|
public static StructProductTradeParam GetStructProductTradeParam(OptionValueCalcRequest req, MarketProxy marketProxy, trade trade, underlying_manager underlying = null)
|
|
{
|
|
if (underlying == null)
|
|
{
|
|
underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(trade.UnderlyingCode);
|
|
}
|
|
var dzStructureService = new DZStructureService(OptUserInfo.SystemUser);
|
|
var components = new StructureComponent[4];
|
|
var componentsLength = 0;
|
|
if (trade.MetaDic.ContainsKey("structures"))
|
|
{
|
|
var structures = dzStructureService.getStructureList(trade.MetaDic["structures"]);
|
|
|
|
|
|
if (structures != null && structures.Count > 0)
|
|
{
|
|
structures.ForEach(p =>
|
|
{
|
|
if (!"None".Equals(p.OptionType))
|
|
{
|
|
components[componentsLength] = new StructureComponent
|
|
{
|
|
componentType = p.OptionType,
|
|
strike = p.Strike != null ? (double)p.Strike : 0,
|
|
barrier = p.BarrierPrice != null ? (double)p.BarrierPrice : 0,
|
|
multiplier = p.Multiplier != null ? (double)p.Multiplier : 0,
|
|
ramp = string.IsNullOrEmpty(p.RAMP) ? 0 : double.Parse(p.RAMP),
|
|
isAccAtStrike = p.IsAccAlSrike == null ? false : (bool)p.IsAccAlSrike
|
|
};
|
|
componentsLength++;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new Exception("未设置结构要素");
|
|
}
|
|
var scheduleList = new List<StructureObsSchedule>();
|
|
|
|
if (trade.MetaDic.ContainsKey("observationDate"))
|
|
{
|
|
var observationDateStr = trade.MetaDic["observationDate"];
|
|
if (!string.IsNullOrEmpty(observationDateStr))
|
|
{
|
|
var arr = observationDateStr.Split(new char[] { ';' });
|
|
var dateArr = arr[0].Split(new char[] { ',' });
|
|
var fixedPriceArr = arr[1].Split(new char[] { ',' });
|
|
var isClosedArr = arr[2].Split(new char[] { ',' });
|
|
var closePriceArr = arr[3].Split(new char[] { ',' });
|
|
|
|
var arrLength = dateArr.Length;
|
|
for (int i = 0; i < arrLength; i++)
|
|
{
|
|
StructureObsSchedule sos = new StructureObsSchedule
|
|
{
|
|
valueDate = GetTotalSecends(DateTime.Parse(dateArr[i])),
|
|
fixedPrice = string.IsNullOrEmpty(fixedPriceArr[i]) ? 0 : double.Parse(fixedPriceArr[i]),
|
|
isClosed = string.IsNullOrEmpty(isClosedArr[i]) ? false : bool.Parse(isClosedArr[i]),
|
|
closePrice = string.IsNullOrEmpty(closePriceArr[i]) ? 0 : double.Parse(closePriceArr[i]),
|
|
};
|
|
scheduleList.Add(sos);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new Exception("未设置观察日");
|
|
}
|
|
var scheduleArr = scheduleList.ToArray();
|
|
|
|
var volsurface = new VolSurface()
|
|
{
|
|
|
|
};
|
|
var vols = VolatilityHelper.GetVol((DateTime)trade.TradeDate, "交易", underlying.UnderlyingCode, "");
|
|
if (vols != null && vols.VolTable != null && vols.VolTable.Any())
|
|
{
|
|
var strikeArr = vols.VolTable.Select(p => p.Strike).Distinct().OrderBy(p => p).ToArray();
|
|
var expireArr = vols.VolTable.Select(p => p.Expire).Distinct().OrderBy(p => p).ToList();
|
|
|
|
var verticalAxisList = new List<ExpireDto>();
|
|
expireArr.ForEach(p =>
|
|
{
|
|
if (p.EndsWith("D"))
|
|
{
|
|
verticalAxisList.Add(new ExpireDto { Days = double.Parse(p.Substring(0, p.Length - 1)), DayStr = p });
|
|
}
|
|
else if (p.EndsWith("M"))
|
|
{
|
|
verticalAxisList.Add(new ExpireDto { Days = double.Parse(p.Substring(0, p.Length - 1)) * 365 / 12, DayStr = p });
|
|
}
|
|
else if (p.EndsWith("Y"))
|
|
{
|
|
verticalAxisList.Add(new ExpireDto { Days = double.Parse(p.Substring(0, p.Length - 1)) * 365, DayStr = p });
|
|
}
|
|
else if (p.EndsWith("W"))
|
|
{
|
|
verticalAxisList.Add(new ExpireDto { Days = double.Parse(p.Substring(0, p.Length - 1)) * 7, DayStr = p });
|
|
}
|
|
});
|
|
verticalAxisList = verticalAxisList.OrderBy(p => p.Days).ToList();
|
|
|
|
|
|
List<double> flattenedVolList = new List<double>();
|
|
foreach (var hor in strikeArr)
|
|
{
|
|
foreach (var ver in verticalAxisList)
|
|
{
|
|
flattenedVolList.Add(vols.VolTable.First(p => p.Strike == hor && p.Expire == ver.DayStr).Vol);
|
|
}
|
|
}
|
|
volsurface.horizontalAxis = Marshal.UnsafeAddrOfPinnedArrayElement(strikeArr, 0);
|
|
volsurface.horizontalAxisLength = strikeArr.Length;
|
|
|
|
|
|
var verticalAxisArr = verticalAxisList.Select(p => p.Days).ToArray();
|
|
volsurface.verticalAxis = Marshal.UnsafeAddrOfPinnedArrayElement(verticalAxisArr, 0);
|
|
volsurface.verticalAxisLength = verticalAxisArr.Length;
|
|
|
|
|
|
var flattenedVolArr = flattenedVolList.ToArray();
|
|
volsurface.flattenedVol = Marshal.UnsafeAddrOfPinnedArrayElement(flattenedVolArr, 0);
|
|
volsurface.flattenedVolLength = flattenedVolArr.Length;
|
|
|
|
log.Info("期权定价波动率请求1:" + JsonHelper.Serialize(strikeArr));
|
|
log.Info("期权定价波动率请求2:" + JsonHelper.Serialize(verticalAxisArr));
|
|
log.Info("期权定价波动率请求3:" + JsonHelper.Serialize(flattenedVolArr));
|
|
}
|
|
|
|
|
|
int isCashSettle = 1;
|
|
if (trade.MetaDic.ContainsKey("cashOrPhysical") && !"Cash".Equals(trade.MetaDic["cashOrPhysical"]))
|
|
{
|
|
isCashSettle = 0;
|
|
}
|
|
|
|
|
|
TradeValueResult result = new TradeValueResult();
|
|
var request = new StructureRequest
|
|
{
|
|
buySell = "卖出".Equals(trade.BuySell) ? -1 : 1,
|
|
r = req.sysRiskFreeRate,
|
|
constantVol = req.vols[0],
|
|
underlierPrice = req.spotPrices[0],
|
|
valueTime = GetTotalSecends(marketProxy.ValueDate),
|
|
isCashSettle = isCashSettle,
|
|
scenarioPrice = 0, //情景价格,计算预付金使用 标的价格*涨跌幅 = 情景价格,调用dll
|
|
|
|
component = components,
|
|
componentLength = componentsLength,
|
|
|
|
//volSurface = new IntPtr(&volsurface),
|
|
|
|
schedule = Marshal.UnsafeAddrOfPinnedArrayElement(scheduleArr, 0),
|
|
scheduleLength = scheduleArr.Length,
|
|
valueType = "a"
|
|
};
|
|
log.Info("期权定价观察日请求:" + JsonHelper.Serialize(scheduleArr));
|
|
|
|
StructureResult sResult = new StructureResult
|
|
{
|
|
message = new string(' ', 100),
|
|
pv = 0,
|
|
delta = 0,
|
|
gamma = 0,
|
|
thetaPerDay = 0,
|
|
rhoPercentage = 0,
|
|
vegaPercentage = 0,
|
|
};
|
|
|
|
return new StructProductTradeParam { Request = request, VolSurface = volsurface };
|
|
}
|
|
|
|
private static long GetTotalSecends(DateTime date)
|
|
{
|
|
return (long)(date - DateTime.Parse("1970-1-1")).TotalSeconds;
|
|
}
|
|
#endregion
|
|
|
|
#region----内部方法----
|
|
|
|
private static void CheckOptionTradeParam(OptionTradeParamBase param)
|
|
{
|
|
if (param is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(param));
|
|
}
|
|
|
|
if (param.underlyingTickers == null || !param.underlyingTickers.Any())
|
|
{
|
|
throw new Exception("缺少标的代码");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// QDP计算参数基础设置
|
|
/// </summary>
|
|
private static T BuildTradeParamBase<T>(OtcTradeBase td, OptionTradeParamRequest request) where T : OptionTradeParamBase, new()
|
|
{
|
|
if (td is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(td));
|
|
}
|
|
|
|
request ??= new OptionTradeParamRequest(valuedateBLL.RiskFreeRate / 100);
|
|
|
|
//----------------------------------
|
|
// 补足交易数据缺失
|
|
//----------------------------------
|
|
|
|
if (!td.StartDate.HasValue)
|
|
{
|
|
td.StartDate = td.TradeDate;
|
|
}
|
|
|
|
if (td.ExerciseDate == null)
|
|
{
|
|
throw new ServiceException("缺少到期日");
|
|
}
|
|
|
|
if (td.HasUnderlying())
|
|
{
|
|
if (string.IsNullOrWhiteSpace(td.UnderlyingCode))
|
|
{
|
|
throw new ServiceException("交易参数缺少标的代码");
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(td.UnderlyingInstrumentType))
|
|
{
|
|
var un = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
|
|
|
|
if (un == null)
|
|
{
|
|
throw new ServiceException("交易参数找不到标的数据:" + td.UnderlyingCode);
|
|
}
|
|
|
|
td.UnderlyingInstrumentType = un.UnderlyingInstrumentType;
|
|
}
|
|
}
|
|
|
|
//----------------------------------
|
|
// 生成计算参数对象
|
|
//----------------------------------
|
|
|
|
var param = new T
|
|
{
|
|
underlyingTickers = new[] { td.UnderlyingCode },
|
|
underlyingInstrumentType = td.UnderlyingInstrumentType,
|
|
|
|
hasNightMarket = request.hasNightMarket,
|
|
|
|
startDate = new Date(td.StartDate.Value),
|
|
originalExerciseDate = td.ExerciseDate.Value,
|
|
exerciseDate = GetExerciseDate(td.ExerciseDate, request.maturityShift),
|
|
endDate = GetEndDate(td.MaturityDate ?? td.ExerciseDate, request.maturityShift),
|
|
settlementDate = td.SettlementDate,
|
|
|
|
buysell = td.BuySell,
|
|
optionType = QdpConverter.ConvertOptionType(td.CallPut),
|
|
exerciseType = td.ExerciseMode,
|
|
|
|
strike = td.Strike ?? 0,
|
|
initialSpotPrice = td.SpotPrice ?? 0,
|
|
isMoneynessOption = td.IsMoneynessOptionData,
|
|
isMoneynessOptionOrigianl = td.IsMoneynessOptionData,
|
|
|
|
participationRate = td.ParticipationRate ?? 1.0,
|
|
principalRate = td.PrincipalRate ?? 0,
|
|
|
|
isAnnualized = td.IsAnnualized,
|
|
annualizedFactor = td.AnnualizeFactor ?? 1.0,
|
|
|
|
notional = td.Notional,
|
|
dividendRate = td.DividendRate ?? td.NoRiskRate ?? request.sysRiskFreeRate,
|
|
riskFreeRate = td.NoRiskRate ?? request.sysRiskFreeRate,
|
|
|
|
preciseTimeMode = request.preciseTimeMode,
|
|
|
|
tradeId = request.tradeId,
|
|
tradeNumber = td.TradeNumber,
|
|
timeToMaturityDays = request.timeToMaturityDays,
|
|
|
|
dividends = request.dividends,
|
|
|
|
volSurfaceNames = request.volSurfaceNames
|
|
};
|
|
|
|
//if (td.TradeType != param.tradeType && (td.TradeType != "场内期权" || param.tradeType != "香草期权"))
|
|
//{
|
|
// throw new ArgumentNullException($"结构类型不符,期望的结构类型为'{param.tradeType}',但传入结构类型为'{td.TradeType}'");
|
|
//}
|
|
|
|
request.ParamOverride?.Invoke(param);
|
|
|
|
return param;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取结束日(endDate必须有值否则QDP会报错)
|
|
/// </summary>
|
|
private static Date GetEndDate(DateTime? maturityDate, int maturityShift)
|
|
{
|
|
if (maturityDate == null || DateTime.Now.AddYears(3) < maturityDate.Value)
|
|
{
|
|
maturityDate = DateTime.Today.AddYears(3);
|
|
}
|
|
|
|
var endDate = QdpCalendarHelper.BizDayShift2(maturityDate.Value);
|
|
|
|
if (maturityShift != 0)
|
|
{
|
|
var dayCount = CalculatorHelper.GetTradeDayCount();
|
|
endDate = QdpCalendarHelper.ShiftDate(endDate, dayCount, maturityShift);
|
|
}
|
|
|
|
return endDate;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取行权日
|
|
/// </summary>
|
|
private static Date GetExerciseDate(DateTime? exerciseDate, int maturityShift)
|
|
{
|
|
if (exerciseDate != null)
|
|
{
|
|
var date = exerciseDate.Value;
|
|
|
|
if (maturityShift != 0)
|
|
{
|
|
var dayCount = CalculatorHelper.GetTradeDayCount();
|
|
date = QdpCalendarHelper.ShiftDate(date, dayCount, maturityShift);
|
|
}
|
|
|
|
return date;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// 雪球期权类型
|
|
/// </summary>
|
|
public enum SnowBallOptionMode
|
|
{
|
|
/// <summary>
|
|
/// 根据传入参数自动判别
|
|
/// </summary>
|
|
Auto,
|
|
|
|
/// <summary>
|
|
/// 普通雪球
|
|
/// </summary>
|
|
SimpleSnowball,
|
|
|
|
/// <summary>
|
|
/// 保本雪球
|
|
/// </summary>
|
|
AnnualizedPremiumSnowball
|
|
}
|
|
}
|
|
|