Files
zszq-trs/YLErpDAL/Modules/VolatilityModule/VolatilityQueryService.cs
2024-05-09 14:06:26 +08:00

726 lines
25 KiB
C#

using System.Linq.Expressions;
using System.Text;
using YieldChain.Helpers;
using YLErp.DBModels.Consts;
using YLErp.QdpModule.Constants;
namespace YLErp.Modules.VolatilityModule
{
/// <summary>
/// 只用于波动率数据查询
/// </summary>
public class VolatilityQueryService : YLBaseService
{
public VolatilityQueryService(OptUserInfo userInfo) : base(userInfo)
{
}
public VolatilityQueryService(YLBaseService baseService) : base(baseService)
{
}
#region----获取单个标的的曲面波动率----
/// <summary>
/// 获取单个标的的曲面波动率
/// </summary>
public volatility GetVolatility(string userGroup, DateTime quotationDate, string volType, string underlyingCode, bool createIfNotFound = true)
{
if (string.IsNullOrWhiteSpace(underlyingCode))
{
throw new ArgumentException("标的代码不能为空", nameof(underlyingCode));
}
var vols = GetVolatility(new SingleVolatilityRequest
{
UserGroup = userGroup,
QuotationDate = quotationDate,
VolType = volType,
TradeVolWithBidAsk = false,
UnderlyingCode = underlyingCode,
UnderlyingId = 0
}, createIfNotFound);
return vols?.FirstOrDefault(n => n.VolType == volType);
}
/// <summary>
/// 获取单个标的的曲面波动率
/// </summary>
public volatility GetVolatility(string userGroup, DateTime quotationDate, string volType, int underlyingId, bool createIfNotFound = true)
{
var vols = GetVolatility(new SingleVolatilityRequest
{
UserGroup = userGroup,
QuotationDate = quotationDate,
VolType = volType,
TradeVolWithBidAsk = false,
UnderlyingCode = string.Empty,
UnderlyingId = underlyingId
}, createIfNotFound);
return vols?.FirstOrDefault(n => n.VolType == volType);
}
/// <summary>
/// VolType为交易的情况下,返回["交易","报价Bid","报价Ask"]波动率
/// </summary>
public IEnumerable<volatility> GetVolatility(SingleVolatilityRequest request, bool createIfNotFound = true)
{
CheckRequest(request);
underlying_manager un = null;
if (!string.IsNullOrEmpty(request.UnderlyingCode))
{
un = DataCacheProvider.GetUnderlyingDataSource().GetData(request.UnderlyingCode);
}
else if (request.UnderlyingId.HasValue)
{
un = DataCacheProvider.GetUnderlyingDataSource().GetData(request.UnderlyingId.Value);
}
else
{
throw new ArgumentException("标的代码或标的ID不能为空", nameof(request.UnderlyingCode));
}
if (un == null)
{
return Enumerable.Empty<volatility>();
}
if (un.CommodityCode == "组合标的")
{
return VolatilityHelper.GetDefaultVols(request);
}
//先对期货标的做特殊处理
if (un.IsFutures() && un.MaturityDate < request.QuotationDate)
{
return VolatilityHelper.GetDefaultVols(request, 0);
}
var volPredicate = BuildPredicate(request);
if (volPredicate == null)
{
return VolatilityHelper.GetDefaultVols(request);
}
request.UnderlyingId = un.id;
request.UnderlyingCode = un.UnderlyingCode;
var groupQuery = from v in DbContext.volatility.Where(volPredicate)
where v.ContractCode == request.UnderlyingCode
group v by new { v.UserGroup, v.ContractCode, v.VolType } into vg
select new VolGroupDto
{
UserGroup = vg.Key.UserGroup,
ContractCode = vg.Key.ContractCode,
VolType = vg.Key.VolType,
QuotationDate = vg.Max(n => n.QuotationDate)
};
var volQuery = from vg in groupQuery
join v in DbContext.volatility
on new { vg.UserGroup, vg.ContractCode, vg.VolType, vg.QuotationDate }
equals new { v.UserGroup, v.ContractCode, v.VolType, v.QuotationDate }
orderby v.ContractCode
select v;
var results = volQuery.ToArray().AsEnumerable();
if (results.Any())
{
foreach (var item in results)
{
item.QuotationDate = request.QuotationDate;
}
}
//如果从数据库中未能获取到波动率数据
else if (createIfNotFound && !results.Any())
{
results = ProcesseMissingVol(request, un);
}
return results;
}
#endregion
#region----批量获取标的的曲面波动率----
/// <summary>
/// 为波动率批量导出业务获取波动率列表(不需要同源波动率)
/// </summary>
public IEnumerable<volatility> GetVolatilities(BatchVolatilityRequest request, bool createIfNotFound)
{
CheckRequest(request);
var volPredicate = BuildPredicate(request, request.StartDate);
if (volPredicate == null)
{
return Enumerable.Empty<volatility>();
}
var resultList = new List<volatility>();
if (request.VarietyIds != null && request.VarietyIds.Any())
{
request.VarietyIds = request.VarietyIds.ToList();
}
//标的关联(返回null表示已没有可以筛选的标的)
var unPredicate = BuildUnderlyingPredicate(request, resultList);
if (unPredicate == null)
{
return resultList;
}
var unQuery = DbContext.underlying_manager.Where(unPredicate);
//数据量小的表尽量靠前
var groupQuery = from un in unQuery
join v in DbContext.volatility.Where(volPredicate) on un.UnderlyingCode equals v.ContractCode
group v by new { v.UserGroup, v.ContractCode, v.VolType } into vg
select new
{
vg.Key.UserGroup,
vg.Key.ContractCode,
vg.Key.VolType,
QuotationDate = vg.Max(n => n.QuotationDate)
};
//var count = groupQuery.Count();
var volQuery = from vg in groupQuery
join v in DbContext.volatility
on vg equals new { v.UserGroup, v.ContractCode, v.VolType, v.QuotationDate }
orderby v.ContractCode
select v;
resultList.AddRange(volQuery.ToList());
foreach (var item in resultList)
{
item.QuotationDate = request.QuotationDate;
}
if (createIfNotFound)
{
var unIds = resultList.Select(n => n.UnderlyingId).ToHashSet();
var missingUns = DataCacheProvider.GetUnderlyingDataSource().AsQueryable()
.Where(unPredicate).Where(n => !unIds.Remove(n.id)).ToArray();
foreach (var un in missingUns)
{
var vols = ProcesseMissingVol(new SingleVolatilityRequest(request, un.UnderlyingCode, un.id), un);
resultList.AddRange(vols);
}
}
return resultList;
}
//resultList用于存储过期标的的波动率
private Expression<Func<underlying_manager, bool>> BuildUnderlyingPredicate(BatchVolatilityRequest request, List<volatility> resultList)
{
Expression<Func<underlying_manager, bool>> predicate = null;
var unSource = DataCacheProvider.GetUnderlyingDataSource();
//优先级1(如果是有效过滤条件则忽略UnderlyingCodes)
if (request.UnderlyingIds != null && request.UnderlyingIds.Any(n => n > 0))
{
var set = request.UnderlyingIds.ToHashSet();
foreach (var unId in request.UnderlyingIds)
{
var un = unSource.GetData(unId);
if (un == null) { }
else if (un.IsFutures() && un.MaturityDate < request.QuotationDate)
{
var vols = VolatilityHelper.GetDefaultVols(new SingleVolatilityRequest(request, un.UnderlyingCode, un.id), 0);
resultList.AddRange(vols);
}
else if (unId > 0)
{
set.Add(unId);
}
}
if (!set.Any())
{
return null;
}
predicate = PredicateBuilder.Create<underlying_manager>(n => set.Contains(n.id));
}
//优先级2
else if (request.UnderlyingCodes != null && request.UnderlyingCodes.Any(n => !string.IsNullOrEmpty(n)))
{
var set = request.UnderlyingCodes.ToHashSet(StringComparer.OrdinalIgnoreCase);
foreach (var unCode in request.UnderlyingCodes)
{
var un = unSource.GetData(unCode);
if (un == null) { }
else if (un.IsFutures() && un.MaturityDate < request.QuotationDate)
{
var vols = VolatilityHelper.GetDefaultVols(new SingleVolatilityRequest(request, un.UnderlyingCode, un.id), 0);
resultList.AddRange(vols);
}
else if (!string.IsNullOrWhiteSpace(unCode))
{
set.Add(unCode);
}
}
if (!set.Any())
{
return null;
}
predicate = PredicateBuilder.Create<underlying_manager>(n => set.Contains(n.UnderlyingCode));
}
else
{
predicate = PredicateBuilder.Create<underlying_manager>(n => n.CommodityCode != "组合标的" && n.LaunchState == "1");
}
if (request.VarietyIds != null && request.VarietyIds.Any(n => n > 0))
{
predicate = predicate.And(n => request.VarietyIds.Contains(n.UnderlyingTypeId));
}
predicate = predicate.And(n => n.UnderlyingInstrumentType != ConsGlobal.InstrumentType.CommodityFutures || n.MaturityDate >= request.QuotationDate);
return predicate;
}
#endregion
#region----处理数据库中找不到波动率的情况----
private IEnumerable<volatility> ProcesseMissingVol(SingleVolatilityRequest request, underlying_manager un)
{
if (PS.Config.ErpElement.SkewMapVolConstruction || (request != null && !ConsVolInfos.TradeVolTypes.Contains(request.VolType)))
{
return Enumerable.Empty<volatility>();
}
if (un == null)
{
if (!string.IsNullOrEmpty(request.UnderlyingCode))
{
un = DataCacheProvider.GetUnderlyingDataSource().GetData(request.UnderlyingCode);
}
else if (request.UnderlyingId > 0)
{
un = DataCacheProvider.GetUnderlyingDataSource().GetData(request.UnderlyingId.Value);
}
}
if (un == null)
{
return Enumerable.Empty<volatility>();
}
var reqVolTypes = request.GetVolTypes();
request.UnderlyingId = un.id;
request.UnderlyingCode = un.UnderlyingCode;
request.TradeVolWithBidAsk = true;
if (!PS.Config.ErpElement.SkewMapVolConstruction && ConsVolInfos.TradeVolTypes.Contains(request.VolType))
{
request.VolType = "交易";
}
var allVolTypes = request.GetVolTypes();
if (un.IsFutures())
{
if (un.CommodityCode == "组合标的")
{
//避免报价获取波动率时出错
return VolatilityHelper.GetDefaultVols(request);
}
if (un.MaturityDate < request.QuotationDate)
{
return VolatilityHelper.GetDefaultVols(request, 0);
}
}
else
{
var defaultVols = VolatilityHelper.GetDefaultVols(request);
SaveMissingVols(defaultVols, "默认波动率");
return defaultVols.Where(n => reqVolTypes.Contains(n.VolType)).ToArray();
}
//获取同源合约代码,先主力合约再历史合约
VolCopyPara sameUn = null;
var match = System.Text.RegularExpressions.Regex.Match(un.UnderlyingCode, "^([a-zA-z]+)\\d+$");
if (match.Success)
{
var mainCode = match.Groups[1].Value + "00";
if (!mainCode.Equals(un.UnderlyingCode, StringComparison.OrdinalIgnoreCase)
&& DbContext.volatility.Any(v => v.UserGroup == request.UserGroup && v.ContractCode == mainCode && allVolTypes.Contains(v.VolType)))
{
sameUn = new VolCopyPara
{
id = un.id,
UnderlyingCode = un.UnderlyingCode,
SameCode = mainCode
};
}
}
else
{
var query = from u1 in DbContext.underlying_manager.Where(n => n.id == un.id)
join u2 in DbContext.underlying_manager on u1.CommodityCode equals u2.CommodityCode
join v in DbContext.volatility on u2.UnderlyingCode equals v.ContractCode
where v.UserGroup == request.UserGroup && u2.MaturityDate.Value < u1.MaturityDate.Value && allVolTypes.Contains(v.VolType)
orderby u2.MaturityDate descending, v.QuotationDate descending
select new VolCopyPara
{
id = u1.id,
UnderlyingCode = u1.UnderlyingCode,
SameCode = u2.UnderlyingCode
};
sameUn = query.FirstOrDefault();
}
IEnumerable<volatility> vols = null;
if (sameUn != null)
{
request.UnderlyingCode = sameUn.SameCode;
vols = GetVolatility(request, false);
if (vols != null && vols.Count() == allVolTypes.Count())
{
foreach (var item in vols)
{
item.UnderlyingId = sameUn.id;
item.ContractCode = sameUn.UnderlyingCode;
item.QuotationDate = request.QuotationDate;
}
SaveMissingVols(vols, "同源复制" + sameUn.SameCode);
}
else
{
vols = null;
}
}
if (vols == null)
{
vols = VolatilityHelper.GetDefaultVols(request);
SaveMissingVols(vols, "默认波动率");
}
return vols.Where(n => reqVolTypes.Contains(n.VolType)).ToArray();
}
//保存同源波动率或默认波动率
private void SaveMissingVols(IEnumerable<volatility> missingVols, string dataSource)
{
if (missingVols == null || !missingVols.Any() || Interlocked.Increment(ref saveLock) > 1) return;
try
{
var date = new DateTime(2000, 1, 1);
var arr = missingVols.Where(n => ConsVolInfos.VolTypes.Contains(n.VolType))
.Select(n =>
{
//n可能是volatility类型的子类,如果clone的话会导致写入数据库出错
var clone = YLAutoMapper.Map<volatility>(n);
clone.OptId = 0;
clone.OptName = dataSource ?? "同源复制";
clone.OptDate = DateTime.Now;
clone.QuotationDate = date;
return clone;
}).ToArray();
if (arr.Any())
{
using (var db = DbContextFactory.GetYLDbContext())
{
var userGroup = arr.First().UserGroup ?? string.Empty;
var ucodes = arr.Select(n => n.ContractCode).ToHashSet();
var filters = db.volatility.Where(n => n.QuotationDate == date && n.UserGroup == userGroup && ucodes.Contains(n.ContractCode))
.Select(n => n.ContractCode + "^^" + n.VolType).ToHashSet(StringComparer.OrdinalIgnoreCase);
if (filters.Any())
{
arr = arr.Where(n => !filters.Contains(n.ContractCode + "^^" + n.VolType)).ToArray();
}
db.volatility.AddRange(arr);
var changes = db.SaveChanges();
}
}
}
catch (Exception ex)
{
LogFactory.GetLogger(nameof(SaveMissingVols)).Error(ex);
}
finally
{
Interlocked.Exchange(ref saveLock, 0);
}
}
#endregion
#region----内部处理----
//检查请求数据是否符合预期
private static void CheckRequest(VolatilityRequest request)
{
if (request is null)
{
throw new ArgumentNullException(nameof(request));
}
if (string.IsNullOrWhiteSpace(request.VolType))
{
if (!(request is BatchVolatilityRequest breq))
{
throw new ArgumentException("VolType不能为空", nameof(request.VolType));
}
else if (breq.VolTypes == null || !breq.VolTypes.Any())
{
throw new ArgumentException("VolType不能为空", nameof(request.VolType));
}
}
if (ConsUserGroup.HasGroup && string.IsNullOrWhiteSpace(request.UserGroup))
{
throw new ArgumentException("UserGroup不能为空", nameof(request.UserGroup));
}
if (request.QuotationDate.Year < 1949)
{
throw new ArgumentException("QuotationDate取值不正确:" + request.QuotationDate, nameof(request.QuotationDate));
}
request.QuotationDate = request.QuotationDate.Date;
}
//构建查询条件(没有波动率类型时返回null)
private static Expression<Func<volatility, bool>> BuildPredicate(VolatilityRequest request, DateTime? startDate = null)
{
if (!ConsUserGroup.HasGroup)
{
request.UserGroup = string.Empty;
}
var volTypes = request.GetVolTypes().Where(n => ConsVolInfos.VolTypes.Contains(n)).ToArray();
if (volTypes.Any())
{
var predicate = PredicateBuilder.Create<volatility>(v => v.QuotationDate <= request.QuotationDate);
if (startDate.HasValue)
{
predicate.And(v => v.QuotationDate >= startDate.Value);
}
return predicate.And(v => v.UserGroup == request.UserGroup && volTypes.Contains(v.VolType));
}
return null;
}
#endregion
class VolGroupDto
{
public string UserGroup { get; set; }
public string ContractCode { get; set; }
public string VolType { get; set; }
public DateTime QuotationDate { get; set; }
}
class VolCopyPara
{
public int id { get; set; }
public string UnderlyingCode { get; set; }
public string SameCode { get; set; }
}
static int saveLock;
}
#region----请求模型类----
/// <summary>
/// 波动率请求基类
/// </summary>
public class VolatilityRequest
{
public VolatilityRequest()
{
}
public VolatilityRequest(VolatilityRequest baseRequest)
{
if (baseRequest is null)
{
throw new ArgumentNullException(nameof(baseRequest));
}
UserGroup = baseRequest.UserGroup;
VolType = baseRequest.VolType;
TradeVolWithBidAsk = baseRequest.TradeVolWithBidAsk;
QuotationDate = baseRequest.QuotationDate;
}
/// <summary>
/// 必须有值
/// </summary>
public string UserGroup { get; set; }
/// <summary>
/// 必须有值
/// </summary>
public string VolType { get; set; }
/// <summary>
/// 取交易波动率时是否附带"报价Bid"和"报价Ask",默认false
/// </summary>
public bool TradeVolWithBidAsk { get; set; }
/// <summary>
/// 必须有值
/// </summary>
public DateTime QuotationDate { get; set; }
/// <summary>
/// 获取相关波动率
/// </summary>
public virtual IEnumerable<string> GetVolTypes()
{
if (TradeVolWithBidAsk && !PS.Config.ErpElement.SkewMapVolConstruction && VolType == "交易")
{
return ConsVolInfos.TradeVolTypes;
}
return new[] { VolType };
}
public override string ToString()
{
return $"{VolType}--{TradeVolWithBidAsk}--{QuotationDate:yyyy-MM-dd}--{UserGroup}";
}
}
/// <summary>
/// 单标的波动率请求
/// </summary>
public class SingleVolatilityRequest : VolatilityRequest
{
public SingleVolatilityRequest()
{
}
public SingleVolatilityRequest(VolatilityRequest baseRequest, string underlyingCode, int? underlyingId = null)
: base(baseRequest)
{
UnderlyingCode = underlyingCode;
UnderlyingId = underlyingId;
}
/// <summary>
/// 标的过滤(优先级1)
/// </summary>
public string UnderlyingCode { get; set; }
/// <summary>
///标的过滤(优先级2)
/// </summary>
public int? UnderlyingId { get; set; }
public SingleVolatilityRequest Clone()
{
return (SingleVolatilityRequest)MemberwiseClone();
}
}
/// <summary>
/// 批量标的波动率请求
/// </summary>
public class BatchVolatilityRequest : VolatilityRequest
{
/// <summary>
/// 从这个日期开始查找数据
/// </summary>
public DateTime? StartDate { get; set; }
/// <summary>
/// 标的过滤,优先级1(如果是有效过滤条件则忽略UnderlyingCodes和VarietyIds)
/// </summary>
public IEnumerable<int> UnderlyingIds { get; set; }
/// <summary>
/// 标的过滤,优先级2(如果是有效过滤条件则忽略VarietyIds)
/// </summary>
public IEnumerable<string> UnderlyingCodes { get; set; }
/// <summary>
/// 品种过滤(和标的过滤取并集)
/// </summary>
public IEnumerable<int> VarietyIds { get; set; }
/// <summary>
/// 波动率类型过滤(如果存在则忽略VolType参数优先使用这个)
/// </summary>
public IEnumerable<string> VolTypes { get; set; }
public override IEnumerable<string> GetVolTypes()
{
return VolTypes != null && VolTypes.Any() ? VolTypes : base.GetVolTypes();
}
/// <summary>
/// 获取唯一key(MD5算法)
/// </summary>
public string GetUniqueKey()
{
var sb = new StringBuilder(500);
sb.Append(UserGroup).Append('^')
.Append(VolType).Append('^')
.Append(TradeVolWithBidAsk).Append('^')
.Append(QuotationDate.ToString("yyyyMMdd")).Append('^');
if (UnderlyingIds != null)
{
sb.Append(string.Join(",", UnderlyingIds)).Append('^');
}
if (UnderlyingCodes != null)
{
sb.Append(string.Join(",", UnderlyingCodes)).Append('^');
}
if (VarietyIds != null)
{
sb.Append(string.Join(",", VarietyIds)).Append('^');
}
return HashHelper.MD5(sb.ToString());
}
}
#endregion
}