using BaseOUDAL; using CsvHelper; using CsvHelper.Configuration; using System.Globalization; using System.Text; using YieldChain.Helpers; using YLErp.BLL; using YLErp.Commons; using YLErp.CustomizedBizLogic; using YLErp.DBModels.Consts; using YLErp.DBModels.Helpers; using YLErp.Models; using YLErp.Modules.ClientModule; using YLErp.Modules.DataCacheModule; using YLErp.Modules.TradeModule; using YLErp.Modules.TradeModule.DealModule; namespace YLErp.Modules.TradeDalModule { /// /// 结构化交易导入服务 /// public class StructureTradeImportService : YLBaseService { public StructureTradeImportService(YLBaseService baseService) : base(baseService) { } public StructureTradeImportService(OptUserInfo userInfo) : base(userInfo) { } /// /// /// public HandleResult ImportFromCsv(Stream stream) { IEnumerable 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(Helpers.CsvTypeConverts.StringConverter.Required); csv.Context.TypeConverterCache.AddConverter(Helpers.CsvTypeConverts.DoubleConverter.Required); csv.Context.TypeConverterCache.AddConverter(Helpers.CsvTypeConverts.Int16Converter.Required); csv.Context.RegisterClassMap(); importModels = csv.GetRecords().ToArray(); } if (!importModels.Any()) { return "没有可导入的数据"; } //检查导入的数据 var set = new HashSet(); foreach (var item in importModels) { item.StartDate = item.StartDate.Date; item.SettlementDate = item.SettlementDate.Date; item.ExpirationDate = item.ExpirationDate.Date; if (!set.Add(item.ContractNumber)) { return "合同编号重复:" + item.ContractNumber; } if (item.StartDate > DateTime.Today) { return "起始日不能大于今日"; } if (item.StartDate > item.SettlementDate) { return "起始日不能大于结算日"; } if (item.StartDate > item.ExpirationDate) { return "起始日不能大于到期日"; } } //检查是否数据库中已存在合同编号 var existsContractNos = new TradeContractGenerateService(this).GetExistsContractCode(set); if (existsContractNos.Any()) { return "合同编号已存在:" + string.Join(",", existsContractNos); } return ImportDatas(importModels); } /// /// 导入数据 /// private HandleResult ImportDatas(IEnumerable importModels) { // foreach (var model in importModels) { using (var transaction = BeginTransaction()) { SaveData(model); transaction.Commit(); } } return HandleResult.Success; } /// /// 保存到数据库 /// private void SaveData(ImportModel model) { //创建结构化交易 var result = CreateStructureTrade(model, out var struInfo); if (!result.IsSuccess) { throw new ServiceException(result.Message); } var parentTrade = struInfo.parentTrade; //保存主交易 SetDBModelCreator(parentTrade); DbContext.trade.Add(parentTrade); DbContext.SaveChanges(); //修正子交易数据 var index = 0; foreach (var subTrade in struInfo.subTrades) { index++; subTrade.ParentTradeId = parentTrade.id; subTrade.StructureType = parentTrade.StructureType; SetDBModelCreator(subTrade); } //添加 DbContext.trade.AddRange(struInfo.subTrades); DbContext.SaveChanges(); //应收预付金 var client = struInfo.client; DbContext.ClientCashInCashOut.Add(new ClientCashInCashOut { Direction = ClientCashInCashOut.应收, ClientId = client.id, ClientName = client.Name, ClientNumber = client.Number, Number = UniqueTimeId.GetStr(), TradeId = parentTrade.id, Action = ClientCashInCashOut.人工操作_预付金, Money = model.Margin, HappenDate = DateTime.Now, State = "已执行", OptId = parentTrade.OptId, OptName = parentTrade.OptName, OptDate = DateTime.Now, CreatorId = parentTrade.OptId, CreatorName = parentTrade.OptName, CreateDate = DateTime.Now, ValidState = "Valid", Comments = model.ContractNumber, TradeNumber = parentTrade.TradeNumber }); ///保存添加交易时开仓波动率,持仓波动率,平滑天数 var addTradesVolatility = new List(); addTradesVolatility.Add(new TradeVolatility() { TradeId = parentTrade.id, NumOfSmoothingDays = parentTrade.NumOfSmoothingDays, OptId = UserId, OptName = UserName, OptDate = DateTime.Now, TradeCloseVolatility = parentTrade.TradeCloseVolatility, TradePositionVolatility = parentTrade.TradeOpenVolatility, ValueDate = parentTrade.TradeDate ?? DateTime.Today, IsFromTradeAdd = true }); struInfo.subTrades.ToList().ForEach(x => { var tradeV = new TradeVolatility() { TradeId = x.id, NumOfSmoothingDays = x.NumOfSmoothingDays, OptId = UserId, OptName = UserName, OptDate = DateTime.Now, TradeCloseVolatility = x.TradeCloseVolatility, TradePositionVolatility = x.TradeOpenVolatility, ValueDate = x.TradeDate ?? DateTime.Today, IsFromTradeAdd = true }; addTradesVolatility.Add(tradeV); }); DbContext.TradeVolatility.AddRange(addTradesVolatility); DbContext.SaveChanges(); //保存对冲波动率 new TradeHisDataService(this).SaveHedgeVol(parentTrade.id, valuedateBLL.ValueDate, parentTrade.TradeSavedVol ?? 0); var tradeMetas = new List(); //添加交易确认书合约关联 if (!string.IsNullOrWhiteSpace(model.ContractNumber)) { tradeMetas.Add(new TradeMeta() { TradeId = parentTrade.id, MetaKey = ConsTradeMetaKey.ContractCode, MetaValue = model.ContractNumber }); } tradeMetas.Add(new TradeMeta() { TradeId = parentTrade.id, MetaKey = ConsTradeMetaKey.ReleaseAnnualPremiumRate, MetaValue = model.ReleaseAnnualPremiumRate.ToString() }); new TradeMetaService(this).AddTradeMetas(tradeMetas); DbContext.SaveChanges(); } /// /// 根据传进来的合约代码过滤出已存在的合约代码 /// private IEnumerable GetExistsContractCode(IEnumerable contractCodes) { if (contractCodes == null) { throw new ArgumentNullException(nameof(contractCodes)); } if (!contractCodes.Any()) { return Enumerable.Empty(); } var query = (from a in DbContext.trade_contract_r.AsNoTracking() join b in DbContext.trade.AsNoTracking() on a.TradeId equals b.id where contractCodes.Contains(a.ContractCode) && b.ValidState != "InValid" && a.IsValid select a.ContractCode).Union (from tm in DbContext.TradeMeta.AsNoTracking() join t in DbContext.trade.AsNoTracking() on tm.TradeId equals t.id where tm.MetaKey == ConsTradeMetaKey.ContractCode && t.ValidState != "InValid" && contractCodes.Contains(tm.MetaValue) select tm.MetaValue).Union (from t in DbContext.trade.AsNoTracking() where t.ValidState != "InValid" && contractCodes.Contains(t.TradeNumber) select t.TradeNumber); return query.ToArray(); } /// /// 生成结构化交易数据 /// private HandleResult CreateStructureTrade(ImportModel model, out StructureTradeInfo structureTradeInfo) { structureTradeInfo = null; if (model == null) { throw new ArgumentNullException(nameof(model)); } var trader = UserBLL.FirstOrDefault(O => O.Name == (model.TraderName ?? "")); if (trader == null) { return "交易员名称不存在:" + model.TraderName; } var assetUnit = DataCacheManager.GetAssetUnitDataSource().AsQueryable() .FirstOrDefault(n => n.Name == (model.AssetBookName ?? "")); if (assetUnit == null) { return "簿记账户名称不存在:" + model.AssetBookName; } if (!assetUnit.TraderIdsInt.Contains(trader.Id)) { return $"当前交易员: {trader.Name} 无法访问簿记账户: {assetUnit.Name}"; } if (model.SpotPrice < 0.01) { return "期初价格不符合要求:" + model.SpotPrice; } //交易客户 var client = ClientDataQueryService.GetClient(model.ClientName); if (client == null) { return "客户名称不存在:" + model.ClientName; } //交易标的 var underlyingCode = NormalizeUnderlyingCode(model.UnderlyingCode); var underlying = DataCacheManager.GetUnderlyingDataSource().GetData(underlyingCode); if (underlying == null) { return $"挂钩标的不存在,原值:{model.UnderlyingCode},转换后:{underlyingCode}"; } //交易品种 var variety = DataCacheManager.GetVarietyDataSource().GetData(underlying.UnderlyingTypeId); if (variety == null) { return "未找到对应的交易品种,标的代码:" + underlying.UnderlyingCode; } //创建交易 var parentTrade = CreateParentTrade(model, underlying, client, trader, assetUnit); var subTrades = new[] { parentTrade.Clone(), parentTrade.Clone() }; //以下处理各价差类型 var strikes = new[] { model.StrikePrice1Percent, model.StrikePrice2Percent }.OrderBy(n => n).ToArray(); parentTrade.Strike = strikes[1]; if (model.OptionStructurType.Equals("bull call spread")) { parentTrade.StructureType = "牛市价差"; //低行权价交易和主交易保持一致 subTrades[0].Strike = strikes[0]; //低行权价子交易分腿编号为-1; subTrades[0].TradeNumber = BizLogicSingleton.Instance.GenerateSubTradeNumberBeforeConfirm(subTrades[0], parentTrade, 1, DbContext); subTrades[1].TradeNumber = BizLogicSingleton.Instance.GenerateSubTradeNumberBeforeConfirm(subTrades[0], parentTrade, 2, DbContext); subTrades[1].Strike = strikes[1]; subTrades[1].TradePrice = 0; subTrades[1].TradeSinglePrice = 0; subTrades[1].InitialMargin = 0; subTrades[1].BuySell = model.BuySell == "买入" ? "卖出" : "买入"; } else if (model.OptionStructurType.Equals("bear call spread")) { parentTrade.StructureType = "熊市价差"; subTrades[0].Strike = strikes[1]; subTrades[0].TradePrice = 0; subTrades[0].TradeSinglePrice = 0; subTrades[0].InitialMargin = 0; subTrades[0].BuySell = model.BuySell == "买入" ? "卖出" : "买入"; //高行权价交易和主交易保持一致 subTrades[1].Strike = strikes[0]; //高行权价子交易分腿编号为-1; subTrades[0].TradeNumber = BizLogicSingleton.Instance.GenerateSubTradeNumberBeforeConfirm(subTrades[0], parentTrade, 1, DbContext); subTrades[1].TradeNumber = BizLogicSingleton.Instance.GenerateSubTradeNumberBeforeConfirm(subTrades[0], parentTrade, 2, DbContext); } else { return "未能辨识的期权类型:" + model.OptionStructurType; } foreach (var subTrade in subTrades) { subTrade.TradeType = "香草期权"; subTrade.MaturityDate = parentTrade.MaturityDate ?? underlying.MaturityDate; } structureTradeInfo = new StructureTradeInfo { parentTrade = parentTrade, subTrades = subTrades, client = client }; return HandleResult.Success; } /// /// 生成父交易 /// private trade CreateParentTrade(ImportModel model, underlying_manager underlying, ClientMainInfo client, SystemUser trader, AssetUnit assetUnit) { var tradeOb = new trade { VolType = "交易", IsMoneynessOption = "是", TradeStatus = "新增待确认", ValidState = "Valid", OptionType = "看涨", TradeType = "结构化交易", TradeSource = "系统交易", UnderlyingInstrumentType = "CommodityFutures", IsUsePremiumRate = true, Comments = model.Remark, BuySell = model.BuySell, DurationDays = model.DurationDays, TradeSavedVol = 0, SettlementType = 0, ProcessOrderId = 0, //HasGeneratedConfirmBook = 0, NumOfSmoothingDays = 1, //ContractNo = model.ContractNumber, NoRiskRate = valuedateBLL.RiskFreeRate * 0.01, TradeDate = model.StartDate, UnderlyingId = underlying.id, UnderlyingCode = underlying.UnderlyingCode, UnderlyingAssetClass = underlying.UnderlyingType, UnderlyingAssetName = underlying.UnderlyingName, ClientId = client.id, ClientName = client.Name, AssetId = assetUnit.id, AssetBookName = assetUnit.Name, TraderId = trader?.Id ?? 0, TraderName = trader?.Name, ExerciseMode = "European", ExerciseDate = model.ExpirationDate, SpotPrice = model.SpotPrice, StartDate = model.StartDate, StockEqvNotional = model.NotionalPrincipal, OriginalStockEqvNotional = model.NotionalPrincipal, Notional = 0, OriginalNotional = 0, OptDate = DateTime.Now, OptId = UserId, OptName = UserName, CreateDate = DateTime.Now, TradeAmount = 0, Strike = null, TradePrice = null, TradeSinglePrice = null, InitialMargin = model.Margin, TradeOpenVolatility = model.Vol, TradeCloseVolatility = model.Vol, ParticipationRate = model.ParticipationRate, CalcFlag = model.CalcFlag, PremiumRate = model.InitialAnnualPremiumRate, SettlementDate = model.SettlementDate, IsAnnualized = model.IsAnnualized == "是", AnnualizeFactor = model.AnnualizeFactor }; tradeOb.Notional = model.NotionalPrincipal / model.SpotPrice; tradeOb.TradeAmount = tradeOb.Notional / underlying.CountRatio; tradeOb.OriginalNotional = tradeOb.Notional; tradeOb.Lots = tradeOb.Notional / underlying.ContractSize; tradeOb.StockEqvNotionalReal = TradeHelper.GetStockEqvNotionalReal(tradeOb.OriginalStockEqvNotional, tradeOb.ParticipationRate, tradeOb.AnnualizeFactor); tradeOb.TradePrice = model.Premium; tradeOb.TradeSinglePrice = TradeHelper.GetTradeSinglePriceByTradePrice(model.Premium, tradeOb.Notional, tradeOb.OriginalPrincipalSum, tradeOb.BuySell, tradeOb.TradeType, true); #region 设置对冲波动率 tradeOb.TradeSavedVol = tradeOb.TradeOpenVolatility; if (PS.Config.ErpElement.VolMode == Configuration.VolModeEnum.VolSurface) { tradeOb.TradeSavedVol = tradeOb.Vol; } #endregion tradeOb.TradeNumber = model.ContractNumber;//BizLogicSingleton.Instance.GenerateTradeNumberBeforeConfirm(tradeOb); return tradeOb; } /// /// 标准化标的代码 /// private string NormalizeUnderlyingCode(string code) { if (string.IsNullOrEmpty(code)) { return code; } var index = code.LastIndexOf('.'); if (index >= 0) { code = code.Substring(0, index); } if (code.All(n => (n >= 'a' && n <= 'z') || (n >= 'A' && n <= 'Z'))) { return code + "00"; } return code; } /// /// 结构化交易信息类 /// class StructureTradeInfo { public trade parentTrade; public trade[] subTrades; public ClientMainInfo client; } /// /// 获取导入模板 /// public static string GetCsvTemplate() { return new ImportModel.ImportMap().GenTemplate(); } } class ImportModel { /// /// 合同编号 /// public string ContractNumber { get; set; } /// /// 对手方(客户名称) /// public string ClientName { get; set; } /// /// 买卖方向 /// public string BuySell { get; set; } /// /// 挂钩标的 /// public string UnderlyingCode { get; set; } /// /// 挂钩标的名称 /// public string UnderlyingName { get; set; } /// /// 期权类型 /// public string OptionStructurType { get; set; } /// /// 名义规模(名义本金) /// public double NotionalPrincipal { get; set; } /// /// 期初价格 /// public double SpotPrice { get; set; } /// /// 行权价%1 /// public double StrikePrice1Percent { get; set; } /// /// 行权价1 /// public double StrikePrice1 { get; set; } /// /// 行权价%2 /// public double StrikePrice2Percent { get; set; } /// /// 行权价2 /// public double StrikePrice2 { get; set; } /// /// 起始日 /// public DateTime StartDate { get; set; } /// /// 结算日 /// public DateTime SettlementDate { get; set; } /// /// 簿记账户 /// public string AssetBookName { get; set; } /// /// 交易员 /// public string TraderName { get; set; } /// /// 期权费 /// public double Premium { get; set; } /// /// 期初年化期权费率 /// public double InitialAnnualPremiumRate { get; set; } /// /// 期末年化期权费率 /// public double ReleaseAnnualPremiumRate { get; set; } /// /// 到期日 /// public DateTime ExpirationDate { get; set; } /// /// 预付金 /// public double Margin { get; set; } /// /// 天数 /// public short DurationDays { get; set; } /// /// 参与率 /// public double ParticipationRate { get; set; } /// /// 波动率 /// public double Vol { get; set; } /// /// 计算标识(0:全部,1:IgnoreMargin(忽略预付金计算使用初始预付金)) /// public int CalcFlag { get; set; } /// /// 是否为期权年化,赋值为“是”或“否” /// public string IsAnnualized { get; set; } /// /// 年化系数 /// public double AnnualizeFactor { get; set; } /// /// 备注 /// public string Remark { get; set; } public override string ToString() { return ContractNumber; } public class ImportMap : ClassMap { public ImportMap() { Map(m => m.ContractNumber).Name("合同编号"); Map(m => m.ClientName).Name("对手方"); Map(m => m.BuySell).Name("买卖方向"); Map(m => m.UnderlyingCode).Name("挂钩标的"); Map(m => m.AssetBookName).Name("簿记账户"); Map(m => m.TraderName).Name("交易员"); Map(m => m.OptionStructurType).Name("期权类型"); Map(m => m.NotionalPrincipal).Name("名义规模"); Map(m => m.SpotPrice).Name("期初价格"); Map(m => m.StrikePrice1Percent).Name("行权价%1").TypeConverter(Helpers.CsvTypeConverts.PercentConverter.Required); Map(m => m.StrikePrice1).Name("行权价1"); Map(m => m.StrikePrice2Percent).Name("行权价%2").TypeConverter(Helpers.CsvTypeConverts.PercentConverter.Required); Map(m => m.StrikePrice2).Name("行权价2"); Map(m => m.StartDate).Name("起始日"); Map(m => m.SettlementDate).Name("结算日"); Map(m => m.Premium).Name("期权费"); Map(m => m.InitialAnnualPremiumRate).Name("期初费率").TypeConverter(Helpers.CsvTypeConverts.PercentConverter.Required); Map(m => m.ReleaseAnnualPremiumRate).Name("期末费率").TypeConverter(Helpers.CsvTypeConverts.PercentConverter.Required); Map(m => m.ExpirationDate).Name("到期日"); Map(m => m.Margin).Name("预付金"); Map(m => m.DurationDays).Name("天数"); Map(m => m.ParticipationRate).Name("参与率").TypeConverter(Helpers.CsvTypeConverts.PercentConverter.Required); Map(m => m.Vol).Name("波动率").TypeConverter(Helpers.CsvTypeConverts.PercentConverter.Required); Map(m => m.IsAnnualized).Name("期权年化"); Map(m => m.AnnualizeFactor).Name("年化系数"); Map(m => m.CalcFlag).Name("计算标识"); Map(m => m.UnderlyingName).Name("挂钩标的名称"); Map(m => m.Remark).Name("备注").TypeConverter(Helpers.CsvTypeConverts.StringConverter.NoValidator); } 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(); } } } }