using CsvHelper; using DocumentFormat.OpenXml.Drawing.Diagrams; using DocumentFormat.OpenXml.Office2010.PowerPoint; using Newtonsoft.Json; using NPOI.SS.Formula.Functions; using Qdp.Pricing.Base.Enums; using System.Linq.Expressions; using YLErp.BLL; using YLErp.Commons; namespace YLErp.Modules.SwapModule { public class SwapTradeBaseService : YLBaseService { public SwapTradeBaseService(OptUserInfo optUser) : base(optUser) { } public SwapTradeBaseService(YLBaseService baseService) : base(baseService) { } #region 可测试化接缝(Seams)——子类共用,override 可在测试中替换 DB/外部调用 /// 查找交易(生产: DbContext.trade.Find;测试: 返回内存对象)。 /// SwapDealService/SwapEodPositionService/SwapFlowEventService 三处实现完全一致,上提基类消除重复。 protected virtual trade FindTrade(int tradeId) { return DbContext.trade.Find(tradeId); } #endregion /// /// 校验标的是否存在 /// /// /// 不存在返回空 public bool GetUnderlyingCode(string underlyingCode) { var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode); return underlying == null ? false : true; } /// ///新增日终归档信息时 修改 持仓腿信息 /// /// public void UpdateSwapPosition(eod_swap_position eodPayPosition, string tradeNumber) { var position = DbContext.swap_position.Find(eodPayPosition.PositionId); position.ContractSize = eodPayPosition.ContractSize; position.PositionType = eodPayPosition.PositionType; position.PosiTradingFee = eodPayPosition.PosiTradingFee; position.PosiTradingFeePending = eodPayPosition.PosiFeePending; position.PosiTradingFeeUnit = eodPayPosition.PosiQuantity == 0 ? 0 : eodPayPosition.PosiTradingFee / eodPayPosition.PosiQuantity; position.UnderlyingCode = eodPayPosition.UnderlyingCode; position.UnderlyingInstrumentType = eodPayPosition.UnderlyingInstrumentType; position.PosiDirection = eodPayPosition.PosiDirection; position.PosiNetPrice = eodPayPosition.PosiNetPrice; position.PosiGrossPrice = eodPayPosition.PosiGrossPrice; position.PosiNetFeePrice = eodPayPosition.PosiNetFeePrice; position.PosiNetNoFeePrice = eodPayPosition.PosiNetNoFeePrice; position.PosiNotionalValue = Math.Round(eodPayPosition.PosiNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); position.PosiQuantity = eodPayPosition.PosiQuantity; position.PosiStartDate = eodPayPosition.PosiStartDate; position.OptTime = DateTime.Now; position.OptId = UserInfo.UserId; position.OptName = UserInfo.UserName; position.PosiNumber = $"{tradeNumber}-{position.id}"; } /// /// 平仓后更新持仓 /// /// public void UpdateSwapPositionWithRealTime(eod_swap_position eodPayPosition) { var position = DbContext.swap_position.FirstOrDefault(x => x.PositionId == eodPayPosition.PositionId); if (position != null) { position.PosiQuantity = eodPayPosition.PosiQuantity; position.PosiTradingFee = eodPayPosition.PosiTradingFee; position.PosiNetPrice = eodPayPosition.PosiNetPrice; position.PosiGrossPrice = eodPayPosition.PosiGrossPrice; position.PosiNetFeePrice = eodPayPosition.PosiNetFeePrice; position.PosiNetNoFeePrice = eodPayPosition.PosiNetNoFeePrice; position.PosiNotionalValue = Math.Round(eodPayPosition.PosiNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); position.PosiTradingFeePending = eodPayPosition.PosiFeePending; position.PosiQuantity = eodPayPosition.PosiQuantity; position.PosiDirection = eodPayPosition.PosiDirection; position.PositionType = eodPayPosition.PositionType; position.OptTime = DateTime.Now; position.OptId = UserInfo.UserId; position.OptName = UserInfo.UserName; } else { position = DbContext.swap_position.FirstOrDefault(x => x.id == eodPayPosition.PositionId); var posi = position.Clone(); posi.id = 0; posi.PositionId = position.id; posi.IsInitial = false; DbContext.swap_position.Add(posi); } } /// /// 获取同一互换编码,标的 持仓id /// /// 互换框架合约id /// 标的代码 /// public long GetMaxPositionId(swap_flow_merge swap_Flow_Summary, DateTime matuirityDate, int direction, string tradeNumber) { long max = 0; var position = DbContext.swap_position.FirstOrDefault(x => x.SwapTradeId == swap_Flow_Summary.SwapTradeId && x.UnderlyingCode == swap_Flow_Summary.UnderlyingCode && x.PosiQuantity != 0 && !x.IsInitial&&!x.Invalid); if (position != null) { max = position.PositionId; } else { swap_position swap_Position = new swap_position(); swap_Position.PosiDirection = direction; swap_Position.PositionType = swap_Flow_Summary.BsType; swap_Position.SwapTradeId = swap_Flow_Summary.SwapTradeId ?? 0; swap_Position.UnderlyingCode = swap_Flow_Summary.UnderlyingCode; swap_Position.PosiQuantity = swap_Flow_Summary.TradingQty; swap_Position.PosiTradingFee = swap_Flow_Summary.TradingFee; swap_Position.ContractSize = swap_Flow_Summary.ContractSize; swap_Position.IsInitial = true; swap_Position.PosiStartDate = swap_Flow_Summary.OccurTime; swap_Position.PosiMatuirityDate = matuirityDate; swap_Position.OptId = UserId; swap_Position.OptName = UserName; swap_Position.OptTime = DateTime.Now; DbContext.swap_position.Add(swap_Position); DbContext.SaveChanges(); max = swap_Position.id; swap_Position.PosiNumber = $"{tradeNumber}-{max}"; } return max; } public long GetPositionId(swap_flow_merge swap_Flow_Summary, DateTime matuirityDate, int direction, string tradeNumber) { long max = 0; swap_position swap_Position = new swap_position(); swap_Position.PosiDirection = direction; swap_Position.PositionType = swap_Flow_Summary.BsType; swap_Position.SwapTradeId = swap_Flow_Summary.SwapTradeId ?? 0; swap_Position.UnderlyingCode = swap_Flow_Summary.UnderlyingCode; swap_Position.PosiQuantity = swap_Flow_Summary.TradingQty; swap_Position.PosiTradingFee = swap_Flow_Summary.TradingFee; swap_Position.ContractSize = swap_Flow_Summary.ContractSize; swap_Position.IsInitial = true; swap_Position.PosiStartDate = swap_Flow_Summary.OccurTime; swap_Position.PosiMatuirityDate = matuirityDate; swap_Position.OptId = UserId; swap_Position.OptName = UserName; swap_Position.OptTime = DateTime.Now; DbContext.swap_position.Add(swap_Position); DbContext.SaveChanges(); max = swap_Position.id; swap_Position.PosiNumber = $"{tradeNumber}-{max}"; return max; } /// /// 添加交易操作日志 /// public void AddTradeOperationHistory(bool saveChanges, OtcTradeBase trade, string optType, string comments = null) { DbContext.TradeAuditLog.Add(new TradeAuditLog { TradeId = trade.id, AuditFlag = TradeAuditFlag.operation, OptType = optType, Changes = comments ?? string.Empty, DataType = "00", OptId = UserId, OptName = UserName, OptDate = OptDate }); if (saveChanges) { DbContext.SaveChanges(); } } /// /// 获取平仓/互换记录 /// /// 交易编码 /// 日期 /// 互换事件类型 /// 是否查询小于日期 /// public List GetSwapFlowEvents(int tradeId, DateTime? valueDate, List eventTypes) { Expression> eventExpression = x => x.SwapTradeId == tradeId && x.DataState == (int)SwapFlowDateStateEnum.完成 && eventTypes.Contains(x.EventType); if (valueDate.HasValue) { eventExpression = eventExpression.And(x => x.UnwindDate == valueDate); } List swapFlowEvents = DbContext.swap_flow_event.Where(eventExpression).ToList(); return swapFlowEvents; } /// /// 获取交易平仓/互换事件所有信息 /// /// /// /// public List GetSwapEvents(int tradeId, List eventTypes) { Expression> eventExpression = x => x.SwapTradeId == tradeId && !x.Invalid && eventTypes.Contains(x.EventType) && x.ClientCashId > 0; var swapEvents = DbContext.swap_event.Where(eventExpression).ToList(); var swapFlowEvents = DbContext.swap_flow_event.Where(x => x.SwapTradeId == tradeId && x.DataState == (int)SwapFlowDateStateEnum.完成); var swapPositions = DbContext.swap_position.Where(x => x.SwapTradeId == tradeId && !string.IsNullOrEmpty(x.UnderlyingCode) && !x.Invalid).ToList(); foreach (var item in swapEvents) { item.unwindData = JsonConvert.DeserializeObject(item.EventData); item.unwindData.FlowEvents = swapFlowEvents.Where(x => x.EventId == item.id).ToList(); item.unwindData.FlowEvents.ForEach(x => { var position = swapPositions.FirstOrDefault(n => n.id == x.PositionId && n.IsInitial); var positionReal = swapPositions.FirstOrDefault(n => n.PositionId == x.PositionId && !n.IsInitial); if (position != null) { x.PosiGrossPrice = position.PosiGrossPrice; x.PosiNetPrice = position.PosiNetPrice; } //if (positionReal!=null) //{ // x.PositionQty = positionReal.PosiQuantity; //} }); } return swapEvents; } /// /// 获取平仓/互换信息 /// /// /// /// public swap_event GetSwapEvent(int tradeId, int eventType) { Expression> eventExpression = x => x.SwapTradeId == tradeId && !x.Invalid && x.EventType == eventType && x.ClientCashId == 0; var swapEvent = DbContext.swap_event.Where(eventExpression).OrderByDescending(o => o.id).FirstOrDefault(); var swapFlowEvents = DbContext.swap_flow_event.Where(x => x.EventId == swapEvent.id); var swapPositions = DbContext.swap_position.Where(x => x.SwapTradeId == tradeId && !string.IsNullOrEmpty(x.UnderlyingCode) && !x.Invalid).ToList(); if (swapEvent != null) { swapEvent.unwindData = JsonConvert.DeserializeObject(swapEvent.EventData); swapEvent.unwindData.FlowEvents = swapFlowEvents.Where(x => x.EventDate == swapEvent.ValueDate).ToList(); swapEvent.unwindData.FlowEvents.ForEach(x => { var position = swapPositions.FirstOrDefault(n => n.id == x.PositionId && n.IsInitial); if (position != null) { x.PosiGrossPrice = position.PosiGrossPrice; x.PosiNetPrice = position.PosiNetPrice; //if (eventType==(int)SwapEventTypeEnum.互换) //{ // x.TradingAmountAvg = x.TradingAmount / (x.PositionQty*x.ContractSize); //} //else //{ // x.TradingAmountAvg = x.TradingAmount / (swapEvent.unwindData.CloseQty * x.ContractSize); //} } }); } return swapEvent; } /// /// 获取上一互换交易事件处理日期 /// /// /// /// /// public virtual DateTime? GetPreDealDate(int tradeId, DateTime valueDate, List eventTypes) { Expression> eventExpression = x => x.SwapTradeId == tradeId && x.ValueDate <= valueDate && !x.Invalid && eventTypes.Contains(x.EventType); var swapEvent = DbContext.swap_event.Where(eventExpression).OrderByDescending(o => o.ValueDate).FirstOrDefault(); if (swapEvent == null) { return null; } return swapEvent.ValueDate; } #region 可测试化接缝(ClearSwapPositions 相关) /// 查找该交易的 flow_event(生产: DbContext.swap_flow_event;测试: 内存列表) protected virtual List FindFlowEventsByEventIds(List eventIds) { return DbContext.swap_flow_event.Where(x => x.EventId.HasValue && eventIds.Contains(x.EventId.Value)).ToList(); } /// 查找手动互换的 ClientCashId(生产: DbContext.swap_event;测试: 内存列表) protected virtual List FindManualClientCashIds(int swapTradeId) { return DbContext.swap_event .Where(x => x.SwapTradeId == swapTradeId && x.ClientCashId > 0 && x.EventType != (int)SwapEventTypeEnum.自动互换) .Select(x => x.ClientCashId) .ToList(); } /// 查找该交易的资金记录(生产: DbContext.ClientCashInCashOut;测试: 内存列表) protected virtual List FindClientCashRecords(int tradeId) { return DbContext.ClientCashInCashOut.Where(x => x.TradeId == tradeId).ToList(); } #endregion public virtual int AddClientCashInCashOut(OtcTradeBase td, double amount, string action, DateTime valueDate) { var cl = DataCacheProvider.GetClientDataSource().GetData(td.ClientId); if (cl == null) { throw new Exception("客户信息未找到,交易编号:" + td.TradeNumber); } //增加出入金记录 var ee = new ClientCashInCashOut(); ee.CreateDate = DateTime.Now; ee.CreatorId = UserId; ee.CreatorName = UserName; ee.Direction = "应收"; ee.Number = UniqueTimeId.GetStr(); ee.ClientId = cl.id; ee.ClientNumber = cl.Number; ee.ClientName = cl.Name; ee.Money = amount; ee.CurrencyCode = td.SettlementCurrency; ee.HappenDate = valueDate; ee.State = ClientCashInCashOut.已确认; ee.OptId = UserId; ee.OptName = UserName; ee.OptDate = DateTime.Now; ee.TradeId = td.id; ee.Action = action; ee.ValidState = "Valid"; ee.TradeNumber = td.TradeNumber; ee.CurrencyCode = "CNY"; DbContext.ClientCashInCashOut.Add(ee); DbContext.SaveChanges(); return ee.id; } /// /// 初始化 利息计算起始日期 /// /// /// 上一交易日 /// 互换交易主信息 /// 计息方式 /// 计息开始日期 /// 计息结束日期 public bool InitInterestDate(DateTime valueDate, DateTime? preSettleDate, trade td, bool tdClose, out DateTime interestStart, out DateTime interestEnd) { interestStart = td.StartDate.Value; var exerciseDate = td.ExerciseDate.Value; interestEnd = valueDate > exerciseDate ? exerciseDate : valueDate; bool calcFirst = true; bool calcLast = true; if (td.trade_extend != null) { calcFirst = td.trade_extend.ExtendObj.InterestCalcMode.StartsWith("1");//算头 calcLast = td.trade_extend.ExtendObj.InterestCalcMode.EndsWith("1");//算尾 } interestStart = calcFirst ? interestStart : interestStart.AddDays(1); if (preSettleDate.HasValue && preSettleDate >= interestStart) { interestStart = preSettleDate.Value; } if ((interestEnd == exerciseDate && !calcLast)) { interestEnd = interestEnd.AddDays(-1); } if (interestStart > interestEnd || td.StartDate > interestStart) { interestStart = interestEnd; return true;//不记利息 } if (tdClose) { interestStart = valueDate; } return false; } public virtual void UpdateDbOption(DBModelBaseV2 dBModel) { dBModel.OptTime = DateTime.Now; dBModel.OptName = UserName; dBModel.OptId = UserId; } /// /// 合成持仓/日终归档 清除互换持仓所有信息 /// /// public virtual void ClearSwapPositions(trade td, DateTime valueDate, List eventTypes, bool delAfter) { var swapEvents = DbContext.swap_event.Where(x => x.SwapTradeId == td.id && x.ValueDate >= valueDate && eventTypes.Contains(x.EventType)); var eventIds = swapEvents.Select(s => s.id).ToList(); var eodSwaps = DbContext.eod_swap.Where(x => x.SwapTradeId == td.id && x.ValueDate >= valueDate).ToList(); if (delAfter) { var swapFlowEvents = DbContext.swap_flow_event.Where(x => x.SwapTradeId == td.id && x.UnwindDate >= valueDate && x.DataState > (int)SwapFlowDateStateEnum.废弃 && eventTypes.Contains(x.EventType)).ToList(); var eodSwapPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == td.id && x.ValueDate >= valueDate).ToList(); DbContext.eod_swap_position.RemoveRange(eodSwapPositions); DbContext.swap_flow_event.RemoveRange(swapFlowEvents); // 删除自动互换产生的资金记录(client_cash_in_out) var autoSwapEvents = swapEvents.ToList(); if (autoSwapEvents.Any()) { // 通过 swap_event 的 ClientCashId 删除对应的资金记录(利息腿) var clientCashIds = autoSwapEvents .Where(s => s.ClientCashId > 0) .Select(s => s.ClientCashId) .ToList(); var legacyAutoEvents = new List(); foreach (var swapEvent in autoSwapEvents) { var eventCashIds = GetAutoSwapClientCashIds(swapEvent); if (eventCashIds.Any()) { clientCashIds.AddRange(eventCashIds); } else { legacyAutoEvents.Add(swapEvent); } } clientCashIds = clientCashIds.Distinct().ToList(); if (clientCashIds.Any()) { var clientCashRecords = DbContext.ClientCashInCashOut.Where(x => clientCashIds.Contains(x.id)).ToList(); DbContext.ClientCashInCashOut.RemoveRange(clientCashRecords); } var legacyCashRecords = GetLegacyAutoSwapClientCashRecords(legacyAutoEvents, clientCashIds); if (legacyCashRecords.Any()) { DbContext.ClientCashInCashOut.RemoveRange(legacyCashRecords); } } } DbContext.swap_event.RemoveRange(swapEvents); DbContext.eod_swap.RemoveRange(eodSwaps); DbContext.SaveChanges(); } private List GetAutoSwapClientCashIds(swap_event swapEvent) { if (swapEvent == null || string.IsNullOrWhiteSpace(swapEvent.EventData)) { return new List(); } try { var unwindData = JsonConvert.DeserializeObject(swapEvent.EventData); return unwindData?.ClientCashIds?.Where(x => x > 0).Distinct().ToList() ?? new List(); } catch { return new List(); } } protected virtual List GetLegacyAutoSwapClientCashRecords(List swapEvents, List excludedClientCashIds) { if (swapEvents == null || !swapEvents.Any()) { return new List(); } var eventIds = swapEvents.Select(x => x.id).ToList(); var flowEvents = FindFlowEventsByEventIds(eventIds); var manualClientCashIds = FindManualClientCashIds(swapEvents.First().SwapTradeId); var records = new List(); foreach (var swapEvent in swapEvents) { UnwindData unwindData = null; if (!string.IsNullOrWhiteSpace(swapEvent.EventData)) { try { unwindData = JsonConvert.DeserializeObject(swapEvent.EventData); } catch { } } var eventFlowEvents = flowEvents.Where(x => x.EventId == swapEvent.id).ToList(); var candidateDates = new HashSet { swapEvent.ValueDate.Date }; if (unwindData?.PayDate != null) { candidateDates.Add(unwindData.PayDate.Value.Date); } eventFlowEvents.Where(x => x.PayDate.HasValue).ToList().ForEach(x => candidateDates.Add(x.PayDate.Value.Date)); var allCashRecords = FindClientCashRecords(swapEvent.SwapTradeId); var eventRecords = allCashRecords .Where(x => !excludedClientCashIds.Contains(x.id) && !manualClientCashIds.Contains(x.id) && (x.Action == ClientCashInCashOut.系统操作_预付金返息 || x.Action == ClientCashInCashOut.系统操作_互换)) .ToList() .Where(x => x.HappenDate.HasValue && candidateDates.Contains(x.HappenDate.Value.Date)) .Where(x => IsLegacyAutoSwapClientCashRecord(x, unwindData)) .ToList(); records.AddRange(eventRecords); } return records.GroupBy(x => x.id).Select(x => x.First()).ToList(); } private bool IsLegacyAutoSwapClientCashRecord(ClientCashInCashOut cashRecord, UnwindData unwindData) { if (unwindData == null) { return true; } var amount = Convert.ToDecimal(cashRecord.Money ?? 0); if (cashRecord.Action == ClientCashInCashOut.系统操作_预付金返息) { return unwindData.SwapMarginRebatePnl != 0 && amount == -unwindData.SwapMarginRebatePnl; } if (cashRecord.Action == ClientCashInCashOut.系统操作_互换) { return (unwindData.SwapCloseAmount != 0 && amount == -unwindData.SwapCloseAmount) || (unwindData.SwapDividendPnl != 0 && amount == -unwindData.SwapDividendPnl) || (unwindData.SwapRealizedPnL != 0 && amount == -unwindData.SwapRealizedPnL); } return false; } /// /// 获取上一交易日 /// /// 当前交易日 /// public DateTime GetPreValueDate(DateTime valueDate) { var preSettleDate = valuedateBLL.GetNonHolidayDefore(valueDate.AddDays(-1));//上一交易日 return preSettleDate; } /// /// 判断交易上一交易日是否收盘 /// /// 交易日期 /// 交易开始日期 /// /// public DateTime CheckLastEod(DateTime valueDate, DateTime tradeStartDate, int tradeId) { var preSettleDate = GetPreValueDate(valueDate);//上一交易日期 List lastEodPositions = new SwapEodPositionService(this).GetPreEodPositions(tradeId, preSettleDate);//上一交易数据 if (preSettleDate > tradeStartDate && lastEodPositions.Count == 0) { throw new ServiceException($"上一交易日【{preSettleDate:D}】未收盘"); } return preSettleDate; } /// /// 是否有审批流程 /// /// public bool HasTradeProcess() { return DbContext.approvalprocess.Where(t => t.processType == "TradeProcess").Any(); } } }