339 lines
13 KiB
C#
339 lines
13 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|