using YLErp.Commons;
using YLErp.Models;
using YLErp.Modules.DataCacheModule;
using YLErp.Office.ExcelModule;
using YLErp.QdpModule.Constants;
namespace YLErp.Modules.VolatilityModule
{
///
/// 波动率导入读取服务
///
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 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 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 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();
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();
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 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; }
}
///
/// 构造波动率曲面,并检查是否需要从交易波动率调整出Bid和Ask
///
private volatility ConstructVolSurfaceWithDeviationCheck(int underlyingId, string contractCode,
DateTime quotationDate, string volType, string volSurfaceMode, List 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;
}
///
/// 申万excel波动率类型匹配,目前只有ask,bid和交易
///
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 _volList = new List();
public abstract void ProcessLine(string[] lineValues, int lineNumber);
public abstract void ProcessEnd();
public List GetResults()
{
return _volList;
}
}
class StandardProcess : ReadProcessBase
{
string contractCode = null;
underlying_manager _underlying;
List strikeList = null;
readonly List singleVolList = new List();
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();
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();
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 _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();
}
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();
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();
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; }
///
/// 审核波动率下限
///
public double ReviewDownLimit { get; set; }
///
/// 审核波动率上限
///
public double ReviewUpLimit { get; set; }
///
/// 用户组
///
public string UserGroup { get; set; }
}
}