1451 lines
57 KiB
C#
1451 lines
57 KiB
C#
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<DateTime, Dictionary<int, double>> volDic = new Dictionary<DateTime, Dictionary<int, double>>();
|
|
private static Action<string> exceptionHandler;
|
|
|
|
static VolCaculator()
|
|
{
|
|
valuedateBLL.ValueDateChanged += (obj, e) =>
|
|
{
|
|
try
|
|
{
|
|
lock (volDic)
|
|
{
|
|
volDic.Clear();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogFactory.GetLogger<VolCaculator>().Error("清理volDic出错", ex);
|
|
}
|
|
};
|
|
exceptionHandler = (msg) => LogFactory.GetLogger<VolCaculator>().Info($"VolCaculator:{msg}"); ;
|
|
}
|
|
|
|
public static void SetExHandler(Action<string> exHandler)
|
|
{
|
|
exceptionHandler = exHandler;
|
|
}
|
|
|
|
public static VolCaculator Instance { get; } = new VolCaculator();
|
|
|
|
public Dictionary<int, double> GetVol(List<trade> tradeList, DateTime? valueDate = null)
|
|
{
|
|
if (tradeList == null || tradeList.Count == 0)
|
|
{
|
|
return new Dictionary<int, double>();
|
|
}
|
|
|
|
return tradeList.ToDictionary(t => t.id, t => GetVol(t.UnderlyingId, valueDate));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 按照OTC-6255修改
|
|
/// </summary>
|
|
/// <param name="underlyingId"></param>
|
|
/// <param name="valueDate"></param>
|
|
/// <returns></returns>
|
|
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<VolCaculator>().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<DateTime, double>();
|
|
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<DateTime, double>();
|
|
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<double>(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
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据给定标的合约的场内期权合约价格,计算隐含波动率曲面
|
|
/// </summary>
|
|
/// <param name="underlyingCode">标的代码</param>
|
|
/// <param name="valueDate">日期</param>
|
|
/// <returns></returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据原始的波动率曲面上的关键点,采用指定的插值方法,插出更密集的点
|
|
/// </summary>
|
|
/// <param name="volSurfacePoints"></param>
|
|
/// <param name="interpolation"></param>
|
|
/// <returns></returns>
|
|
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<object[]>();
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据用户选择的期限,获得相应的波动率曲线的点,这些点除了包括原始的点之外,还包括通过插值计算出更密集的点
|
|
/// </summary>
|
|
/// <param name="volSurfaces">原始波动率曲面</param>
|
|
/// <param name="interpolation">插值方法</param>
|
|
/// <param name="expire">期限</param>
|
|
/// <returns></returns>
|
|
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<string>(),
|
|
YPoints = new List<double>()
|
|
};
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据原始的行权价点,获取更密的插值点:
|
|
/// 在每两个行权价点之间,等距离地插入若干个新的点。
|
|
/// 例如,在0.9和1.0之间,插入0.933和0.967
|
|
/// </summary>
|
|
/// <param name="keyStrikes">原始的行权价点</param>
|
|
/// <param name="count">每两个原始行权价点之间插入的新点的个数</param>
|
|
/// <returns>所有新插入的点的集合</returns>
|
|
private List<double> GetMoreStrikes(List<double> keyStrikes, int count = 2)
|
|
{
|
|
keyStrikes.Sort();
|
|
var morePoints = new List<double>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据原始的期限点,获取更密的插值点:
|
|
/// 在每两个期限点之间,等距离地插入若干个新的点。
|
|
/// 例如,在1W和1M之间,插入2W和3W。
|
|
///
|
|
/// 具体规则为:
|
|
/// 1. 在小于等于1W时,每1D新增一个点
|
|
/// 2. 在大于1W且小于等于3M时,每1W新增一个点
|
|
/// 3. 在大于3M且小于等于1Y时,每1M新增一个点
|
|
/// 4. 在大于1Y时,每3M新增一个点
|
|
/// </summary>
|
|
/// <param name="keyExpires">原始的期限点</param>
|
|
/// <returns></returns>
|
|
private Term[] GetMoreExpires(string[] keyExpires)
|
|
{
|
|
var keyTerms = keyExpires.Select(t => new Term(t)).ToList();
|
|
keyTerms.Sort();
|
|
//return keyTerms.ToArray();
|
|
|
|
var morePoints = new List<Term>();
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 当结束期限小于等于1W时,获取更密集的插值点
|
|
/// </summary>
|
|
/// <param name="start"></param>
|
|
/// <param name="end"></param>
|
|
/// <returns></returns>
|
|
private List<Term> GetInterpolatedPointsLessThan1Week(Term start, Term end)
|
|
{
|
|
var points = new List<Term>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 当结束期限小于等于3M且大于1W时,获取更密集的插值点
|
|
/// </summary>
|
|
/// <param name="start"></param>
|
|
/// <param name="end"></param>
|
|
/// <returns></returns>
|
|
private List<Term> GetInterpolatedPointsLessThan3Month(Term start, Term end)
|
|
{
|
|
var points = new List<Term>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 当结束期限小于等于1Y且大于3M时,获取更密集的插值点
|
|
/// </summary>
|
|
/// <param name="start"></param>
|
|
/// <param name="end"></param>
|
|
/// <returns></returns>
|
|
private List<Term> GetInterpolatedPointsLessThan1Year(Term start, Term end)
|
|
{
|
|
var points = new List<Term>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 当结束期限大于1Y时,获取更密集的插值点
|
|
/// </summary>
|
|
/// <param name="start"></param>
|
|
/// <param name="end"></param>
|
|
/// <returns></returns>
|
|
private List<Term> GetInterpolatedPointsMoreThan1Year(Term start, Term end)
|
|
{
|
|
var points = new List<Term>();
|
|
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<string, double>
|
|
{
|
|
[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<string, double>, Dictionary<DateTime, double>> 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<DateTime, double>();
|
|
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<string, double> underlyingPriceDict,
|
|
List<ExchangeListOption> options,
|
|
EodExchangeOptionPriceProvider optionPrices,
|
|
double riskFreeRate)
|
|
{
|
|
var strikes = options.Select(o => o.Strike).Distinct().Select(s => s).ToList();
|
|
strikes.Sort();
|
|
var strikeIndexDict = new Dictionary<double, int>();
|
|
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<DateTime, int>();
|
|
for (var i = 0; i < maturityDates.Count; ++i)
|
|
{
|
|
maturityDateIndexDict[maturityDates[i]] = i;
|
|
}
|
|
|
|
var volTable = new List<SingleVol>() { 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<List<double>>();
|
|
var validMaturityDates = new List<DateTime>();
|
|
for (var i = 0; i < maturityDates.Count; ++i)
|
|
{
|
|
var row = new List<double>();
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算某标的的隐含波动率曲面
|
|
/// </summary>
|
|
/// <param name="underlyingId"></param>
|
|
/// <param name="underlyingCode"></param>
|
|
/// <param name="valueDate"></param>
|
|
/// <param name="underlyingPrice">标的价格</param>
|
|
/// <param name="options">场内期权列表</param>
|
|
/// <param name="optionPrices">场内期权价格</param>
|
|
/// <param name="riskFreeRate">无风险利率</param>
|
|
/// <returns></returns>
|
|
private volatility CalculateImpliedVolSurfaceForVariety(
|
|
underlying_manager underlying,
|
|
DateTime valueDate,
|
|
Dictionary<string, double> underlyingPriceDict,
|
|
Dictionary<DateTime, double> maturityPriceDict,
|
|
List<ExchangeListOption> options,
|
|
EodExchangeOptionPriceProvider optionPrices,
|
|
double riskFreeRate)
|
|
{
|
|
var strikes = options.Select(o => o.Strike).Distinct().Select(s => s).ToList();
|
|
strikes.Sort();
|
|
var strikeIndexDict = new Dictionary<double, int>();
|
|
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<DateTime, int>();
|
|
for (var i = 0; i < maturityDates.Count; ++i)
|
|
{
|
|
maturityDateIndexDict[maturityDates[i]] = i;
|
|
}
|
|
|
|
var volTable = new List<SingleVol>() { 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<List<double>>();
|
|
//var validMaturityDates = new List<DateTime>();
|
|
//for (var i = 0; i < maturityDates.Count; ++i)
|
|
//{
|
|
// var row = new List<double>();
|
|
// 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 将一个二维数组每一行的开头startCount个元素和结尾endCount个元素删除
|
|
/// </summary>
|
|
/// <param name="mergeGrid"></param>
|
|
/// <param name="startCount"></param>
|
|
/// <param name="endCount"></param>
|
|
/// <returns></returns>
|
|
private List<List<double>> RemoveZeroFromGrid(List<List<double>> mergeGrid, int startCount, int endCount)
|
|
{
|
|
var result = new List<List<double>>();
|
|
foreach (var row in mergeGrid)
|
|
{
|
|
result.Add(row.Skip(startCount).Take(row.Count - startCount - endCount).ToList());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取二维表格中,每一行开始的连续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
|
|
/// </summary>
|
|
/// <param name="mergeGrid"></param>
|
|
/// <returns></returns>
|
|
private Tuple<int, int> GetValidGridShapeWithoutMinZero(List<List<double>> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取二维表格中,每一行开始的连续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
|
|
/// </summary>
|
|
/// <param name="mergeGrid"></param>
|
|
/// <returns></returns>
|
|
private Tuple<int, int> GetValidGridShapeWithoutMaxZero(List<List<double>> 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<List<double>> MergeCallPutGridOfVarietySurface(double[,] callGrid, double[,] putGrid, List<DateTime> maturityDates, List<double> strikes)
|
|
{
|
|
var mergeGrid = new List<List<double>>();
|
|
for (var i = 0; i < maturityDates.Count; ++i)
|
|
{
|
|
var row = new List<double>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据一组到期日、行权价,以及对应的波动率,生成volatility对象
|
|
/// </summary>
|
|
/// <param name="underlyingId"></param>
|
|
/// <param name="underlyingCode"></param>
|
|
/// <param name="valueDate"></param>
|
|
/// <param name="validMaturityDates">到期日</param>
|
|
/// <param name="strikes">行权价</param>
|
|
/// <param name="mergeGrid"></param>
|
|
/// <returns></returns>
|
|
private volatility CreateVolSurfaceFromGridForSingleUnderlying(
|
|
int underlyingId,
|
|
string underlyingCode,
|
|
double underlyingPrice,
|
|
DateTime valueDate,
|
|
List<DateTime> validMaturityDates,
|
|
List<double> strikes,
|
|
List<List<double>> mergeGrid)
|
|
{
|
|
var vol = new volatility()
|
|
{
|
|
UnderlyingId = underlyingId,
|
|
QuotationDate = valueDate,
|
|
ContractCode = underlyingCode,
|
|
InterpolationMethod = "BiLinear",
|
|
Data = "[]",
|
|
VolType = "隐含"
|
|
};
|
|
var singleVols = new List<SingleVol>();
|
|
//当只有一个期限时,将为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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据一组到期日、行权价,以及对应的波动率,生成volatility对象
|
|
/// </summary>
|
|
/// <param name="underlyingId"></param>
|
|
/// <param name="underlyingCode"></param>
|
|
/// <param name="valueDate"></param>
|
|
/// <param name="validMaturityDates">到期日</param>
|
|
/// <param name="strikes">行权价</param>
|
|
/// <param name="mergeGrid"></param>
|
|
/// <returns></returns>
|
|
private volatility CreateVolSurfaceFromGridForVariety(
|
|
int underlyingId,
|
|
string underlyingCode,
|
|
Dictionary<DateTime, double> maturityPriceDict,
|
|
DateTime valueDate,
|
|
List<DateTime> validMaturityDates,
|
|
List<double> strikes,
|
|
List<List<double>> mergeGrid)
|
|
{
|
|
var vol = new volatility()
|
|
{
|
|
UnderlyingId = underlyingId,
|
|
QuotationDate = valueDate,
|
|
ContractCode = underlyingCode,
|
|
InterpolationMethod = "BiLinear",
|
|
Data = "[]",
|
|
VolType = "隐含"
|
|
};
|
|
var singleVols = new List<SingleVol>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 将天数转换为恰当的Term
|
|
/// 例如:
|
|
/// 7天应该是1W,而不是7D
|
|
/// </summary>
|
|
/// <param name="days"></param>
|
|
/// <returns></returns>
|
|
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<string, double> underlyingPriceDict,
|
|
List<SingleVol> singleVols,
|
|
Dictionary<double, int> strikeIndexDict,
|
|
Dictionary<DateTime, int> maturityIndexDict,
|
|
List<ExchangeListOption> options,
|
|
EodExchangeOptionPriceProvider optionPrices,
|
|
double riskFreeRate)
|
|
{
|
|
var vols = new List<Tuple<DateTime, double, double>>();
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取某一标的的活跃的场内期权合约
|
|
///
|
|
/// </summary>
|
|
/// <param name="underlyingCode">标的代码</param>
|
|
/// <param name="valueDate">日期</param>
|
|
/// <returns></returns>
|
|
private List<ExchangeListOption> 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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 对于某一商品期货的合约,其对应的场内期权只有一个到期日,无法形成一个波动率曲面
|
|
/// 因此可以查询同一品种的所有场内期权合约来计算。
|
|
/// </summary>
|
|
/// <param name="underlying"></param>
|
|
/// <param name="valueDate"></param>
|
|
/// <param name="aggregateContract"></param>
|
|
/// <returns></returns>
|
|
private List<ExchangeListOption> 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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取标的在valueDate之前,有收盘价的最近一个日期
|
|
/// </summary>
|
|
/// <param name="valueDate"></param>
|
|
/// <param name="underlying"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取标的的历史价格序列
|
|
/// </summary>
|
|
/// <param name="underlying"></param>
|
|
/// <param name="lastUnderlyingSettleDate"></param>
|
|
/// <param name="priceCount">获取的价格序列个数</param>
|
|
/// <returns></returns>
|
|
public Dictionary<DateTime, double> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取标的的历史价格序列
|
|
/// </summary>
|
|
/// <param name="underlying"></param>
|
|
/// <param name="lastUnderlyingSettleDate"></param>
|
|
/// <param name="priceCount">获取的价格序列个数</param>
|
|
/// <returns></returns>
|
|
public Dictionary<DateTime, double> GetHistoricalPrices(underlying_manager underlying, DateTime lastUnderlyingSettleDate, int priceCount)
|
|
{
|
|
var workingDates = QdpCalendarHelper.GetWorkingDatesBeforeDate(lastUnderlyingSettleDate, priceCount);
|
|
var eodStartDate = workingDates.Last();
|
|
var eodEndDate = workingDates.First();
|
|
|
|
Dictionary<DateTime, double> 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<double> GetLnList(List<double> priceList)
|
|
{
|
|
if (priceList == null || priceList.Count == 0)
|
|
{
|
|
return new List<double>();
|
|
}
|
|
|
|
var list = new List<double>();
|
|
for (var i = 0; i < priceList.Count - 1; i++)
|
|
{
|
|
list.Add(Math.Log(priceList[i + 1] / priceList[i]));
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
public List<double> GetLnDiffList(List<double> priceList)
|
|
{
|
|
if (priceList == null || priceList.Count == 0)
|
|
{
|
|
return new List<double>();
|
|
}
|
|
|
|
var list = new List<double>();
|
|
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<double> 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];
|
|
}
|
|
|
|
/// <summary>
|
|
/// 从标的合约的历史价格,计算该标的合约的波动率
|
|
/// </summary>
|
|
/// <param name="priceList"></param>
|
|
/// <returns></returns>
|
|
public double GetVolFromUnderlyingPrices(List<double> priceList, int daysInYear = 245)
|
|
{
|
|
var lnDiffList = GetLnDiffList(priceList);
|
|
return GetVolFromUnderlyingLogDiffs(lnDiffList, daysInYear);
|
|
}
|
|
|
|
public double GetVolFromUnderlyingLogDiffs(List<double> 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<int, double>();
|
|
}
|
|
volDic[valueDate][underlyingId] = vol;
|
|
}
|
|
}
|
|
}
|
|
|
|
public class HistoricalVolCurve
|
|
{
|
|
public List<string> Dates { get; set; }
|
|
public List<double> 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<string> XPoints { get; set; }
|
|
public List<double> YPoints { get; set; }
|
|
}
|
|
}
|