从山证v2.3.0拷贝
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
namespace YLErp.Modules.VolatilityModule.ApiModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 批量标的波动率请求(为了兼容海通API)
|
||||
/// </summary>
|
||||
public class BatchVolatilityRequest2 : BatchVolatilityRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// QuotationDate别名,海通API使用了这个,故保留
|
||||
/// </summary>
|
||||
public DateTime SystemDate
|
||||
{
|
||||
get { return QuotationDate; }
|
||||
set { QuotationDate = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// QuotationDate别名,海通API使用了这个,故保留
|
||||
/// </summary>
|
||||
public DateTime ValueDate
|
||||
{
|
||||
get { return QuotationDate; }
|
||||
set { QuotationDate = value; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace YLErp.Modules.VolatilityModule.ApiModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 批量标的波动率请求V2
|
||||
/// </summary>
|
||||
public class UnderlyingVolQueryApiRequestV2
|
||||
{
|
||||
/// <summary>
|
||||
/// 标的过滤
|
||||
/// </summary>
|
||||
public IEnumerable<string> UnderlyingCodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 必须有值,查看波动率曲面的日期
|
||||
/// </summary>
|
||||
public DateTime ValueDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 必须有值,波动率类型
|
||||
/// </summary>
|
||||
public string[] VolTypes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户组
|
||||
/// </summary>
|
||||
public string UserGroup { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using YLErp.Models;
|
||||
|
||||
namespace YLErp.Modules.VolatilityModule.ApiModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 曲面波动率提供
|
||||
/// </summary>
|
||||
public class UnderlyingVolQueryApiResultV2
|
||||
{
|
||||
/// <summary>
|
||||
/// 波动率类型
|
||||
/// </summary>
|
||||
public string VolType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 波动率报价日期
|
||||
/// </summary>
|
||||
public string QuotationDate
|
||||
{
|
||||
get => InnerQuotationDate.ToString("yyyy-MM-dd");
|
||||
set { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标的代码
|
||||
/// </summary>
|
||||
public string UnderlyingCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public List<SingleVol> VolTable { get; set; }
|
||||
|
||||
//--------内部类----------------
|
||||
|
||||
internal string VolTableJson { get; set; }
|
||||
|
||||
internal DateTime InnerQuotationDate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using YLErp.Models;
|
||||
|
||||
namespace YLErp.Modules.VolatilityModule.ApiModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 标的波动率保存请求V2
|
||||
/// </summary>
|
||||
public class UnderlyingVolSaveApiRequestV2
|
||||
{
|
||||
/// <summary>
|
||||
/// 合约代码
|
||||
/// </summary>
|
||||
public string ContractCode { set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// 波动率类型
|
||||
/// </summary>
|
||||
public string VolType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 报价日期
|
||||
/// </summary>
|
||||
public DateTime ValueDate { set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// 报价日期
|
||||
/// </summary>
|
||||
public DateTime QuotationDate { set => ValueDate = value; get => ValueDate; }
|
||||
|
||||
/// <summary>
|
||||
/// 插值方法
|
||||
/// </summary>
|
||||
public string InterpolationMethod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 波动率表格
|
||||
/// </summary>
|
||||
public List<SingleVol> VolTable { set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// 所属用户组
|
||||
/// </summary>
|
||||
public string UserGroup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 如果传入的合约代码为连续合约则覆盖同合约其它标的
|
||||
/// </summary>
|
||||
public bool OverridByMainCode { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using YLErp.BLL;
|
||||
using YLErp.Models;
|
||||
|
||||
namespace YLErp.Modules.VolatilityModule.ApiModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 曲面波动率提供(用于API服务)
|
||||
/// </summary>
|
||||
public class UnderlyingVolService : YLBaseService
|
||||
{
|
||||
public UnderlyingVolService(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API获取波动率
|
||||
/// </summary>
|
||||
public List<UnderlyingVol> GetUnderlyingVolSurfaces(BatchVolatilityRequest2 request)
|
||||
{
|
||||
request.TradeVolWithBidAsk = true;
|
||||
|
||||
var vols = new VolatilityQueryService(this).GetVolatilities(request, false);
|
||||
|
||||
if (request.VolType != "交易")
|
||||
{
|
||||
return vols.Select(x => new UnderlyingVol()
|
||||
{
|
||||
VolTable = x.VolTable,
|
||||
UnderlyingCode = x.ContractCode
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
return vols.ToLookup(n => n.ContractCode).Select(n => new UnderlyingVol
|
||||
{
|
||||
UnderlyingCode = n.Key,
|
||||
VolTable = n.FirstOrDefault(y => y.VolType == "交易")?.VolTable,
|
||||
VolTableBid = n.FirstOrDefault(y => y.VolType == "报价Bid")?.VolTable,
|
||||
VolTableAsk = n.FirstOrDefault(y => y.VolType == "报价Ask")?.VolTable
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API保存波动率
|
||||
/// </summary>
|
||||
public volatility SaveVolatility(volatility vol)
|
||||
{
|
||||
var targetUnderlying = underlying_managerBLL.GetByCode(vol.ContractCode);
|
||||
if (targetUnderlying != null)
|
||||
{
|
||||
vol.UnderlyingId = targetUnderlying.id;
|
||||
vol.ContractCode = targetUnderlying.UnderlyingCode;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(vol.Data))
|
||||
{
|
||||
throw new ServiceException("缺少VolTable");
|
||||
}
|
||||
|
||||
var vols = new VolatilitySaveService(this).SaveVol(vol);
|
||||
return vols.FirstOrDefault();
|
||||
}
|
||||
|
||||
public class UnderlyingVol
|
||||
{
|
||||
public string UnderlyingCode { get; set; }
|
||||
public List<SingleVol> VolTable { get; set; }
|
||||
public List<SingleVol> VolTableBid { get; set; }
|
||||
public List<SingleVol> VolTableAsk { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
using Newtonsoft.Json;
|
||||
using YLErp.DBModels.Consts;
|
||||
using YLErp.Models;
|
||||
using YLErp.QdpModule.Constants;
|
||||
|
||||
namespace YLErp.Modules.VolatilityModule.ApiModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 曲面波动率提供(用于API服务)
|
||||
/// </summary>
|
||||
public partial class UnderlyingVolServiceV2 : YLBaseService
|
||||
{
|
||||
public UnderlyingVolServiceV2(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API获取波动率
|
||||
/// </summary>
|
||||
public IEnumerable<UnderlyingVolQueryApiResultV2> GetVolSurfaces(UnderlyingVolQueryApiRequestV2 request)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
if (request.VolTypes == null || !request.VolTypes.Any())
|
||||
{
|
||||
throw new ArgumentException("VolTypes不能为空", nameof(request.VolTypes));
|
||||
}
|
||||
|
||||
if (request.UnderlyingCodes == null || !request.UnderlyingCodes.Any())
|
||||
{
|
||||
throw new ArgumentException("UnderlyingCodes不能为空", nameof(request.UnderlyingCodes));
|
||||
}
|
||||
|
||||
if (!ConsUserGroup.HasGroup)
|
||||
{
|
||||
request.UserGroup = string.Empty;
|
||||
}
|
||||
else if (string.IsNullOrWhiteSpace(request.UserGroup))
|
||||
{
|
||||
throw new ArgumentException("UserGroup不能为空", nameof(request.UserGroup));
|
||||
}
|
||||
|
||||
request.ValueDate = request.ValueDate.Date;
|
||||
|
||||
if (request.ValueDate.Year < 1949)
|
||||
{
|
||||
throw new ArgumentException("ValueDate填写不正确:" + request.ValueDate, nameof(request.ValueDate));
|
||||
}
|
||||
|
||||
//数据量小的表尽量靠前
|
||||
var groupQuery = from v in DbContext.volatility
|
||||
where v.QuotationDate <= request.ValueDate
|
||||
&& request.VolTypes.Contains(v.VolType)
|
||||
&& request.UnderlyingCodes.Contains(v.ContractCode)
|
||||
&& v.UserGroup == request.UserGroup
|
||||
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 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 new UnderlyingVolQueryApiResultV2
|
||||
{
|
||||
VolType = v.VolType,
|
||||
InnerQuotationDate = v.QuotationDate,
|
||||
UnderlyingCode = v.ContractCode,
|
||||
VolTableJson = v.Data
|
||||
};
|
||||
|
||||
var vols = volQuery.ToArray();
|
||||
|
||||
foreach (var item in vols)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(item.VolTableJson))
|
||||
{
|
||||
item.VolTable = JsonConvert.DeserializeObject<List<SingleVol>>(item.VolTableJson);
|
||||
}
|
||||
}
|
||||
|
||||
return vols;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API保存波动率
|
||||
/// </summary>
|
||||
public volatility SaveVolSurface(UnderlyingVolSaveApiRequestV2 request)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(request.ContractCode))
|
||||
{
|
||||
throw new ServiceException("标的代码 必须填写");
|
||||
}
|
||||
|
||||
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(request.ContractCode);
|
||||
|
||||
if (underlying == null)
|
||||
{
|
||||
throw new ServiceException("标的信息未存在:" + request.ContractCode);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(request.VolType))
|
||||
{
|
||||
throw new ServiceException("波动率类型 必须填写");
|
||||
}
|
||||
|
||||
request.ValueDate = request.ValueDate.Date;
|
||||
|
||||
if (request.ValueDate.Year < 1949)
|
||||
{
|
||||
throw new ServiceException("ValueDate填写不正确:" + request.ValueDate);
|
||||
}
|
||||
|
||||
if (request.VolTable?.Any() != true)
|
||||
{
|
||||
throw new ServiceException("缺少VolTable");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(request.InterpolationMethod))
|
||||
{
|
||||
request.InterpolationMethod = ConsVolInfos.defInterpolationMethod;
|
||||
}
|
||||
|
||||
if (!ConsUserGroup.HasGroup)
|
||||
{
|
||||
request.UserGroup = string.Empty;
|
||||
}
|
||||
else if (string.IsNullOrWhiteSpace(request.UserGroup))
|
||||
{
|
||||
throw new ArgumentException("UserGroup不能为空", nameof(request.UserGroup));
|
||||
}
|
||||
|
||||
volatility retVol = null;
|
||||
|
||||
var underlyingList = new List<InnerUnderlying> {
|
||||
new InnerUnderlying{ UnderlyingId = underlying.id,UnderlyingCode = underlying.UnderlyingCode}
|
||||
};
|
||||
|
||||
//波动率上传 以连续合约 覆盖所有标的的 麻烦尽快实现
|
||||
if (request.OverridByMainCode)
|
||||
{
|
||||
int underlyingTypeId = 0;
|
||||
|
||||
if (System.Text.RegularExpressions.Regex.IsMatch(request.ContractCode, "^[a-zA-Z]+00$"))
|
||||
{
|
||||
var un = DataCacheProvider.GetUnderlyingDataSource().GetData(request.ContractCode);
|
||||
|
||||
if (un?.IsFutures() == true && un.UnderlyingTypeId > 0)
|
||||
{
|
||||
underlyingTypeId = un.UnderlyingTypeId;
|
||||
}
|
||||
}
|
||||
|
||||
if (underlyingTypeId > 0)
|
||||
{
|
||||
var query = from un in DbContext.underlying_manager
|
||||
where un.UnderlyingTypeId == underlyingTypeId
|
||||
&& (un.MaturityDate >= request.QuotationDate)
|
||||
&& un.UnderlyingCode != request.ContractCode
|
||||
select new InnerUnderlying
|
||||
{
|
||||
UnderlyingId = un.id,
|
||||
UnderlyingCode = un.UnderlyingCode
|
||||
};
|
||||
|
||||
underlyingList.AddRange(query.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var un in underlyingList)
|
||||
{
|
||||
var dbVol = DbContext.volatility.FirstOrDefault(n => n.QuotationDate == request.ValueDate
|
||||
&& n.ContractCode == un.UnderlyingCode && n.VolType == request.VolType && n.UserGroup == request.UserGroup);
|
||||
|
||||
if (dbVol == null)
|
||||
{
|
||||
dbVol = new volatility
|
||||
{
|
||||
UnderlyingId = un.UnderlyingId,
|
||||
ContractCode = un.UnderlyingCode,
|
||||
|
||||
VolType = request.VolType,
|
||||
UserGroup = request.UserGroup,
|
||||
QuotationDate = request.ValueDate,
|
||||
|
||||
VolSurfaceMode = ConsVolInfos.defVolMode
|
||||
};
|
||||
|
||||
DbContext.volatility.Add(dbVol);
|
||||
}
|
||||
|
||||
dbVol.SetOpt(OptUser);
|
||||
dbVol.SetData(request.VolTable);
|
||||
dbVol.InterpolationMethod = request.InterpolationMethod;
|
||||
|
||||
if (retVol == null)
|
||||
{
|
||||
retVol = dbVol;
|
||||
}
|
||||
}
|
||||
|
||||
DbContext.SaveChanges();
|
||||
|
||||
return retVol;
|
||||
}
|
||||
|
||||
class InnerUnderlying
|
||||
{
|
||||
public int UnderlyingId { get; set; }
|
||||
public string UnderlyingCode { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
namespace YLErp.Modules.VolatilityModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 波动率取值请求
|
||||
/// </summary>
|
||||
public class SingleVolReq
|
||||
{
|
||||
public SingleVolReq()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public SingleVolReq(OtcTrade trade, underlying_manager udm)
|
||||
{
|
||||
if (trade is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(trade));
|
||||
}
|
||||
|
||||
if (udm is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(udm));
|
||||
}
|
||||
|
||||
VolType = trade.VolType;
|
||||
Strike = trade.Strike ?? 0;
|
||||
SpotPrice = trade.SpotPrice ?? 0;
|
||||
TradeDate = trade.TradeDate.Value;
|
||||
ExerciseDate = trade.ExerciseDate.Value;
|
||||
IsMoneynessOption = trade.IsMoneynessOption;
|
||||
CallPut = trade.CallPut;
|
||||
|
||||
UnderlyingId = udm.id;
|
||||
UnderlyingCode = udm.UnderlyingCode;
|
||||
UnderlyingName = udm.UnderlyingName;
|
||||
UnderlyingTypeId = udm.UnderlyingTypeId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取波动率时宏源这样的多团队需要赋值此字段
|
||||
/// </summary>
|
||||
public string UserGroup { get; set; }
|
||||
|
||||
//--------------------------------------
|
||||
// trade
|
||||
//--------------------------------------
|
||||
|
||||
public string VolType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 行权价
|
||||
/// </summary>
|
||||
public double Strike { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 标的价格
|
||||
/// </summary>
|
||||
public double SpotPrice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 成交日(波动率的日期取这个日期)
|
||||
/// </summary>
|
||||
public DateTime TradeDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 行权日
|
||||
/// </summary>
|
||||
public DateTime ExerciseDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否相对行权价(是|其它值)
|
||||
/// </summary>
|
||||
public string IsMoneynessOption { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// [skew]CallPut
|
||||
/// </summary>
|
||||
public string CallPut { get; set; }
|
||||
|
||||
//--------------------------------------
|
||||
// underlying
|
||||
//--------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// 标的ID
|
||||
/// </summary>
|
||||
public int UnderlyingId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 标的代码
|
||||
/// </summary>
|
||||
public string UnderlyingCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 标的名称
|
||||
/// </summary>
|
||||
public string UnderlyingName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// [skew]品种ID
|
||||
/// </summary>
|
||||
public int UnderlyingTypeId { get; set; }
|
||||
|
||||
//--------------------------------------
|
||||
// skew
|
||||
//--------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// [skew]BaseVol
|
||||
/// </summary>
|
||||
public double? BaseVol { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// [skew]BidVar
|
||||
/// </summary>
|
||||
public int? BidVar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// [skew]AskVar
|
||||
/// </summary>
|
||||
public int? AskVar { get; set; }
|
||||
|
||||
//--------------------------------------
|
||||
// vols
|
||||
//--------------------------------------
|
||||
|
||||
public Abstract.IVolatility Vols { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
namespace YLErp.Modules.VolatilityModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 交易对冲波动率数据提供
|
||||
/// </summary>
|
||||
public class TradeHedgeVolProvider
|
||||
{
|
||||
readonly DateTime _valueDate;
|
||||
|
||||
Dictionary<int, double?> _dicData;
|
||||
|
||||
public TradeHedgeVolProvider(DateTime valueDate)
|
||||
{
|
||||
_valueDate = valueDate.Date;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 如果设为true程序内部将不会再进行数据初始化,
|
||||
/// 这样做的目的是为了少量数据获取时避免初始化带来的性能损失
|
||||
/// </summary>
|
||||
public bool Initialized { get; set; }
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (_dicData != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (this)
|
||||
{
|
||||
if (_dicData != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Initialized)
|
||||
{
|
||||
_dicData = new Dictionary<int, double?>();
|
||||
}
|
||||
else
|
||||
{
|
||||
//只取3个月以内的
|
||||
var startDate = _valueDate.AddMonths(-3);
|
||||
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
var query1 = from a in db.trade_hedge_vol
|
||||
where a.ValueDate > startDate && a.ValueDate <= _valueDate
|
||||
group a by a.TradeId into g
|
||||
select new { TradeId = g.Key, ValueDate = g.Max(n => n.ValueDate) };
|
||||
|
||||
var query2 = from a in query1
|
||||
join b in db.trade_hedge_vol on a equals new { b.TradeId, b.ValueDate }
|
||||
select new { b.TradeId, b.TradeSavedVol };
|
||||
|
||||
_dicData = query2.ToDictionary(n => n.TradeId, m => (double?)m.TradeSavedVol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetVol(int tradeId, out double vol)
|
||||
{
|
||||
Initialize();
|
||||
|
||||
if (!_dicData.TryGetValue(tradeId, out var dvol) && tradeId > 0)
|
||||
{
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
_dicData[tradeId] = dvol = db.trade_hedge_vol
|
||||
.Where(v => v.TradeId == tradeId && v.ValueDate <= _valueDate)
|
||||
.OrderByDescending(v => v.ValueDate).Select(n => (double?)n.TradeSavedVol).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
if (dvol.HasValue)
|
||||
{
|
||||
vol = dvol.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
vol = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
using Qdp.Pricing.Base.Implementations;
|
||||
using Qdp.Pricing.Library.Equity.Engines.Analytical;
|
||||
|
||||
namespace YLErp.Modules.VolatilityModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 交易波动率提供(适用于UseTradeVol)
|
||||
/// </summary>
|
||||
public class TradeVolitalityProvider
|
||||
{
|
||||
protected readonly DateTime _valueDate;
|
||||
|
||||
Dictionary<int, InnerTradeVolatility> _dicData;
|
||||
|
||||
public TradeVolitalityProvider(DateTime valueDate)
|
||||
{
|
||||
_valueDate = valueDate.Date;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 如果设为true程序内部将不会再进行数据初始化,
|
||||
/// 这样做的目的是为了少量数据获取时避免初始化带来的性能损失
|
||||
/// </summary>
|
||||
public bool Initialized { get; set; }
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (_dicData != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (this)
|
||||
{
|
||||
if (_dicData != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Initialized)
|
||||
{
|
||||
_dicData = new Dictionary<int, InnerTradeVolatility>();
|
||||
}
|
||||
else
|
||||
{
|
||||
//只取3个月以内的
|
||||
var startDate = _valueDate.AddMonths(-3);
|
||||
|
||||
using var db = DbContextFactory.GetYLDbContext();
|
||||
var query1 = from a in db.TradeVolatility
|
||||
where a.ValueDate > startDate && a.ValueDate <= _valueDate
|
||||
group a by a.TradeId into g
|
||||
select new { TradeId = g.Key, ValueDate = g.Max(n => n.ValueDate) };
|
||||
|
||||
var query2 = from a in query1
|
||||
join b in db.TradeVolatility on a equals new { b.TradeId, b.ValueDate }
|
||||
select new InnerTradeVolatility
|
||||
{
|
||||
TradeId = b.TradeId,
|
||||
ValueDate = b.ValueDate,
|
||||
NumOfSmoothingDays = b.NumOfSmoothingDays,
|
||||
TradePositionVolatility = b.TradePositionVolatility,
|
||||
TradeCloseVolatility = b.TradeCloseVolatility,
|
||||
IsFromTradeAdd = b.IsFromTradeAdd
|
||||
};
|
||||
|
||||
_dicData = query2.ToDictionary(n => n.TradeId, m => m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetVol(int tradeId, DateTime tradeExerciseDate, out double vol)
|
||||
{
|
||||
Initialize();
|
||||
|
||||
if (!_dicData.TryGetValue(tradeId, out var tradeVol) && tradeId > 0)
|
||||
{
|
||||
using var db = DbContextFactory.GetYLDbContext();
|
||||
_dicData[tradeId] = tradeVol = db.TradeVolatility
|
||||
.Where(v => v.TradeId == tradeId && v.ValueDate <= _valueDate)
|
||||
.OrderByDescending(v => v.ValueDate)
|
||||
.Select(b => new InnerTradeVolatility
|
||||
{
|
||||
TradeId = b.TradeId,
|
||||
ValueDate = b.ValueDate,
|
||||
NumOfSmoothingDays = b.NumOfSmoothingDays,
|
||||
TradePositionVolatility = b.TradePositionVolatility,
|
||||
TradeCloseVolatility = b.TradeCloseVolatility,
|
||||
IsFromTradeAdd = b.IsFromTradeAdd
|
||||
}).FirstOrDefault();
|
||||
}
|
||||
|
||||
if (tradeVol == null)
|
||||
{
|
||||
vol = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tradeVol.ResultVol.HasValue)
|
||||
{
|
||||
vol = tradeVol.ResultVol.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_valueDate < tradeVol.ValueDate)
|
||||
{
|
||||
vol = tradeVol.TradePositionVolatility ?? 0;
|
||||
}
|
||||
else if (_valueDate > tradeExerciseDate)
|
||||
{
|
||||
vol = tradeVol.TradeCloseVolatility ?? 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var daycountMode = PS.Config.ErpElement.SmoothingDaycountMode == Configuration.Enums.SmoothingDaycountMode.CalendarDay
|
||||
? Qdp.Pricing.Base.Enums.DayCountMode.CalendarDay
|
||||
: Qdp.Pricing.Base.Enums.DayCountMode.TradingDay;
|
||||
|
||||
//新增交易当天的持仓波动率需要划掉一天,修改后的持仓波动率不需要再划一天
|
||||
vol = AnalyticalOptionTradeVolInterp.tradeVolLinearInterp(
|
||||
new Qdp.Foundation.Implementations.Date(_valueDate),
|
||||
tradeVol.TradePositionVolatility ?? 0,
|
||||
tradeVol.TradeCloseVolatility ?? 0,
|
||||
new Qdp.Foundation.Implementations.Date(tradeVol.ValueDate),
|
||||
new Qdp.Foundation.Implementations.Date(tradeExerciseDate),
|
||||
tradeVol.NumOfSmoothingDays ?? 0,
|
||||
daycountMode,
|
||||
CalendarImpl.Get("chn"),
|
||||
includeStartDate: tradeVol.IsFromTradeAdd);
|
||||
}
|
||||
|
||||
tradeVol.ResultVol = vol;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class InnerTradeVolatility
|
||||
{
|
||||
public int TradeId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作系统日
|
||||
/// </summary>
|
||||
public DateTime ValueDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 持仓波动率
|
||||
/// </summary>
|
||||
public double? TradePositionVolatility { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 目标波动率
|
||||
/// </summary>
|
||||
public double? TradeCloseVolatility { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 平滑天数
|
||||
/// </summary>
|
||||
public int? NumOfSmoothingDays { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否是新增交易时添加的波动率记录
|
||||
/// </summary>
|
||||
public bool IsFromTradeAdd { get; set; }
|
||||
|
||||
public double? ResultVol { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
namespace YLErp.Modules.VolatilityModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 曲面波动率提供(适用于渤海因为没有考虑UserGroup)
|
||||
/// </summary>
|
||||
public class UnderlyingVolitalityProvider
|
||||
{
|
||||
protected readonly DateTime _valueDate;
|
||||
protected readonly string[] _volTypes;
|
||||
|
||||
Dictionary<string, volatility> _dicData;
|
||||
|
||||
public UnderlyingVolitalityProvider(DateTime valueDate, IEnumerable<string> volTypes)
|
||||
{
|
||||
if (volTypes == null || !volTypes.Any(n => !string.IsNullOrEmpty(n)))
|
||||
{
|
||||
_volTypes = new[] { "交易" };
|
||||
}
|
||||
else
|
||||
{
|
||||
_volTypes = volTypes.Where(n => !string.IsNullOrEmpty(n)).ToArray();
|
||||
}
|
||||
_valueDate = valueDate.Date;
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (_dicData != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (this)
|
||||
{
|
||||
if (_dicData != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//只取1个月以内的
|
||||
var startDate = _valueDate.AddMonths(-3);
|
||||
|
||||
var predicate = PredicateBuilder.Create<volatility>(
|
||||
v => v.QuotationDate >= startDate && v.QuotationDate <= _valueDate && _volTypes.Contains(v.VolType));
|
||||
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
//数据量小的表尽量靠前
|
||||
var groupQuery = from v in db.volatility.Where(predicate)
|
||||
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 volQuery = from vg in groupQuery
|
||||
join v in db.volatility.AsNoTracking()
|
||||
on vg equals new { v.UserGroup, v.ContractCode, v.VolType, v.QuotationDate }
|
||||
select v;
|
||||
|
||||
_dicData = volQuery.ToDictionary(n =>
|
||||
{
|
||||
var index = Array.IndexOf(_volTypes, n.VolType);
|
||||
return $"{index}^{n.UserGroup}^{n.ContractCode}";
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public volatility GetVol(string volType, string userGroup, string underlyingCode)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(volType) || string.IsNullOrWhiteSpace(underlyingCode))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (userGroup is null)
|
||||
{
|
||||
userGroup = string.Empty;
|
||||
}
|
||||
|
||||
var index = Array.IndexOf(_volTypes, volType);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Initialize();
|
||||
|
||||
var key = $"{index}^{userGroup}^{underlyingCode}";
|
||||
|
||||
if (!_dicData.TryGetValue(key, out var unVol))
|
||||
{
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
var volQuery = from v in db.volatility.AsNoTracking()
|
||||
where v.ContractCode == underlyingCode && v.UserGroup == userGroup
|
||||
&& v.QuotationDate <= _valueDate && _volTypes.Contains(v.VolType)
|
||||
orderby v.QuotationDate descending
|
||||
select v;
|
||||
|
||||
_dicData[key] = unVol = volQuery.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
return unVol;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
namespace YLErp.Modules.VolatilityModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 光证波动率数据提供
|
||||
/// </summary>
|
||||
public class VarietyVolProvider
|
||||
{
|
||||
readonly DateTime _valueDate;
|
||||
|
||||
Dictionary<int, double?> _dicData;
|
||||
|
||||
public VarietyVolProvider(DateTime valueDate)
|
||||
{
|
||||
_valueDate = valueDate.Date;
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (_dicData != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (this)
|
||||
{
|
||||
if (_dicData != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//只取3个月以内的
|
||||
var startDate = _valueDate.AddMonths(-3);
|
||||
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
var query1 = from a in db.variety_vol
|
||||
where a.ValueDate > startDate && a.ValueDate <= _valueDate
|
||||
group a by a.VarietyId into g
|
||||
select new { VarietyId = g.Key, ValueDate = g.Max(n => n.ValueDate) };
|
||||
|
||||
var query2 = from a in query1
|
||||
join b in db.variety_vol on a equals new { b.VarietyId, b.ValueDate }
|
||||
select new { b.VarietyId, b.Vol };
|
||||
|
||||
_dicData = query2.ToDictionary(n => n.VarietyId, m => (double?)m.Vol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetVol(int varietyId, out double vol)
|
||||
{
|
||||
vol = 0;
|
||||
|
||||
if (varietyId < 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Initialize();
|
||||
|
||||
if (!_dicData.TryGetValue(varietyId, out var dvol))
|
||||
{
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
_dicData[varietyId] = dvol = db.variety_vol
|
||||
.Where(v => v.VarietyId == varietyId && v.ValueDate <= _valueDate)
|
||||
.OrderByDescending(v => v.ValueDate).Select(n => (double?)n.Vol).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
if (dvol.HasValue)
|
||||
{
|
||||
vol = dvol.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetVol(string underlyingCode, out double vol)
|
||||
{
|
||||
vol = 0;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(underlyingCode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var un = DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode);
|
||||
|
||||
if (un != null)
|
||||
{
|
||||
return TryGetVol(un.UnderlyingTypeId, out vol);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
using BaseOUDAL;
|
||||
using YLErp.Modules.CalculationModule;
|
||||
using YLErp.Modules.SkewMapVolModule;
|
||||
using YLErp.QdpModule;
|
||||
using YLErp.QdpModule.Constants;
|
||||
|
||||
namespace YLErp.Modules.VolatilityModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 波动率取值服务
|
||||
/// </summary>
|
||||
public class SingleVolService
|
||||
{
|
||||
public static double GetSingleVol(SingleVolReq singleVolReq, int userId, bool isEodCalc = false)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(singleVolReq.VolType))
|
||||
{
|
||||
throw new ServiceException("波动率类型不能为空");
|
||||
}
|
||||
|
||||
if (singleVolReq.ExerciseDate == DateTime.MinValue)
|
||||
{
|
||||
throw new ServiceException("行权日不能为空");
|
||||
}
|
||||
|
||||
if (singleVolReq.TradeDate == DateTime.MinValue)
|
||||
{
|
||||
throw new ServiceException("交易日期不能为空");
|
||||
}
|
||||
|
||||
if (PS.Config.ErpElement.SkewMapVolConstruction)
|
||||
{
|
||||
return GetSingleVolWithSkewMapMode(singleVolReq, userId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetSingleVolWithNormalMode(singleVolReq, userId, isEodCalc);
|
||||
}
|
||||
}
|
||||
|
||||
//获取正常模式的波动率
|
||||
private static double GetSingleVolWithNormalMode(SingleVolReq singleVolReq, int userId, bool isEodCalc = false)
|
||||
{
|
||||
var userGroup = singleVolReq.UserGroup.TrimToNull() ?? UserBLL.GetUserGroup(userId);
|
||||
var volatility = new VolatilityQueryService(OptUserInfo.SystemUser)
|
||||
.GetVolatility(userGroup, singleVolReq.TradeDate, singleVolReq.VolType, singleVolReq.UnderlyingCode);
|
||||
if (volatility == null)
|
||||
{
|
||||
throw new ServiceException($"没有找到{singleVolReq.VolType}波动率数据:{singleVolReq.UnderlyingCode}");
|
||||
}
|
||||
if (volatility is VolatilityDefault)
|
||||
{
|
||||
return ConsVolInfos.defVol;
|
||||
}
|
||||
var req = new InterpolatedVolReq
|
||||
{
|
||||
valueDate = singleVolReq.TradeDate,
|
||||
exerciseDate = singleVolReq.ExerciseDate,
|
||||
strike = singleVolReq.Strike,
|
||||
isMoneynessOption = singleVolReq.IsMoneynessOption == "是",
|
||||
isEodCalc = isEodCalc,
|
||||
spot = singleVolReq.SpotPrice,
|
||||
volSurfaceType = volatility.VolSurfaceMode
|
||||
};
|
||||
return QdpVolHelper.GetInterpolatedVolFromNormalSurface(volatility.VolTable, req, volatility.InterpolationMethod);
|
||||
}
|
||||
|
||||
//获取skew模式的波动率
|
||||
private static double GetSingleVolWithSkewMapMode(SingleVolReq req, int userId)
|
||||
{
|
||||
if (req.Vols == null)
|
||||
{
|
||||
var userGroup = UserBLL.GetUserGroup(userId);
|
||||
req.Vols = new VolatilityQueryService(OptUserInfo.SystemUser)
|
||||
.GetVolatility(userGroup, req.TradeDate, "交易", req.UnderlyingCode);
|
||||
}
|
||||
|
||||
if (req.Vols == null || string.IsNullOrEmpty(req.Vols.VolSurfaceMode))
|
||||
{
|
||||
throw new InvalidOperationException($"找不到波动率曲面{req.UnderlyingCode}");
|
||||
}
|
||||
|
||||
if (!req.BaseVol.HasValue)
|
||||
{
|
||||
throw new InvalidOperationException($"参数BaseVol缺失");
|
||||
}
|
||||
|
||||
//var initParam = new VolSurfaceInitParamsBuilder(userId.ToString()).SetValueDate(singleVolReq.TradeDate)
|
||||
// .SetUnderlying(singleVolReq.UnderlyingId, singleVolReq.UnderlyingCode, singleVolReq.UnderlyingName).SetVolatility(volatility).Build();
|
||||
|
||||
//VolSurfaceInitializerSingleton.GetInitializer(false).InitializeMarketProxy(initParam);
|
||||
|
||||
if (req.VolType == "报价Bid")
|
||||
{
|
||||
if (!req.BidVar.HasValue || req.BidVar.Value < 1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
//var marketProxy = QdpMarketManager.Instance.GetPrebuiltMarketProxy(userId.ToString());
|
||||
var baseVolSurface = SkewMapVolHelper.GetSkewMapBaseVolSurface(req.UnderlyingCode, req.Vols);
|
||||
var skewMapVolSurface = new SkewMapVolSurface(baseVolSurface.BaseVol);
|
||||
|
||||
var t = TradeCalcHelper.CalculateTTMDays(
|
||||
req.TradeDate,
|
||||
req.ExerciseDate,
|
||||
req.UnderlyingTypeId,
|
||||
precisionOfMinute: false);
|
||||
|
||||
return skewMapVolSurface.GetVolWithBaseVol(
|
||||
baseVol: req.BaseVol.Value,
|
||||
t: Math.Ceiling(t), //不考虑日内精确时间
|
||||
k: req.Strike,
|
||||
spot: req.SpotPrice,
|
||||
isCall: req.CallPut == "Call",
|
||||
isBuy: true,
|
||||
var: req.BidVar ?? 0);
|
||||
}
|
||||
else if (req.VolType == "报价Ask")
|
||||
{
|
||||
if (!req.AskVar.HasValue || req.AskVar.Value < 1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
//var marketProxy = QdpMarketManager.Instance.GetPrebuiltMarketProxy(userId.ToString());
|
||||
var baseVolSurface = SkewMapVolHelper.GetSkewMapBaseVolSurface(req.UnderlyingCode, req.Vols);
|
||||
var skewMapVolSurface = new SkewMapVolSurface(baseVolSurface.BaseVol);
|
||||
|
||||
var t = TradeCalcHelper.CalculateTTMDays(
|
||||
req.TradeDate,
|
||||
req.ExerciseDate,
|
||||
req.UnderlyingTypeId,
|
||||
precisionOfMinute: false);
|
||||
|
||||
return skewMapVolSurface.GetVolWithBaseVol(
|
||||
baseVol: req.BaseVol.Value,
|
||||
t: Math.Ceiling(t), //不考虑日内精确时间
|
||||
k: req.Strike,
|
||||
spot: req.SpotPrice,
|
||||
isCall: req.CallPut == "Call",
|
||||
isBuy: false,
|
||||
var: req.AskVar ?? 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("波动率类型应为'报价Bid'或'报价Ask'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Data;
|
||||
|
||||
namespace YLErp.Modules.SkewMapVolModule
|
||||
{
|
||||
public interface IVolSkewMapInitializer
|
||||
{
|
||||
DataTable GetSkewMapData(bool isBuy);
|
||||
|
||||
bool SetSkewMapData(string table, bool isBuy);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
using System.Data;
|
||||
|
||||
namespace YLErp.Modules.SkewMapVolModule
|
||||
{
|
||||
public struct SkewMapVolSurface
|
||||
{
|
||||
public SkewMapVolSurface(Dictionary<string, double> baseVol)
|
||||
{
|
||||
_baseVol = baseVol;
|
||||
}
|
||||
|
||||
public double GetVol(double t, double k, double spot, bool isCall, bool isBuy, int var)
|
||||
{
|
||||
var basevol = GetBaseVol(t: t);
|
||||
return GetVolWithBaseVol(baseVol: basevol, t: t, k: k, spot: spot, isCall: isCall, isBuy: isBuy, var: var);
|
||||
}
|
||||
|
||||
public double GetVolWithBaseVol(double baseVol, double t, double k, double spot, bool isCall, bool isBuy, int var)
|
||||
{
|
||||
var moneyness = k / spot - 1;
|
||||
if (isCall)
|
||||
{
|
||||
moneyness *= -1;
|
||||
}
|
||||
|
||||
return InterpolateVol(term: t, moneyness: moneyness, basevol: baseVol, isBuy: isBuy, var: var);
|
||||
}
|
||||
|
||||
public double GetBaseVol(double t)
|
||||
{
|
||||
var m = 21;
|
||||
double vol;
|
||||
if (t <= 21)
|
||||
{
|
||||
vol = _baseVol["1M"];
|
||||
}
|
||||
else if (t > m && t < 3 * m)
|
||||
{
|
||||
vol = ((3 * m - t) * _baseVol["1M"] + (t - m) * _baseVol["3M"]) / (2 * m);
|
||||
}
|
||||
else if (t > 3 * m)
|
||||
{
|
||||
vol = ((6 * m - t) * _baseVol["3M"] + (t - 3 * m) * _baseVol["6M"]) / (3 * m);
|
||||
}
|
||||
else
|
||||
{
|
||||
vol = _baseVol["6M"];
|
||||
}
|
||||
|
||||
return vol;
|
||||
}
|
||||
|
||||
private double InterpolateVol(double term, double moneyness, double basevol, bool isBuy, int var)
|
||||
{
|
||||
var index = new Dictionary<int, double>();
|
||||
var weight = new Dictionary<int, double>();
|
||||
|
||||
if (term <= 10)
|
||||
{
|
||||
index[1] = index[2] = 10;
|
||||
weight[1] = weight[2] = 0.5;
|
||||
}
|
||||
else if (term >= 80)
|
||||
{
|
||||
index[1] = index[2] = 80;
|
||||
weight[1] = weight[2] = 0.5;
|
||||
}
|
||||
else
|
||||
{
|
||||
index[1] = Math.Max(10, Math.Floor(term / 20) * 20);
|
||||
index[2] = (Math.Floor(term / 20) + 1) * 20;
|
||||
weight[1] = (index[2] - term) / (index[2] - index[1]);
|
||||
weight[2] = (term - index[1]) / (index[2] - index[1]);
|
||||
}
|
||||
|
||||
if (moneyness <= -0.1)
|
||||
{
|
||||
index[3] = index[4] = -0.1;
|
||||
weight[3] = weight[4] = 0.5;
|
||||
}
|
||||
else if (moneyness >= 0.1)
|
||||
{
|
||||
index[3] = index[4] = 0.1;
|
||||
weight[3] = weight[4] = 0.5;
|
||||
}
|
||||
else
|
||||
{
|
||||
index[3] = Math.Floor(moneyness / 0.01) * 0.01;
|
||||
index[4] = (Math.Floor(moneyness / 0.01) + 1) * 0.01;
|
||||
weight[3] = (index[4] - moneyness) / (index[4] - index[3]);
|
||||
weight[4] = (moneyness - index[3]) / (index[4] - index[3]);
|
||||
}
|
||||
|
||||
if (basevol <= 0.1)
|
||||
{
|
||||
index[5] = index[6] = 0.1;
|
||||
weight[5] = weight[6] = 0.5 * basevol / 0.1;
|
||||
}
|
||||
else if (basevol >= 0.4)
|
||||
{
|
||||
index[5] = index[6] = 0.4;
|
||||
weight[5] = weight[6] = 0.5 * basevol / 0.4;
|
||||
}
|
||||
else
|
||||
{
|
||||
index[5] = Math.Floor(basevol / 0.05) * 0.05;
|
||||
index[6] = (Math.Floor(basevol / 0.05) + 1) * 0.05;
|
||||
weight[5] = (index[6] - basevol) / (index[6] - index[5]);
|
||||
weight[6] = (basevol - index[5]) / (index[6] - index[5]);
|
||||
}
|
||||
|
||||
double vol = 0;
|
||||
for (var i = 1; i <= 2; i++)
|
||||
{
|
||||
for (var j = 3; j <= 4; j++)
|
||||
{
|
||||
for (var k = 5; k <= 6; k++)
|
||||
{
|
||||
var current_weight = weight[i] * weight[j] * weight[k];
|
||||
var vol_to_add = FindSkew(term: index[i], moneyness: index[j], atm: index[k], isBuy: isBuy, var: var);
|
||||
vol += vol_to_add * current_weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vol;
|
||||
}
|
||||
|
||||
private double FindSkew(double term, double moneyness, double atm, bool isBuy, int var)
|
||||
{
|
||||
var skewMap = VolSkewMapInitializerSingleton.Instance.GetSkewMapData(isBuy);
|
||||
if (skewMap == null)
|
||||
{
|
||||
throw new Exception("无法获取全局SkewMap数据.");
|
||||
}
|
||||
var all = from DataRow row in skewMap.Rows
|
||||
where Math.Abs((double)row["term"] - term) < 1e-7 &&
|
||||
Math.Abs((double)row["moneyness"] - moneyness) < 1e-7 &&
|
||||
Math.Abs((double)row["basevol"] - atm) < 1e-7
|
||||
select row;
|
||||
var res = all.First();
|
||||
return (double)res[var.ToString()];
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, double> _baseVol;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using BaseOUDAL;
|
||||
using System.Text.RegularExpressions;
|
||||
using YLErp.Modules.CalculationModule;
|
||||
|
||||
namespace YLErp.Modules.VolatilityModule.SkewMapVolModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 从数据库中查询SkewVol
|
||||
/// </summary>
|
||||
public class SkewVolQueryService
|
||||
{
|
||||
public static volatility GetVol(int userId, SkewVolRequest req)
|
||||
{
|
||||
if (req is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(req));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(req.VolType))
|
||||
{
|
||||
throw new Exception("波动率类型不能为空");
|
||||
}
|
||||
|
||||
var userGroup = UserBLL.GetUserGroup(userId);
|
||||
|
||||
var vols = VolatilityHelper.GetVol(req.valueDate, "交易", req.UnderlyingCode, userGroup);
|
||||
|
||||
if (vols == null || string.IsNullOrEmpty(vols.VolSurfaceMode))
|
||||
{
|
||||
throw new InvalidOperationException($"找不到波动率曲面{req.UnderlyingCode}");
|
||||
}
|
||||
|
||||
var singleVols = vols.VolTable;
|
||||
|
||||
for (var i = 0; i < singleVols.Count; i++)
|
||||
{
|
||||
if (!Regex.IsMatch(singleVols[i].Expire, @"\d")) { continue; }
|
||||
|
||||
var ExerciseDate = GetExerciseDate(req.valueDate, singleVols[i].Expire);
|
||||
|
||||
singleVols[i].Vol = SkewMapVolHelper.GetInterpolatedVol(
|
||||
volSurface: vols,
|
||||
valueDate: req.valueDate,
|
||||
underlyingCode: req.UnderlyingCode,
|
||||
exerciseDate: ExerciseDate,
|
||||
strikePrice: req.Strike,
|
||||
isBuy: req.VolType == "报价Bid",
|
||||
isCall: false,
|
||||
spotPrice: req.Strike,
|
||||
skewMapVolVar: (int)(req.VolType == "报价Bid" ? vols.GetBidVar() : vols.GetAskVar())
|
||||
);
|
||||
}
|
||||
vols.Data = singleVols.ToJson();
|
||||
return vols;
|
||||
}
|
||||
|
||||
private static DateTime GetExerciseDate(DateTime valueDate, string term)
|
||||
{
|
||||
var result = valueDate;
|
||||
var m = Regex.Match(term, @"^(?<num>\d+)(?<unit>[D|W|M|Y])$");
|
||||
if (!m.Success) { return result; }
|
||||
var number = int.Parse(m.Groups["num"].Value);
|
||||
switch (m.Groups["unit"].Value)
|
||||
{
|
||||
case "D":
|
||||
result = result.AddDays(number);
|
||||
break;
|
||||
case "W":
|
||||
result = result.AddDays(number * 7);
|
||||
break;
|
||||
case "M":
|
||||
result = result.AddMonths(number).AddDays(-1);
|
||||
break;
|
||||
case "Y":
|
||||
result = result.AddYears(number).AddDays(-1);
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public class SkewVolRequest
|
||||
{
|
||||
public DateTime valueDate { get; set; }
|
||||
|
||||
public string VolType { get; set; }
|
||||
|
||||
public string UnderlyingCode { get; set; }
|
||||
|
||||
public double Strike { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
using System.Data;
|
||||
using YLErp.BLL;
|
||||
|
||||
namespace YLErp.Modules.SkewMapVolModule
|
||||
{
|
||||
public class VolSkewMapDbInitializer : IVolSkewMapInitializer
|
||||
{
|
||||
/// <summary>
|
||||
/// 从数据库表读取SkewMapData
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public DataTable GetSkewMapData(bool isBuy)
|
||||
{
|
||||
if (isBuy)
|
||||
{
|
||||
if (_bidTable == null)
|
||||
{
|
||||
var rawTable = ReadDataFromDb(isBuy);
|
||||
if (rawTable == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
_bidTable = ExpandSkewMap(rawTable);
|
||||
}
|
||||
|
||||
return _bidTable;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_askTable == null)
|
||||
{
|
||||
var rawTable = ReadDataFromDb(isBuy);
|
||||
if (rawTable == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
_askTable = ExpandSkewMap(rawTable);
|
||||
}
|
||||
|
||||
return _askTable;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetSkewMapData(string table, bool isBuy)
|
||||
{
|
||||
if (SaveDataToDb(table, isBuy))
|
||||
{
|
||||
var rawTable = ReadDataFromDb(isBuy);
|
||||
if (isBuy)
|
||||
{
|
||||
_bidTable = ExpandSkewMap(rawTable);
|
||||
}
|
||||
else
|
||||
{
|
||||
_askTable = ExpandSkewMap(rawTable);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static DataTable ExpandSkewMap(DataTable base_map)
|
||||
{
|
||||
int[] base_var = { 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50 };
|
||||
var target_var = new int[50];
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
target_var[i] = i + 1;
|
||||
}
|
||||
|
||||
var tb = new DataTable();
|
||||
tb.Columns.Add("term", System.Type.GetType("System.Double"));
|
||||
tb.Columns.Add("moneyness", System.Type.GetType("System.Double"));
|
||||
tb.Columns.Add("basevol", System.Type.GetType("System.Double"));
|
||||
foreach (int i in target_var)
|
||||
{
|
||||
tb.Columns.Add(i.ToString(), System.Type.GetType("System.Double"));
|
||||
}
|
||||
|
||||
foreach (DataRow dr in base_map.Rows)
|
||||
{
|
||||
var r = tb.NewRow();
|
||||
r[0] = Convert.ToSingle(dr["term"]);
|
||||
r[1] = Convert.ToSingle(dr["moneyness"]);
|
||||
r[2] = Convert.ToSingle(dr["basevol"]);
|
||||
int loc = 3;
|
||||
|
||||
foreach (int v in target_var)
|
||||
{
|
||||
var var_col = v.ToString();
|
||||
if (base_var.Contains(v))
|
||||
{
|
||||
r[loc] = Convert.ToSingle(dr[var_col]);
|
||||
}
|
||||
else
|
||||
{
|
||||
int l_var = base_var.Where(b => b < v).Last();
|
||||
int h_var = base_var.Where(b => b > v).First();
|
||||
float l_v = Convert.ToSingle(dr[l_var.ToString()]);
|
||||
float h_v = Convert.ToSingle(dr[h_var.ToString()]);
|
||||
float n_value = l_v + (v - l_var) * (h_v - l_v) / (h_var - l_var);
|
||||
r[loc] = n_value;
|
||||
}
|
||||
loc++;
|
||||
}
|
||||
tb.Rows.Add(r);
|
||||
}
|
||||
return tb;
|
||||
|
||||
}
|
||||
|
||||
private static DataTable ReadDataFromDb(bool isBuy)
|
||||
{
|
||||
using (var db = new YLContext())
|
||||
{
|
||||
var skewMapType = ConvertToSkewMapType(isBuy);
|
||||
var skewMap = db.globalSkewMap.FirstOrDefault(x => x.SkewMapType == skewMapType);
|
||||
if (skewMap == null || string.IsNullOrWhiteSpace(skewMap.SkewMapTableData))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var tb = new DataTable();
|
||||
var rows = skewMap.SkewMapTableData.Split('\n');
|
||||
for (int i = 0; i < rows.Count() - 1; i++)
|
||||
{
|
||||
var rowValues = rows[i].Split(',');
|
||||
if (i == 0)
|
||||
{
|
||||
//tb.Columns.Add(rowValues[0].Trim());
|
||||
for (int j = 0; j < rowValues.Count(); j++)
|
||||
{
|
||||
tb.Columns.Add(rowValues[j].Trim());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var dr = tb.NewRow();
|
||||
for (int k = 0; k < rowValues.Count(); k++)
|
||||
{
|
||||
dr[k] = Convert.ToSingle(rowValues[k]);
|
||||
}
|
||||
tb.Rows.Add(dr);
|
||||
}
|
||||
}
|
||||
return tb;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool SaveDataToDb(string tableStr, bool isBuy)
|
||||
{
|
||||
try
|
||||
{
|
||||
var skewMapType = ConvertToSkewMapType(isBuy);
|
||||
using (var db = new YLContext())
|
||||
{
|
||||
var existingRecord = db.globalSkewMap.FirstOrDefault(x => x.SkewMapType == skewMapType);
|
||||
if (existingRecord == null)
|
||||
{
|
||||
existingRecord = new Model.GlobalSkewMap
|
||||
{
|
||||
SkewMapTableData = tableStr,
|
||||
SkewMapType = skewMapType,
|
||||
UpdateTime = DateTime.Now
|
||||
};
|
||||
db.globalSkewMap.Add(existingRecord);
|
||||
}
|
||||
else
|
||||
{
|
||||
existingRecord.SkewMapTableData = tableStr;
|
||||
existingRecord.UpdateTime = DateTime.Now;
|
||||
}
|
||||
db.SaveChanges();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ConvertToSkewMapType(bool isBuy)
|
||||
{
|
||||
return isBuy ? "Bid" : "Ask";
|
||||
}
|
||||
|
||||
private DataTable _bidTable = null;
|
||||
private DataTable _askTable = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace YLErp.Modules.SkewMapVolModule
|
||||
{
|
||||
public class VolSkewMapInitializerSingleton
|
||||
{
|
||||
public static IVolSkewMapInitializer Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new VolSkewMapDbInitializer();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private static IVolSkewMapInitializer _instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using YLErp.BLL;
|
||||
|
||||
namespace YLErp.Modules.VolatilityModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 交易对冲波动率服务
|
||||
/// </summary>
|
||||
public class TradeHedgeVolService
|
||||
{
|
||||
/// <summary>
|
||||
/// 批量保存对冲波动率
|
||||
/// </summary>
|
||||
public void SaveTradeHedgeVols(Dictionary<int, double> tradeIdHedgeVolDic, int userId, string userName, DateTime? valueDate = null)
|
||||
{
|
||||
if (tradeIdHedgeVolDic == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tradeIdHedgeVolDic));
|
||||
}
|
||||
if (!valueDate.HasValue)
|
||||
{
|
||||
valueDate = valuedateBLL.ValueDate;
|
||||
}
|
||||
|
||||
var tradeIds = tradeIdHedgeVolDic.Keys.ToList();
|
||||
using (var con = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
var tradeHedgeVols = con.trade_hedge_vol.Where(v => tradeIds.Contains(v.TradeId) && v.ValueDate == valueDate).ToDictionary(v => v.TradeId, v => v);
|
||||
foreach (var tradeIdHedgeVol in tradeIdHedgeVolDic)
|
||||
{
|
||||
trade_hedge_vol hedgeTradeVol;
|
||||
if (tradeHedgeVols.ContainsKey(tradeIdHedgeVol.Key))
|
||||
{
|
||||
hedgeTradeVol = tradeHedgeVols[tradeIdHedgeVol.Key];
|
||||
}
|
||||
else
|
||||
{
|
||||
hedgeTradeVol = new trade_hedge_vol
|
||||
{
|
||||
TradeId = tradeIdHedgeVol.Key,
|
||||
ValueDate = valueDate,
|
||||
};
|
||||
con.trade_hedge_vol.Add(hedgeTradeVol);
|
||||
}
|
||||
|
||||
hedgeTradeVol.OptId = userId;
|
||||
hedgeTradeVol.OptName = userName;
|
||||
hedgeTradeVol.OptDate = DateTime.Now;
|
||||
hedgeTradeVol.TradeSavedVol = tradeIdHedgeVol.Value;
|
||||
|
||||
}
|
||||
|
||||
con.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取对冲波动率
|
||||
/// </summary>
|
||||
public static double GetTradeHedgeVol(OtcTrade trade, DateTime valueDate)
|
||||
{
|
||||
if (trade is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(trade));
|
||||
}
|
||||
|
||||
return GetTradeHedgeVol(trade.id, valueDate, trade.TradeSavedVol ?? ConsGlobal.DefaultVol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取对冲波动率
|
||||
/// </summary>
|
||||
/// <param name="tradeId">交易ID</param>
|
||||
/// <param name="valueDate">取值日期</param>
|
||||
/// <param name="tradeSavedVol">交易表数据中tradeSavedVol值</param>
|
||||
public static double GetTradeHedgeVol(int tradeId, DateTime valueDate, double? tradeSavedVol = null)
|
||||
{
|
||||
if (tradeId <= 0)
|
||||
{
|
||||
throw new ServiceException($"{nameof(tradeId)}应该大于0");
|
||||
}
|
||||
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
var tradeHedgeVol = db.trade_hedge_vol.Where(v => v.TradeId == tradeId && v.ValueDate <= valueDate)
|
||||
.OrderByDescending(v => v.ValueDate).Select(n => (double?)n.TradeSavedVol).FirstOrDefault();
|
||||
|
||||
if (tradeHedgeVol.HasValue)
|
||||
{
|
||||
return tradeHedgeVol.Value;
|
||||
}
|
||||
|
||||
if (tradeSavedVol.HasValue)
|
||||
{
|
||||
return tradeSavedVol.Value;
|
||||
}
|
||||
|
||||
var trade = db.trade.Where(t => t.id == tradeId && t.ValidState != "InValid")
|
||||
.Select(n => new { n.TradeSavedVol }).FirstOrDefault();
|
||||
|
||||
if (trade == null)
|
||||
{
|
||||
throw new ServiceException("系统中不存在相关交易");
|
||||
}
|
||||
|
||||
return trade.TradeSavedVol ?? ConsGlobal.DefaultVol;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
using CsvHelper;
|
||||
using CsvHelper.Configuration;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using YieldChain.Helpers;
|
||||
using YLErp.Model;
|
||||
|
||||
namespace YLErp.Modules.VolatilityModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 品种波动率调整服务
|
||||
/// </summary>
|
||||
public class VarietyVolAdjustService : YLBaseService
|
||||
{
|
||||
public VarietyVolAdjustService(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
}
|
||||
|
||||
public IEnumerable<VarietyVolAdjustModel> GetList()
|
||||
{
|
||||
var query = from n in DbContext.variety
|
||||
//where n.UnderlyingInstrumentType == "CommodityFutures"
|
||||
orderby n.VarietyCode
|
||||
select new VarietyVolAdjustModel
|
||||
{
|
||||
VarietyId = n.id,
|
||||
VarietyCode = n.VarietyCode,
|
||||
Volitality = n.VolatilityAdjust
|
||||
};
|
||||
return query.ToArray();
|
||||
}
|
||||
|
||||
public int SaveData(VarietyVolAdjustModel model)
|
||||
{
|
||||
if (model is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(model));
|
||||
}
|
||||
|
||||
var dbData = DbContext.variety.Find(model.VarietyId);
|
||||
if (dbData == null)
|
||||
{
|
||||
throw new ServiceException("保存失败:品种数据不存在");
|
||||
}
|
||||
dbData.VolatilityAdjust = model.Volitality;
|
||||
return DbContext.SaveChanges();
|
||||
}
|
||||
|
||||
public ImportResultModel ImportCsvDatas(Stream stream)
|
||||
{
|
||||
var result = new ImportResultModel();
|
||||
|
||||
IEnumerable<ImportModel> importModels;
|
||||
|
||||
var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
|
||||
{
|
||||
//规避空数据行
|
||||
ShouldSkipRecord = n => n.Row.Parser.Record == null || n.Row.Parser.Record.All(m => string.IsNullOrWhiteSpace(m))
|
||||
};
|
||||
|
||||
//当前编码支持ansi和utf with bom
|
||||
using (var sr = new StreamReader(stream, Encoding.Default))
|
||||
using (var csv = new CsvReader(sr, csvConfig))
|
||||
{
|
||||
csv.Context.TypeConverterCache.AddConverter<string>(Helpers.CsvTypeConverts.StringConverter.Required);
|
||||
csv.Context.TypeConverterCache.AddConverter<double>(Helpers.CsvTypeConverts.DoubleConverter.Required);
|
||||
csv.Context.RegisterClassMap<ImportModel.ImportMap>();
|
||||
importModels = csv.GetRecords<ImportModel>().ToArray();
|
||||
}
|
||||
|
||||
if (!importModels.Any())
|
||||
{
|
||||
throw new ServiceException("没有可导入的数据");
|
||||
}
|
||||
|
||||
result.TotalCount = importModels.Count();
|
||||
|
||||
var dic = DbContext.variety.ToDictionary(n => n.VarietyCode, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var item in importModels)
|
||||
{
|
||||
if (dic.TryGetValue(item.code, out var variety))
|
||||
{
|
||||
result.SuccessCount++;
|
||||
variety.VolatilityAdjust = item.vol;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Errors.Add(item.code + ": 品种数据不存在");
|
||||
}
|
||||
}
|
||||
|
||||
DbContext.SaveChanges();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取导入模板
|
||||
/// </summary>
|
||||
public static string GetCsvTemplate()
|
||||
{
|
||||
return new ImportModel.ImportMap().GenTemplate();
|
||||
}
|
||||
|
||||
class ImportModel
|
||||
{
|
||||
public string code { get; set; }
|
||||
|
||||
public double vol { get; set; }
|
||||
|
||||
public class ImportMap : ClassMap<ImportModel>
|
||||
{
|
||||
public ImportMap()
|
||||
{
|
||||
Map(m => m.code).Name("品种代码").TypeConverter(Helpers.CsvTypeConverts.StringConverter.Required);
|
||||
Map(m => m.vol).Name("历史波动率").TypeConverter(Helpers.CsvTypeConverts.DoubleConverter.Required);
|
||||
}
|
||||
|
||||
public string GenTemplate()
|
||||
{
|
||||
var sb = new StringBuilder(512);
|
||||
|
||||
foreach (var map in MemberMaps)
|
||||
{
|
||||
sb.AppendCSVCell(map.Data.Names.First()).Append(',');
|
||||
}
|
||||
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
sb.Remove(sb.Length - 1, 1);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
namespace YLErp.Modules.VolatilityModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 品种波动率数据服务
|
||||
/// </summary>
|
||||
public class VarietyVolService : YLBaseService
|
||||
{
|
||||
public VarietyVolService(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
}
|
||||
|
||||
public VarietyVolService(YLBaseService baseService) : base(baseService)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取距valueDate最近的品种波动率
|
||||
/// </summary>
|
||||
public VarietyVol GetVol(long varietyId, DateTime valueDate)
|
||||
{
|
||||
var query = from volDb in DbContext.variety_vol
|
||||
where volDb.VarietyId == varietyId
|
||||
&& volDb.ValueDate <= valueDate
|
||||
orderby volDb.ValueDate descending
|
||||
select volDb;
|
||||
return query.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取距date最近的品种波动率
|
||||
/// </summary>
|
||||
public List<VarietyVolDto> GetVols(DateTime date, IEnumerable<int> varietyIds)
|
||||
{
|
||||
if (varietyIds is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(varietyIds));
|
||||
}
|
||||
|
||||
var idSet = varietyIds.ToHashSet();
|
||||
|
||||
var grpQry = from vol in DbContext.variety_vol
|
||||
where vol.ValueDate <= date && idSet.Contains(vol.VarietyId)
|
||||
group vol by vol.VarietyId into grp
|
||||
select new
|
||||
{
|
||||
VarietyId = grp.Key,
|
||||
ValueDate = grp.Max(n => n.ValueDate)
|
||||
};
|
||||
|
||||
var qry = from gv in grpQry
|
||||
join vol in DbContext.variety_vol on gv equals new { vol.VarietyId, vol.ValueDate }
|
||||
orderby gv.VarietyId
|
||||
select new VarietyVolDto
|
||||
{
|
||||
VarietyId = vol.VarietyId,
|
||||
ValueDate = vol.ValueDate,
|
||||
Vol = vol.Vol
|
||||
};
|
||||
|
||||
var list = qry.ToList();
|
||||
|
||||
foreach (var data in list)
|
||||
{
|
||||
idSet.Remove(data.VarietyId);
|
||||
}
|
||||
|
||||
var defArr = idSet.Select(n => new VarietyVolDto { VarietyId = n, ValueDate = date, Vol = ConsGlobal.DefaultVol }).ToArray();
|
||||
|
||||
list.AddRange(defArr);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存数据
|
||||
/// </summary>
|
||||
public void SaveData(int varietyId, DateTime valueDate, double vol)
|
||||
{
|
||||
if (varietyId == default)
|
||||
{
|
||||
throw new ArgumentNullException("varietyId");
|
||||
}
|
||||
|
||||
if (valueDate == default)
|
||||
{
|
||||
throw new ArgumentNullException("valueDate");
|
||||
}
|
||||
|
||||
if (vol == default(int))
|
||||
{
|
||||
throw new ArgumentNullException("vol");
|
||||
}
|
||||
|
||||
if (!DbContext.variety.Any(n => n.id == varietyId))
|
||||
{
|
||||
throw new ServiceException("品种不存在");
|
||||
}
|
||||
|
||||
var dbModel = DbContext.variety_vol.FirstOrDefault(n => n.VarietyId == varietyId && n.ValueDate == valueDate);
|
||||
|
||||
if (dbModel == null)
|
||||
{
|
||||
DbContext.variety_vol.Add(new VarietyVol
|
||||
{
|
||||
ValueDate = valueDate,
|
||||
VarietyId = varietyId,
|
||||
Vol = vol,
|
||||
OptDate = DateTime.Now,
|
||||
OptId = UserId,
|
||||
OptName = UserName
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
dbModel.Vol = vol;
|
||||
dbModel.OptId = UserId;
|
||||
dbModel.OptName = UserName;
|
||||
dbModel.OptDate = OptDate;
|
||||
}
|
||||
|
||||
DbContext.SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public Dictionary<int, double> GetVols(DateTime date, IEnumerable<trade> tradeList)
|
||||
{
|
||||
var result = new Dictionary<int, double>();
|
||||
var codes = tradeList.Select(O => O.UnderlyingCode).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var varietyMap = DbContext.underlying_manager
|
||||
.Where(underlyingDb => codes.Contains(underlyingDb.UnderlyingCode))
|
||||
.ToDictionary(K => K.UnderlyingCode, V => V.UnderlyingTypeId, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var vols = GetVols(date, varietyMap.Values);
|
||||
|
||||
foreach (var t in tradeList)
|
||||
{
|
||||
if (varietyMap.TryGetValue(t.UnderlyingCode, out var vid))
|
||||
{
|
||||
result[t.id] = vols.Find(O => O.VarietyId == vid).Vol;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"标的'{t.UnderlyingCode}'找不到光证波动率,因未关联品种");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static double? GetVarietyVol(DateTime valueDate, int varietyId)
|
||||
{
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
var query = from v in db.variety_vol
|
||||
where v.VarietyId == varietyId && v.ValueDate <= valueDate
|
||||
orderby v.ValueDate descending
|
||||
select (double?)v.Vol;
|
||||
return query.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static double? GetVarietyVol(DateTime valueDate, string underlyingCode)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(underlyingCode))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
var query = from un in db.underlying_manager.Where(n => n.UnderlyingCode == underlyingCode)
|
||||
join v in db.variety_vol on un.UnderlyingTypeId equals v.VarietyId
|
||||
where v.ValueDate <= valueDate
|
||||
orderby v.ValueDate descending
|
||||
select (double?)v.Vol;
|
||||
return query.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
using YLErp.QdpModule;
|
||||
using YLErp.QdpModule.Constants;
|
||||
|
||||
namespace YLErp.DBModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 波动率构造
|
||||
/// </summary>
|
||||
public class VolatilityBuilder
|
||||
{
|
||||
readonly volatility _volatility;
|
||||
|
||||
public VolatilityBuilder(DateTime quotationDate, string volMode = ConsVolInfos.defVolMode)
|
||||
{
|
||||
_volatility = new volatility
|
||||
{
|
||||
QuotationDate = quotationDate,
|
||||
InterpolationMethod = ConsVolInfos.defInterpolationMethod,
|
||||
VolSurfaceMode = string.IsNullOrEmpty(volMode) ? ConsVolInfos.defVolMode : volMode,
|
||||
VolType = ConsVolInfos.defVolType,
|
||||
UnderlyingId = null,
|
||||
ContractCode = null,
|
||||
OptId = 0,
|
||||
OptDate = DateTime.Now,
|
||||
OptName = ConsVolInfos.defOptName
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 必须设置--波动率值
|
||||
/// </summary>
|
||||
public VolatilityBuilder SetData(double vol)
|
||||
{
|
||||
_volatility.SetData(QdpVolHelper.GenerateFlatSingleVols(vol));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 必须设置--波动率值
|
||||
/// </summary>
|
||||
public VolatilityBuilder SetDefaultData()
|
||||
{
|
||||
_volatility.SetData(QdpVolHelper.GenerateFlatSingleVols(ConsVolInfos.defVol));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 必须设置--标的信息
|
||||
/// </summary>
|
||||
public VolatilityBuilder SetUnderlying(int underlyingId, string underlyingCode)
|
||||
{
|
||||
_volatility.UnderlyingId = underlyingId;
|
||||
_volatility.ContractCode = underlyingCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 必须设置--标的信息
|
||||
/// </summary>
|
||||
public VolatilityBuilder SetUnderlying(IUnderlyingBasic underlying)
|
||||
{
|
||||
if (underlying is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(underlying));
|
||||
}
|
||||
|
||||
_volatility.UnderlyingId = underlying.id;
|
||||
_volatility.ContractCode = underlying.UnderlyingCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可选配置--波动率类型
|
||||
/// </summary>
|
||||
public VolatilityBuilder SetVolType(string volType)
|
||||
{
|
||||
_volatility.VolType = volType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可选配置--波动率模式
|
||||
/// </summary>
|
||||
public VolatilityBuilder SetVolMode(string volMode)
|
||||
{
|
||||
_volatility.VolSurfaceMode = volMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可选配置--插值方法
|
||||
/// </summary>
|
||||
public VolatilityBuilder SetInterpolationMethod(string interMethod)
|
||||
{
|
||||
_volatility.InterpolationMethod = interMethod;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可选配置--操作人信息
|
||||
/// </summary>
|
||||
public VolatilityBuilder SetOpt(int optid, string optName)
|
||||
{
|
||||
_volatility.OptId = optid;
|
||||
_volatility.OptName = optName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可选配置--用户组
|
||||
/// </summary>
|
||||
public VolatilityBuilder SetUserGroup(string userGroup)
|
||||
{
|
||||
_volatility.UserGroup = userGroup;
|
||||
return this;
|
||||
}
|
||||
|
||||
public volatility Build(IUnderlyingBasic underlying)
|
||||
{
|
||||
return SetUnderlying(underlying).Build();
|
||||
}
|
||||
|
||||
public volatility Build(double vol)
|
||||
{
|
||||
return SetData(vol).Build();
|
||||
}
|
||||
|
||||
public volatility Build()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_volatility.ContractCode))
|
||||
{
|
||||
throw new ArgumentException("ContractCode 必须有值", nameof(_volatility.ContractCode));
|
||||
}
|
||||
|
||||
if (_volatility.QuotationDate.Year < 2000)
|
||||
{
|
||||
throw new ArgumentException("QuotationDate 必须为有效值,合约代码:" + _volatility.ContractCode, nameof(_volatility.QuotationDate));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_volatility.VolType))
|
||||
{
|
||||
throw new ArgumentException("VolType 必须有值,合约代码:" + _volatility.ContractCode, nameof(_volatility.VolType));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_volatility.InterpolationMethod))
|
||||
{
|
||||
throw new ArgumentException("InterpolationMethod 必须有值,合约代码:" + _volatility.ContractCode, nameof(_volatility.InterpolationMethod));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_volatility.VolSurfaceMode))
|
||||
{
|
||||
throw new ArgumentException("VolSurfaceMode 必须有值,合约代码:" + _volatility.ContractCode, nameof(_volatility.VolSurfaceMode));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_volatility.Data))
|
||||
{
|
||||
throw new ArgumentException("VolData 必须有值,合约代码:" + _volatility.ContractCode, nameof(_volatility.Data));
|
||||
}
|
||||
|
||||
return _volatility;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static VolatilityBuilder CreateMoneynessVolBuilder(DateTime quotationDate, string volType = ConsVolInfos.defVolType)
|
||||
{
|
||||
return new VolatilityBuilder(quotationDate, "MoneynessVol").SetVolType(volType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,426 @@
|
||||
using BaseOUDAL;
|
||||
using Qdp.Pricing.Base.Implementations;
|
||||
using Qdp.Pricing.Library.Equity.Engines.Analytical;
|
||||
using System.Data;
|
||||
using YLErp.Abstract;
|
||||
using YLErp.BLL;
|
||||
using YLErp.Modules.CalculationModule;
|
||||
using YLErp.QdpModule;
|
||||
using YLErp.QdpModule.Constants;
|
||||
|
||||
namespace YLErp.Modules.VolatilityModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 波动率操作帮助类
|
||||
/// </summary>
|
||||
public static class VolatilityHelper
|
||||
{
|
||||
public static volatility GetVol(DateTime date, string volType, string underlyingCode, string userGroup)
|
||||
{
|
||||
return new VolatilityQueryService(OptUserInfo.SystemUser).GetVolatility(userGroup, date, volType, underlyingCode, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取结算波动率
|
||||
/// </summary>
|
||||
public static volatility GetEodSettlementVols(OtcTradeBase trade, underlying_manager underlying, DateTime valueDate)
|
||||
{
|
||||
if (PS.Config.IsTradeVol)
|
||||
{
|
||||
return GetSurfaceFromTradeVol(trade, valueDate, isEodSettle: true);
|
||||
}
|
||||
//再非tradevol的情况下,默认就使用财务波动率
|
||||
var userGroup = UserBLL.GetUserGroup(trade.TraderId);
|
||||
return GetVol(valueDate, "财务", underlying.UnderlyingCode, userGroup);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取隐含波动率
|
||||
/// </summary>
|
||||
public static double GetImpliedVol(DateTime valueDate, OtcTradeBase trade, double? ttmdays, double underlyingPrice, bool isEod)
|
||||
{
|
||||
var riskFreeRate = trade.NoRiskRate ?? (valuedateBLL.GetRiskFreeRateFromCurve(valueDate, trade.ExerciseDate) / 100);
|
||||
|
||||
var strike = trade.Strike ?? 0;
|
||||
|
||||
var variety = DataCacheProvider.GetVariety(trade.UnderlyingCode);
|
||||
|
||||
if (!ttmdays.HasValue || double.IsNaN(ttmdays.Value))
|
||||
{
|
||||
ttmdays = TradeCalcHelper.CalculateTTMDays(valueDate, trade.ExerciseDate.Value, variety.id, false);
|
||||
}
|
||||
|
||||
var volSurfaceName = Guid.NewGuid().ToString();
|
||||
|
||||
var paramReq = new OptionTradeParamRequest(riskFreeRate)
|
||||
{
|
||||
dividends = null,
|
||||
fixings = null,
|
||||
hasNightMarket = variety != null && variety.HasNightMarket,
|
||||
maturityShift = 0,
|
||||
ParamOverride = n =>
|
||||
{
|
||||
n.notional = 1;
|
||||
n.riskFreeRate = riskFreeRate;
|
||||
n.buysell = "买入"; //默认买入,如果填卖出会报错
|
||||
},
|
||||
preciseTimeMode = !isEod,
|
||||
timeToMaturityDays = ttmdays.Value,
|
||||
tradeId = null,
|
||||
volSurfaceNames = new[] { volSurfaceName }
|
||||
};
|
||||
|
||||
var tp = QdpTradeBuilder.GetVanillaOptionTradeParam(trade, paramReq, false);
|
||||
|
||||
if (trade.IsUsePremiumRate == true && trade.PremiumRate.HasValue && trade.SpotPrice.HasValue)
|
||||
{
|
||||
trade.TradeSinglePrice = trade.PremiumRate * trade.SpotPrice;
|
||||
}
|
||||
return ImpliedVolCalcService.ImpliedVolFromPremium(trade.TradeSinglePrice ?? 0, valueDate, tp, underlyingPrice);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取交易波动率
|
||||
/// </summary>
|
||||
public static double GetTradeVol(OtcTradeBase trade, DateTime valueDate, bool isEodSettle = false)
|
||||
{
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
if (isEodSettle && trade.id > 0)
|
||||
{
|
||||
var overrideVol = db.eod_trade_vol_override
|
||||
.Where(x => x.valuedate == valueDate && x.tradeid == trade.id)
|
||||
.Select(n => (double?)n.vol).FirstOrDefault();
|
||||
|
||||
if (overrideVol.HasValue)
|
||||
{
|
||||
return overrideVol.Value;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
TradeVolatility tradeVol = null;
|
||||
|
||||
if (trade.id > 0)
|
||||
{
|
||||
tradeVol = db.TradeVolatility.AsNoTracking()
|
||||
.Where(x => x.TradeId == trade.id && x.ValueDate <= valueDate)
|
||||
.OrderByDescending(x => x.ValueDate).FirstOrDefault();
|
||||
}
|
||||
|
||||
if (tradeVol == null || tradeVol.TradePositionVolatility == null || tradeVol.TradeCloseVolatility == null || tradeVol.NumOfSmoothingDays == null)
|
||||
{
|
||||
var openVol = trade.TradeOpenVolatility ?? 0;
|
||||
var closeVol = trade.TradeCloseVolatility ?? openVol;
|
||||
return GetTradeVol(valueDate, trade.StartDate.Value, trade.ExerciseDate.Value, openVol, closeVol, trade.NumOfSmoothingDays ?? 0);
|
||||
}
|
||||
|
||||
//新增交易当天的持仓波动率需要划掉一天,修改后的持仓波动率不需要再划一天
|
||||
|
||||
return GetTradeVol(valueDate: valueDate,
|
||||
valueStartDate: tradeVol.ValueDate,
|
||||
exerciseDate: trade.ExerciseDate.Value,
|
||||
openVol: tradeVol.TradePositionVolatility ?? 0,
|
||||
closeVol: tradeVol.TradeCloseVolatility ?? 0,
|
||||
mumOfSmoothingDays: tradeVol.NumOfSmoothingDays ?? 0,
|
||||
includeStartDate: tradeVol.IsFromTradeAdd);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"交易'{trade.TradeNumber}'获取TradeVol出错:{ex.Messages()}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static double GetTradeVol(DateTime valueDate, DateTime valueStartDate, DateTime exerciseDate
|
||||
, double openVol, double closeVol, int mumOfSmoothingDays, bool includeStartDate = true)
|
||||
{
|
||||
if (valueDate < valueStartDate)
|
||||
{
|
||||
return openVol;
|
||||
}
|
||||
|
||||
if (valueDate > exerciseDate)
|
||||
{
|
||||
return closeVol;
|
||||
}
|
||||
|
||||
var daycountMode = PS.Config.ErpElement.SmoothingDaycountMode == Configuration.Enums.SmoothingDaycountMode.CalendarDay
|
||||
? Qdp.Pricing.Base.Enums.DayCountMode.CalendarDay
|
||||
: Qdp.Pricing.Base.Enums.DayCountMode.TradingDay;
|
||||
|
||||
return AnalyticalOptionTradeVolInterp.tradeVolLinearInterp(
|
||||
new Qdp.Foundation.Implementations.Date(valueDate),
|
||||
openVol, closeVol,
|
||||
new Qdp.Foundation.Implementations.Date(valueStartDate),
|
||||
new Qdp.Foundation.Implementations.Date(exerciseDate),
|
||||
mumOfSmoothingDays,
|
||||
daycountMode,
|
||||
CalendarImpl.Get("chn"),
|
||||
includeStartDate);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取交易波动率曲面
|
||||
/// </summary>
|
||||
public static volatility GetSurfaceFromTradeVol(OtcTradeBase trade, DateTime valueDate, bool isEodSettle = false)
|
||||
{
|
||||
var constVol = GetTradeVol(trade, valueDate, isEodSettle);
|
||||
var userGroup = UserBLL.GetUserGroup(trade.TraderId);
|
||||
|
||||
//生成3*3水平的波动率曲面
|
||||
return GetDefaultVol(new SingleVolatilityRequest
|
||||
{
|
||||
UserGroup = userGroup,
|
||||
QuotationDate = valueDate,
|
||||
VolType = ConsVolInfos.defVolType,
|
||||
UnderlyingCode = trade.UnderlyingCode,
|
||||
UnderlyingId = trade.UnderlyingId,
|
||||
TradeVolWithBidAsk = false
|
||||
}, constVol);
|
||||
}
|
||||
|
||||
#region----获取默认波动率----
|
||||
|
||||
/// <summary>
|
||||
/// 获取默认波动率(不考虑标的是否过期)
|
||||
/// </summary>
|
||||
public static IEnumerable<VolatilityDefault> GetDefaultVols(SingleVolatilityRequest request, double volValue = ConsVolInfos.defVol)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
if (PS.Config.Is光大光子 || !ConsVolInfos.TradeVolTypes.Contains(request.VolType))
|
||||
{
|
||||
return Enumerable.Empty<VolatilityDefault>();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(request.UnderlyingCode))
|
||||
{
|
||||
if (!request.UnderlyingId.HasValue || request.UnderlyingId.Value < 1)
|
||||
{
|
||||
request.UnderlyingId = DataCacheProvider.GetUnderlyingDataSource().GetData(request.UnderlyingCode)?.id;
|
||||
}
|
||||
}
|
||||
else if (request.UnderlyingId > 0)
|
||||
{
|
||||
request.UnderlyingCode = DataCacheProvider.GetUnderlyingDataSource().GetData(request.UnderlyingId.Value)?.UnderlyingCode;
|
||||
}
|
||||
|
||||
var volaty = new VolatilityDefault
|
||||
{
|
||||
VolType = request.VolType,
|
||||
QuotationDate = request.QuotationDate,
|
||||
UserGroup = request.UserGroup,
|
||||
|
||||
UnderlyingId = request.UnderlyingId,
|
||||
ContractCode = request.UnderlyingCode,
|
||||
|
||||
VolSurfaceMode = ConsVolInfos.defVolMode,
|
||||
InterpolationMethod = ConsVolMethod.Default,
|
||||
ReviewDownLimit = ConsVolInfos.defReviewDownLimit,
|
||||
ReviewUpLimit = ConsVolInfos.defReviewUpLimit,
|
||||
|
||||
OptId = 0,
|
||||
OptName = "系统",
|
||||
OptDate = DateTime.Now
|
||||
};
|
||||
|
||||
volaty.Data = QdpVolHelper.GenerateFlatSingleVols(volValue).ToJson();
|
||||
|
||||
return request.GetVolTypes().Select(n => (VolatilityDefault)volaty.Clone(n)).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取默认波动率
|
||||
/// </summary>
|
||||
public static VolatilityDefault GetDefaultVol(SingleVolatilityRequest request, double volValue = ConsVolInfos.defVol)
|
||||
{
|
||||
var vols = GetDefaultVols(request, volValue);
|
||||
|
||||
if (vols != null & vols.Any())
|
||||
{
|
||||
return vols.First();
|
||||
}
|
||||
|
||||
//一定要返回值否则某些计算会报错
|
||||
return new VolatilityDefault
|
||||
{
|
||||
VolSurfaceMode = ConsVolInfos.defVolMode,
|
||||
InterpolationMethod = ConsVolInfos.defInterpolationMethod,
|
||||
|
||||
VolType = request.VolType,
|
||||
UserGroup = request.UserGroup,
|
||||
QuotationDate = request.QuotationDate,
|
||||
UnderlyingId = request.UnderlyingId,
|
||||
ContractCode = request.UnderlyingCode,
|
||||
Data = QdpVolHelper.GenerateFlatSingleVols(volValue).ToJson(),
|
||||
|
||||
OptId = 0,
|
||||
OptName = "系统",
|
||||
OptDate = DateTime.Now
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取默认波动率
|
||||
/// </summary>
|
||||
public static VolatilityDefault GetDefaultVol(string userGroup, DateTime quotationDate, string volType, string underlyingCode, int? underlyingId = null)
|
||||
{
|
||||
return GetDefaultVol(new SingleVolatilityRequest
|
||||
{
|
||||
QuotationDate = quotationDate,
|
||||
TradeVolWithBidAsk = false,
|
||||
UnderlyingCode = underlyingCode,
|
||||
UnderlyingId = underlyingId,
|
||||
UserGroup = userGroup,
|
||||
VolType = volType
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region----从波动率曲面取一个点----
|
||||
|
||||
public static double GetInterpolatedVol(
|
||||
VolConstructionType volConstructionType,
|
||||
IVolatility volSurface,
|
||||
DateTime valueDate,
|
||||
string underlyingCode,
|
||||
DateTime exerciseDate,
|
||||
double strike,
|
||||
bool isBuy,
|
||||
bool isCall,
|
||||
double spotPrice,
|
||||
bool isMoneynessOption = true,
|
||||
double timeToMaturityDays = double.NaN,
|
||||
int? skewMapVolVar = null,
|
||||
bool isEodCalc = false)
|
||||
{
|
||||
if (volConstructionType == VolConstructionType.SkewMap)
|
||||
{
|
||||
return SkewMapVolHelper.GetInterpolatedVol(
|
||||
volSurface: volSurface,
|
||||
valueDate: valueDate,
|
||||
underlyingCode: underlyingCode,
|
||||
exerciseDate: exerciseDate,
|
||||
strikePrice: strike,
|
||||
isBuy: isBuy,
|
||||
isCall: isCall,
|
||||
spotPrice: spotPrice,
|
||||
timeToMaturityDays: timeToMaturityDays,
|
||||
skewMapVolVar: skewMapVolVar);
|
||||
}
|
||||
else
|
||||
{
|
||||
return QdpVolHelper.GetInterpolatedVolFromNormalSurface(
|
||||
volSurface.VolTable,
|
||||
new InterpolatedVolReq()
|
||||
{
|
||||
valueDate = valueDate,
|
||||
exerciseDate = exerciseDate,
|
||||
strike = strike,
|
||||
isMoneynessOption = isMoneynessOption,
|
||||
isEodCalc = isEodCalc,
|
||||
volSurfaceType = volSurface.VolSurfaceMode,
|
||||
spot = spotPrice
|
||||
},
|
||||
volSurface.InterpolationMethod);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 导出用的矩阵
|
||||
/// </summary>
|
||||
public static DataTable GetMatrix(IEnumerable<volatility> vols)
|
||||
{
|
||||
var dataTable = new DataTable();
|
||||
|
||||
var dataColumns = new DataColumn[10];
|
||||
for (var i = 0; i < dataColumns.Length; i++)
|
||||
{
|
||||
dataColumns[i] = new DataColumn();
|
||||
}
|
||||
dataTable.Columns.AddRange(dataColumns);
|
||||
|
||||
var strikeSet = new HashSet<double>(10);
|
||||
|
||||
var volFormat = PS.Config.ErpElement.VolMoreAccurate ? "0.0###%" : "0.0%";
|
||||
try
|
||||
{
|
||||
foreach (var vol in vols)
|
||||
{
|
||||
strikeSet.Clear();
|
||||
|
||||
//转换并按到期分组
|
||||
var lookup = vol.VolTable.Select(n =>
|
||||
{
|
||||
strikeSet.Add(n.Strike);
|
||||
return new { n.Expire, Strike = n.Strike.OtcFormatUmPrice(true), Vol = n.Vol.ToString(volFormat) };
|
||||
}).OrderBy(n => new Term(n.Expire)).ToLookup(n => n.Expire);
|
||||
|
||||
//写入标的和执行价
|
||||
var strikes = strikeSet.OrderBy(n => n).Select(n => n.OtcFormatUmPrice(true)).ToArray();
|
||||
var values = strikes.Prepend(vol.ContractCode).ToArray();
|
||||
while (values.Length > dataTable.Columns.Count)
|
||||
{
|
||||
dataTable.Columns.Add();
|
||||
}
|
||||
dataTable.Rows.Add(values);
|
||||
|
||||
//写入到期和波动率
|
||||
foreach (var g in lookup)
|
||||
{
|
||||
var garr = g.ToArray();
|
||||
var query = from s in strikes
|
||||
join item in garr on s equals item.Strike into items
|
||||
from item in items
|
||||
select item?.Vol;
|
||||
values = query.ToArray().Prepend(g.Key).ToArray();
|
||||
dataTable.Rows.Add(values);
|
||||
}
|
||||
|
||||
dataTable.Rows.Add();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ex);
|
||||
}
|
||||
|
||||
return dataTable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取曲面波动率类型
|
||||
/// </summary>
|
||||
/// <param name="calcVolType">用于计算的波动率类型</param>
|
||||
/// <returns></returns>
|
||||
public static string GetUnderlyingVolType(string calcVolType)
|
||||
{
|
||||
calcVolType = calcVolType.TrimToNull();
|
||||
|
||||
if (PS.Config.IsTradeVol)
|
||||
{
|
||||
return calcVolType switch
|
||||
{
|
||||
null or "持仓" or "对冲" or "交易曲面" => "交易",
|
||||
_ => calcVolType
|
||||
};
|
||||
}
|
||||
|
||||
return calcVolType switch
|
||||
{
|
||||
null or "持仓" => valuedateBLL.SystemDate.EodSettleVolMode.TrimToNull() ?? "财务",
|
||||
"对冲" or "交易曲面" => "交易",
|
||||
_ => calcVolType,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,699 @@
|
||||
using YLErp.Commons;
|
||||
using YLErp.Models;
|
||||
using YLErp.Modules.DataCacheModule;
|
||||
using YLErp.Office.ExcelModule;
|
||||
using YLErp.QdpModule.Constants;
|
||||
|
||||
namespace YLErp.Modules.VolatilityModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 波动率导入读取服务
|
||||
/// </summary>
|
||||
public class VolatilityImportReadService : YLBaseService
|
||||
{
|
||||
VolatilityImportReadModel _model;
|
||||
|
||||
//三种空格\u0020:32--半角空格(英文符号),\u3000:全角空格(中文符号),\u00a0:160--不间断空格(office常用)
|
||||
readonly static char[] TrimChars;
|
||||
readonly static char[] TrimStrikeChars;
|
||||
|
||||
static VolatilityImportReadService()
|
||||
{
|
||||
TrimChars = new[] { '"', '\t', ' ', '\u00a0', '\u3000' };
|
||||
TrimStrikeChars = new[] { '"', '\t', ' ', '\u00a0', '\u3000', '%' };
|
||||
}
|
||||
|
||||
public VolatilityImportReadService(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
class SheetHandleModel
|
||||
{
|
||||
public string sheetName;
|
||||
public string volType;
|
||||
public string volSurfaceMode;
|
||||
public double reviewDownLimit;
|
||||
public double reviewUpLimit;
|
||||
public string userGroup;
|
||||
|
||||
public SheetHandleModel(string sheetName, string volType, VolatilityImportReadModel baseModel)
|
||||
{
|
||||
this.sheetName = sheetName;
|
||||
this.volType = volType;
|
||||
volSurfaceMode = baseModel.volSurfaceMode;
|
||||
reviewDownLimit = baseModel.ReviewDownLimit;
|
||||
reviewUpLimit = baseModel.ReviewUpLimit;
|
||||
userGroup = baseModel.UserGroup;
|
||||
}
|
||||
}
|
||||
|
||||
public List<volatility> ImportFile(Stream file, VolatilityImportReadModel model)
|
||||
{
|
||||
_model = model ?? new VolatilityImportReadModel();
|
||||
|
||||
var reader = new NpoiExcelReader(file);
|
||||
|
||||
if (model.volType == "BidAsk")
|
||||
{
|
||||
return ImportBidAskVols(reader, model);
|
||||
}
|
||||
|
||||
if (model.volType == "All")
|
||||
{
|
||||
return ImportAllVols(model, reader);
|
||||
}
|
||||
|
||||
if (model.volType != "BidAsk" && reader.NumberOfSheets() == 2)
|
||||
{
|
||||
throw new ServiceException($"文件选择有误!当前文件仅支持BidAsk波动率类型导入,请重新选择!");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!reader.TrySetSheet(0, out var sheet) || sheet.LastRowNum < 0)
|
||||
{
|
||||
throw new ServiceException("导入数据空,请重新导入!");
|
||||
}
|
||||
var result = SheetHandle(reader, new SheetHandleModel(sheet.SheetName, model.volType, model), false);
|
||||
|
||||
if (!result.Any())
|
||||
{
|
||||
throw new ServiceException($"导入名为{sheet.SheetName}的Sheet中数据为空,请重新导入!");
|
||||
}
|
||||
var codes = result.GroupBy(x => $"[{x.VolType}]{x.ContractCode}").Select(g => (new { name = g.Key, count = g.Count() }))
|
||||
.Where(x => x.count > 1).Select(O => O.name);
|
||||
if (codes.Any())
|
||||
{
|
||||
throw new ServiceException($"导入名为{sheet.SheetName}的Sheet中数据有重复合约,请重新导入!\r\n重复合约:\r\n" + string.Join("\r\n", codes));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
private List<volatility> SheetHandle(NpoiExcelReader reader, SheetHandleModel model, bool needSetSheet = true)
|
||||
{
|
||||
if (model is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(model));
|
||||
}
|
||||
|
||||
ReadProcessBase proc = null;
|
||||
|
||||
if (needSetSheet && (!reader.TrySetSheet(model.sheetName, out var lastRowNum) || lastRowNum < 0))
|
||||
{
|
||||
throw new ServiceException($"【Sheet: {model.sheetName} 】导入数据空,请重新导入!");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
for (var i = 0; i <= 100000; i++)
|
||||
{
|
||||
if (reader.ReadRow(out var rowNum, out var lineValues))
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
var isMultiMode = false;
|
||||
//0voltype,1ms,2expire
|
||||
var volinfo = lineValues[0].Split('_').ToList();
|
||||
if (volinfo.Count > 1)
|
||||
{
|
||||
isMultiMode = volinfo[1].IndexOf("Moneyness") >= 0 || volinfo[1].IndexOf("Strike") >= 0;
|
||||
}
|
||||
if (isMultiMode && volinfo.Count < 3)
|
||||
{
|
||||
throw new ServiceException("请输入格式如:bid_Moneyless_1M");
|
||||
}
|
||||
//判断第一行第一列为MoneynessVol/StrikeVol_期限 时为申万导入相对行权价简易模式
|
||||
proc = isMultiMode
|
||||
? (ReadProcessBase)new ShenWanProcess(this, model.reviewDownLimit, model.reviewUpLimit, model.userGroup)
|
||||
: new StandardProcess(this, model.volType, model.volSurfaceMode, model.reviewDownLimit, model.reviewUpLimit, model.userGroup);
|
||||
}
|
||||
|
||||
proc.ProcessLine(lineValues, rowNum + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception($"【Sheet:{model.sheetName}】导入出错。{e.Message}");
|
||||
}
|
||||
|
||||
proc.ProcessEnd();
|
||||
|
||||
return proc.GetResults();
|
||||
}
|
||||
|
||||
//导入bidask类型的波动率
|
||||
private List<volatility> ImportBidAskVols(NpoiExcelReader reader, VolatilityImportReadModel model)
|
||||
{
|
||||
if (!reader.TryGetSheet("BID", out var sheet) || sheet.LastRowNum < 0)
|
||||
{
|
||||
throw new ServiceException("导入名为BID的Sheet中数据为空,请重新导入!");
|
||||
}
|
||||
if (!reader.TryGetSheet("ASK", out sheet) || sheet.LastRowNum < 0)
|
||||
{
|
||||
throw new ServiceException("导入名为ASK的Sheet中数据为空,请重新导入!");
|
||||
}
|
||||
var bidList = SheetHandle(reader, new SheetHandleModel("BID", "报价Bid", model));
|
||||
if (bidList.Any())
|
||||
{
|
||||
var codes = bidList.GroupBy(x => x.ContractCode).Select(g => (new { name = g.Key, count = g.Count() })).Where(x => x.count > 1).Select(O => O.name);
|
||||
if (codes.Any())
|
||||
{
|
||||
throw new ServiceException("导入名为BID的Sheet中数据有重复合约,请重新导入!\r\n重复合约:\r\n" + string.Join("\r\n", codes));
|
||||
}
|
||||
var askList = SheetHandle(reader, new SheetHandleModel("ASK", "报价Ask", model));
|
||||
if (askList.Any())
|
||||
{
|
||||
codes = askList.GroupBy(x => x.ContractCode).Select(g => (new { name = g.Key, count = g.Count() })).Where(x => x.count > 1).Select(O => O.name);
|
||||
if (codes.Any())
|
||||
{
|
||||
throw new ServiceException("导入名为ASK的Sheet中数据有重复合约,请重新导入!\r\n重复合约:\r\n" + string.Join("\r\n", codes));
|
||||
}
|
||||
|
||||
#region 检查数据匹配并顺便计算mid
|
||||
var midList = new List<volatility>();
|
||||
foreach (var itema in askList)
|
||||
{
|
||||
try
|
||||
{
|
||||
var itemb = bidList.Single(x => x.ContractCode == itema.ContractCode);
|
||||
if (itema.VolTable.Count != itemb.VolTable.Count)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
var midVolTable = new List<SingleVol>();
|
||||
foreach (var avol in itema.VolTable)
|
||||
{
|
||||
var bvol = itemb.VolTable.Single(x => x.Expire == avol.Expire && x.Strike == avol.Strike);
|
||||
// mid曲面的 strike 和 期限一致, 波动率值为 (bid+ask)/2.
|
||||
midVolTable.Add(new SingleVol() { Expire = avol.Expire, Strike = avol.Strike, Vol = (avol.Vol + bvol.Vol) / 2 });
|
||||
}
|
||||
|
||||
var mid = ConstructVolSurfaceWithDeviationCheck(itema.UnderlyingId ?? 0, itema.ContractCode
|
||||
, itema.QuotationDate, "", itema.VolSurfaceMode, midVolTable);
|
||||
mid.VolType = "交易";//计算Deviation?
|
||||
mid.ReviewDownLimit = _model.ReviewDownLimit;
|
||||
mid.ReviewUpLimit = _model.ReviewUpLimit;
|
||||
mid.UserGroup = _model.UserGroup;
|
||||
midList.Add(mid);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new ServiceException($"匹配合约{itema.ContractCode}出错,请检查Bid/Ask合约-期限-执行价是否匹配!");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
bidList.AddRange(askList);
|
||||
bidList.AddRange(midList);
|
||||
return bidList;
|
||||
}
|
||||
|
||||
throw new ServiceException("导入ASK的Sheet数据空,请重新导入!");
|
||||
}
|
||||
|
||||
throw new ServiceException("导入BID的Sheet数据空,请重新导入!");
|
||||
}
|
||||
|
||||
//全量导入
|
||||
private List<volatility> ImportAllVols(VolatilityImportReadModel model, NpoiExcelReader reader)
|
||||
{
|
||||
var volSurfaceMode = model.volSurfaceMode;
|
||||
if (!reader.TryGetSheet("BID", out var sheetBid) && sheetBid.LastRowNum < 0)
|
||||
{
|
||||
throw new ServiceException("导入名为BID的Sheet中数据为空,请重新导入!");
|
||||
}
|
||||
|
||||
if (!reader.TryGetSheet("ASK", out var sheetAsk) && sheetAsk.LastRowNum < 0)
|
||||
{
|
||||
throw new ServiceException("导入名为ASK的Sheet中数据为空,请重新导入!");
|
||||
}
|
||||
|
||||
if (!reader.TryGetSheet("MID", out var sheetMid) && sheetAsk.LastRowNum < 0)
|
||||
{
|
||||
throw new ServiceException("导入名为MID的Sheet中数据为空,请重新导入!");
|
||||
}
|
||||
|
||||
var bidList = SheetHandle(reader, new SheetHandleModel("BID", "报价Bid", model));
|
||||
if (!bidList.Any())
|
||||
{
|
||||
throw new ServiceException("导入名为BID的Sheet中数据为空,请重新导入!");
|
||||
}
|
||||
var codes = bidList.GroupBy(x => x.ContractCode).Select(g => (new { name = g.Key, count = g.Count() })).Where(x => x.count > 1).Select(O => O.name);
|
||||
if (codes.Any())
|
||||
{
|
||||
throw new ServiceException("导入名为BID的Sheet中数据有重复合约,请重新导入!\r\n重复合约:\r\n" + string.Join("\r\n", codes));
|
||||
}
|
||||
|
||||
var askList = SheetHandle(reader, new SheetHandleModel("ASK", "报价Ask", model));
|
||||
if (!askList.Any())
|
||||
{
|
||||
throw new ServiceException("导入名为ASK的Sheet中数据为空,请重新导入!");
|
||||
}
|
||||
codes = askList.GroupBy(x => x.ContractCode).Select(g => (new { name = g.Key, count = g.Count() })).Where(x => x.count > 1).Select(O => O.name);
|
||||
if (codes.Any())
|
||||
{
|
||||
throw new ServiceException("导入名为ASK的Sheet中数据有重复合约,请重新导入!\r\n重复合约:\r\n" + string.Join("\r\n", codes));
|
||||
}
|
||||
|
||||
var midList = SheetHandle(reader, new SheetHandleModel("MID", "交易", model));
|
||||
if (!midList.Any())
|
||||
{
|
||||
throw new ServiceException("导入名为MID的Sheet中数据为空,请重新导入!");
|
||||
}
|
||||
codes = midList.GroupBy(x => x.ContractCode).Select(g => (new { name = g.Key, count = g.Count() })).Where(x => x.count > 1).Select(O => O.name);
|
||||
if (codes.Any())
|
||||
{
|
||||
throw new ServiceException("导入名为MID的Sheet中数据有重复合约,请重新导入!\r\n重复合约:\r\n" + string.Join("\r\n", codes));
|
||||
}
|
||||
|
||||
#region 检查数据匹配并顺便计算mid
|
||||
foreach (var itemm in midList)
|
||||
{
|
||||
try
|
||||
{
|
||||
var itemb = bidList.Single(x => x.ContractCode == itemm.ContractCode);
|
||||
if (itemm.VolTable.Count != itemb.VolTable.Count)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
var itema = askList.Single(x => x.ContractCode == itemm.ContractCode);
|
||||
if (itemm.VolTable.Count != itema.VolTable.Count)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new ServiceException($"匹配合约{itemm.ContractCode}出错,请检查Bid/Ask/Mid合约-期限-执行价是否匹配!");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
bidList.AddRange(askList);
|
||||
bidList.AddRange(midList);
|
||||
return bidList;
|
||||
}
|
||||
|
||||
public DateTime quotationDate
|
||||
{
|
||||
get { return _model.quotationDate; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造波动率曲面,并检查是否需要从交易波动率调整出Bid和Ask
|
||||
/// </summary>
|
||||
private volatility ConstructVolSurfaceWithDeviationCheck(int underlyingId, string contractCode,
|
||||
DateTime quotationDate, string volType, string volSurfaceMode, List<SingleVol> singleVolList)
|
||||
{
|
||||
var newVol = new volatility
|
||||
{
|
||||
UnderlyingId = underlyingId,
|
||||
ContractCode = contractCode,
|
||||
OptId = UserId,
|
||||
OptName = UserName,
|
||||
QuotationDate = quotationDate,
|
||||
VolType = volType,
|
||||
VolSurfaceMode = volSurfaceMode
|
||||
};
|
||||
|
||||
newVol.Data = singleVolList.ToJson();
|
||||
newVol.InterpolationMethod = ConsVolMethod.Default;
|
||||
|
||||
if (newVol.VolType == "交易")
|
||||
{
|
||||
newVol.Ask_Deviation = _model.Ask_Deviation;
|
||||
newVol.Bid_Deviation = _model.Bid_Deviation;
|
||||
}
|
||||
|
||||
return newVol;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 申万excel波动率类型匹配,目前只有ask,bid和交易
|
||||
/// </summary>
|
||||
public static string GetVolTypeViaPre(string pre)
|
||||
{
|
||||
switch (pre)
|
||||
{
|
||||
case "ask":
|
||||
return ConsVolInfos.报价Ask;
|
||||
case "bid":
|
||||
return ConsVolInfos.报价Bid;
|
||||
case "trade":
|
||||
return "交易";
|
||||
}
|
||||
return "交易";
|
||||
}
|
||||
|
||||
public static string GetVolModeViaPre(string mode)
|
||||
{
|
||||
if (mode.IndexOf("Moneyness") >= 0)
|
||||
{
|
||||
return "MoneynessVol";
|
||||
}
|
||||
|
||||
if (mode.IndexOf("Strike") >= 0)
|
||||
{
|
||||
return "StrikeVol";
|
||||
}
|
||||
|
||||
return "MoneynessVol";
|
||||
}
|
||||
|
||||
|
||||
abstract class ReadProcessBase
|
||||
{
|
||||
protected readonly List<volatility> _volList = new List<volatility>();
|
||||
|
||||
public abstract void ProcessLine(string[] lineValues, int lineNumber);
|
||||
|
||||
public abstract void ProcessEnd();
|
||||
|
||||
public List<volatility> GetResults()
|
||||
{
|
||||
return _volList;
|
||||
}
|
||||
}
|
||||
|
||||
class StandardProcess : ReadProcessBase
|
||||
{
|
||||
string contractCode = null;
|
||||
underlying_manager _underlying;
|
||||
List<double> strikeList = null;
|
||||
readonly List<SingleVol> singleVolList = new List<SingleVol>();
|
||||
bool nextFlag = true;
|
||||
|
||||
readonly string _volType, _volSurfaceMode, _userGroup;
|
||||
readonly double _reviewDownLimit, _reviewUpLimit;
|
||||
readonly VolatilityImportReadService _service;
|
||||
|
||||
public StandardProcess(VolatilityImportReadService service, string volType, string volSurfaceMode,
|
||||
double reviewDownLimit, double reviewUpLimit, string userGroup)
|
||||
{
|
||||
_service = service;
|
||||
_volType = volType;
|
||||
_volSurfaceMode = volSurfaceMode;
|
||||
_reviewDownLimit = reviewDownLimit;
|
||||
_reviewUpLimit = reviewUpLimit;
|
||||
_userGroup = userGroup;
|
||||
}
|
||||
|
||||
public override void ProcessLine(string[] lineValues, int lineNumber)
|
||||
{
|
||||
if (!lineValues.Any(O => !string.IsNullOrWhiteSpace(O)))
|
||||
{
|
||||
nextFlag = true;
|
||||
|
||||
if (_underlying == null)
|
||||
{
|
||||
throw new ServiceException($"第{lineNumber}行处理失败:不能确认标的");
|
||||
}
|
||||
|
||||
if (singleVolList.Count > 0)
|
||||
{
|
||||
var newVol = _service.ConstructVolSurfaceWithDeviationCheck(
|
||||
_underlying.id, contractCode,
|
||||
_service.quotationDate, _volType, _volSurfaceMode, singleVolList);
|
||||
newVol.ReviewDownLimit = _reviewDownLimit;
|
||||
newVol.ReviewUpLimit = _reviewUpLimit;
|
||||
newVol.UserGroup = _userGroup;
|
||||
_volList.Add(newVol);
|
||||
singleVolList.Clear();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (nextFlag)
|
||||
{
|
||||
contractCode = lineValues[0].Trim(TrimChars);
|
||||
_underlying = DataCacheManager.GetUnderlyingDataSource().GetData(contractCode);
|
||||
if (_underlying == null)
|
||||
{
|
||||
throw new ServiceException($"第{lineNumber}行{contractCode}不存在\r\n");
|
||||
}
|
||||
|
||||
nextFlag = false;
|
||||
strikeList = new List<double>();
|
||||
foreach (var x in lineValues.Skip(1))
|
||||
{
|
||||
if (x.Contains("%"))
|
||||
{
|
||||
var value = double.Parse(x.Trim(TrimStrikeChars));
|
||||
if (value <= 0)
|
||||
{
|
||||
throw new ServiceException($"导入文件中存在非正数行权价! 请检查第{lineNumber}行");
|
||||
}
|
||||
if (strikeList.Any(s => s == value / 100))
|
||||
{
|
||||
throw new ServiceException("行权价重复:" + x);
|
||||
}
|
||||
strikeList.Add(value / 100);
|
||||
}
|
||||
else
|
||||
{
|
||||
strikeList.Add(double.Parse(x.Trim(TrimChars)));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var expire = lineValues[0].Trim(TrimChars).ToUpper();
|
||||
//检查导入文件中到期日格式是否正确
|
||||
if (!new System.Text.RegularExpressions.Regex(@"^\d+(W|Y|M|D)$").IsMatch(expire))
|
||||
{
|
||||
throw new ServiceException($"导入文件中到期日格式错误! 请检查第{lineNumber}行");
|
||||
}
|
||||
if (lineValues.Length < (strikeList.Count + 1))
|
||||
{
|
||||
throw new ServiceException($"请检查导入文件内容,内容有误! 请检查第{lineNumber}行");
|
||||
}
|
||||
double vol = 0;
|
||||
var oneRowVol = new List<SingleVol>();
|
||||
for (var x = 1; x < lineValues.Length; x++)
|
||||
{
|
||||
if (double.TryParse(lineValues[x].Trim(TrimChars), out var value))
|
||||
{
|
||||
if (PS.Config.ErpElement.VolMoreAccurate)
|
||||
{
|
||||
vol = OtcFormatHelper.FormatValue(value, 6);
|
||||
}
|
||||
else
|
||||
{
|
||||
vol = OtcFormatHelper.FormatValue(value, 4);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ServiceException($"非法的波动率数值! 请检查第{lineNumber}行");
|
||||
}
|
||||
var singleVol = new SingleVol { Strike = strikeList[x - 1], Expire = expire, Vol = vol };
|
||||
oneRowVol.Add(singleVol);
|
||||
}
|
||||
if (singleVolList.Any(s => s.Expire == expire))
|
||||
{
|
||||
throw new ServiceException($"重复的到期日{expire}");
|
||||
}
|
||||
singleVolList.AddRange(oneRowVol);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ProcessEnd()
|
||||
{
|
||||
//当文件最后面有空行时,文件中的最后一个波动率曲面已经在上面的循环中保存了
|
||||
//当文件最后面没有空行时,需要在这里保存最后一个波动率曲面
|
||||
if (singleVolList.Count > 0)
|
||||
{
|
||||
if (_underlying == null)
|
||||
{
|
||||
throw new ServiceException($"尾行处理失败:不能确认标的");
|
||||
}
|
||||
var newVol = _service.ConstructVolSurfaceWithDeviationCheck(
|
||||
_underlying.id, contractCode, _service.quotationDate, _volType, _volSurfaceMode, singleVolList);
|
||||
newVol.ReviewDownLimit = _reviewDownLimit;
|
||||
newVol.ReviewUpLimit = _reviewUpLimit;
|
||||
newVol.UserGroup = _userGroup;
|
||||
_volList.Add(newVol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ShenWanProcess : ReadProcessBase
|
||||
{
|
||||
readonly VolatilityImportReadService _service;
|
||||
readonly List<double> _strikes;
|
||||
|
||||
private string _simpleExpire;
|
||||
private string _volType;
|
||||
private string _volSurfaceMode;
|
||||
private readonly string _userGroup;
|
||||
readonly double _reviewDownLimit, _reviewUpLimit;
|
||||
|
||||
public ShenWanProcess(VolatilityImportReadService service, double reviewDownLimit, double reviewUpLimit, string userGroup)
|
||||
{
|
||||
_service = service;
|
||||
_reviewDownLimit = reviewDownLimit;
|
||||
_reviewUpLimit = reviewUpLimit;
|
||||
_userGroup = userGroup;
|
||||
_strikes = new List<double>();
|
||||
}
|
||||
|
||||
public override void ProcessLine(string[] lineValues, int lineNumber)
|
||||
{
|
||||
//0voltype,1ms,2expire
|
||||
if (lineValues.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var volheadinfo = lineValues[0].Split('_').ToList();
|
||||
if (volheadinfo.Count == 3)
|
||||
{
|
||||
_strikes.Clear();
|
||||
|
||||
_simpleExpire = volheadinfo[2];
|
||||
_volType = GetVolTypeViaPre(volheadinfo[0]);
|
||||
_volSurfaceMode = GetVolModeViaPre(volheadinfo[1]);
|
||||
|
||||
//当前Strike信息
|
||||
foreach (var s in lineValues.Skip(1).ToList())
|
||||
{
|
||||
var isPercent = s.IndexOf("%") >= 0;
|
||||
var strike = Convert.ToDouble(s.Trim(TrimStrikeChars));
|
||||
if (isPercent)
|
||||
{
|
||||
strike = strike / 100;
|
||||
}
|
||||
|
||||
if (_strikes.Any(sk => sk == strike))
|
||||
{
|
||||
throw new ServiceException($"{strike}行权价重复");
|
||||
}
|
||||
_strikes.Add(strike);
|
||||
}
|
||||
}
|
||||
else if (lineValues.Length > 0 && !string.IsNullOrWhiteSpace(lineValues[0]))
|
||||
{
|
||||
//有数据
|
||||
var voldata = lineValues.Skip(1).ToList();
|
||||
var contractCode = lineValues[0].Split('.')[0];
|
||||
var underlying = DataCacheManager.GetUnderlyingDataSource().GetData(contractCode);
|
||||
if (underlying == null)
|
||||
{
|
||||
throw new ServiceException($"标的代码[{contractCode}]在系统中不存在!");
|
||||
}
|
||||
|
||||
double vol = 0;
|
||||
var singleVolList = new List<SingleVol>();
|
||||
|
||||
for (var i = 0; i < voldata.Count; i++)
|
||||
{
|
||||
if (double.TryParse(voldata[i].Trim(TrimChars), out var value))
|
||||
{
|
||||
if (PS.Config.ErpElement.VolMoreAccurate)
|
||||
{
|
||||
vol = OtcFormatHelper.FormatValue(value, 6);
|
||||
}
|
||||
else
|
||||
{
|
||||
vol = OtcFormatHelper.FormatValue(value, 4);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ServiceException($"非法的波动率数值! 请检查第{lineNumber}行");
|
||||
}
|
||||
var singleVol = new SingleVol { Strike = _strikes[i], Expire = _simpleExpire, Vol = vol };
|
||||
singleVolList.Add(singleVol);
|
||||
}
|
||||
|
||||
var newVol = _service.ConstructVolSurfaceWithDeviationCheck(
|
||||
underlying.id, contractCode, _service.quotationDate, _volType, _volSurfaceMode, singleVolList);
|
||||
newVol.ReviewDownLimit = _reviewDownLimit;
|
||||
newVol.ReviewUpLimit = _reviewUpLimit;
|
||||
newVol.UserGroup = _userGroup;
|
||||
_volList.Add(newVol);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ProcessEnd()
|
||||
{
|
||||
//申万模式特殊处理,交易不要放在第一个,否则导入时会以为是交易波动率,按照之前api规则替换ask,bid
|
||||
var vgroup = _volList.GroupBy(v => v.ContractCode).ToArray();
|
||||
|
||||
//检查 是否存在相同标的,不同波动率模式的数据,存在则不让导入提示错误
|
||||
var invalidGroup = vgroup.FirstOrDefault(n => n.GroupBy(v => v.VolSurfaceMode).Count() > 1);
|
||||
if (invalidGroup != null)
|
||||
{
|
||||
throw new ServiceException($"导入失败,{invalidGroup.Key}有多种模式波动率,请只输入一种!");
|
||||
}
|
||||
|
||||
_volList.Clear();
|
||||
|
||||
foreach (var single in vgroup)
|
||||
{
|
||||
var singlevols = single.ToList();
|
||||
var singlevoltypes = singlevols.GroupBy(s => s.VolType).ToArray();
|
||||
|
||||
foreach (var singlevoltype in singlevoltypes)
|
||||
{
|
||||
var first = singlevoltype.First();
|
||||
var importvol = new volatility
|
||||
{
|
||||
Ask_Deviation = 0,
|
||||
Bid_Deviation = 0,
|
||||
VolSurfaceMode = first.VolSurfaceMode,
|
||||
UnderlyingId = first.UnderlyingId,
|
||||
ContractCode = first.ContractCode,
|
||||
VolType = singlevoltype.Key,
|
||||
QuotationDate = _service.quotationDate,
|
||||
OptDate = DateTime.Now,
|
||||
OptId = _service.UserId,
|
||||
OptName = _service.UserName,
|
||||
ReviewDownLimit = _reviewDownLimit,
|
||||
ReviewUpLimit = _reviewUpLimit,
|
||||
UserGroup = _userGroup
|
||||
};
|
||||
var ssv = new List<SingleVol>();
|
||||
foreach (var sv in singlevoltype)
|
||||
{
|
||||
ssv.AddRange(sv.VolTable);
|
||||
}
|
||||
importvol.Data = ssv.ToJson();
|
||||
_volList.Add(importvol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class VolatilityImportReadModel
|
||||
{
|
||||
public DateTime quotationDate { get; set; }
|
||||
|
||||
public string volType { get; set; }
|
||||
|
||||
public string volSurfaceMode { get; set; }
|
||||
|
||||
public double Bid_Deviation { get; set; }
|
||||
|
||||
public double Ask_Deviation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 审核波动率下限
|
||||
/// </summary>
|
||||
public double ReviewDownLimit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 审核波动率上限
|
||||
/// </summary>
|
||||
public double ReviewUpLimit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户组
|
||||
/// </summary>
|
||||
public string UserGroup { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,725 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
using YLErp.Commons;
|
||||
using YLErp.DBModels.Consts;
|
||||
using YLErp.Models;
|
||||
using YLErp.QdpModule.Constants;
|
||||
|
||||
namespace YLErp.Modules.VolatilityModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 波动率保存服务
|
||||
/// </summary>
|
||||
public class VolatilitySaveService : YLBaseService
|
||||
{
|
||||
public VolatilitySaveService(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public VolatilitySaveService(YLBaseService baseService) : base(baseService)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public List<volatility> SaveVol(volatility vol)
|
||||
{
|
||||
if (vol is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(vol));
|
||||
}
|
||||
|
||||
return SaveVols(new List<volatility> { vol });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public List<volatility> SaveVols(List<volatility> vols, bool overridByMainCode = false)
|
||||
{
|
||||
if (ConsUserGroup.HasGroup)
|
||||
{
|
||||
if (string.IsNullOrEmpty(OptUser.UserGroup))
|
||||
{
|
||||
throw new ServiceException("缺少用户组");
|
||||
}
|
||||
|
||||
if (!ConsUserGroup.GetGroups().Any(n => n.ItemValue == OptUser.UserGroup))
|
||||
{
|
||||
throw new ServiceException("用户组不存在:" + OptUser.UserGroup);
|
||||
}
|
||||
}
|
||||
|
||||
var checkResult = PrepareVols(vols);
|
||||
|
||||
if (!checkResult.IsSuccess)
|
||||
{
|
||||
throw new ServiceException(checkResult.Message);
|
||||
}
|
||||
|
||||
var date = vols[0].QuotationDate;
|
||||
|
||||
if (overridByMainCode)
|
||||
{
|
||||
Dictionary<string, volatility[]> mainDic = null;
|
||||
|
||||
//PrepareVols方法处理,可能会产生多个波动率类型(比如交易会分出来mid,ask,bid)
|
||||
foreach (var vl in vols.GroupBy(n => n.ContractCode.ToLowerInvariant()))
|
||||
{
|
||||
var ContractCode = vl.Key;
|
||||
if (System.Text.RegularExpressions.Regex.IsMatch(ContractCode, "^[a-zA-Z]+00$"))
|
||||
{
|
||||
var un = DataCacheProvider.GetUnderlyingDataSource().GetData(ContractCode);
|
||||
if (un != null && un.IsFutures() && !string.IsNullOrEmpty(un.CommodityCode))
|
||||
{
|
||||
if (mainDic == null)
|
||||
{
|
||||
mainDic = new Dictionary<string, volatility[]>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
mainDic[un.CommodityCode] = vl.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mainDic != null)
|
||||
{
|
||||
var existsCodes = vols.Select(n => n.ContractCode).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var query = from un in DbContext.underlying_manager
|
||||
where un.CommodityCode != null
|
||||
&& (un.MaturityDate == null || un.MaturityDate.Value >= date)
|
||||
&& mainDic.Keys.Contains(un.CommodityCode)
|
||||
&& !existsCodes.Contains(un.UnderlyingCode)
|
||||
select new { un.CommodityCode, un.UnderlyingCode, un.id };
|
||||
|
||||
var overrideUns = query.ToArray();
|
||||
|
||||
var overrideVols = overrideUns.SelectMany(un => mainDic[un.CommodityCode].Select(n =>
|
||||
{
|
||||
var clone = n.Clone();
|
||||
clone.UnderlyingId = un.id;
|
||||
clone.ContractCode = un.UnderlyingCode;
|
||||
return clone;
|
||||
}));
|
||||
|
||||
vols.AddRange(overrideVols);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var v in vols)
|
||||
{
|
||||
v.UserGroup = OptUser.UserGroup;
|
||||
var dbVol = DbContext.volatility.FirstOrDefault(n =>
|
||||
n.ContractCode == v.ContractCode && n.VolType == v.VolType &&
|
||||
n.UserGroup == v.UserGroup && n.QuotationDate == date);
|
||||
if (v.InterpolationMethod == null)
|
||||
{
|
||||
v.InterpolationMethod = ConsVolMethod.Default;
|
||||
}
|
||||
if (dbVol == null)
|
||||
{
|
||||
dbVol = DbContext.volatility.Add(v).Entity;
|
||||
}
|
||||
else
|
||||
{
|
||||
dbVol.Data = v.Data;
|
||||
dbVol.InterpolationMethod = v.InterpolationMethod;
|
||||
}
|
||||
|
||||
dbVol.OptId = UserId;
|
||||
dbVol.OptName = UserName;
|
||||
dbVol.OptDate = OptDate;
|
||||
}
|
||||
|
||||
int changes = DbContext.SaveChanges();
|
||||
|
||||
return vols;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
private HandleResult PrepareVols(List<volatility> vols)
|
||||
{
|
||||
if (vols is null || !vols.Any())
|
||||
{
|
||||
throw new ArgumentException("参数不能为空", nameof(vols));
|
||||
}
|
||||
|
||||
if (vols.Any(n => string.IsNullOrEmpty(n.ContractCode)))
|
||||
{
|
||||
return "标的代码不允许为空!";
|
||||
}
|
||||
|
||||
var firstVol = vols.First();
|
||||
|
||||
if (firstVol.QuotationDate.Year < 2000)
|
||||
{
|
||||
return "报价日期不正确:" + firstVol.QuotationDate.ToString("yyyy-MM-dd");
|
||||
}
|
||||
|
||||
if (vols.Select(O => O.QuotationDate).Distinct().Count() > 1)
|
||||
{
|
||||
return "存在多个报价日期的波动率!";
|
||||
}
|
||||
|
||||
//检查标的信息
|
||||
if (!firstVol.UnderlyingId.HasValue)
|
||||
{
|
||||
var unCodes = vols.Select(n => n.ContractCode).ToHashSet();
|
||||
var unCodeMap = DbContext.underlying_manager.Where(n => unCodes.Contains(n.UnderlyingCode))
|
||||
.ToDictionary(n => n.UnderlyingCode, n => n.id);
|
||||
foreach (var vol in vols)
|
||||
{
|
||||
if (!unCodeMap.TryGetValue(vol.ContractCode, out var unId))
|
||||
{
|
||||
return "标的信息未找到:" + vol.ContractCode;
|
||||
}
|
||||
vol.UnderlyingId = unId;
|
||||
}
|
||||
}
|
||||
|
||||
//检查标的到期日期是否大于等于报价日期
|
||||
var unIds = vols.Select(n => n.UnderlyingId).ToHashSet();
|
||||
var maturedUns = DbContext.underlying_manager.Where(n =>
|
||||
unIds.Contains(n.id) && n.UnderlyingInstrumentType == "CommodityFutures" && n.MaturityDate.Value < firstVol.QuotationDate)
|
||||
.Select(n => new { n.UnderlyingCode, MaturityDate = n.MaturityDate.Value }).ToArray();
|
||||
|
||||
if (maturedUns.Any())
|
||||
{
|
||||
var arr = maturedUns.Select(n => $"{n.UnderlyingCode}(到期日:{n.MaturityDate:yyyyMMdd})").ToArray();
|
||||
return $"标的到期日期需大于等于报价日期({firstVol.QuotationDate:yyyyMMdd}),到期标的:{string.Join(",", arr)}";
|
||||
}
|
||||
|
||||
//检查到期日和行权价是否一致
|
||||
if (vols.Any(O => ConsVolInfos.subTradeVolType.Contains(O.VolType)))
|
||||
{
|
||||
var dict = vols.GroupBy(O => $"{O.ContractCode}").ToDictionary(K => K.Key, V => V.ToList());
|
||||
var volTypeGroup = dict.Select(O => O.Value.Count()).Distinct().ToArray();
|
||||
if (volTypeGroup.Length > 1)
|
||||
{
|
||||
return "列表中存在多个波动率类型!";
|
||||
}
|
||||
if (volTypeGroup[0] == 3)
|
||||
{
|
||||
if (!CheckVolTables(vols, out var errMessage))
|
||||
{
|
||||
return errMessage;
|
||||
}
|
||||
}
|
||||
else if (volTypeGroup[0] == 2)
|
||||
{
|
||||
if (!CheckVolTables(vols, out var errMessage))
|
||||
{
|
||||
return errMessage;
|
||||
}
|
||||
|
||||
foreach (var item in dict)
|
||||
{
|
||||
if (item.Value.Count != 2)
|
||||
{
|
||||
return $"列表中 {item.Key} 波动率有重复!";
|
||||
}
|
||||
|
||||
volatility vol1 = item.Value[0];
|
||||
volatility vol2 = item.Value[1];
|
||||
volatility vol = vol1.Clone();
|
||||
vol.VolType = "交易";
|
||||
var volTable = new List<SingleVol>();
|
||||
foreach (var volItem in vol2.VolTable)
|
||||
{
|
||||
var singleVol1 = vol1.VolTable.Find(O => O.Expire == volItem.Expire && O.Strike == volItem.Strike);
|
||||
volTable.Add(new SingleVol(volItem.Strike, volItem.Expire, ((volItem.Vol + singleVol1.Vol) / 2).FormatValue(2)));
|
||||
}
|
||||
vol.Data = volTable.ToJson();
|
||||
vols.Add(vol);
|
||||
}
|
||||
}
|
||||
else if (volTypeGroup[0] == 1)
|
||||
{
|
||||
var dbVols = new VolatilityQueryService(OptUser).GetVolatilities(new BatchVolatilityRequest
|
||||
{
|
||||
QuotationDate = vols[0].QuotationDate,
|
||||
TradeVolWithBidAsk = false,
|
||||
UnderlyingCodes = dict.Keys,
|
||||
UserGroup = OptUser.UserGroup,
|
||||
VolType = "交易"
|
||||
}, false);
|
||||
foreach (var item in dict)
|
||||
{
|
||||
volatility tempVol = null;
|
||||
if ((tempVol = dbVols.FirstOrDefault(O => O.ContractCode == item.Key)) == null)
|
||||
{
|
||||
return $"系统中不存在 {item.Key} 的波动率!";
|
||||
}
|
||||
if (!CheckVolTables(new List<volatility>() { item.Value[0], tempVol }, out var errMessage))
|
||||
{
|
||||
return errMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (vols.GroupBy(O => O.VolType).Count() > 1)
|
||||
{
|
||||
return "列表中存在多个波动率类型的波动率!";
|
||||
}
|
||||
else if (vols.All(O => O.VolType == "交易") && !PS.Config.Is光大光子)
|
||||
{
|
||||
List<volatility> tempList = new List<volatility>();
|
||||
foreach (var item in vols)
|
||||
{
|
||||
volatility bidVol = item.Clone();
|
||||
bidVol.VolType = "报价Bid";
|
||||
volatility askVol = item.Clone();
|
||||
askVol.VolType = "报价Ask";
|
||||
List<SingleVol> bidVolTable = new List<SingleVol>();
|
||||
List<SingleVol> askVolTable = new List<SingleVol>();
|
||||
foreach (var volItem in item.VolTable)
|
||||
{
|
||||
double bVol = volItem.Vol + (item.Bid_Deviation ?? 0);
|
||||
double aVol = volItem.Vol + (item.Ask_Deviation ?? 0);
|
||||
if (bVol < 0 || aVol < 0)
|
||||
{
|
||||
return "波动率偏离后不应小于0";
|
||||
}
|
||||
bidVolTable.Add(new SingleVol(volItem.Strike, volItem.Expire, bVol));
|
||||
askVolTable.Add(new SingleVol(volItem.Strike, volItem.Expire, aVol));
|
||||
}
|
||||
bidVol.Data = bidVolTable.ToJson();
|
||||
askVol.Data = askVolTable.ToJson();
|
||||
tempList.Add(bidVol);
|
||||
tempList.Add(askVol);
|
||||
}
|
||||
vols.AddRange(tempList);
|
||||
}
|
||||
|
||||
return HandleResult.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查VolTable是否匹配
|
||||
/// </summary>
|
||||
private bool CheckVolTables(IEnumerable<volatility> vols, out string errMessage)
|
||||
{
|
||||
Dictionary<string, List<volatility>> volDict = vols.GroupBy(O => $"{O.ContractCode}{O.UserGroup}{O.QuotationDate}").ToDictionary(K => K.Key, V => V.ToList());
|
||||
|
||||
errMessage = "";
|
||||
foreach (var singleVols in volDict)
|
||||
{
|
||||
if (singleVols.Value.Count() < 2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var first = singleVols.Value.First();
|
||||
|
||||
var set = first.VolTable.Select(n => n.Expire.ToUpper() + "^" + n.Strike.ToString("F6")).ToHashSet();
|
||||
|
||||
foreach (var item in singleVols.Value.Skip(1))
|
||||
{
|
||||
if (first.VolTable.Count != item.VolTable.Count)
|
||||
{
|
||||
errMessage = $"{first.ContractCode} 波动率行列不匹配!";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!item.VolTable.All(n => set.Contains(n.Expire.ToUpper() + "^" + n.Strike.ToString("F6"))))
|
||||
{
|
||||
errMessage = $"{first.ContractCode} 行权价或期限不匹配!";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
using BaseOUDAL;
|
||||
using Qdp.Pricing.Base.Implementations;
|
||||
using Qdp.Pricing.Library.Equity.Engines.Analytical;
|
||||
using YLErp.BLL;
|
||||
using YLErp.Model;
|
||||
using YLErp.QdpModule;
|
||||
|
||||
namespace YLErp.Modules.VolatilityModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 波动率处理服务
|
||||
/// </summary>
|
||||
public class VolatilityService : YLBaseService
|
||||
{
|
||||
public VolatilityService(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public double GetTradeVol(trade trade, DateTime date)
|
||||
{
|
||||
TradeVolatility tradeVol = null;
|
||||
using (YLContext ylDb = new YLContext())
|
||||
{
|
||||
tradeVol = ylDb.TradeVolatility.Where(n => n.TradeId == trade.id && n.ValueDate <= date)
|
||||
.OrderByDescending(O => O.ValueDate).FirstOrDefault();
|
||||
}
|
||||
return GetTradeVol(trade, tradeVol, date);
|
||||
}
|
||||
|
||||
public void GetTradeVol(List<trade> trades, DateTime date)
|
||||
{
|
||||
Dictionary<int, TradeVolatility> volDic = new Dictionary<int, TradeVolatility>();
|
||||
List<int> ids = trades.Where(t => !ConsTrade.TradeTypesForHedge.Contains(t.TradeType) && t.StartDate <= date).Select(O => O.id).ToList();
|
||||
using (YLContext ylDb = new YLContext())
|
||||
{
|
||||
var tempQuery = DbContext.TradeVolatility.Where(O => O.ValueDate <= date && ids.Contains(O.TradeId));
|
||||
if (tempQuery.Any())
|
||||
{
|
||||
var groupQuery = tempQuery.GroupBy(O => O.TradeId).Select(n => new { TradeId = n.Key, ValueDate = n.Max(m => m.ValueDate) });
|
||||
var query = from a in DbContext.TradeVolatility
|
||||
join b in groupQuery on new { a.TradeId, a.ValueDate } equals new { b.TradeId, b.ValueDate }
|
||||
select a;
|
||||
|
||||
volDic = query.ToDictionary(K => K.TradeId);
|
||||
}
|
||||
}
|
||||
foreach (var t in trades)
|
||||
{
|
||||
if (t.StartDate > date || ConsTrade.TradeTypesForHedge.Contains(t.TradeType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
volDic.TryGetValue(t.id, out var tradeVol);
|
||||
t.Vol = GetTradeVol(t, tradeVol, date);
|
||||
}
|
||||
}
|
||||
|
||||
private double GetTradeVol(trade trade, TradeVolatility tradeVol, DateTime date)
|
||||
{
|
||||
var daycountMode = PS.Config.ErpElement.SmoothingDaycountMode == Configuration.Enums.SmoothingDaycountMode.CalendarDay
|
||||
? Qdp.Pricing.Base.Enums.DayCountMode.CalendarDay
|
||||
: Qdp.Pricing.Base.Enums.DayCountMode.TradingDay;
|
||||
|
||||
if (tradeVol == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return AnalyticalOptionTradeVolInterp.tradeVolLinearInterp(
|
||||
new Qdp.Foundation.Implementations.Date(date),
|
||||
trade.TradeOpenVolatility ?? 0,
|
||||
trade.TradeCloseVolatility ?? 0,
|
||||
new Qdp.Foundation.Implementations.Date(trade.StartDate ?? DateTime.Today),
|
||||
new Qdp.Foundation.Implementations.Date(trade.ExerciseDate ?? DateTime.Today),
|
||||
trade.NumOfSmoothingDays ?? 0,
|
||||
daycountMode,
|
||||
CalendarImpl.Get("chn"));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return double.NaN;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return AnalyticalOptionTradeVolInterp.tradeVolLinearInterp(
|
||||
new Qdp.Foundation.Implementations.Date(date),
|
||||
tradeVol.TradePositionVolatility ?? 0,
|
||||
tradeVol.TradeCloseVolatility ?? 0,
|
||||
new Qdp.Foundation.Implementations.Date(tradeVol.ValueDate),
|
||||
new Qdp.Foundation.Implementations.Date(trade.ExerciseDate ?? DateTime.Today),
|
||||
tradeVol.NumOfSmoothingDays ?? 0,
|
||||
daycountMode,
|
||||
CalendarImpl.Get("chn"),
|
||||
includeStartDate: tradeVol.IsFromTradeAdd);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库中查询波动率信息(此处的userGroup参数允许为NULL)
|
||||
private IQueryable<volatility> _getVolQuery(DateTime date, IEnumerable<string> codes, IEnumerable<string> volTypes = null, string userGroup = null)
|
||||
{
|
||||
codes = codes.ToHashSet();
|
||||
if (volTypes == null)
|
||||
{
|
||||
volTypes = DbContext.volatility.Select(O => O.VolType).GroupBy(O => O).Select(O => O.Key).ToList();
|
||||
}
|
||||
|
||||
var queryGroup = (from vol in DbContext.volatility
|
||||
where vol.QuotationDate <= date
|
||||
&& codes.Contains(vol.ContractCode)
|
||||
&& (userGroup == null || vol.UserGroup == userGroup)
|
||||
&& volTypes.Contains(vol.VolType)
|
||||
group vol by new { vol.QuotationDate, vol.ContractCode, vol.UserGroup, vol.VolType } into grp
|
||||
select grp.Key);
|
||||
var queryKey = queryGroup.GroupBy(O => new { O.ContractCode, O.UserGroup, O.VolType }).Select(O => new { O.Key.ContractCode, O.Key.UserGroup, O.Key.VolType, QuotationDate = O.Max(M => M.QuotationDate) });
|
||||
return from vol in DbContext.volatility.AsNoTracking()
|
||||
join dict in queryKey
|
||||
on new { vol.QuotationDate, vol.ContractCode, vol.UserGroup, vol.VolType } equals new { dict.QuotationDate, dict.ContractCode, dict.UserGroup, dict.VolType }
|
||||
select vol;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库中查询波动率信息(此处的userGroup参数允许为NULL)
|
||||
private IQueryable<volatility> _getVolQuery(DateTime startDate, DateTime endDate, IEnumerable<string> codes, IEnumerable<string> volTypes = null, string userGroup = null)
|
||||
{
|
||||
codes = codes.ToHashSet();
|
||||
if (volTypes == null)
|
||||
{
|
||||
volTypes = DbContext.volatility.Select(O => O.VolType).GroupBy(O => O).Select(O => O.Key).ToList();
|
||||
}
|
||||
|
||||
var query = from vol in DbContext.volatility.AsNoTracking()
|
||||
where vol.QuotationDate >= startDate
|
||||
&& vol.QuotationDate <= endDate
|
||||
&& codes.Contains(vol.ContractCode)
|
||||
&& (userGroup == null || vol.UserGroup == userGroup)
|
||||
&& volTypes.Contains(vol.VolType)
|
||||
select vol;
|
||||
return _getVolQuery(startDate, codes, volTypes, userGroup).Union(query);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询volatility
|
||||
/// </summary>
|
||||
public SearchListResult<volatility> SearchList(VolatilityReq req)
|
||||
{
|
||||
IEnumerable<string> codes = null;
|
||||
|
||||
if (req.UnderlyingId != null)
|
||||
{
|
||||
codes = new[] { underlying_managerBLL.GetById(req.UnderlyingId.Value).UnderlyingCode };
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(req.UnderlyingName))
|
||||
{
|
||||
codes = new[] { underlying_managerBLL.GetQuery().Where(O => O.UnderlyingName == req.UnderlyingName).FirstOrDefault().UnderlyingCode };
|
||||
}
|
||||
if (!string.IsNullOrEmpty(req.ContractCode))
|
||||
{
|
||||
codes = new[] { req.ContractCode };
|
||||
}
|
||||
if (req.QuotationDate == null || req.QuotationDate?.Year <= 2000)
|
||||
{
|
||||
if ((req.QuotationDateStart == null || req.QuotationDateStart?.Year < 2000) && (req.QuotationDateEnd == null || req.QuotationDateEnd?.Year < 2000))
|
||||
{
|
||||
req.QuotationDate = SystemValueDate;
|
||||
req.QuotationDateStart = SystemValueDate;
|
||||
req.QuotationDateEnd = req.QuotationDateStart;
|
||||
}
|
||||
else if (req.QuotationDateStart == null || req.QuotationDateStart?.Year < 2000)
|
||||
{
|
||||
req.QuotationDateStart = req.QuotationDateEnd;
|
||||
}
|
||||
else if (req.QuotationDateEnd == null || req.QuotationDateEnd?.Year < 2000)
|
||||
{
|
||||
req.QuotationDateEnd = SystemValueDate;
|
||||
}
|
||||
if (req.QuotationDateStart >= req.QuotationDateEnd)
|
||||
{
|
||||
req.QuotationDateEnd = req.QuotationDateStart;
|
||||
}
|
||||
}
|
||||
|
||||
if (codes == null)
|
||||
{
|
||||
codes = (from temp in underlying_managerBLL.GetQuery()
|
||||
where temp.LaunchState == "1"
|
||||
select temp.UnderlyingCode).ToList();
|
||||
}
|
||||
IQueryable<volatility> query = null;
|
||||
if (req.QuotationDate != null && req.QuotationDate.Value > DateTime.MinValue)
|
||||
{
|
||||
query = _getVolQuery(req.QuotationDate.Value, codes, userGroup: req.UserGroup);
|
||||
}
|
||||
else
|
||||
{
|
||||
query = _getVolQuery(req.QuotationDateStart.Value, req.QuotationDateEnd.Value, codes, userGroup: req.UserGroup);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(req.VolType))
|
||||
{
|
||||
query = query.Where(d => d.VolType.Contains(req.VolType));
|
||||
}
|
||||
|
||||
if (req.OptId != null)
|
||||
{
|
||||
query = query.Where(d => d.OptId == req.OptId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(req.OptName))
|
||||
{
|
||||
query = query.Where(d => d.OptName.Contains(req.OptName));
|
||||
}
|
||||
|
||||
req.sidx = "QuotationDate";
|
||||
req.sord = "asc";
|
||||
|
||||
var tempSearchList = query.ToSearchList(req);
|
||||
List<DateTime> dates = QdpCalendarHelper.AllBizDays(req.QuotationDateStart.Value, req.QuotationDateEnd.Value.AddDays(1));
|
||||
List<volatility> vols = new List<volatility>();
|
||||
var firstVol = tempSearchList.rows.FirstOrDefault();
|
||||
if (firstVol != null)
|
||||
{
|
||||
foreach (var item in dates)
|
||||
{
|
||||
var vol = tempSearchList.rows.FirstOrDefault(O => O.QuotationDate == item);
|
||||
if (vol == null && firstVol.QuotationDate < item)
|
||||
{
|
||||
vol = firstVol.Clone(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
firstVol = vol;
|
||||
}
|
||||
vols.Add(vol);
|
||||
}
|
||||
tempSearchList.records = vols.Count;
|
||||
tempSearchList.rows = vols;
|
||||
}
|
||||
return tempSearchList;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user