Files
zszq-trs/YLErpUnitTest/Modules/CalculationModule/SSpreadOptionCalcTest.cs
T
2024-05-09 14:06:26 +08:00

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();
}
}
}