455 lines
17 KiB
C#
455 lines
17 KiB
C#
using Qdp.Pricing.Base.Implementations;
|
|
using Qdp.Pricing.Library.Equity.Engines.Analytical;
|
|
using YLErp.Abstract;
|
|
using YLErp.Commons;
|
|
using YLErp.Enums;
|
|
using YLErp.Modules.CalculationModule.Abstract;
|
|
using YLErp.Modules.VolatilityModule;
|
|
using YLErp.QdpModule;
|
|
|
|
namespace YLErp.Modules.CalculationModule
|
|
{
|
|
/// <summary>
|
|
/// 期权估值计算上下文
|
|
/// </summary>
|
|
public class OptionValueCalcContext : IOtcTradeValueCalcContext
|
|
{
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
protected OptionValueCalcContext(DateTime valueDate, IOtcTradeValueCalcContext baseContext)
|
|
{
|
|
if (baseContext == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(baseContext));
|
|
}
|
|
|
|
ValueDate = valueDate;
|
|
VolType = baseContext.VolType;
|
|
IsEodCalc = baseContext.IsEodCalc;
|
|
DataProvider = baseContext.DataProvider;
|
|
AddingVolRate = baseContext.AddingVolRate;
|
|
ErrorHandler = baseContext.ErrorHandler;
|
|
SysRiskFreeRate = baseContext.SysRiskFreeRate;
|
|
CalcScenario = baseContext.CalcScenario;
|
|
UserGroup = baseContext.UserGroup;
|
|
CalcDeltaT1 = baseContext.CalcDeltaT1;
|
|
|
|
MarketProxy = new MarketProxy(valueDate, SysRiskFreeRate)
|
|
{
|
|
Trace = baseContext.Trace
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public OptionValueCalcContext(string volType, bool isEodCalc, DateTime valueDate, double sysRiskFreeRate, IOptionCalcDataProvider dataProvider)
|
|
{
|
|
DataProvider = CalcCheckHelper.CheckOptionCalcDataProvider(dataProvider);
|
|
|
|
VolType = volType;
|
|
IsEodCalc = isEodCalc;
|
|
ValueDate = valueDate;
|
|
SysRiskFreeRate = sysRiskFreeRate;
|
|
|
|
MarketProxy = new MarketProxy(valueDate, sysRiskFreeRate);
|
|
}
|
|
|
|
#region----属性定义----
|
|
|
|
/// <summary>
|
|
/// 计算场景枚举
|
|
/// </summary>
|
|
public CalcScenarioEnum CalcScenario { get; set; }
|
|
|
|
/// <summary>
|
|
/// 波动率类型(默认:'对冲')
|
|
/// </summary>
|
|
public string VolType { get; } = "对冲";
|
|
|
|
public bool IsEodCalc { get; }
|
|
|
|
/// <summary>
|
|
/// 波动率用户组
|
|
/// </summary>
|
|
public string UserGroup { get; set; }
|
|
|
|
/// <summary>
|
|
/// 是否精确时间模式
|
|
/// </summary>
|
|
public virtual bool IsPreciseTimeMode => CalcScenario != CalcScenarioEnum.EodSettlement;
|
|
|
|
/// <summary>
|
|
/// 波动率加点值
|
|
/// </summary>
|
|
public double AddingVolRate { get; set; }
|
|
|
|
/// <summary>
|
|
/// 是否计算T+1日Delta
|
|
/// </summary>
|
|
public bool CalcDeltaT1 { get; set; }
|
|
|
|
//--------------------------------
|
|
|
|
/// <summary>
|
|
/// 估值日
|
|
/// </summary>
|
|
public DateTime ValueDate { get; }
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public MarketProxy MarketProxy { get; }
|
|
|
|
/// <summary>
|
|
/// 期权计算数据提供接口
|
|
/// </summary>
|
|
public IOptionCalcDataProvider DataProvider { get; protected set; }
|
|
|
|
/// <summary>
|
|
/// 错误处理接口
|
|
/// </summary>
|
|
public IErrorHandler ErrorHandler { get; set; }
|
|
|
|
/// <summary>
|
|
/// 系统全局无风险利率
|
|
/// </summary>
|
|
public double SysRiskFreeRate { get; }
|
|
|
|
/// <summary>
|
|
/// 用于计算跟踪
|
|
/// </summary>
|
|
public TraceWrap Trace { get => MarketProxy.Trace; set => MarketProxy.Trace = value; }
|
|
|
|
public string SkipTradeTypes { get; set; }
|
|
|
|
#endregion
|
|
|
|
#region----方法定义----
|
|
|
|
/// <summary>
|
|
/// 获取期权计算模式
|
|
/// </summary>
|
|
public virtual PricingRequest GetPricingRequest(OtcTradeBase trade)
|
|
{
|
|
if (trade is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(trade));
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(SkipTradeTypes) && SkipTradeTypes.Contains(trade.TradeType))
|
|
{
|
|
return PricingRequest.None;
|
|
}
|
|
|
|
if (CalcScenario == CalcScenarioEnum.RealtimeRisk
|
|
&& !PS.Config.ErpElement.CalcAutocallGreeksInRisk
|
|
&& (trade.TradeType == "凤凰期权" || trade.TradeType == "雪球期权"))
|
|
{
|
|
return QdpPricingRequest.BASIC_PRICING;
|
|
}
|
|
|
|
var pr = QdpPricingRequest.BASIC_GREEKS | PricingRequest.TimeValue;
|
|
|
|
if (trade.TradeType == "亚式期权")
|
|
{
|
|
pr |= PricingRequest.SA_Delta;
|
|
}
|
|
|
|
//20220118:上期做了定制处理,可以返回DDeltaDt指标值
|
|
return PS.Config.Company != Configuration.CompanyEnum.上期资本 ? pr : pr | PricingRequest.DDeltaDt;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public double GetCorrelation(int underlyingId1, int underlyingId2)
|
|
{
|
|
var correlation = DataProvider.UnderlyingDataProvider.GetCorrelation(underlyingId1, underlyingId2);
|
|
return correlation == null || correlation.Correlation == null ? 0.0 : correlation.Correlation.Value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据交易品种ID获取此交易品种是否存在夜盘
|
|
/// </summary>
|
|
public bool HasNightMarket(int varietyId)
|
|
{
|
|
var variety = DataProvider.UnderlyingDataProvider.GetVariety(varietyId);
|
|
if (variety != null)
|
|
{
|
|
return variety.HasNightMarket;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#region----波动率----
|
|
|
|
//TODO:PrepareVolatility欠缺多标的交易的波动率处理
|
|
|
|
/// <summary>
|
|
/// 准备波动率(仅适用于场外期权和场内期权交易)
|
|
/// </summary>
|
|
public bool PrepareVolatility(string qdpTradeId, OtcTradeBase tradeObj, double spotPrice, out string[] volsurfaceNames)
|
|
{
|
|
volsurfaceNames = new[] { qdpTradeId };
|
|
|
|
if (tradeObj.TradeType == "场内期权")
|
|
{
|
|
bool prepareExOptionSavedVol()
|
|
{
|
|
var vol = DataProvider.VolatilityDataProvider.GetExOptionSavedVol(tradeObj.ExchangeOptionCode, ValueDate);
|
|
|
|
if (vol != null)
|
|
{
|
|
var volitality = QdpVolHelper.GenerateFlatSurface(vol.Value);
|
|
MarketProxy.SetVolSurface(qdpTradeId, volitality, AddingVolRate);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (PS.Config.Company == Configuration.CompanyEnum.光大光子)
|
|
{
|
|
return prepareExOptionSavedVol();
|
|
}
|
|
|
|
if (VolType != "交易曲面") //交易曲面时获取交易Mid波动率
|
|
{
|
|
if (prepareExOptionSavedVol())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (PS.Config.ErpElement.ExchangeOptionVolType == Configuration.Enums.ExchangeOptionVolType.ImpliedVol)
|
|
{
|
|
double volValue = tradeObj.Vol ?? 0;
|
|
if (!tradeObj.Vol.IsNormalize())
|
|
{
|
|
if (!tradeObj.TradeSinglePrice.HasValue)
|
|
{
|
|
throw new ServiceException($"计算场内期权隐含波动率失败,期权代码:{tradeObj.ExchangeOptionCode},错误信息:期权价格未获取到");
|
|
}
|
|
|
|
|
|
try
|
|
{
|
|
volValue = VolatilityHelper.GetImpliedVol(ValueDate, tradeObj, null, spotPrice, IsEodCalc);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new ServiceException($"计算场内期权隐含波动率失败,期权代码:{tradeObj.ExchangeOptionCode},期权价格:{tradeObj.TradeSinglePrice:0.####},错误信息:{ex.Message}");
|
|
}
|
|
}
|
|
|
|
MarketProxy.SetVolSurface(qdpTradeId, volValue);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else //场外期权
|
|
{
|
|
if (VolType == "对冲")
|
|
{
|
|
var vol = DataProvider.VolatilityDataProvider.GetOtcHedgingVol(tradeObj.id, ValueDate) ?? tradeObj.TradeSavedVol;
|
|
if (vol != null)
|
|
{
|
|
var volitality = QdpVolHelper.GenerateFlatSurface(vol.Value);
|
|
if (volitality == null)
|
|
{
|
|
return false;
|
|
}
|
|
MarketProxy.SetVolSurface(qdpTradeId, volitality, AddingVolRate);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//只有在tradeVol模式下才应从开平仓波动率中插值,否则应去曲面上插值
|
|
if (VolType == "持仓" && PS.Config.IsTradeVol)
|
|
{
|
|
//场外期权交易使用tradingVol
|
|
var volitality = GetTradingVolatility(tradeObj, false);
|
|
if (volitality == null)
|
|
{
|
|
return false;
|
|
}
|
|
MarketProxy.SetVolSurface(qdpTradeId, volitality, AddingVolRate);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//设置标的波动率
|
|
var volitalityU = GetUnderlyingVolatility(tradeObj, VolType, tradeObj.UnderlyingCode, spotPrice);
|
|
if (volitalityU == null)
|
|
{
|
|
return false;
|
|
}
|
|
MarketProxy.SetVolSurface(qdpTradeId, volitalityU, AddingVolRate);
|
|
return true;
|
|
}
|
|
|
|
//获取交易波动率(如果是结算,先查找结算波动率)
|
|
private IVolatility GetTradingVolatility(OtcTradeBase tradeObj, bool isEod)
|
|
{
|
|
double? vol = null;
|
|
|
|
if (isEod)
|
|
{
|
|
vol = DataProvider.VolatilityDataProvider.GetOtcEodOverrideVol(tradeObj.id, ValueDate);
|
|
}
|
|
|
|
if (vol == null)
|
|
{
|
|
var tradeVol = DataProvider.VolatilityDataProvider.GetOtcPositionVol(tradeObj.id, ValueDate);
|
|
var daycountMode = PS.Config.ErpElement.SmoothingDaycountMode == Configuration.Enums.SmoothingDaycountMode.CalendarDay
|
|
? Qdp.Pricing.Base.Enums.DayCountMode.CalendarDay
|
|
: Qdp.Pricing.Base.Enums.DayCountMode.TradingDay;
|
|
if (tradeVol == null)
|
|
{
|
|
vol = AnalyticalOptionTradeVolInterp.tradeVolLinearInterp(
|
|
new Qdp.Foundation.Implementations.Date(ValueDate),
|
|
tradeObj.TradeOpenVolatility ?? 0,
|
|
tradeObj.TradeCloseVolatility ?? 0,
|
|
new Qdp.Foundation.Implementations.Date(tradeObj.StartDate.Value),
|
|
new Qdp.Foundation.Implementations.Date(tradeObj.ExerciseDate.Value),
|
|
tradeObj.NumOfSmoothingDays ?? 0,
|
|
daycountMode,
|
|
CalendarImpl.Get("chn"));
|
|
}
|
|
else
|
|
{
|
|
//新增交易当天的持仓波动率需要划掉一天,修改后的持仓波动率不需要再划一天
|
|
vol = AnalyticalOptionTradeVolInterp.tradeVolLinearInterp(
|
|
new Qdp.Foundation.Implementations.Date(ValueDate),
|
|
tradeVol.OpenVol,
|
|
tradeVol.CloseVol,
|
|
new Qdp.Foundation.Implementations.Date(tradeVol.ValueDate),
|
|
new Qdp.Foundation.Implementations.Date(tradeObj.ExerciseDate.Value),
|
|
tradeVol.SmoothingDays,
|
|
daycountMode,
|
|
CalendarImpl.Get("chn"), tradeVol.IsFirst);
|
|
}
|
|
}
|
|
|
|
return QdpVolHelper.GenerateFlatSurface(OtcFormatHelper.FormatValue(vol.Value, PS.Config.ErpElement.VolMoreAccurate ? 6 : 4));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取vol 没有则按照默认值新增
|
|
/// </summary>
|
|
private IVolatility GetUnderlyingVolatility(OtcTradeBase tradeObj, string voltype, string underlyingCode, double spotPrice, string volmode = "MoneynessVol")
|
|
{
|
|
voltype = VolatilityHelper.GetUnderlyingVolType(voltype);
|
|
|
|
var vol = DataProvider.VolatilityDataProvider.GetUnderlyingVol(PS.Config.Is润和 && !IsEodCalc ? DateTime.Today : ValueDate, voltype, underlyingCode, UserGroup);
|
|
|
|
if (vol != null)
|
|
{
|
|
var constVol = VolatilityHelper.GetInterpolatedVol(
|
|
volConstructionType: PS.Config.ErpElement.SkewMapVolConstruction ? VolConstructionType.SkewMap : VolConstructionType.Normal,
|
|
volSurface: vol,
|
|
valueDate: PS.Config.Is润和 && !IsEodCalc ? DateTime.Today : ValueDate,
|
|
underlyingCode: underlyingCode,
|
|
exerciseDate: tradeObj.ExerciseDate.Value,
|
|
strike: tradeObj.Strike ?? 0,
|
|
isBuy: tradeObj.BuySell == "买入",
|
|
isCall: ConsGlobal.CallPut.IsCall(tradeObj.CallPut),
|
|
spotPrice: spotPrice,
|
|
isMoneynessOption: tradeObj.IsMoneynessOption == "是",
|
|
isEodCalc: IsEodCalc);
|
|
|
|
return QdpVolHelper.GenerateFlatSurface(constVol);
|
|
}
|
|
|
|
var defVol = VolatilityHelper.GetDefaultVol(new SingleVolatilityRequest
|
|
{
|
|
QuotationDate = ValueDate,
|
|
UnderlyingCode = underlyingCode,
|
|
VolType = voltype,
|
|
TradeVolWithBidAsk = false,
|
|
UserGroup = UserGroup
|
|
});
|
|
defVol.VolSurfaceMode = volmode;
|
|
return defVol;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取交易的无风险利率
|
|
/// </summary>
|
|
public virtual double GetRiskFreeRate(OtcTradeBase trade)
|
|
{
|
|
return trade.NoRiskRate ?? SysRiskFreeRate;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取交易的分红率
|
|
/// </summary>
|
|
public virtual double GetDividendRate(OtcTradeBase trade)
|
|
{
|
|
if (trade.TradeType == "场内期权" && !string.IsNullOrWhiteSpace(trade.ExchangeOptionCode))
|
|
{
|
|
var exOption = DataProvider.UnderlyingDataProvider.GetExchange_List_Option(trade.ExchangeOptionCode);
|
|
var underlying = DataProvider.UnderlyingDataProvider.GetUnderlying(exOption?.UnderlyingCode);
|
|
return underlying?.DividendRate ?? SysRiskFreeRate;
|
|
}
|
|
|
|
return trade.DividendRate ?? GetRiskFreeRate(trade);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
bool _disposed;
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (_disposed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (disposing)
|
|
{
|
|
MarketProxy.Dispose();
|
|
}
|
|
|
|
_disposed = true;
|
|
}
|
|
|
|
public virtual string GetFixingString(OtcTradeBase trade, trade_asian_option asianOption, double spotPrice)
|
|
{
|
|
var fixing = AsianOptionFixingService.GetFixingString(ValueDate, trade, asianOption);
|
|
|
|
if (CalcScenario == CalcScenarioEnum.RealtimeRisk && IsPreciseTimeMode)
|
|
{
|
|
//修复实时计算中fixing最后一天的价格不是实时价格
|
|
fixing = FixingService.AddOrReplaceLastDateSpotPrice(fixing, ValueDate, spotPrice);
|
|
}
|
|
|
|
if (PS.Config.Is润和 && DateTime.Now.Hour < 15)
|
|
{
|
|
var index = fixing.IndexOf(ValueDate.ToString("yyyy-MM-dd"));
|
|
if (index >= 0)
|
|
{
|
|
fixing = fixing.Remove(index).TrimEnd(';');
|
|
}
|
|
}
|
|
|
|
return fixing;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"{ValueDate:yyyy-MM-dd}--{VolType}";
|
|
}
|
|
}
|
|
}
|