402 lines
15 KiB
C#
402 lines
15 KiB
C#
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
using Qdp.ComputeService.Data.CommonModels.ValuationParams.Equity;
|
|
using Qdp.ComputeServiceV2.Data.CommonModels.TradeInfos.Options;
|
|
using Qdp.Foundation.Implementations;
|
|
using Qdp.Pricing.Base.Enums;
|
|
using Qdp.Pricing.Base.Implementations;
|
|
using Qdp.Pricing.Base.Utilities;
|
|
using Qdp.Pricing.Library.Options.Products.SyntheticSpread;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using YLErp.BLL.Calculation;
|
|
using YLErp.DBModels;
|
|
using YLErp.DBModels.Helpers;
|
|
using YLErp.QdpModule;
|
|
|
|
namespace YLErp.Modules.CalculationModule
|
|
{
|
|
[TestClass]
|
|
public class SSpreadOptionCalcTest
|
|
{
|
|
[TestMethod("测试雪球期权PV")]
|
|
public void TestSSpreadOptionCalcCrossGammas()
|
|
{
|
|
var spotPrice = 3200d;
|
|
var coefficients = new double[] { -1, 1 };
|
|
var td = new trade
|
|
{
|
|
id = 1,
|
|
AssetBookName = "test",
|
|
AssetId = 1,
|
|
BasisGap = 0,
|
|
BasisUnderlyingCode = null,
|
|
BasisUnderlyingId = 0,
|
|
BuySell = "卖出",
|
|
CalcFlag = 1,
|
|
ClientId = 1,
|
|
ClientName = "客户名称",
|
|
Comments = null,
|
|
CreateDate = DateTime.Now,
|
|
UnderlyingCode = "RB00-TA00",
|
|
UnderlyingInstrumentType = ConsGlobal.InstrumentType.CommodityFutures,
|
|
Strike = 3000,
|
|
StartDate = new DateTime(2020, 12, 1),
|
|
ExerciseDate = new DateTime(2020, 12, 31),
|
|
MaturityDate = new DateTime(2023, 12, 31),
|
|
OptionType = "看涨",
|
|
ExerciseMode = ConsGlobal.ExerciseMode.American,
|
|
Notional = 100,
|
|
NoRiskRate = 0.05,
|
|
ParticipationRate = 1,
|
|
PrincipalRate = 0,
|
|
IsAnnualized = false,
|
|
AnnualizeFactor = 1,
|
|
DividendRate = 0,
|
|
IsMoneynessOption = "否",
|
|
SpotPrice = 3200
|
|
};
|
|
|
|
var marketProxy = QdpMarketManager.Instance.GetPrebuiltMarketProxy("111");
|
|
|
|
var crossGammas = SSpreadOptionCalc.CalculateSyntheticNormalSpreadCrossGammas(
|
|
marketProxy,
|
|
"2020-12-25",
|
|
td.UnderlyingCode,
|
|
td.UnderlyingInstrumentType,
|
|
td.Strike ?? 0,
|
|
td.StartDate.Value.ToString("yyyy-MM-dd"),
|
|
td.MaturityDate.Value.ToString("yyyy-MM-dd"),
|
|
td.CallPut,
|
|
td.ExerciseMode,
|
|
spotPrice,
|
|
coefficients.ToArray(),
|
|
td.Notional,
|
|
"1111111",
|
|
td.NoRiskRate ?? 0.0,
|
|
td.BuySell,
|
|
td.ExerciseDate.Value.ToString("yyyy-MM-dd"),
|
|
td.ParticipationRate ?? 1.0,
|
|
td.PrincipalRate ?? 0.0,
|
|
td.IsAnnualized,
|
|
td.AnnualizeFactor ?? 1.0,
|
|
td.DividendRate ?? 0,
|
|
td.IsMoneynessOptionData,
|
|
td.SpotPrice ?? 0,
|
|
hasNightMarket: false,
|
|
commodityFuturesPreciseTimeMode: false,
|
|
riskFreeRateOverride: td.NoRiskRate ?? double.NaN,
|
|
dividendRateOverride: td.DividendRate ?? double.NaN);
|
|
Assert.IsNotNull(crossGammas);
|
|
|
|
var crossGammas2 = OptionCalculatorV2.CalcSSpreadCrossGammas(new DateTime(2020, 12, 25), td, new OptionValueCalcRequest(0.05)
|
|
{
|
|
spotPrices = new[] { spotPrice },
|
|
vols = new[] { 1.3 },
|
|
}, coefficients);
|
|
|
|
Assert.IsNotNull(crossGammas);
|
|
}
|
|
}
|
|
|
|
static class SSpreadOptionCalc
|
|
{
|
|
/// <summary>
|
|
/// 股指期货类型转换为商品期货类型
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static string ConvertInstrumentType(string instrumentType)
|
|
{
|
|
return ConsGlobal.InstrumentType.IsStockIF(instrumentType) ? ConsGlobal.InstrumentType.CommodityFutures : instrumentType;
|
|
}
|
|
|
|
public static OptionExercise ConvertExerciseType(string exerciseType)
|
|
{
|
|
if (exerciseType != null)
|
|
{
|
|
switch (exerciseType.ToUpper())
|
|
{
|
|
case "美式":
|
|
case "AMERICAN":
|
|
return OptionExercise.American;
|
|
default:
|
|
return OptionExercise.European;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return OptionExercise.European;
|
|
}
|
|
}
|
|
|
|
public static SyntheticNormalSpreadOptionTrade CreateSyntheticNormalSpreadOptionTrade(
|
|
string tradeId,
|
|
string volSurfaceName,
|
|
string tradeDate,
|
|
string underlyingTicker,
|
|
string underlyingInstrumentType,
|
|
double strike,
|
|
string startDate,
|
|
string endDate,
|
|
string optionType,
|
|
string exerciseType,
|
|
double notional,
|
|
string tradeType,
|
|
string exerciseDate,
|
|
double participationRate,
|
|
double principalRate,
|
|
bool isAnnualized,
|
|
double annualizeFactor,
|
|
double[] coefficients = null,
|
|
bool isMoneynessOption = false,
|
|
double initialSpotPrice = 0.0,
|
|
Dictionary<Date, double> dividends = null,
|
|
bool hasNightMarket = false,
|
|
bool commodityFuturesPreciseTimeMode = false,
|
|
double timeToMaturityDays = double.NaN,
|
|
double riskFreeRateOverride = double.NaN,
|
|
double dividendRateOverride = double.NaN)
|
|
{
|
|
underlyingInstrumentType = ConvertInstrumentType(underlyingInstrumentType);
|
|
|
|
var exercise = ConvertExerciseType(exerciseType);
|
|
var optionStartDate = startDate.ToDate();
|
|
var underlyingMaturityDate = string.IsNullOrWhiteSpace(endDate) ? null : endDate.ToDate();
|
|
var temp_exerciseDate = exerciseDate.ToDate();
|
|
|
|
if (temp_exerciseDate < optionStartDate)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Date[] exerciseDates = null;
|
|
Date[] observationDates = null;
|
|
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 };
|
|
}
|
|
|
|
if (underlyingInstrumentType == null)
|
|
{
|
|
throw new Exception("标的资产类型不能为空");
|
|
}
|
|
|
|
var optionDayCount = CalculatorHelper.GetTradeDayCount();
|
|
var pricingTOverride = double.IsNaN(timeToMaturityDays) ? double.NaN : QdpCalendarHelper.CalculateTFromDays(timeToMaturityDays, optionDayCount, optionStartDate.DateTime);
|
|
|
|
var syntheticNormalSpreadOption =
|
|
new SyntheticNormalSpreadOption(
|
|
optionStartDate,
|
|
exercise,
|
|
(OptionType)Enum.Parse(typeof(OptionType), optionType),
|
|
strike,
|
|
(InstrumentType)Enum.Parse(typeof(InstrumentType), underlyingInstrumentType),
|
|
CalendarImpl.Get("chn"),
|
|
optionDayCount.ToDayCountImpl(),
|
|
CurrencyCode.CNY,
|
|
CurrencyCode.CNY,
|
|
exerciseDates,
|
|
observationDates,
|
|
coefficients, // coefficients
|
|
notional,
|
|
null,
|
|
null,
|
|
0.0,
|
|
isMoneynessOption,
|
|
initialSpotPrice,
|
|
dividends,
|
|
hasNightMarket: hasNightMarket,
|
|
commodityFuturesPreciseTimeMode: commodityFuturesPreciseTimeMode,
|
|
pricingToverride: pricingTOverride,
|
|
riskFreeRateOverride: riskFreeRateOverride,
|
|
dividendRateOverride: dividendRateOverride,
|
|
participationRate: participationRate,
|
|
isAnnualized: isAnnualized,
|
|
annualizedFactor: annualizeFactor)
|
|
{
|
|
UnderlyingTickers = new string[] { underlyingTicker }
|
|
};
|
|
|
|
if (string.IsNullOrWhiteSpace(tradeId))
|
|
{
|
|
tradeId = Guid.NewGuid().ToString();
|
|
}
|
|
return new SyntheticNormalSpreadOptionTrade(
|
|
tradeId,
|
|
tradeDate.ToDate(),
|
|
syntheticNormalSpreadOption.StartDate,
|
|
syntheticNormalSpreadOption.ExerciseDates.Last(),
|
|
QdpConverter.ConvertTradeType(tradeType),
|
|
syntheticNormalSpreadOption.Notional,
|
|
0.0,
|
|
syntheticNormalSpreadOption)
|
|
{
|
|
ValuationParameters = new OptionValuationParameters("RiskFreeDiscountCurve", MarketProxy.ConstantZeroCurve, volSurfaceName, syntheticNormalSpreadOption.UnderlyingTickers[0]),
|
|
ProtectionRate = principalRate,
|
|
ParticipationRate = participationRate,
|
|
AnnualizedFactor = annualizeFactor,
|
|
OriginalNotional = TradeHelper.GetStockEqvNotional(notional * initialSpotPrice, participationRate, annualizeFactor)
|
|
};
|
|
}
|
|
|
|
public static double[] CalculateSyntheticNormalSpreadCrossGammas(
|
|
IQdpMarketProxy marketProxy,
|
|
string valueDate,
|
|
string underlyingTicker,
|
|
string underlyingInstrumentType,
|
|
double strike,
|
|
string startDate,
|
|
string endDate,
|
|
string optionType,
|
|
string exerciseType,
|
|
double spotPrice,
|
|
double[] coefficients,
|
|
double notional,
|
|
string volSurfaceName,
|
|
double riskFreeRate,
|
|
string tradeType,
|
|
string exerciseDate,
|
|
double participationRate,
|
|
double principalRate,
|
|
bool isAnnualized,
|
|
double annualizeFactor,
|
|
double dividendRate = 0.0,
|
|
bool isMoneynessOption = false,
|
|
double initialSpotPrice = 0.0,
|
|
Dictionary<Date, double> dividends = null,
|
|
bool hasNightMarket = false,
|
|
bool commodityFuturesPreciseTimeMode = false,
|
|
string engineName = null,
|
|
string discountCurveName = null,
|
|
bool ignoreSkewMap = false,
|
|
bool isForwardTrade = false,
|
|
double timeToMaturityDays = double.NaN,
|
|
double riskFreeRateOverride = double.NaN,
|
|
double dividendRateOverride = double.NaN)
|
|
{
|
|
if (coefficients == null || coefficients.Length == 1)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
string tradeId = null;
|
|
|
|
var trade = CreateSyntheticNormalSpreadOptionTrade(
|
|
tradeId,
|
|
volSurfaceName,
|
|
valueDate,
|
|
underlyingTicker,
|
|
underlyingInstrumentType,
|
|
strike,
|
|
startDate,
|
|
endDate,
|
|
optionType,
|
|
exerciseType,
|
|
notional,
|
|
tradeType,
|
|
exerciseDate,
|
|
participationRate,
|
|
principalRate,
|
|
isAnnualized,
|
|
annualizeFactor,
|
|
coefficients,
|
|
isMoneynessOption,
|
|
initialSpotPrice,
|
|
dividends,
|
|
hasNightMarket,
|
|
commodityFuturesPreciseTimeMode,
|
|
timeToMaturityDays,
|
|
riskFreeRateOverride,
|
|
dividendRateOverride);
|
|
|
|
var market = marketProxy.GetQdpMarket(valueDate);
|
|
if (market == null)
|
|
{
|
|
marketProxy.CreateMarket(valueDate);
|
|
market = marketProxy.GetQdpMarket(valueDate);
|
|
if (market == null)
|
|
{
|
|
return null;
|
|
}
|
|
var volPart = QdpVolHelper.GetDefaultVolatility(1.3);
|
|
|
|
var volSurfaceWrap = new VolSurfaceBuilder
|
|
{
|
|
volSurfaceName = volSurfaceName,
|
|
volSurfaceType = "MoneynessVol",
|
|
interpolation = "BiLinear"
|
|
}.SetVectors(volPart.VolTable).Build(valueDate);
|
|
marketProxy.SaveVolSurface(volSurfaceWrap);
|
|
}
|
|
|
|
var useLocalDiscountCurve = string.IsNullOrWhiteSpace(discountCurveName);
|
|
|
|
//设置DiscountCurve
|
|
if (useLocalDiscountCurve)
|
|
{
|
|
discountCurveName = Guid.NewGuid().ToString();
|
|
var discountCurve = CalculatorHelper.CreateConstantRiskFreeCurve(discountCurveName, riskFreeRate);
|
|
marketProxy.AddYieldCurve(discountCurveName, valueDate, discountCurve);
|
|
}
|
|
|
|
//设置标的价格
|
|
marketProxy.AddStockPrice(underlyingTicker, valueDate, spotPrice);
|
|
|
|
OptionValuationParameters parameters;
|
|
if (underlyingInstrumentType == "Stock")
|
|
{
|
|
//设置DividendCurve
|
|
var dividendCurveName = Guid.NewGuid().ToString();
|
|
var dividendCurve = CalculatorHelper.CreateConstantRiskFreeCurve(dividendCurveName, dividendRate);
|
|
marketProxy.AddYieldCurve(dividendCurveName, valueDate, dividendCurve);
|
|
|
|
parameters = new OptionValuationParameters(
|
|
isForwardTrade ? MarketProxy.ConstantZeroCurve : discountCurveName,
|
|
dividendCurveName,
|
|
volSurfaceName,
|
|
underlyingTicker);
|
|
}
|
|
else
|
|
{
|
|
parameters = new OptionValuationParameters(
|
|
isForwardTrade ? MarketProxy.ConstantZeroCurve : discountCurveName,
|
|
MarketProxy.ConstantZeroCurve,
|
|
volSurfaceName,
|
|
underlyingTicker);
|
|
}
|
|
|
|
var result = trade.CalcCrossGammas(marketProxy.GetQdpMarket(valueDate), parameters);
|
|
if (result == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var crossGammas = new List<double>();
|
|
|
|
// 先获取返回的结果矩阵中的对角线上的元素,对应的是每个标的的Gamma
|
|
for (var i = 0; i < coefficients.Length; ++i)
|
|
{
|
|
crossGammas.Add(result[i, i]);
|
|
}
|
|
|
|
// 再获取两两对应的Cross Gamma
|
|
for (var i = 0; i < coefficients.Length - 1; ++i)
|
|
{
|
|
for (var j = i + 1; j < coefficients.Length; ++j)
|
|
{
|
|
crossGammas.Add(result[i, j]);
|
|
}
|
|
}
|
|
|
|
return crossGammas.ToArray();
|
|
}
|
|
}
|
|
}
|