Files
zszq-trs/YLErpDAL/BLL/VolCaculator.cs
T
2024-05-09 14:06:26 +08:00

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