using Qdp.Foundation.Implementations; using Qdp.Pricing.Base.Enums; using Qdp.Pricing.Base.Implementations; using Qdp.Pricing.Base.Utilities; using System.Text.RegularExpressions; using YLErp.Models; using YLErp.Modules.CalculationModule; using YLErp.Modules.DataProviderModule; using YLErp.QdpModule; namespace YLErp.BLL { public class VolCaculator { private static readonly Dictionary> volDic = new Dictionary>(); private static Action exceptionHandler; static VolCaculator() { valuedateBLL.ValueDateChanged += (obj, e) => { try { lock (volDic) { volDic.Clear(); } } catch (Exception ex) { LogFactory.GetLogger().Error("清理volDic出错", ex); } }; exceptionHandler = (msg) => LogFactory.GetLogger().Info($"VolCaculator:{msg}"); ; } public static void SetExHandler(Action exHandler) { exceptionHandler = exHandler; } public static VolCaculator Instance { get; } = new VolCaculator(); public Dictionary GetVol(List tradeList, DateTime? valueDate = null) { if (tradeList == null || tradeList.Count == 0) { return new Dictionary(); } return tradeList.ToDictionary(t => t.id, t => GetVol(t.UnderlyingId, valueDate)); } /// /// 按照OTC-6255修改 /// /// /// /// public double GetVol(int underlyingId, DateTime? valueDate = null) { var underlying = underlying_managerBLL.GetById(underlyingId); if (underlying == null) { exceptionHandler($"找到不id为{underlyingId}的标的"); return 0; } if (valueDate == null) { valueDate = valuedateBLL.ValueDate; } //获取标的最新收盘价的日期 var lastUnderlyingSettleDate = GetLastUnderlyingSettleDate(valueDate.Value, underlying); if (lastUnderlyingSettleDate == null) { return 0; } var cacheVol = GetCache(lastUnderlyingSettleDate.Value, underlyingId); if (cacheVol.HasValue) { return cacheVol.Value; } var priceDict = GetHistoricalPrices(underlying, lastUnderlyingSettleDate.Value, 31); var priceList = priceDict.Select(f => f.Value).ToList(); var delta = GetVolFromUnderlyingPrices(priceList); LogFactory.GetLogger().Info("标的underlyingId:" + underlyingId + "的平均波动率为" + delta); Task.Run(() => SetCache(lastUnderlyingSettleDate.Value, underlyingId, delta)); return delta; } public HistoricalVolCurve GetHistoricalVolCurvePointsEx(string underlyingCode, DateTime? valueDate, int lookBackDays, int curveLength, double lowLimit, double highLimit) { var priceCount = lookBackDays + curveLength + curveLength + 1; var priceDict = GetHistoricalPrices(underlyingCode, valueDate.Value.AddDays(-1), priceCount) ?? new Dictionary(); if (priceDict.Count > 0 && priceDict.Count < priceCount) { var code = Regex.Replace(underlyingCode, @"(?<=\D)\d+$", "00"); var date = priceDict.Last().Key.AddDays(-1); var mastPrice = GetHistoricalPrices(code, date, priceCount - priceDict.Count) ?? new Dictionary(); foreach (var item in mastPrice) { priceDict[item.Key] = item.Value; } } var prices = priceDict.Select(f => f.Value).ToList(); var daysInYear = (int)valuedateBLL.TradeDayCount.ToDayCountImpl().DaysInYear(); var lnDiffs = GetLnList(prices); var actCount = Math.Max(lnDiffs.Count - 1, 0); var length = curveLength + curveLength; var curvePoints = Enumerable.Range(0, (actCount < length) ? actCount : length).Select(i => GetVolFromUnderlyingLogDiffs(lnDiffs.Skip(i).Take(lookBackDays).ToList(), daysInYear)).ToList(); actCount = Math.Max(curvePoints.Count - 1, 0); var avgPoints = Enumerable.Range(0, (actCount < curveLength) ? actCount : curveLength) .Select(i => curvePoints.Skip(i).Take(lookBackDays).Average()).ToList(); var dates = priceDict.Keys. Take(avgPoints.Count). Select(d => d.ToString("MM/dd")). ToList(); //GetHistoricalPrices函数查询出的日期和价格序列是倒序的,所以这里要倒过来 dates.Reverse(); avgPoints.Reverse(); //计算中位数和高低分位数 double median = 0.0, percentileHigh = 0.0, percentileLow = 0.0; if (avgPoints.Count > 1) { median = GetPercentile(avgPoints, 0.5); percentileHigh = GetPercentile(avgPoints, highLimit); percentileLow = GetPercentile(avgPoints, lowLimit); } return new HistoricalVolCurve() { Dates = dates, Points = avgPoints, Median = median, PercentileHigh = percentileHigh, PercentileLow = percentileLow }; } public HistoricalVolCurve GetHistoricalVolCurvePoints(string underlyingCode, DateTime? valueDate, int lookBackDays, int curveLength = 60) { var underlying = underlying_managerBLL.GetByCode(underlyingCode); if (underlying == null) { return null; } if (valueDate == null) { valueDate = valuedateBLL.ValueDate; } var lastUnderlyingSettleDate = GetLastUnderlyingSettleDate(valueDate.Value, underlying); if (lastUnderlyingSettleDate == null) { return null; } var priceDict = GetHistoricalPrices(underlying, lastUnderlyingSettleDate.Value, lookBackDays + curveLength + 1); var prices = priceDict.Select(f => f.Value).ToList(); var daysInYear = (int)valuedateBLL.TradeDayCount.ToDayCountImpl().DaysInYear(); var lnDiffs = GetLnDiffList(prices); var curvePoints = Enumerable.Range(0, curveLength).Select(i => GetVolFromUnderlyingLogDiffs(lnDiffs.Skip(i).Take(lookBackDays).ToList(), daysInYear)).ToList(); var dates = priceDict.Keys. Take(curvePoints.Count). Select(d => d.ToString("MM/dd")). ToList(); //GetHistoricalPrices函数查询出的日期和价格序列是倒序的,所以这里要倒过来 dates.Reverse(); curvePoints.Reverse(); //计算中位数和高低分位数 double median = 0.0, percentileHigh = 0.0, percentileLow = 0.0; if (curvePoints.Count > 1) { var sortedPoints = new List(curvePoints); sortedPoints.Sort(); median = sortedPoints.Count % 2 == 0 ? (sortedPoints[sortedPoints.Count / 2 - 1] + sortedPoints[sortedPoints.Count / 2]) / 2.0 : sortedPoints[sortedPoints.Count / 2]; percentileHigh = sortedPoints[(int)(sortedPoints.Count * 0.75 - 1)]; percentileLow = sortedPoints[Math.Max((int)(sortedPoints.Count * 0.25 - 1), 0)]; } return new HistoricalVolCurve() { Dates = dates, Points = curvePoints, Median = median, PercentileHigh = percentileHigh, PercentileLow = percentileLow }; } /// /// 根据给定标的合约的场内期权合约价格,计算隐含波动率曲面 /// /// 标的代码 /// 日期 /// public volatility GetImpliedVolSurface(string underlyingCode, DateTime? valueDate) { var underlying = underlying_managerBLL.GetByCode(underlyingCode); if (underlying == null) { return null; } if (valueDate == null) { valueDate = valuedateBLL.ValueDate; } var aggregateContract = underlying.UnderlyingInstrumentType == "CommodityFutures"; if (aggregateContract) { return GetImpliedVolSurfaceForVariety(underlying, valueDate.Value); } else { return GetImpliedVolSurfaceForSingleUnderlying(underlying, valueDate.Value); } } private volatility GetImpliedVolSurfaceForVariety(underlying_manager underlying, DateTime valueDate) { var options = GetActiveExchangeOptionsForVariety(underlying, valueDate); if (options == null || options.Count == 0) { return null; } var lastUnderlyingSettleDate = GetLastUnderlyingSettleDate(valueDate, underlying); if (lastUnderlyingSettleDate == null) { return null; } var result = GetOptionUnderlyingPricesForVariety(underlying, lastUnderlyingSettleDate.Value); if (result == null) { return null; } var underlyingPriceDict = result.Item1; var maturityPriceDict = result.Item2; if (underlyingPriceDict == null || underlyingPriceDict.Count == 0) { return null; } var optionPrices = new EodExchangeOptionPriceProvider(lastUnderlyingSettleDate.Value,true).Initialize(options.Select(o => o.ContractCode).ToArray()); if (!optionPrices.HasAnyPrice()) { return null; } var riskFreeRate = valuedateBLL.RiskFreeRate / 100.0; return CalculateImpliedVolSurfaceForVariety(underlying, valueDate, underlyingPriceDict, maturityPriceDict, options, optionPrices, riskFreeRate); } /// /// 根据原始的波动率曲面上的关键点,采用指定的插值方法,插出更密集的点 /// /// /// /// public object[][] FillInterpolatePoints(SingleVol[] singleVols, string interpolation) { //通过原始的点构造波动率曲面 var valueDateStr = DateTime.Now.ToString("yyyy-MM-dd"); var surfaceWrap = new VolSurfaceBuilder { interpolation = interpolation, volSurfaceName = "tempVolSurface" }.SetVectors(singleVols).Build(valueDateStr); //获取更多的行权价插值点 var keyStrikes = singleVols.Select(v => v.Strike).Distinct().ToList(); var moreStrikes = GetMoreStrikes(keyStrikes); //获取更多期限插值点 var keyExpires = singleVols.Select(v => v.Expire).Distinct().ToArray(); var moreExpires = GetMoreExpires(keyExpires); //计算这些新的插值点所对应的波动率,并组成新的波动率曲面上的点 var points = new List(); var qdpValueDate = new Date(DateTime.Now); foreach (var expire in moreExpires) { foreach (var strike in moreStrikes) { var vol = QdpVolHelper.GetInterpolatedVol(surfaceWrap.VolSurface, expire.Next(qdpValueDate), strike, "True", 1.0); points.Add(new object[3] { strike, expire.ToString(), vol }); } } return points.ToArray(); } /// /// 根据用户选择的期限,获得相应的波动率曲线的点,这些点除了包括原始的点之外,还包括通过插值计算出更密集的点 /// /// 原始波动率曲面 /// 插值方法 /// 期限 /// public VolSmileCurve FillVolSmileCurvePoints(SingleVol[] singleVols, string interpolation, string expire) { //通过原始的点构造波动率曲面 var valueDateStr = DateTime.Now.ToString("yyyy-MM-dd"); var surfaceWrap = new VolSurfaceBuilder { interpolation = interpolation, volSurfaceName = "tempVolSurface" }.SetVectors(singleVols).Build(valueDateStr); //获取更多的行权价插值点 var keyStrikes = singleVols.Select(v => v.Strike).Distinct().ToList(); var moreStrikes = GetMoreStrikes(keyStrikes, 3); //计算这些新的插值点所对应的波动率,并组成新的波动率曲面上的点 var qdpValueDate = new Date(DateTime.Now); var expireTerm = new Term(expire); var curve = new VolSmileCurve() { XPoints = new List(), YPoints = new List() }; foreach (var strike in moreStrikes) { var vol = QdpVolHelper.GetInterpolatedVol(surfaceWrap.VolSurface, expireTerm.Next(qdpValueDate), strike, "True", 1.0); curve.XPoints.Add(strike.ToString()); curve.YPoints.Add(vol); } return curve; } /// /// 根据原始的行权价点,获取更密的插值点: /// 在每两个行权价点之间,等距离地插入若干个新的点。 /// 例如,在0.9和1.0之间,插入0.933和0.967 /// /// 原始的行权价点 /// 每两个原始行权价点之间插入的新点的个数 /// 所有新插入的点的集合 private List GetMoreStrikes(List keyStrikes, int count = 2) { keyStrikes.Sort(); var morePoints = new List(); for (var i = 0; i < keyStrikes.Count - 1; ++i) { var step = (keyStrikes[i + 1] - keyStrikes[i]) / (count + 1); for (var j = 1; j <= count; ++j) { morePoints.Add(Math.Round(keyStrikes[i] + step * j, 3, MidpointRounding.AwayFromZero)); } } //向两侧再各扩展count个点 const double SCALE_FACTOR = 0.1; for (var i = 1; i <= count; ++i) { morePoints.Add(keyStrikes.First() * Math.Pow(1 - SCALE_FACTOR, i)); morePoints.Add(keyStrikes.Last() * Math.Pow(1 + SCALE_FACTOR, i)); } morePoints.AddRange(keyStrikes); morePoints = morePoints.Distinct().ToList(); morePoints.Sort(); return morePoints; } /// /// 根据原始的期限点,获取更密的插值点: /// 在每两个期限点之间,等距离地插入若干个新的点。 /// 例如,在1W和1M之间,插入2W和3W。 /// /// 具体规则为: /// 1. 在小于等于1W时,每1D新增一个点 /// 2. 在大于1W且小于等于3M时,每1W新增一个点 /// 3. 在大于3M且小于等于1Y时,每1M新增一个点 /// 4. 在大于1Y时,每3M新增一个点 /// /// 原始的期限点 /// private Term[] GetMoreExpires(string[] keyExpires) { var keyTerms = keyExpires.Select(t => new Term(t)).ToList(); keyTerms.Sort(); //return keyTerms.ToArray(); var morePoints = new List(); var oneWeek = new Term(1, Period.Week); var oneMonth = new Term(1, Period.Month); var threeMonth = new Term(3, Period.Month); var oneYear = new Term(1, Period.Year); for (var i = 0; i < keyTerms.Count - 1; ++i) { if (keyTerms[i + 1].CompareTo(oneWeek) <= 0) { morePoints.AddRange(GetInterpolatedPointsLessThan1Week(keyTerms[i], keyTerms[i + 1])); } else if (keyTerms[i + 1].CompareTo(threeMonth) <= 0) { morePoints.AddRange(GetInterpolatedPointsLessThan3Month(keyTerms[i], keyTerms[i + 1])); } else if (keyTerms[i + 1].CompareTo(oneYear) <= 0) { morePoints.AddRange(GetInterpolatedPointsLessThan1Year(keyTerms[i], keyTerms[i + 1])); } else { morePoints.AddRange(GetInterpolatedPointsMoreThan1Year(keyTerms[i], keyTerms[i + 1])); } } morePoints.AddRange(keyTerms); morePoints = morePoints.Distinct().ToList(); morePoints.Sort(); morePoints.Reverse(); return morePoints.ToArray(); } /// /// 当结束期限小于等于1W时,获取更密集的插值点 /// /// /// /// private List GetInterpolatedPointsLessThan1Week(Term start, Term end) { var points = new List(); var term = new Term(start.Length + 1, start.Period); while (term.CompareTo(end) < 0) { points.Add(term); term = new Term(term.Length + 1, start.Period); } return points; } /// /// 当结束期限小于等于3M且大于1W时,获取更密集的插值点 /// /// /// /// private List GetInterpolatedPointsLessThan3Month(Term start, Term end) { var points = new List(); if (start.Period == Period.Day) { var term = new Term(start.Length + 7, Period.Day); while (term.CompareTo(end) < 0) { points.Add(term); term = new Term(term.Length + 7, Period.Day); } } else if (start.Period == Period.Week) { var term = new Term(start.Length + 1, Period.Week); while (term.CompareTo(end) < 0) { points.Add(term); term = new Term(term.Length + 1, Period.Week); } } else { var term = new Term((int)Math.Ceiling(start.Length * 4.5) + 1, Period.Week); while (term.CompareTo(end) < 0) { points.Add(term); term = new Term(term.Length + 1, Period.Week); } } return points; } /// /// 当结束期限小于等于1Y且大于3M时,获取更密集的插值点 /// /// /// /// private List GetInterpolatedPointsLessThan1Year(Term start, Term end) { var points = new List(); if (start.Period == Period.Day) { var term = new Term(start.Length + 30, Period.Day); while (term.CompareTo(end) < 0) { points.Add(term); term = new Term(term.Length + 30, Period.Day); } } else if (start.Period == Period.Week) { var term = new Term(start.Length + 4, Period.Week); while (term.CompareTo(end) < 0) { points.Add(term); term = new Term(term.Length + 4, Period.Week); } } else { var term = new Term(start.Length + 1, Period.Month); while (term.CompareTo(end) < 0) { points.Add(term); term = new Term(term.Length + 1, Period.Month); } } return points; } /// /// 当结束期限大于1Y时,获取更密集的插值点 /// /// /// /// private List GetInterpolatedPointsMoreThan1Year(Term start, Term end) { var points = new List(); if (start.Period == Period.Day) { var term = new Term(start.Length + 90, Period.Day); while (term.CompareTo(end) < 0) { points.Add(term); term = new Term(term.Length + 90, Period.Day); } } else if (start.Period == Period.Week) { var term = new Term(start.Length + 12, Period.Week); while (term.CompareTo(end) < 0) { points.Add(term); term = new Term(term.Length + 12, Period.Week); } } else { var term = new Term(start.Length + 3, Period.Month); while (term.CompareTo(end) < 0) { points.Add(term); term = new Term(term.Length + 3, Period.Month); } } return points; } private volatility GetImpliedVolSurfaceForSingleUnderlying(underlying_manager underlying, DateTime valueDate) { var options = GetActiveExchangeOptionsForSingleUnderlying(underlying, valueDate); if (options == null || options.Count == 0) { return null; } var lastUnderlyingSettleDate = GetLastUnderlyingSettleDate(valueDate, underlying); if (lastUnderlyingSettleDate == null) { return null; } var datePriceDict = GetHistoricalPrices(underlying, lastUnderlyingSettleDate.Value, 1); if (datePriceDict == null) { return null; } var underlyingPrices = datePriceDict.Select(f => f.Value).ToList(); var underlyingPriceDict = new Dictionary { [underlying.UnderlyingCode] = underlyingPrices[0] }; var optionPrices = new EodExchangeOptionPriceProvider(lastUnderlyingSettleDate.Value,true).Initialize(options.Select(o => o.ContractCode).ToArray()); if (!optionPrices.HasAnyPrice()) { return null; } var riskFreeRate = valuedateBLL.RiskFreeRate / 100.0; return CalculateImpliedVolSurfaceForSingleUnderlying(underlying, valueDate, underlyingPriceDict, options, optionPrices, riskFreeRate); } private Tuple, Dictionary> GetOptionUnderlyingPricesForVariety(underlying_manager underlying, DateTime valueDate) { using (var db = new YLContext()) { var underlyings = db.underlying_manager.AsNoTracking(). Where(u => u.CommodityCode == underlying.CommodityCode && u.MaturityDate.Value > valueDate).ToList(); var underlyingCodes = underlyings.Where(u => !u.UnderlyingCode.EndsWith("00")).Select(u => u.UnderlyingCode).ToList(); //var maturityDict = underlyings.ToDictionary(u => u.UnderlyingCode, u => u.MaturityDate.Value); var optionMaturityDict = db.exchange_list_option.AsNoTracking(). Where(o => underlyingCodes.Contains(o.UnderlyingCode)). Select(o => new { key = o.UnderlyingCode, value = o.MaturityDate }). Distinct().ToDictionary(o => o.key, o => o.value); var codePriceDict = (from price in db.eod_commodity_future_price where price.ValueDate == valueDate && underlyingCodes.Contains(price.UnderlyingCode) select price).ToDictionary(p => p.UnderlyingCode.ToUpper(), p => p.ClosePrice); var maturityPriceDict = new Dictionary(); foreach (var kv in codePriceDict) { if (optionMaturityDict.ContainsKey(kv.Key)) { maturityPriceDict[optionMaturityDict[kv.Key]] = codePriceDict[kv.Key]; } } return Tuple.Create(codePriceDict, maturityPriceDict); } } private volatility CalculateImpliedVolSurfaceForSingleUnderlying( underlying_manager underlying, DateTime valueDate, Dictionary underlyingPriceDict, List options, EodExchangeOptionPriceProvider optionPrices, double riskFreeRate) { var strikes = options.Select(o => o.Strike).Distinct().Select(s => s).ToList(); strikes.Sort(); var strikeIndexDict = new Dictionary(); for (var i = 0; i < strikes.Count; ++i) { strikeIndexDict[strikes[i]] = i; } var maturityDates = options.Select(o => o.MaturityDate).Distinct().ToList(); maturityDates.Sort(); var maturityDateIndexDict = new Dictionary(); for (var i = 0; i < maturityDates.Count; ++i) { maturityDateIndexDict[maturityDates[i]] = i; } var volTable = new List() { new SingleVol() { Expire = "1M", Strike = 1.0, Vol = 0.3 } }; var callOptions = options.Where(o => o.OptionType == "看涨").ToList(); var callGrid = CalculateVolGrid(valueDate, underlying.UnderlyingCode, underlyingPriceDict, volTable, strikeIndexDict, maturityDateIndexDict, callOptions, optionPrices, riskFreeRate); var putOptions = options.Where(o => o.OptionType == "看跌").ToList(); var putGrid = CalculateVolGrid(valueDate, underlying.UnderlyingCode, underlyingPriceDict, volTable, strikeIndexDict, maturityDateIndexDict, putOptions, optionPrices, riskFreeRate); // 将看涨和看跌计算出的曲面合并, 如果有值小于等于0(无效值),忽略整行 var mergeGrid = new List>(); var validMaturityDates = new List(); for (var i = 0; i < maturityDates.Count; ++i) { var row = new List(); for (var j = 0; j < strikes.Count; ++j) { var value = 0.0; if (callGrid[i, j] > 0.0 && putGrid[i, j] > 0.0) { value = (callGrid[i, j] + putGrid[i, j]) / 2.0; } else if (callGrid[i, j] > 0.0) { value = callGrid[i, j]; } else { value = putGrid[i, j]; } //如果只有一行,则不忽略该行 //所以只有当行数大于1,且该行有小于等于0的点,才忽略该行 if (maturityDates.Count > 1 && value <= 0) { break; } else { row.Add(value); } } if (row.Count == strikes.Count) { mergeGrid.Add(row); validMaturityDates.Add(maturityDates[i]); } } return CreateVolSurfaceFromGridForSingleUnderlying( underlying.id, underlying.UnderlyingCode, underlyingPriceDict[underlying.UnderlyingCode], valueDate, validMaturityDates, strikes, mergeGrid); } /// /// 计算某标的的隐含波动率曲面 /// /// /// /// /// 标的价格 /// 场内期权列表 /// 场内期权价格 /// 无风险利率 /// private volatility CalculateImpliedVolSurfaceForVariety( underlying_manager underlying, DateTime valueDate, Dictionary underlyingPriceDict, Dictionary maturityPriceDict, List options, EodExchangeOptionPriceProvider optionPrices, double riskFreeRate) { var strikes = options.Select(o => o.Strike).Distinct().Select(s => s).ToList(); strikes.Sort(); var strikeIndexDict = new Dictionary(); for (var i = 0; i < strikes.Count; ++i) { strikeIndexDict[strikes[i]] = i; } var maturityDates = options.Select(o => o.MaturityDate).Distinct().ToList(); maturityDates.Sort(); var maturityDateIndexDict = new Dictionary(); for (var i = 0; i < maturityDates.Count; ++i) { maturityDateIndexDict[maturityDates[i]] = i; } var volTable = new List() { new SingleVol() { Expire = "1M", Strike = 1.0, Vol = 0.3 } }; var callOptions = options.Where(o => o.OptionType == "看涨").ToList(); var callGrid = CalculateVolGrid(valueDate, underlying.UnderlyingCode, underlyingPriceDict, volTable, strikeIndexDict, maturityDateIndexDict, callOptions, optionPrices, riskFreeRate); var putOptions = options.Where(o => o.OptionType == "看跌").ToList(); var putGrid = CalculateVolGrid(valueDate, underlying.UnderlyingCode, underlyingPriceDict, volTable, strikeIndexDict, maturityDateIndexDict, putOptions, optionPrices, riskFreeRate); var mergeGrid = MergeCallPutGridOfVarietySurface(callGrid, putGrid, maturityDates, strikes); //var validGridShape = GetValidGridShapeWithoutMinZero(mergeGrid); var validGridShape = GetValidGridShapeWithoutMaxZero(mergeGrid); var startCount = validGridShape.Item1; var endCount = validGridShape.Item2; strikes = strikes.Skip(startCount).Take(strikes.Count - startCount - endCount).ToList(); mergeGrid = RemoveZeroFromGrid(mergeGrid, startCount, endCount); // 将看涨和看跌计算出的曲面合并, 如果有值小于等于0(无效值),忽略整行 //var mergeGrid = new List>(); //var validMaturityDates = new List(); //for (var i = 0; i < maturityDates.Count; ++i) //{ // var row = new List(); // for (var j = 0; j < strikes.Count; ++j) // { // var value = 0.0; // if (callGrid[i, j] > 0.0 && putGrid[i, j] > 0.0) // { // value = (callGrid[i, j] + putGrid[i, j]) / 2.0; // } // else if (callGrid[i, j] > 0.0) // { // value = callGrid[i, j]; // } // else // { // value = putGrid[i, j]; // } // if (value <= 0) // { // break; // } // else // { // row.Add(value); // } // } // if (row.Count == strikes.Count) // { // mergeGrid.Add(row); // validMaturityDates.Add(maturityDates[i]); // } //} return CreateVolSurfaceFromGridForVariety(underlying.id, underlying.UnderlyingCode, maturityPriceDict, valueDate, maturityDates, strikes, mergeGrid); } /// /// 将一个二维数组每一行的开头startCount个元素和结尾endCount个元素删除 /// /// /// /// /// private List> RemoveZeroFromGrid(List> mergeGrid, int startCount, int endCount) { var result = new List>(); foreach (var row in mergeGrid) { result.Add(row.Skip(startCount).Take(row.Count - startCount - endCount).ToList()); } return result; } /// /// 获取二维表格中,每一行开始的连续0的个数的最小值,和结尾的连续0的个数的最小值, /// 例如: /// 0 0 0 1 1 1 1 0 1 1 0 0 0 0 /// 0 0 0 0 1 1 1 1 1 0 0 0 0 0 /// 0 0 0 1 1 1 1 1 1 0 0 0 0 0 /// /// 开始连续0的个数的最小值是3个,结尾的连续0的个数的最小值是4 /// /// /// private Tuple GetValidGridShapeWithoutMinZero(List> mergeGrid) { int startMinCount = int.MaxValue, endMinCount = int.MaxValue; for (var i = 0; i < mergeGrid.Count; ++i) { int startZeroCount = 0, endZeroCount = 0; var startFlag = true; for (var j = 0; j < mergeGrid[i].Count; ++j) { if (startFlag && mergeGrid[i][j] <= 0) { ++startZeroCount; } else if (mergeGrid[i][j] <= 0) { ++endZeroCount; } else if (mergeGrid[i][j] > 0) { startFlag = false; endZeroCount = 0; } } if (startZeroCount < startMinCount) { startMinCount = startZeroCount; } if (endZeroCount < endMinCount) { endMinCount = endZeroCount; } } return Tuple.Create(startMinCount, endMinCount); } /// /// 获取二维表格中,每一行开始的连续0的个数的最大值,和结尾的连续0的个数的最大值, /// 例如: /// 0 0 0 1 1 1 1 0 1 1 0 0 0 0 /// 0 0 0 0 1 1 1 1 1 0 0 0 0 0 /// 0 0 0 1 1 1 1 1 1 0 0 0 0 0 /// /// 开始连续0的个数的最大值是4个,结尾的连续0的个数的最大值是5 /// /// /// private Tuple GetValidGridShapeWithoutMaxZero(List> mergeGrid) { int startMaxCount = int.MinValue, endMaxCount = int.MinValue; for (var i = 0; i < mergeGrid.Count; ++i) { int startZeroCount = 0, endZeroCount = 0; var startFlag = true; for (var j = 0; j < mergeGrid[i].Count; ++j) { if (startFlag && mergeGrid[i][j] <= 0) { ++startZeroCount; } else if (mergeGrid[i][j] <= 0) { ++endZeroCount; } else if (mergeGrid[i][j] > 0) { startFlag = false; endZeroCount = 0; } } if (startZeroCount > startMaxCount) { startMaxCount = startZeroCount; } if (endZeroCount > endMaxCount) { endMaxCount = endZeroCount; } } return Tuple.Create(startMaxCount, endMaxCount); } private List> MergeCallPutGridOfVarietySurface(double[,] callGrid, double[,] putGrid, List maturityDates, List strikes) { var mergeGrid = new List>(); for (var i = 0; i < maturityDates.Count; ++i) { var row = new List(); for (var j = 0; j < strikes.Count; ++j) { var value = 0.0; if (callGrid[i, j] > 0.0 && putGrid[i, j] > 0.0) { value = (callGrid[i, j] + putGrid[i, j]) / 2.0; } else if (callGrid[i, j] > 0.0) { value = callGrid[i, j]; } else { value = putGrid[i, j]; } row.Add(value); } mergeGrid.Add(row); } return mergeGrid; } /// /// 根据一组到期日、行权价,以及对应的波动率,生成volatility对象 /// /// /// /// /// 到期日 /// 行权价 /// /// private volatility CreateVolSurfaceFromGridForSingleUnderlying( int underlyingId, string underlyingCode, double underlyingPrice, DateTime valueDate, List validMaturityDates, List strikes, List> mergeGrid) { var vol = new volatility() { UnderlyingId = underlyingId, QuotationDate = valueDate, ContractCode = underlyingCode, InterpolationMethod = "BiLinear", Data = "[]", VolType = "隐含" }; var singleVols = new List(); //当只有一个期限时,将为0的点都去掉 if (validMaturityDates.Count == 1) { for (var i = 0; i < strikes.Count; ++i) { if (mergeGrid[0][i] > 0) { singleVols.Add(new SingleVol() { Expire = ToProperTerm((validMaturityDates[0] - valueDate).Days).ToString(), Strike = strikes[i] / underlyingPrice, Vol = mergeGrid[0][i] }); } } } else { for (var i = 0; i < strikes.Count; ++i) { for (var j = 0; j < validMaturityDates.Count; ++j) { singleVols.Add(new SingleVol() { Expire = ToProperTerm((validMaturityDates[j] - valueDate).Days).ToString(), Strike = strikes[i] / underlyingPrice, Vol = mergeGrid[j][i] }); } } } vol.Data = singleVols.ToJson(); return vol; } /// /// 根据一组到期日、行权价,以及对应的波动率,生成volatility对象 /// /// /// /// /// 到期日 /// 行权价 /// /// private volatility CreateVolSurfaceFromGridForVariety( int underlyingId, string underlyingCode, Dictionary maturityPriceDict, DateTime valueDate, List validMaturityDates, List strikes, List> mergeGrid) { var vol = new volatility() { UnderlyingId = underlyingId, QuotationDate = valueDate, ContractCode = underlyingCode, InterpolationMethod = "BiLinear", Data = "[]", VolType = "隐含" }; var singleVols = new List(); for (var i = 0; i < strikes.Count; ++i) { for (var j = 0; j < validMaturityDates.Count; ++j) { singleVols.Add(new SingleVol() { Expire = ToProperTerm((validMaturityDates[j] - valueDate).Days).ToString(), //Strike = Math.Truncate(strikes[i] / maturityPriceDict[validMaturityDates[j]] * 100) / 100, Strike = strikes[i], Vol = mergeGrid[j][i] }); } } vol.Data = singleVols.ToJson(); return vol; } /// /// 将天数转换为恰当的Term /// 例如: /// 7天应该是1W,而不是7D /// /// /// private Term ToProperTerm(int days) { if (days >= 365 && days % 365 == 0) { return new Term(days / 365, Period.Year); } else if (days >= 30 && days % 30 == 0) { return new Term(days / 30, Period.Month); } else if (days >= 7 && days % 7 == 0) { return new Term(days / 7, Period.Week); } else { return new Term(days, Period.Day); } } private double[,] CalculateVolGrid( DateTime valueDate, string underlyingCode, Dictionary underlyingPriceDict, List singleVols, Dictionary strikeIndexDict, Dictionary maturityIndexDict, List options, EodExchangeOptionPriceProvider optionPrices, double riskFreeRate) { var vols = new List>(); var calcVol = new VolatilityImpl { VolTable = singleVols }; foreach (var option in options) { if (optionPrices.TryGetPrice(option.ContractCode,out var optionPrice) && underlyingPriceDict.ContainsKey(option.UnderlyingCode)) { try { var vol = ImpliedVolCalcService.ImpliedVolFromPremium( premium: optionPrice, valueDate: valueDate, underlyingTicker: underlyingCode, underlyingInstrumentType: "CommodityFutures", strike: option.Strike, startDate: valueDate, endDate: option.MaturityDate, optionType: option.OptionType == "看跌" ? "Put" : "Call", exerciseType: "European", spotPrice: underlyingPriceDict[option.UnderlyingCode], notional: 1.0, riskFreeRate: riskFreeRate, tradeType: "Buy", exerciseDate: option.MaturityDate, participationRate: 1.0, principalRate: 0.0, isAnnualized: false, annualizeFactor: 1.0, volatility: calcVol); if (!double.IsNaN(vol)) { vols.Add(Tuple.Create(option.MaturityDate, option.Strike, vol)); } } catch (Exception ex) { //计算隐含波动率失败,一般是场内期权价格偏离太多,暂时不作任何处理 System.Diagnostics.Debug.WriteLine("计算隐含波动率失败," + ex); } } } var grid = new double[maturityIndexDict.Count, strikeIndexDict.Count]; vols.ForEach(v => { grid[maturityIndexDict[v.Item1], strikeIndexDict[v.Item2]] = v.Item3; }); return grid; } /// /// 获取某一标的的活跃的场内期权合约 /// /// /// 标的代码 /// 日期 /// private List GetActiveExchangeOptionsForSingleUnderlying(underlying_manager underlying, DateTime valueDate) { using (var db = new YLContext()) { return db.exchange_list_option.AsNoTracking().Where(o => o.UnderlyingCode == underlying.UnderlyingCode && o.MaturityDate > valueDate).ToList(); } } /// /// 对于某一商品期货的合约,其对应的场内期权只有一个到期日,无法形成一个波动率曲面 /// 因此可以查询同一品种的所有场内期权合约来计算。 /// /// /// /// /// private List GetActiveExchangeOptionsForVariety(underlying_manager underlying, DateTime valueDate) { using (var db = new YLContext()) { var underlyingCodes = db.underlying_manager.AsNoTracking(). Where(u => u.CommodityCode == underlying.CommodityCode && (u.MaturityDate == null || u.MaturityDate.Value > valueDate)). Select(u => u.UnderlyingCode).ToList(); return db.exchange_list_option.AsNoTracking().Where(o => underlyingCodes.Contains(o.UnderlyingCode) && o.MaturityDate > valueDate).ToList(); } } /// /// 获取标的在valueDate之前,有收盘价的最近一个日期 /// /// /// /// public DateTime? GetLastUnderlyingSettleDate(DateTime valueDate, underlying_manager underlying) { var lastUnderlyingSettleDate = new DateTime(); using (var db = new YLContext()) { if (underlying.UnderlyingInstrumentType == "CommodityFutures") { var prices = db.eod_commodity_future_price.Where(e => e.UnderlyingCode == underlying.UnderlyingCode && e.ValueDate <= valueDate).OrderByDescending(x => x.ValueDate); if (prices.Any()) { lastUnderlyingSettleDate = prices.First().ValueDate; } else { exceptionHandler($"{underlying.UnderlyingCode}没有收盘价"); return null; } } else { var prices = db.eod_stock_price.Where(e => e.UnderlyingCode == underlying.UnderlyingCode && e.ValueDate <= valueDate).OrderByDescending(x => x.ValueDate); if (prices.Any()) { lastUnderlyingSettleDate = prices.First().ValueDate; } else { exceptionHandler($"{underlying.UnderlyingCode}没有收盘价"); return null; } } } return lastUnderlyingSettleDate; } /// /// 获取标的的历史价格序列 /// /// /// /// 获取的价格序列个数 /// public Dictionary GetHistoricalPrices(string underlyingCode, DateTime? valueDate, int priceCount) { var underlying = underlying_managerBLL.GetByCode(underlyingCode); if (underlying == null) { return null; } if (valueDate == null) { valueDate = valuedateBLL.ValueDate; } var lastUnderlyingSettleDate = GetLastUnderlyingSettleDate(valueDate.Value, underlying); if (lastUnderlyingSettleDate == null) { return null; } var priceDict = GetHistoricalPrices(underlying, lastUnderlyingSettleDate.Value, priceCount); return priceDict; } /// /// 获取标的的历史价格序列 /// /// /// /// 获取的价格序列个数 /// public Dictionary GetHistoricalPrices(underlying_manager underlying, DateTime lastUnderlyingSettleDate, int priceCount) { var workingDates = QdpCalendarHelper.GetWorkingDatesBeforeDate(lastUnderlyingSettleDate, priceCount); var eodStartDate = workingDates.Last(); var eodEndDate = workingDates.First(); Dictionary priceDic; var eodSettlePriceMode = valuedateBLL.SystemDate.EodSettlePriceMode; using (var db = new YLContext()) { if (underlying.UnderlyingInstrumentType == "CommodityFutures" || underlying.UnderlyingInstrumentType == "CommoditySpot") { priceDic = db.eod_commodity_future_price.Where(e => e.UnderlyingCode == underlying.UnderlyingCode && e.ValueDate <= eodEndDate && e.ValueDate >= eodStartDate) .OrderByDescending(e => e.ValueDate).ToList() .ToDictionary(f => f.ValueDate, f => eodSettlePriceMode == "结算价" ? f.SettlePrice : f.ClosePrice); } else { priceDic = db.eod_stock_price.Where(e => e.UnderlyingCode == underlying.UnderlyingCode && e.ValueDate <= eodEndDate && e.ValueDate >= eodStartDate) .OrderByDescending(e => e.ValueDate) .ToDictionary(f => f.ValueDate, f => f.ClosePrice); } } #region 验证是否缺少某个交易日的收盘价 priceDic = priceDic.Where(d => workingDates.Contains(d.Key)).ToDictionary(d => d.Key, d => d.Value); var missingDates = workingDates.Where(d => !priceDic.ContainsKey(d)).OrderBy(d => d).Select(d => d.ToString("yyyy-MM-dd")); if (missingDates.Any()) { exceptionHandler($"{underlying.UnderlyingCode}在{string.Join(",", missingDates)}没有收盘价"); } #endregion return priceDic; } private List GetLnList(List priceList) { if (priceList == null || priceList.Count == 0) { return new List(); } var list = new List(); for (var i = 0; i < priceList.Count - 1; i++) { list.Add(Math.Log(priceList[i + 1] / priceList[i])); } return list; } public List GetLnDiffList(List priceList) { if (priceList == null || priceList.Count == 0) { return new List(); } var list = new List(); for (var i = 0; i < priceList.Count - 1; i++) { list.Add(Math.Log(priceList[i]) - Math.Log(priceList[i + 1])); } return list; } private double GetPercentile(List objs, double percentile) { objs.Sort(); var realIndex = percentile * (objs.Count - 1); var index = (int)realIndex; var frac = realIndex - index; if (index + 1 < objs.Count) { return objs[index] + (objs[index + 1] - objs[index]) * frac; } return objs[index]; } /// /// 从标的合约的历史价格,计算该标的合约的波动率 /// /// /// public double GetVolFromUnderlyingPrices(List priceList, int daysInYear = 245) { var lnDiffList = GetLnDiffList(priceList); return GetVolFromUnderlyingLogDiffs(lnDiffList, daysInYear); } public double GetVolFromUnderlyingLogDiffs(List lnDiffs, int daysInYear) { if (lnDiffs == null || lnDiffs.Count == 0) { return 0; } var averageDiff = lnDiffs.Average(); var quadraticSum = lnDiffs.Sum(l => Math.Pow(l - averageDiff, 2)); return Math.Sqrt(quadraticSum / (lnDiffs.Count - 1) * daysInYear); } private double? GetCache(DateTime valueDate, int underlyingId) { if (!volDic.ContainsKey(valueDate)) { return null; } if (!volDic[valueDate].ContainsKey(underlyingId)) { return null; } return volDic[valueDate][underlyingId]; } private void SetCache(DateTime valueDate, int underlyingId, double vol) { lock (volDic) { if (!volDic.ContainsKey(valueDate)) { volDic[valueDate] = new Dictionary(); } volDic[valueDate][underlyingId] = vol; } } } public class HistoricalVolCurve { public List Dates { get; set; } public List Points { get; set; } public double Median { get; set; } public double PercentileLow { get; set; } public double PercentileHigh { get; set; } } public class VolSurfacePoints { public string SurfaceName { get; set; } public SingleVol[] Points { get; set; } } public class VolSmileCurve { public List XPoints { get; set; } public List YPoints { get; set; } } }