697 lines
31 KiB
C#
697 lines
31 KiB
C#
using Qdp.ComputeService.Data.CommonModels.MarketInfos;
|
|
using Qdp.ComputeService.Data.CommonModels.MarketInfos.CurveDefinitions;
|
|
using Qdp.Foundation.Implementations;
|
|
using Qdp.Pricing.Base.Enums;
|
|
using Qdp.Pricing.Base.Implementations;
|
|
using Qdp.Pricing.Library.Base.Curves.Interpolators;
|
|
using Qdp.Pricing.Library.Common.Market;
|
|
using Qdp.Pricing.Library.Equity.Utilities;
|
|
using Qdp.Pricing.Library.Options.Products.Vanilla;
|
|
using System.Text.RegularExpressions;
|
|
using YLErp.Abstract.DataProviders;
|
|
using YLErp.BLL.Calculation.V2;
|
|
using YLErp.BLL.Calculation.V2.Parameter;
|
|
using YLErp.Enums;
|
|
using YLErp.Model;
|
|
using YLErp.Models;
|
|
using YLErp.Modules;
|
|
using YLErp.Modules.CalculationModule;
|
|
using YLErp.Modules.ClientModule;
|
|
using YLErp.Modules.DataProviderModule;
|
|
using YLErp.Modules.VolatilityModule;
|
|
using YLErp.QdpModule;
|
|
using YLErp.QdpModule.Constants;
|
|
|
|
namespace YLErp.BLL.Calculation
|
|
{
|
|
public static class CalculatorHelper
|
|
{
|
|
static readonly IYcLogger logger;
|
|
|
|
static CalculatorHelper()
|
|
{
|
|
logger = LogFactory.GetLogger(nameof(CalculatorHelper));
|
|
}
|
|
|
|
public static InstrumentCurveDefinition CreateConstantRiskFreeCurve(string curveName, double riskFreeRate)
|
|
{
|
|
return QdpHelper.CreateRiskFreeCurve(curveName, riskFreeRate, valuedateBLL.SystemDate.CurveDayCount);
|
|
}
|
|
|
|
public static YieldCurve BuildIRCurve(string curveName, DateTime curveDate, List<SimpleIRCurvePoint> curveData)
|
|
{
|
|
if (curveData == null || curveData.Count < 2)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var rateMktData = curveData.Where(c => c.Rate > 0).Select(c => new RateMktData(c.Tenor, c.Rate / 100, "Spot", "None", curveName)).ToArray();
|
|
|
|
if (!rateMktData.Any())
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var curveDayCount = valuedateBLL.SystemDate.CurveDayCount.TrimToNull() ?? "Act365";
|
|
|
|
var curveConvention = new CurveConvention(Guid.NewGuid().ToString(),
|
|
"CNY",
|
|
"ModifiedFollowing",
|
|
"chn",
|
|
curveDayCount,
|
|
"Continuous",
|
|
"Linear");
|
|
var curveDefinition = new InstrumentCurveDefinition(
|
|
curveName,
|
|
curveConvention,
|
|
rateMktData,
|
|
"SpotCurve");
|
|
|
|
return PrebuiltQdpMarketProxy.BuildYieldCurve(new Date(curveDate), curveDefinition);
|
|
}
|
|
|
|
public static string GetTradeDayCount()
|
|
{
|
|
return valuedateBLL.TradeDayCount == "" ? "Act365" : valuedateBLL.TradeDayCount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算一组交易的风险指标
|
|
/// </summary>
|
|
/// <param name="volType">波动率类型</param>
|
|
/// <param name="overrideVolsForTrade">用户自定义的交易波动率</param>
|
|
/// <param name="isUseTradeVol">是否使用交易波动率</param>
|
|
public static TradeRiskResult CalculateRisksForTrades(
|
|
DateTime valueDate,
|
|
IEnumerable<trade> tradeList,
|
|
CalcScenarioEnum calcScenario,
|
|
IPriceProvider priceProvider,
|
|
PricingRequest pricingRequest,
|
|
Dictionary<int, double> addVolRateDic = null,
|
|
string volType = "交易",
|
|
SettlementTypeEnum settlementType = SettlementTypeEnum.ClosePrice,
|
|
Dictionary<int, double> overrideVolsForTrade = null,
|
|
bool isUseTradeVol = false,
|
|
bool preciseTimeMode = true,
|
|
bool isAddVolPercent = true,
|
|
bool canUseManual = false)
|
|
{
|
|
var scalc = new SimpleOtcTradeRiskCalc
|
|
{
|
|
AddVolRateDic = addVolRateDic,
|
|
IsAddVolPercent = isAddVolPercent,
|
|
IsMarginCalc = false,
|
|
IsUseTradeVol = isUseTradeVol,
|
|
OverrideVolsForTrade = overrideVolsForTrade,
|
|
PreciseTimeMode = preciseTimeMode,
|
|
PricingRequest = pricingRequest,
|
|
VolType = volType,
|
|
SettlementType = settlementType,
|
|
CalcScenario = calcScenario,
|
|
CanUseManual = canUseManual
|
|
};
|
|
|
|
return scalc.CalculateRisksForTrades(valueDate, tradeList, priceProvider, calcScenario);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算一组交易的风险指标
|
|
/// 暂时不处理多标的交易
|
|
/// </summary>
|
|
public static TradeRiskResult CalculateRisksForTrades(CalculateRisksForTradesReq req)
|
|
{
|
|
var scalc = new SimpleOtcTradeRiskCalc
|
|
{
|
|
AddVolRateDic = req.addVolRateDic,
|
|
IsAddVolPercent = req.isAddVolPercent,
|
|
IsMarginCalc = req.isMarginCalc,
|
|
IsUseTradeVol = req.isUseTradeVol,
|
|
OverrideVolsForTrade = req.overrideVolsForTrade,
|
|
PreciseTimeMode = req.PreciseTimeMode,
|
|
PricingRequest = req.pricingRequest,
|
|
VolType = req.volType,
|
|
CalcScenario = req.calcScenario,
|
|
CanUseManual = req.canUseManual
|
|
};
|
|
|
|
return scalc.CalculateRisksForTrades(req.valueDate, req.tradeList, req.priceProvider,req.calcScenario);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 应通过api调用
|
|
/// </summary>
|
|
/// <param name="maturityDate"></param>
|
|
/// <param name="userGroup">波动率使用的用户组</param>
|
|
/// <param name="onlyMainContract"></param>
|
|
/// <returns></returns>
|
|
public static List<AtMoneyOptionQuoteRecord> AtMoneyOptionQuotesV2(string maturityDate, string userGroup, bool onlyMainContract = true)
|
|
{
|
|
var atMoneyQuotes = new List<AtMoneyOptionQuoteRecord>();
|
|
try
|
|
{
|
|
var valueDate = RealtimeQuoteValueDate();
|
|
var valueDateStr = valueDate.ToString("yyyy-MM-dd");
|
|
|
|
////最小的合约到期日,应晚于的到期日的前一个月的15日
|
|
//var oneMonthBefore = DateTime.Parse(maturityDate).AddMonths(1);
|
|
//var minMaturityDate = new DateTime(oneMonthBefore.Year, oneMonthBefore.Month, 15);
|
|
|
|
//根据查询valueDate查询合约列表
|
|
var maturityDateValue = DateTime.Parse(maturityDate);
|
|
|
|
//用户会从OTC系统中设置修改报价合约列表,这里要强制重新从数据库中读取
|
|
underlying_main_contractBLL.IsListOld = true;
|
|
var underlyings = underlying_main_contractBLL.GetDefaultAtMoneyQuoteList(maturityDateValue, onlyMainContract);
|
|
|
|
var varieties = VarietyBLL.GetAllvarietyModel();
|
|
underlyings.ForEach(u =>
|
|
{
|
|
u.Variety = varieties.FirstOrDefault(x => x.VarietyCode == u.CommodityCode);
|
|
});
|
|
|
|
//计算平值期权
|
|
var qdpMarketId = Guid.NewGuid().ToString();
|
|
var marketProxy = QdpMarketManager.Instance.GetPrebuiltMarketProxy(qdpMarketId);
|
|
|
|
//使用全局的DiscountCurve以提高计算效率
|
|
var discountCurveName = Guid.NewGuid().ToString();
|
|
var discountCurve = CreateConstantRiskFreeCurve(discountCurveName, valuedateBLL.RiskFreeRate / 100.0);
|
|
marketProxy.AddYieldCurve(discountCurveName, valueDateStr, discountCurve);
|
|
|
|
//获取当天所有波动率
|
|
var volitities = new VolatilityQueryService(OptUserInfo.SystemUser)
|
|
.GetVolatilities(new BatchVolatilityRequest
|
|
{
|
|
QuotationDate = valueDate.Date,
|
|
TradeVolWithBidAsk = true,
|
|
VolType = "交易",
|
|
UserGroup = userGroup,
|
|
UnderlyingIds = underlyings.Select(n => n.id).ToArray()
|
|
}, true);
|
|
//.GetVols(valueDate.Date, new List<string>() { "交易", "报价Bid", "报价Ask" }, underlyings.Select(O => O.UnderlyingCode).ToList(), userGroup);
|
|
|
|
//根据用户设置的bid/ask天数调整规则来分别调整到期日
|
|
//TODO: bidMaturityShift和askMaturityShift应该从某数据库表读取
|
|
var bidMaturityShift = 0;
|
|
var askMaturityShift = 0;
|
|
var client_param = ClientPricingParamService.GetPricingParam(valueDate.Date, maturityDateValue.Date);
|
|
if (client_param != null)
|
|
{
|
|
askMaturityShift = client_param.ask_tuning_day ?? 0;
|
|
bidMaturityShift = client_param.bid_tuning_day ?? 0;
|
|
}
|
|
|
|
var dayCount = CalculatorHelper.GetTradeDayCount();
|
|
var bidMaturityDate = QdpCalendarHelper.ShiftDate(maturityDateValue, dayCount, bidMaturityShift).DateTime;
|
|
var askMaturityDate = QdpCalendarHelper.ShiftDate(maturityDateValue, dayCount, askMaturityShift).DateTime;
|
|
|
|
var buyTrade = new trade()
|
|
{
|
|
TradeType = "香草期权",
|
|
TradeDate = valueDate,
|
|
ExerciseDate = bidMaturityDate,
|
|
OptionType = "看涨",
|
|
ExerciseMode = "European",
|
|
BuySell = "Buy",
|
|
Notional = 1.0
|
|
};
|
|
|
|
var sellTrade = new trade()
|
|
{
|
|
TradeType = "香草期权",
|
|
TradeDate = valueDate,
|
|
ExerciseDate = askMaturityDate,
|
|
OptionType = "看涨",
|
|
ExerciseMode = "European",
|
|
BuySell = "Sell",
|
|
Notional = 1.0
|
|
};
|
|
|
|
var parameter = new VanillaOptionParameter()
|
|
{
|
|
ValueDate = valueDate,
|
|
DiscountCurveName = discountCurveName,
|
|
PreciseTimeMode = true
|
|
};
|
|
|
|
foreach (var underlying in underlyings)
|
|
{
|
|
if (!underlying.Price.HasValue)
|
|
{
|
|
logger.Error($"{underlying.UnderlyingCode}缺少价格,无法报价");
|
|
continue;
|
|
}
|
|
|
|
underlying.QuotationDate = valueDate;
|
|
var variety = VarietyBLL.GetAllvarietyModel().FirstOrDefault(v => v.id == underlying.UnderlyingTypeId);
|
|
if (variety == null)
|
|
{
|
|
logger.Error($"{underlying.UnderlyingCode}没有品种,无法报价");
|
|
continue;
|
|
//throw new Exception($"{underlying.UnderlyingCode}没有品种,请去设置!");
|
|
}
|
|
|
|
buyTrade.SpotPrice = underlying.Price;
|
|
buyTrade.UnderlyingCode = underlying.UnderlyingCode;
|
|
buyTrade.UnderlyingInstrumentType = underlying.UnderlyingInstrumentType;
|
|
|
|
sellTrade.SpotPrice = underlying.Price;
|
|
sellTrade.UnderlyingCode = underlying.UnderlyingCode;
|
|
sellTrade.UnderlyingInstrumentType = underlying.UnderlyingInstrumentType;
|
|
|
|
volatility volsBid = null, volsAsk = null;
|
|
if (PS.Config.ErpElement.SkewMapVolConstruction)
|
|
{
|
|
volsBid = volsAsk = volitities.FirstOrDefault(v => v.VolType == "交易" && v.ContractCode.ToLower() == underlying.UnderlyingCode.ToLower() && v.VolSurfaceMode == "MoneynessVol");
|
|
}
|
|
else
|
|
{
|
|
volsBid = volitities.FirstOrDefault(v => v.VolType == "报价Bid" && v.ContractCode.ToLower() == underlying.UnderlyingCode.ToLower() && v.VolSurfaceMode == "MoneynessVol"); //volatilityBLL.GetVolsFromDb("报价Bid", underlying, "MoneynessVol");
|
|
volsAsk = volitities.FirstOrDefault(v => v.VolType == "报价Ask" && v.ContractCode.ToLower() == underlying.UnderlyingCode.ToLower() && v.VolSurfaceMode == "MoneynessVol");//volatilityBLL.GetVolsFromDb("报价Ask", underlying, "MoneynessVol");
|
|
if (volsBid == null)
|
|
{
|
|
volsBid = VolatilityBuilder.CreateMoneynessVolBuilder(valueDate, "报价Bid").SetUnderlying(underlying).Build(ConsVolInfos.defVol);
|
|
}
|
|
if (volsAsk == null)
|
|
{
|
|
volsAsk = VolatilityBuilder.CreateMoneynessVolBuilder(valueDate, "报价Ask").SetUnderlying(underlying).Build(ConsVolInfos.defVol);
|
|
}
|
|
}
|
|
if (volsBid == null || volsAsk == null)
|
|
{
|
|
logger.Error($"{underlying.UnderlyingCode}没有波动率,无法报价");
|
|
continue;
|
|
}
|
|
|
|
parameter.HasNightMarket = variety.HasNightMarket;
|
|
parameter.SpotPrices = new Dictionary<string, double>() { { underlying.UnderlyingCode, underlying.Price ?? 0.0 } };
|
|
|
|
buyTrade.MaturityDate = underlying.MaturityDate ?? DateTime.MaxValue;
|
|
buyTrade.Strike = underlying.Price ?? 0.0;
|
|
var bidVol = VolatilityHelper.GetInterpolatedVol(
|
|
volConstructionType: PS.Config.ErpElement.SkewMapVolConstruction ? VolConstructionType.SkewMap : VolConstructionType.Normal,
|
|
volSurface: volsBid,
|
|
valueDate: valueDate,
|
|
underlyingCode: underlying.UnderlyingCode,
|
|
exerciseDate: bidMaturityDate,
|
|
strike: buyTrade.Strike.Value,
|
|
isBuy: true,
|
|
isCall: true,
|
|
spotPrice: underlying.Price ?? 0,
|
|
isMoneynessOption: false);
|
|
parameter.Volatility = bidVol;
|
|
if (PS.Config.Is润和)
|
|
{
|
|
parameter.OverrideTTM = TradeCalcHelper.CalculateTTMDays(valueDate, buyTrade.ExerciseDate.Value, underlying?.UnderlyingTypeId ?? 0, false);
|
|
}
|
|
var result = ValueCalculator.CalculateTradeValue(qdpMarketId, buyTrade, underlying, parameter);
|
|
|
|
sellTrade.MaturityDate = underlying.MaturityDate ?? DateTime.MaxValue;
|
|
sellTrade.Strike = underlying.Price ?? 0.0;
|
|
var askVol = VolatilityHelper.GetInterpolatedVol(
|
|
volConstructionType: PS.Config.ErpElement.SkewMapVolConstruction ? VolConstructionType.SkewMap : VolConstructionType.Normal,
|
|
volSurface: volsAsk,
|
|
valueDate: valueDate,
|
|
underlyingCode: underlying.UnderlyingCode,
|
|
exerciseDate: askMaturityDate,
|
|
strike: sellTrade.Strike.Value,
|
|
isBuy: false,
|
|
isCall: true,
|
|
spotPrice: underlying.Price ?? 0,
|
|
isMoneynessOption: false);
|
|
parameter.Volatility = askVol;
|
|
if (PS.Config.Is润和)
|
|
{
|
|
parameter.OverrideTTM = TradeCalcHelper.CalculateTTMDays(valueDate, sellTrade.ExerciseDate.Value, underlying?.UnderlyingTypeId ?? 0, false);
|
|
}
|
|
var result2 = ValueCalculator.CalculateTradeValue(qdpMarketId, sellTrade, underlying, parameter);
|
|
|
|
if (result != null && result2 != null)
|
|
{
|
|
var record = new AtMoneyOptionQuoteRecord()
|
|
{
|
|
marketCode = underlying.MarketCode,
|
|
marketName = underlying.MarketName,
|
|
underlyingType = underlying.UnderlyingType,
|
|
underlyingTypeShortName = underlying.Variety != null ? underlying.Variety.ShortName : underlying.UnderlyingType,
|
|
underlyingCode = underlying.UnderlyingCode,
|
|
spotPrice = underlying.Price.Normalize(),
|
|
comparedToLastDayPrice = (underlying.PrevClosePrice.HasValue && underlying.Price.HasValue) ?
|
|
(underlying.Price.Normalize() > underlying.PrevClosePrice.Normalize()) ? 1 : ((underlying.Price.Normalize() < underlying.PrevClosePrice.Normalize()) ? -1 : 0) :
|
|
1,
|
|
buyOptionPrice = Math.Abs(result.Pv).Normalize(),
|
|
buyOptionVol = result.Vol.Normalize(),
|
|
sellOptionPrice = Math.Abs(result2.Pv).Normalize(),
|
|
sellOptionVol = result2.Vol.Normalize()
|
|
};
|
|
|
|
atMoneyQuotes.Add(record);
|
|
}
|
|
}
|
|
QdpMarketManager.Instance.RemovePrebuiltMarketProxy(qdpMarketId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.Error("AtMoneyOptionQuotes", ex);
|
|
}
|
|
return atMoneyQuotes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算平值报价的默认到期日,仅用于手机实时报价。
|
|
/// 报价预览页面的到期日计算请参见函数FlatPriceQuotationBLL.GetAtmOptionQuotePreviewMaturity
|
|
/// </summary>
|
|
public static string RealtimeQuoteDefaultMaturityDate()
|
|
{
|
|
var valueDate = RealtimeQuoteValueDate();
|
|
// 1个月平值期权的到期日是报价日后一个月减一天
|
|
var maturityDate = valueDate.AddMonths(1).AddDays(-1);
|
|
return AtmQuoteMaturityDateShift(maturityDate).ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算平值报价的最大到期日,仅用于光大光子手机实时报价。
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public static string RealtimeQuoteMaxMaturityDate()
|
|
{
|
|
var date = DateTime.MaxValue;
|
|
var term = valuedateBLL.SystemDate.QuotationCycle;
|
|
if (string.IsNullOrWhiteSpace(term)) { term = "3M"; }
|
|
var m = Regex.Match(term, @"^(?<num>\d+)(?<unit>[D|W|M|Y|d|w|m|y])$");
|
|
if (m.Success)
|
|
{
|
|
var num = int.Parse(m.Groups["num"].Value);
|
|
switch (m.Groups["unit"].Value.ToUpper())
|
|
{
|
|
case "D":
|
|
date = DateTime.Now.AddDays(num).AddDays(-1);
|
|
break;
|
|
case "W":
|
|
date = DateTime.Now.AddDays(num * 7).AddDays(-1);
|
|
break;
|
|
case "M":
|
|
date = DateTime.Now.AddMonths(num).AddDays(-1);
|
|
break;
|
|
case "Y":
|
|
date = DateTime.Now.AddYears(num).AddDays(-1);
|
|
break;
|
|
}
|
|
date = QdpCalendarHelper.GetNonHolidayDefore(date);
|
|
}
|
|
return date.ToString("yyyy-MM-dd");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据AtmQuotePreviousBizDayAdjust参数对报价到期日进行调整
|
|
/// </summary>
|
|
/// <param name="date">原到期日</param>
|
|
/// <returns>调整后的到期日</returns>
|
|
public static Date AtmQuoteMaturityDateShift(DateTime date)
|
|
{
|
|
return AtmQuoteMaturityDateShift(new Date(date));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据AtmQuotePreviousBizDayAdjust参数对报价到期日进行调整
|
|
/// </summary>
|
|
/// <param name="date">原到期日</param>
|
|
/// <returns>调整后的到期日</returns>
|
|
private static Date AtmQuoteMaturityDateShift(Date date)
|
|
{
|
|
var calendar = CalendarImpl.Get("chn");
|
|
// 如果那天是非交易日,根据参数往前或往后移
|
|
return calendar.IsBizDay(date)
|
|
? date
|
|
: (PS.Config.ErpElement.AtmQuotePreviousBizDayAdjust ? calendar.PrevBizDay(date) : calendar.NextBizDay(date));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 对实时报价,计算报价日
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public static DateTime RealtimeQuoteValueDate()
|
|
{
|
|
var calendar = CalendarImpl.Get("chn");
|
|
var now = DateTime.Now;
|
|
var qdpValueDate = new Date(now);
|
|
if (now.Hour >= 21 || QdpCalendarHelper.IsHoliday(now.Date))
|
|
{
|
|
// 如果已经是夜盘开盘或者当前为节假日,则认为报价日期是下一个交易日
|
|
qdpValueDate = calendar.NextBizDay(qdpValueDate);
|
|
}
|
|
return qdpValueDate.DateTime;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据合约的到期日和合约代码来获取在报价中所使用的合约到期日
|
|
/// 主要是为了处理像SC2001这样的合约
|
|
/// 合约代码 | 实时到期日 | 代码月份 | 在报价中使用的到期日
|
|
/// RB1912 | 2019-12-15 | 12 | 2019-12-15
|
|
/// RB2001 | 2020-01-15 | 1 | 2020-01-15
|
|
/// SC1912 | 2019-11-20 | 12 | 2019-11-20
|
|
/// SC2001 | 2019-12-20 | 1 | 2020-01-20
|
|
/// </summary>
|
|
/// <param name="contractMaturityDate">合约实际到期日</param>
|
|
/// <param name="contractCode">合约代码</param>
|
|
/// <returns></returns>
|
|
public static DateTime GetContractMaturityDateForQuote(DateTime contractMaturityDate, string contractCode)
|
|
{
|
|
int.TryParse(contractCode.Substring(contractCode.Length - 2), out var monthInCode);
|
|
if (monthInCode == contractMaturityDate.Month)
|
|
{
|
|
return contractMaturityDate;
|
|
}
|
|
else if (monthInCode < 1 || monthInCode > 12)
|
|
{
|
|
LogFactory.GetLogger("GetContractMaturityDateForQuote").Error($"未能正确解析:{contractMaturityDate} -- {contractCode}");
|
|
return contractMaturityDate;
|
|
}
|
|
else
|
|
{
|
|
var year = contractMaturityDate.Year + (monthInCode == 1 ? 1 : 0);
|
|
var days = DateTime.DaysInMonth(year, monthInCode);
|
|
return new DateTime(year, monthInCode, days < contractMaturityDate.Day ? days : contractMaturityDate.Day);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 为特定的波动率曲面设置自定义波动率
|
|
/// </summary>
|
|
/// <param name="valueDate"></param>
|
|
/// <param name="userId"></param>
|
|
/// <param name="volSurfaceNameKey"></param>
|
|
/// <param name="vol"></param>
|
|
public static VolSurfaceInitParams PrepareOverrideVol(DateTime valueDate, string userId, string volSurfaceNameKey, double vol)
|
|
{
|
|
//用自定义波动率值构造一个水平的波动率曲面
|
|
var vols = new List<SingleVol>
|
|
{
|
|
new SingleVol
|
|
{
|
|
Strike = 1,
|
|
Expire = "1D",
|
|
Vol = vol
|
|
},
|
|
new SingleVol
|
|
{
|
|
Strike = 1,
|
|
Expire = "1Y",
|
|
Vol = vol
|
|
}
|
|
};
|
|
var initParam = new VolSurfaceInitParamsBuilder(userId)
|
|
.SetValueDate(valueDate).Build("MoneynessVol", vols, volSurfaceNameKey);
|
|
VolSurfaceInitializerSingleton.GetInitializer(true).InitializeMarketProxy(initParam);
|
|
return initParam;
|
|
}
|
|
|
|
public static string GenerateVolSurfaceName(this trade trade, string secondUnderlyingCode = null)
|
|
{
|
|
return string.IsNullOrWhiteSpace(secondUnderlyingCode)
|
|
? $"{trade.id}{QdpVolHelper.VOL_SURFACE_SUFFIX}"
|
|
: $"{trade.id}_{secondUnderlyingCode}{QdpVolHelper.VOL_SURFACE_SUFFIX}";
|
|
}
|
|
|
|
public static string GenerateVolSurfaceName(this underlying_manager underlying)
|
|
{
|
|
return $"{underlying.UnderlyingCode}{QdpVolHelper.VOL_SURFACE_SUFFIX}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据传入的香草期权组合,生成Payoff图的关键点
|
|
/// </summary>
|
|
/// <param name="trades">期权交易组合</param>
|
|
/// <returns></returns>
|
|
public static List<PlotPoint> GetPayoffPlotPoints(IEnumerable<trade> trades)
|
|
{
|
|
var options = new List<OptionTradeForPayoff>();
|
|
foreach (var trade in trades)
|
|
{
|
|
var option = ConvertTradeForPayoffCalc(trade);
|
|
if (option == null)
|
|
{
|
|
throw new Exception("只支持香草期权组合的Payoff图形展示.");
|
|
}
|
|
options.Add(option);
|
|
}
|
|
return PayoffPlotHelper.GetPayoffPoints(options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 将trade对象转换为OptionTradeForPayoff对象,用作展现Payoff图
|
|
/// </summary>
|
|
/// <param name="trade"></param>
|
|
/// <returns></returns>
|
|
private static OptionTradeForPayoff ConvertTradeForPayoffCalc(trade trade)
|
|
{
|
|
if (trade.TradeType != "香草期权")
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var startDate = new Date(trade.StartDate.Value);
|
|
var endDate = new Date(trade.ExerciseDate.Value);
|
|
|
|
var vanillaOption = new VanillaOption(
|
|
startDate,
|
|
endDate,
|
|
OptionExercise.European,
|
|
trade.CallPut == "Put" ? OptionType.Put : OptionType.Call,
|
|
trade.IsMoneynessOptionData ? (trade.Strike ?? 0.0) * (trade.SpotPrice ?? 0.0) : (trade.Strike ?? 0.0),
|
|
(InstrumentType)Enum.Parse(typeof(InstrumentType), trade.UnderlyingInstrumentType),
|
|
CalendarImpl.Get("chn"),
|
|
new Bus244(),
|
|
CurrencyCode.CNY,
|
|
CurrencyCode.CNY,
|
|
new Date[] { endDate },
|
|
new Date[] { endDate },
|
|
trade.Notional,
|
|
optionPremium: trade.TradePrice ?? 0.0);
|
|
|
|
return new OptionTradeForPayoff()
|
|
{
|
|
Option = vanillaOption,
|
|
BuySell = trade.BuySell == "卖出" ? Position.Sell : Position.Buy
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据UnderyingCode来判断获取商品现货价格
|
|
/// </summary>
|
|
/// <param name="valueDate"></param>
|
|
/// <param name="maturityDate"></param>
|
|
/// <param name="UnderlyingCode"></param>
|
|
/// <returns></returns>
|
|
public static double GetCommoditySpotPriceByValueDate(DateTime valueDate, DateTime maturityDate, bool isClosePrice, string UnderlyingCode)
|
|
{
|
|
double CommoditySpotPrice = 0;
|
|
using (var db = new YLContext())
|
|
{
|
|
var BasisCurveList = new List<Underlying_BasiscurveList>();
|
|
var underlyingcodelist = new List<string>();
|
|
var tenors = new List<string>();
|
|
var spreads = new List<double>();
|
|
var GetBasisCurve = db.underlying_basis_curve.FirstOrDefault(c => c.UnderlyingCode == UnderlyingCode);
|
|
if (GetBasisCurve == null)
|
|
{
|
|
if (isClosePrice)
|
|
{
|
|
EodPriceQueryService.TryGetClosePrice(valueDate, UnderlyingCode, out CommoditySpotPrice);
|
|
}
|
|
else
|
|
{
|
|
DataCacheProvider.GetUnderlyingDataSource().TryGetPrice(UnderlyingCode, out CommoditySpotPrice);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BasisCurveList = JsonHelper.Deserialize<List<Underlying_BasiscurveList>>(GetBasisCurve.InterpolationData);
|
|
foreach (var item in BasisCurveList)
|
|
{
|
|
underlyingcodelist.Add(item.UnderlyingCodes);
|
|
tenors.Add(item.term);
|
|
spreads.Add(item.BasisCurve);
|
|
}
|
|
CommoditySpotPrice = GetSpotPriceFromBasisSpread(valueDate, maturityDate, isClosePrice, underlyingcodelist, tenors, spreads);
|
|
}
|
|
}
|
|
return CommoditySpotPrice;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据期货价格和基差曲线计算现货价格
|
|
/// </summary>
|
|
/// <param name="valueDate">估值日</param>
|
|
/// <param name="maturityDate">交易到期日</param>
|
|
/// <param name="futurePrice">期货价格</param>
|
|
/// <param name="tenors">基差曲线的关键期限列表</param>
|
|
/// <param name="spreads">基差曲线的基差列表</param>
|
|
/// <returns></returns>
|
|
public static double GetSpotPriceFromBasisSpread(DateTime valueDate, DateTime maturityDate, bool isClosePrice, List<string> underlyingcodelist, List<string> tenors, List<double> spreads)
|
|
{
|
|
if (tenors == null || spreads == null || tenors.Count != spreads.Count)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var qdpStartDate = new Date(valueDate);
|
|
var keyTs = tenors.Select(t => (double)((new Term(t).Next(qdpStartDate)).DateTime - valueDate).Days).ToArray();
|
|
var keyPoints = keyTs.Zip(spreads, (x, y) => Tuple.Create(x, y)).ToArray();
|
|
var codelist = underlyingcodelist.ToArray();
|
|
var underlyingcode = "";
|
|
var days = (maturityDate - valueDate).Days;
|
|
for (var i = 0; i < keyPoints.Length; i++)
|
|
{
|
|
if (days <= keyPoints[i].Item1 && (i == 0 || i > 0 && days > keyPoints[i - 1].Item1))
|
|
{
|
|
underlyingcode = codelist[i];
|
|
}
|
|
}
|
|
double CommoditySpotPrice = 0;
|
|
if (isClosePrice)
|
|
{
|
|
EodPriceQueryService.TryGetClosePrice(valueDate, underlyingcode, out CommoditySpotPrice);
|
|
}
|
|
else
|
|
{
|
|
DataCacheProvider.GetUnderlyingDataSource().TryGetPrice(underlyingcode, out CommoditySpotPrice);
|
|
}
|
|
var interpolator = new LinearInterpolator(keyPoints);
|
|
return (CommoditySpotPrice + interpolator.GetValue(days));
|
|
}
|
|
}
|
|
|
|
public class SpreadOptionPricingInput
|
|
{
|
|
public underlying_manager[] Underlyings { get; set; }
|
|
public double[] SpotPrices { get; set; }
|
|
public double[] Correlations { get; set; }
|
|
}
|
|
|
|
#region T型报价的结果类
|
|
public class AtMoneyOptionQuoteRecord
|
|
{
|
|
public string marketCode { get; set; }
|
|
public string marketName { get; set; }
|
|
public string underlyingType { get; set; }
|
|
public string underlyingTypeShortName { get; set; }
|
|
public string underlyingCode { get; set; }
|
|
public double spotPrice { get; set; }
|
|
public int comparedToLastDayPrice { get; set; }
|
|
public double buyOptionPrice { get; set; }
|
|
public double sellOptionPrice { get; set; }
|
|
public double buyOptionVol { get; set; }
|
|
public double sellOptionVol { get; set; }
|
|
}
|
|
#endregion
|
|
|
|
[Serializable]
|
|
public class SimpleIRCurvePoint
|
|
{
|
|
public string Tenor { get; set; }
|
|
public double Rate { get; set; }
|
|
}
|
|
|
|
}
|