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

700 lines
29 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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; }
}
}