从山证v2.3.0拷贝

This commit is contained in:
吴方海
2024-05-09 14:06:26 +08:00
parent 566ff33259
commit f9d8a256a6
4471 changed files with 1203456 additions and 9 deletions
@@ -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;
}
}
}