using BaseOUDAL; using ClosedXML.Report.Options; using Confluent.Kafka; using CsvHelper; using MoreLinq; using Newtonsoft.Json; using NPOI.POIFS.NIO; using Org.BouncyCastle.Ocsp; using Qdp.ComputeServiceV2.Data.CommonModels.TradeInfos; using Qdp.Foundation.Implementations; using Qdp.Pricing.Ecosystem.Trade.FixedIncome; using Qdp.Pricing.Library.Base.Utilities; using Qdp.Pricing.Library.Common.Products.Rates; using System; using System.Data; using System.Linq.Expressions; using System.Text; using YLErp.BLL; using YLErp.BLL.Eod; using YLErp.Configuration; using YLErp.Configuration.Enums; using YLErp.CustomizedBizLogic; using YLErp.DBModels; using YLErp.DBModels.Enums; using YLErp.Enums; using YLErp.Helpers; using YLErp.Model; using YLErp.Model.Enum; using YLErp.Models; using YLErp.Modules.DataProviderModule; using YLErp.Modules.EodModule; using YLErp.Modules.RiskModule; using YLErp.Modules.SalesModule; using YLErp.Modules.TradeModule; using YLErp.Modules.TradeModule.DealModule; using YLErp.Modules.TradeModule.DocGenerateModule; using YLErp.Modules.TradeModule.QueryModule; using YLErp.Modules.UnderlyingModule; using YLErp.QdpModule; using static YLErp.ConsGlobal; namespace YLErp.Modules.SwapModule { /// /// 新版互换交易服务 /// public class SwapTradeService : SwapTradeBaseService { public SwapTradeService(OptUserInfo optUser) : base(optUser) { } public SwapTradeService(YLBaseService baseService) : base(baseService) { } private static decimal? RoundSwapBondNetPriceAndYtm(decimal? value) { return value.HasValue ? Math.Round(value.Value, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero) : null; } #region 互换交易保存 /// /// 新版收益互换预付金校验 /// public string ValidateMargin(trade req) { var errorMsg = string.Empty; var client = DataCacheProvider.GetClientDataSource().GetData(req.ClientId); if (!client.DerivativesInvestmentVarieties.Contains((int)DerivativesInvestmentVarietiesEnum.场外互换 + "")) { return $"客户:{client.Name}未设置交易种类“场外互换”,无法生成互换交易!"; } //收益互换初始预付金校验 errorMsg = checkMarginRate(req); return errorMsg; } /// /// 互换交易保存 /// ignoreMoneyCheck=true:资金不足特批放行(additionalProcessing=LackOfMoney,controller 按 系统参数"允许交易特批" /// 判定后传入,与确认/审批环节 ignoreMoneyCheck 同口径);确认成交时资金校验独立再跑,特批链路在那边仍有兜底。 /// /// /// /// public trade SaveTrade(trade req, bool ignoreMoneyCheck = false, bool allowMarginCreditSplit = false) { //资金来源必填(现金/授信,默认现金):保存前归一,兜住 DMA 自动建仓等绕过录入页的链路 if (req.TradeType == "收益互换" && string.IsNullOrWhiteSpace(req.MarginFundSource)) { req.MarginFundSource = ConsFundTag.Cash; } // §2.3 保存前授信拆单(2026-08-26 业务确认):保存检查授信→不足拦截(UI 确认)→拆完再保存。 // 特批(ignoreMoneyCheck)语义为全现金不占授信,跳过拆单 if (!ignoreMoneyCheck && req.TradeType == "收益互换") { new SwapFundTagService(this).PreSplitMarginLegsByCredit(req, allowMarginCreditSplit); } var um = checkUnderlying(req); trade dbTrade = new trade(); //交易保存处理(PrepareInitialMargin 在此把 trade_Initial_Margin 折算进 req.InitialMargin, //资金校验须在其后取值,否则互换表单不填平铺 InitialMargin 时校验会按 0 放行) var tradeNumberGenerated = PrepareTrade(req, TradeSourceEnum.系统交易, um); // R4 簿记资金校验:可用资金(现金结存+授信−已使用授信,口径见 RealtimePnlCalc.TradeCanBeConfirm) // 需覆盖 应付预付金+成交金额;不足时抛 TradeLackOfMoneyException——controller 依"允许交易特批"开关 // 走 AdditionalProcessing/LackOfMoney 特批协议(与确认/审批环节一致)或按保存失败拦截。 // ExerciseDate 为空(异常数据)时跳过该校验,避免 TradeCanBeConfirm 内部解引用抛错。 if (!ignoreMoneyCheck && req.ExerciseDate.HasValue && !RealtimePnlCalc.TradeCanBeConfirm(req.ClientId, req, out var fundErrorMsg)) { throw new TradeLackOfMoneyException(fundErrorMsg); } var trans = DbContext.Database.BeginTransaction(); try { var isAddNew = req.id == 0; dbTrade = isAddNew ? InnerSaveNewTrade(req, tradeNumberGenerated) : InnerSaveEditTrade(req, out var changeConfirmStatus); if (req.SalesCommission != null) { req.SalesCommission.ClientId = dbTrade.ClientId; } if (PS.Config.ErpElement.SalesCommissionCalculation == "默认" || PS.Config.ErpElement.SalesCommissionCalculation == "" || PS.Config.ErpElement.SalesCommissionCalculation == null) { new SalesCommissionDataService(this).SaveTradeCommission(dbTrade.id, req.SalesCommission, false); } else if (PS.Config.ErpElement.SalesCommissionCalculation == "公式1") { if (req.SalesCommission?.SalesIds != null && req.SalesCommission.SalesIds.Any()) { new SalesCommissionDetailDataService(this).SaveTradeCommissionDetail(dbTrade.id, req.SalesCommission.ClientId, req.SalesCommission.Commission, req.SalesCommission.CommissionFixed, req.SalesCommission.SalesIds, false); } else { new SalesCommissionDetailDataService(this).RemoveTradeCommissionDetail(dbTrade.id); } } //保证金模板V2迁移:按交易页选择的保证金模板(MarginTemplateName 存模板V2名称)维护交易模板绑定 SyncTradeMarginTemplate(dbTrade); trans.Commit(); } finally { trans.Dispose(); } return dbTrade; } /// /// 保证金模板V2迁移:按交易上选择的保证金模板(MarginTemplateName 存预付金模板V2的名称)维护 trade_margin_template 绑定。 /// 未选择或名称匹配不到有效自定义模板时仅清除旧绑定,不新增(保持无预付金占用的现状)。 /// private void SyncTradeMarginTemplate(trade dbTrade) { var olds = DbContext.trade_margin_template.Where(x => x.TradeId == dbTrade.id).ToList(); DbContext.trade_margin_template.RemoveRange(olds); if (!string.IsNullOrWhiteSpace(dbTrade.MarginTemplateName)) { //匹配自定义+全局默认模板(重名时优先自定义),模板"适用结构"需包含 收益互换 var template = DbContext.margin_template_v2 .Where(x => x.Name == dbTrade.MarginTemplateName && !x.IsForClient && x.IsValid && x.TradeTypes.Contains("收益互换")) .OrderBy(x => x.IsDefault) .FirstOrDefault(); if (template != null) { DbContext.trade_margin_template.Add(new trade_margin_template() { TradeId = dbTrade.id, ValueDate = new DateTime(2000, 1, 1), MarginTemplateId = template.id, IsLatest = true, OptId = UserId, OptName = UserName, OptDate = DateTime.Now }); } } DbContext.SaveChanges(); } /// /// 单标的生成开仓事件 /// /// public void AddPositionEvent(trade td, string optLog) { var initials = DbContext.swap_position.Where(x => x.SwapTradeId == td.id && x.IsInitial && !x.Invalid).ToList(); if (td.StructureType == "多空组合") { initials = initials.Where(x => x.PosiDirection == 0).ToList(); } new SwapFlowEventService(this).InitEvent(initials, td, optLog); } /// /// 删除互换交易 /// /// public void deleteTrade(int id, bool addLog = true) { var dbTrade = DbContext.trade.Find(id); if (dbTrade == null) { throw new ServiceException("找不到交易信息"); } dbTrade.OptId = UserId; dbTrade.OptName = UserName; dbTrade.OptDate = OptDate; dbTrade.ValidState = ConsGlobal.InValid; var tradeNumber = dbTrade.TradeNumber; if (!string.IsNullOrWhiteSpace(dbTrade.TradeNumber)) { dbTrade.TradeNumber = dbTrade.TradeNumber.Insert(0, "XX"); var contractBll = new TradeContractBLL(UserId, UserName); contractBll.DeleteConfirmBook(dbTrade.id); } var swapPositions = DbContext.swap_position.Where(x => x.SwapTradeId == id).ToList(); swapPositions.ForEach(x => { x.Invalid = true; x.OptTime = OptDate; }); new SwapEventService(this).AddSwapEventDate(DateTime.Now.Date, id, (int)SwapEventTypeEnum.删除, string.Empty, 0, false); DeleteTradeDetials(id); DeleteTradeCashInCashOut(id); DbContext.SaveChanges(); } private void DeleteTradeDetials(int tradeId) { var eodSwapPosiSql = $"{nameof(eod_swap_position.SwapTradeId)}={tradeId}"; var eodSwapSql = $"{nameof(eod_swap.SwapTradeId)}={tradeId}"; DbContext.BulkDelete(eodSwapPosiSql); DbContext.BulkDelete(eodSwapSql); } public void DeleteTradeCashInCashOut(int tradeId) { var predicate_cashIncashOut = PredicateBuilder.Create(x => x.TradeId == tradeId); var delCashInCashOutArr = DbContext.ClientCashInCashOut.Where(predicate_cashIncashOut); DbContext.ClientCashInCashOut.RemoveRange(delCashInCashOutArr); // R4:授信出入记录与资金记录同生命周期,随资金记录一并清理(回退到开仓/删除交易), // 重新确认时按最新标签与额度重写,避免授信占用悬挂 new ClientCreditInoutService(this).RemoveByTrade(tradeId); } /// /// 单标的初始化实时持仓 /// /// public void InitialPosition(trade td, bool save = false) { var swapPostions = DbContext.swap_position.Where(x => x.SwapTradeId == td.id); var initials = swapPostions.Where(x => x.IsInitial); var positions = swapPostions.Where(x => !x.IsInitial); if (td.StructureType == "多空组合") { initials = initials.Where(x => x.PosiDirection == 0); positions = swapPostions.Where(x => !x.IsInitial && x.PosiDirection == 0); } List _Positions = new List(positions); DbContext.swap_position.RemoveRange(positions); foreach (var item in initials) { if (string.IsNullOrEmpty(item.PosiNumber)) { item.PosiNumber = $"{td.TradeNumber}-{item.id}"; } if (td.StructureType == "多空组合") { var position = _Positions.FirstOrDefault(x => x.PositionId == item.id); if (position == null) { position = item.Clone(); position.id = 0; position.PositionId = item.id; position.IsInitial = false; DbContext.swap_position.Add(position); } } else { var position = item.Clone(); position.id = 0; position.PositionId = item.id; position.IsInitial = false; DbContext.swap_position.Add(position); } } if (save) { DbContext.SaveChanges(); } } //新增交易保存 private trade InnerSaveNewTrade(trade req, bool tradeNumberGenerated) { var dbTrade = req.Clone(); // 新增场景同样从 swap_positions 取 InitYtm,无则默认 0,避免数据库 NOT NULL 约束报错 dbTrade.InitYtm = req.swap_positions.FirstOrDefault(p => p.InitYtm != null)?.InitYtm ?? 0; DbContext.trade.Add(dbTrade); InnerSaveTrade(true, dbTrade, ""); return dbTrade; } /// /// 新增互换交易并确认开仓 /// /// /// /// /// /// /// /// public trade NewSwapTrade(swap_flow_merge flowMerge, Client client, AssetUnit asset, underlying_manager underlying, SwapFloatRate swapFloatRate, string clearingAgency, string structureType = "普通债券类收益互换", bool cashNeedAfter = false) { trade td; using (var trans = BeginTransaction()) { int SwapEndDays = UnderlyingHelper.GetApplicableMarginRate(client.id,underlying.UnderlyingCode,flowMerge.OccurTime)?.swap_days??14; td = PrepareTrade(flowMerge, client, asset, underlying, SwapEndDays, structureType); PrepareTradeExtend(flowMerge, td, underlying, swapFloatRate); if (!GuolianContractNoGenerator.IsGuolianSwapTrade(td)) { td.TradeNumber = BizLogicSingleton.Instance.GenerateTradeNumberBeforeConfirm(td, DbContext); } PrepareSwapTrade(td, TradeSourceEnum.系统交易, underlying); DbContext.trade.Add(td); DbContext.SaveChanges(); if (GuolianContractNoGenerator.TryGenerateTradeNumberAfterSave(DbContext, td)) { DbContext.SaveChanges(); } flowMerge.SwapTradeNo = td.TradeNumber; td.trade_Initial_Margin = new trade_initial_margin() { TradeId = td.id, MarginType = 1, Direction = (int)SwapDirectionEnum.收取, MarginValue = 0, }; AddTradeMeta(false, td.id, YLErp.DBModels.Consts.ConsTradeMetaKey.MainProtocolCode, client.MainProtocolCode); AddTradeMeta(false, td.id, YLErp.DBModels.Consts.ConsTradeMetaKey.SupProtocolCode, client.SupProtocolCode); AddTradeMeta(false, td.id, YLErp.DBModels.Consts.ConsTradeMetaKey.TradingPlace, "柜台市场"); if (!string.IsNullOrEmpty(clearingAgency)) { AddTradeMeta(false, td.id, YLErp.DBModels.Consts.ConsTradeMetaKey.ClearingAgency, clearingAgency); } TradeExtendJson tradeExtendJson = new TradeExtendJson() { FlowBookMode = (int)FlowBookModeEnum.先进先出, FloatingPnlAnnualized = false, NeedOpenFee = true, OpenFeeType = 1, Direction = 1, InterestCalcMode = "10", SettlementRules = cashNeedAfter ? 1 : 0, }; if (structureType != "普通债券类收益互换") { tradeExtendJson.FlowBookMode = (int)FlowBookModeEnum.加权平均; } td.trade_extend = new trade_extend() { TradeId = td.id, ExtendJson = JsonHelper.Serialize(tradeExtendJson) }; DbContext.trade_initial_margin.Add(td.trade_Initial_Margin); DbContext.trade_extend.Add(td.trade_extend); foreach (var item in td.swap_positions) { item.SwapTradeId = td.id; DbContext.swap_position.Add(item); } DbContext.SaveChanges(); trans.Commit(); } new TradeConfirmService(UserInfo).SwapTradeConfirm(td, "流水自动簿记确认交易", true, flowMerge.OccurTime, "流水自动"); return td; } /// /// 准备框架合约数据 /// /// /// /// /// /// private trade PrepareTrade(swap_flow_merge flowMerge, Client client, AssetUnit asset, underlying_manager underlying,int swapEndDays, string structureType = "普通债券类收益互换") { trade td = new trade() { TradeType = "收益互换", BuySell = "卖出", StartDate = flowMerge.OccurTime, TradeDate = flowMerge.OccurTime, TraderId = asset.TraderIdsInt.FirstOrDefault(), TraderName = asset.TraderNamesList.FirstOrDefault(), MarginTemplateName = null, OpponentRole = "乙方", StructureType = structureType, InitialMargin = 0, ClientId = client.id, ClientNumber = client.Number, ClientName = client.Name, AssetId = asset.id, AssetBookName = asset.Name, Notional = Convert.ToDouble(flowMerge.TradingQtyAbs), TradeAmount = Convert.ToDouble(flowMerge.TradingQtyAbs), StockEqvNotional = Math.Round(Convert.ToDouble(flowMerge.TradingAmount), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero), IsAutoGenerate = true, }; if (flowMerge.SettleDate.HasValue) { td.StartDate = flowMerge.SettleDate.Value; } td.ExerciseDate = QdpCalendarHelper.GetNonHolidayDefore(td.StartDate.Value.AddDays(swapEndDays)); PrepareUnderlying(td, underlying); PrepareSwapTradeClient(td, underlying); td.TradeOldStatus = ConsTrade.新增待确认; td.OptId = UserId; td.OptName = UserName; td.OptDate = OptDate; td.CreateDate = OptDate; td.ValidState = "Valid"; td.TradeSource = "系统交易"; td.TradeStatus = ConsTrade.确认成交; td.InitYtm = RoundSwapBondNetPriceAndYtm(flowMerge.InitYtm) ?? 0; return td; } /// /// 准备框架合约明细数据 /// /// /// /// /// private void PrepareTradeExtend(swap_flow_merge flowMerge, trade td, underlying_manager underlying, SwapFloatRate swapFloatRate) { swap_position floatPosition = new swap_position() { PosiDirection = (int)SwapDirectionEnum.支付, PositionType = flowMerge.BsType, UnderlyingCode = flowMerge.UnderlyingCode, CountRatio = underlying.CountRatio, ContractSize = Convert.ToDecimal(underlying.ContractSize), PosiNetPrice = flowMerge.TradingAmountFeeAvgAbs, PosiGrossPrice = Math.Round( flowMerge.TradingAmountAvg, underlying.IsBond() ? ConsGlobal.PriceRound : ConsGlobal.SwapDeliveryPriceRound, MidpointRounding.AwayFromZero), PosiNetFeePrice = flowMerge.TradingAmountNetFeeAvg ?? 0, PosiNetNoFeePrice = flowMerge.TradingAmountNetAvg ?? 0, PosiQuantity = flowMerge.TradingQtyAbs, PosiNotionalValue = Math.Round(flowMerge.TradingAmount, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero), PosiTradingFeePending = flowMerge.TradingFeePending, PosiTradingFee = 0, PosiTradingFeeUnit = 0, SwapTradeId = td.id, PosiMatuirityDate = td.ExerciseDate.Value, PosiStartDate = td.StartDate.Value, IsInitial = true, OptTime = DateTime.Now, OptId = UserInfo.UserId, OptName = UserInfo.UserName, UnderlyingInstrumentType = underlying.UnderlyingInstrumentType, InitYtm = RoundSwapBondNetPriceAndYtm(flowMerge.InitYtm) }; td.swap_positions.Add(floatPosition); swap_position interestPosition = new swap_position() { InterestDirection = flowMerge.BsType == (int)PositionTypeFlag.Long ? (int)SwapDirectionEnum.收取 : (int)SwapDirectionEnum.支付, InterestRateDefault = 0, InterestMode = (int)InterestModeEnum.标的期初全价, InterestPrincipalFix = 0, InterestType = 1, IsAnnualized = true, SwapTradeId = td.id, PosiMatuirityDate = td.ExerciseDate.Value, PosiStartDate = td.StartDate.Value, IsInitial = true, interest_rest_days = 7, OptTime = DateTime.Now, OptId = UserInfo.UserId, OptName = UserInfo.UserName, }; if (swapFloatRate != null) { interestPosition.InterestRateDefault = flowMerge.BsType == (int)PositionTypeFlag.Long ? (swapFloatRate.LongPricePoint ?? 0) : (swapFloatRate.ShortPricePoint ?? 0) * -1; interestPosition.InterestRateDefault = interestPosition.InterestRateDefault * 0.0001m; interestPosition.FloatRateUnderlyingCode = swapFloatRate.BaseUnderlyingCode; interestPosition.interest_rule = swapFloatRate.interest_rule; interestPosition.interest_rest_days = swapFloatRate.interest_rest_days; interestPosition.IsAnnualized = swapFloatRate.is_annualized ?? false; interestPosition.InterestType = swapFloatRate.interest_type??0; } var interval = new IntervalModel() { Date = td.ExerciseDate.Value, Rate = interestPosition.InterestRateDefault, Settlement = 0 }; List intervalModels = new List() { interval }; interestPosition.InterestSwapInterval = JsonConvert.SerializeObject(intervalModels); td.swap_positions.Add(interestPosition); } //修改交易保存 private trade InnerSaveEditTrade(trade req, out bool changeConfirmStatus) { var dbTrade = DbContext.trade.Find(req.id); if (dbTrade == null) { throw new ServiceException("保存失败,数据不存在"); } dbTrade.SalesCommission = new SalesCommissionDataService(OptUser).GetTradeCommissionInfo(dbTrade.id); if (dbTrade.SalesCommission?.Commission == null && req.SalesCommission == null) { dbTrade.SalesCommission = null; } if (!ConsGlobal.IsValid(dbTrade.ValidState)) { throw new NotSupportedException("不支持对已无效的交易进行修改:" + dbTrade.TradeNumber); } if (valuedateBLL.SystemDate.EditTradeNeedRemark) { req.MetaDic.TryGetValue("remark", out var remark); req.MetaDic.Remove("remark"); } //使用明确的IsUsePremiumRate布尔值 if (!req.IsUsePremiumRate.HasValue && dbTrade.IsUsePremiumRate.HasValue) { req.IsUsePremiumRate = false; } req.CalcFlag = dbTrade.CalcFlag; var oldClientId = dbTrade.ClientId; var oldTradeStatus = dbTrade.TradeStatus; var tradeDateChanged = dbTrade.TradeDate != req.TradeDate; //移除价格观察 if (dbTrade.UnderlyingId != req.UnderlyingId) { RemoveEntities(x => x.TradeId == req.id); } //交易编号调整 var canGenerateTradeNumber = false; if (req.ValidState == "InValid") { if (!string.IsNullOrWhiteSpace(dbTrade.TradeNumber)) { new TradeDocumentDataService(this).DeleteConfirmBook(dbTrade.id); if (dbTrade.TradeNumber.StartsWith("CW")) { req.TradeNumber = dbTrade.TradeNumber.Replace("CW", "XX"); } else // if (PS.Config.Is光大光子) { req.TradeNumber = dbTrade.TradeNumber.Insert(0, "XX"); } } } else { canGenerateTradeNumber = string.IsNullOrWhiteSpace(req.TradeNumber); if (canGenerateTradeNumber) { req.TradeNumber = dbTrade.TradeNumber; } } req.ContractVersion = dbTrade.ContractVersion; req.PairTrade = dbTrade.PairTrade; req.CreateDate = dbTrade.CreateDate; //确认成交状态的交易修改后变为原始标记 req.DividendDate = new DateTime(2000, 1, 1); //记录交易变更信息 var changsStr = string.Empty; changeConfirmStatus = true; var changes = DataChangeHelper.GetDataChanges(dbTrade, req); var dbTradeMeta = DbContext.TradeMeta.Where(a => a.TradeId == dbTrade.id).ToDictionary(a => a.MetaKey, b => b.MetaValue); var changMetas = new List>(); var keys = new string[] { "交易场所", "清算机构", "主协议编号", "补充协议编号" }; foreach (var key in keys) { bool isdbMeta = dbTradeMeta.TryGetValue(key, out string oldVal); bool isreqMeta = req.MetaDic.TryGetValue(key, out string newVal); if (!isdbMeta && !isreqMeta || oldVal == newVal || (string.IsNullOrWhiteSpace(oldVal) && string.IsNullOrWhiteSpace(newVal))) { continue; } var changeMeta = new List() { "", key, oldVal, newVal }; changMetas.Add(changeMeta); } if (changMetas.Any()) { changes.AddRange(changMetas); } changsStr = changes.ToJson(); if (dbTrade.TradeStatus != ConsTrade.新增待确认) { //修改备注和簿记不需要提交审核和审批 changeConfirmStatus = !changes.All(n => string.IsNullOrEmpty(n[0]) || n[0] == nameof(trade.Comments) || n[0] == nameof(trade.AssetId) || n[0] == nameof(trade.AssetBookName)); } //更新数据库实体 UpdateEntity(dbTrade, req); //交易状态处理 if (dbTrade.CheckTradeUpdate == Convert.ToInt32(TradeCheckEnum.StatusOfNew) || dbTrade.TradeStatus == ConsTrade.确认成交) { if (changeConfirmStatus) { dbTrade.TradeStatus = ConsTrade.修改待确认; dbTrade.CheckTradeUpdate = Convert.ToInt32(TradeCheckEnum.StatusOfOld); dbTrade.CheckStatus = null; } } else if (dbTrade.TradeStatus != ConsTrade.修改待确认) { dbTrade.TradeStatus = ConsTrade.新增待确认; dbTrade.CheckTradeUpdate = Convert.ToInt32(TradeCheckEnum.StatusOfOld); dbTrade.CheckStatus = null; //如果是新增待确认并且修改了客户,需要重新生成交易编号 if (oldClientId != dbTrade.ClientId && canGenerateTradeNumber && !GuolianContractNoGenerator.IsGuolianSwapTrade(dbTrade)) { dbTrade.TradeNumber = BizLogicSingleton.Instance.GenerateTradeNumberBeforeConfirm(dbTrade, DbContext); } } dbTrade.TradeOldStatus = oldTradeStatus; //重置历史数据(20210928:如果是当天修改也支持重置) var resetHisData = dbTrade.TradeStatus == "新增待确认" || tradeDateChanged || valuedateBLL.ValueDate == dbTrade.TradeDate; if (resetHisData) { dbTrade.TradeSavedVol = PS.Config.Is厦门象屿 ? dbTrade.TradeCloseVolatility : dbTrade.TradeOpenVolatility; if (PS.Config.ErpElement.VolMode == Configuration.VolModeEnum.VolSurface) { dbTrade.TradeSavedVol = dbTrade.Vol; } } dbTrade.trade_Initial_Margin = req.trade_Initial_Margin; dbTrade.trade_extend = req.trade_extend; dbTrade.swap_positions = req.swap_positions; dbTrade.MetaDic = req.MetaDic; dbTrade.InitYtm = req.swap_positions.FirstOrDefault(p => p.InitYtm != null)?.InitYtm; InnerSaveTrade(false, dbTrade, changsStr, changeConfirmStatus); return dbTrade; } //用于交易(不能是结构化主交易)入库后续处理 //changeConfirmStatus:保存交易修改时需要 private void InnerSaveTrade(bool isAddNew, trade dbTrade, string changsStr, bool changeConfirmStatus = false) { //保存修改 DbContext.SaveChanges(); if (isAddNew && GuolianContractNoGenerator.TryGenerateTradeNumberAfterSave(DbContext, dbTrade)) { DbContext.SaveChanges(); } SaveTradeExend(dbTrade); SaveTradeMargin(dbTrade); SaveSwapPositions(dbTrade.swap_positions, dbTrade); ClearSwapPositions(dbTrade); //交易元数据 if (dbTrade.MetaDic.Any()) { foreach (var kv in dbTrade.MetaDic) { if (isAddNew) { if (!string.IsNullOrEmpty(kv.Value)) { AddTradeMeta(false, dbTrade.id, kv.Key, kv.Value); } } else { AddOrUpdateTradeMeta(false, dbTrade.id, kv.Key, kv.Value); } } } if (changeConfirmStatus) { if (GuolianSwapConfirmBookScope.IsPreApprovalTrade(dbTrade)) { new GuolianSwapConfirmBookCleanupService(this).InvalidateAfterEdit(dbTrade); } else { //删除交易确认书 TODO new TradeDocumentDataService(this).DeleteBooksAfterEditTrade(dbTrade); } } int eventType = isAddNew ? (int)SwapEventTypeEnum.新增交易 : (int)SwapEventTypeEnum.修改交易; new SwapEventService(UserInfo).AddSwapEventDate(dbTrade.TradeDate.Value, dbTrade.id, eventType, "", 0, true, "", true); DbContext.SaveChanges(); } /// /// 添加交易元数据 /// private void AddTradeMeta(bool saveChanges, int tradeId, string metaKey, string metaValue) { DbContext.TradeMeta.Add(new TradeMeta { TradeId = tradeId, MetaKey = metaKey, MetaValue = metaValue, CreateTime = DateTime.Now }); if (saveChanges) { DbContext.SaveChanges(); } } /// /// 添加或更新交易元数据 /// private void AddOrUpdateTradeMeta(bool saveChanges, int tradeId, string metaKey, string metaValue) { new TradeMetaService(this).AddTradeMeta(tradeId, metaKey, metaValue, saveChanges); } /// /// 保存交易扩展信息 /// /// private void SaveTradeExend(trade dbTrade) { RemoveEntities(x => x.TradeId == dbTrade.id); var trade_extend = dbTrade.trade_extend.Clone(); trade_extend.TradeId = dbTrade.id; DbContext.trade_extend.Add(trade_extend); } /// /// 保存交易预付金信息 /// public void SaveTradeMargin(trade dbTrade) { RemoveEntities(x => x.TradeId == dbTrade.id); var trade_Initial_Margin = dbTrade.trade_Initial_Margin.Clone(); trade_Initial_Margin.TradeId = dbTrade.id; DbContext.trade_initial_margin.Add(trade_Initial_Margin); } private bool PrepareTrade(trade req, TradeSourceEnum dataSource, underlying_manager um) { bool tradeNumberGenerated = false; PrepareInitialMargin(req); var isAddNew = req.id == 0; if (isAddNew) { req.TradeStatus = ConsTrade.新增待确认; req.TradeSource = dataSource.ToString(); if (string.IsNullOrWhiteSpace(req.TradeNumber) || (PS.Config.Company == CompanyEnum.光大光子 && (req.TradeNumber == "STG" || req.TradeNumber == "STD" || req.TradeNumber == "FWD" || req.TradeNumber == "SPD"))) { tradeNumberGenerated = true; if (req.UnderlyingCode != null && req.UnderlyingCode != "") { PrepareUnderlying(req, um); } if (!GuolianContractNoGenerator.IsGuolianSwapTrade(req)) { req.TradeNumber = BizLogicSingleton.Instance.GenerateTradeNumberBeforeConfirm(req, DbContext); } } else if (DbContext.trade.Any(n => n.TradeNumber == req.TradeNumber)) { throw new ServiceException($"交易编号'{req.TradeNumber}'已经存在"); } else if (PS.Config.ErpElement.UpperTradeNumber) { req.TradeNumber = req.TradeNumber.ToUpperInvariant(); } } else if (!string.IsNullOrWhiteSpace(req.TradeNumber)) { if (DbContext.trade.Any(n => n.id != req.id && n.TradeNumber == req.TradeNumber)) { throw new ServiceException($"交易编号'{req.TradeNumber}'已经存在"); } if (PS.Config.ErpElement.UpperTradeNumber) { req.TradeNumber = req.TradeNumber.ToUpperInvariant(); } } PrepareSingleTrade(req, dataSource, isAddNew, um); req.PrincipalRateWrite = req.PrincipalRateWrite.IsNormalize() ? req.PrincipalRateWrite : null; return tradeNumberGenerated; } private static void PrepareInitialMargin(trade req) { // 初始预付金依赖最终入库的名义本金,须先统一金额精度,避免两者无法勾稽。 req.StockEqvNotional = Math.Round(req.StockEqvNotional, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); req.InitialMargin = Convert.ToDouble(req.trade_Initial_Margin.MarginValue); if (req.trade_Initial_Margin.MarginType == 0) { req.InitialMargin = req.StockEqvNotional == 0 ? 0 : Convert.ToDouble(req.trade_Initial_Margin.MarginValue) * req.StockEqvNotional; } } //准备单个交易 private trade PrepareSingleTrade(trade req, TradeSourceEnum dataSource, bool isAddNew, underlying_manager um) { //准备交易基础信息 PrepareBaseInfo(req, um); PrepareSwapTrade(req, dataSource, um); req.OptId = UserId; req.OptName = UserName; req.OptDate = OptDate; if (isAddNew) { req.ValidState = ConsGlobal.Valid; req.CreateDate = DateTime.Now; req.TradeSavedVol = PS.Config.Is厦门象屿 ? req.TradeCloseVolatility : req.TradeOpenVolatility; if (PS.Config.ErpElement.VolMode == Configuration.VolModeEnum.VolSurface) { req.TradeSavedVol = req.Vol; } } return req; } //准备互换交易 private void PrepareSwapTrade(trade req, TradeSourceEnum dataSource, underlying_manager um) { PrepareSwapTradeClient(req, um); req.OriginalNotional = req.Notional; req.BuySell = "买入"; if (req.StructureType == ClientMarginTypeEnum.多空组合.ToString()) { req.BuySell = req.trade_extend.ExtendObj.Direction == 1 ? "买入" : "卖出"; } else { var swapPosition = req.swap_positions.FirstOrDefault(x => !string.IsNullOrEmpty(x.UnderlyingCode)); req.BuySell = swapPosition.PosiDirection == 1 ? "买入" : "卖出"; var currencyRate = new EodCurrencyRateService(UserInfo).GetCurrencyRate(req.QuoteCurrency, req.SettlementCurrency, req.TradeDate.Value , seekPreday: dataSource == TradeSourceEnum.系统交易); var tradingFees = req.swap_positions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode)).Sum(x => x.PosiTradingFee * (x.PosiDirection == 1 ? -1 : 1)); req.TradePrice = Convert.ToDouble(tradingFees) * currencyRate; req.SpotPrice = Convert.ToDouble(swapPosition.PosiNetPrice); } req.Strike = null; req.StockEqvNotional = Math.Round(req.StockEqvNotional, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); req.OriginalStockEqvNotional = req.StockEqvNotional; req.StockEqvNotionalReal = req.StockEqvNotional; } //互换交易 private void PrepareSwapTradeClient(trade req, underlying_manager _underlying) { var client = PrepareBaseTradeClient(req); if (client.EvaluateExpireDate < req.TradeDate) { throw new ServiceException("适当性评估已经过期,只有在适当性有效期内才可以新开仓"); } req.ClientName = client.Name; req.SettlementCurrency = client.SettlementCurrency; } //客户信息 private Client PrepareBaseTradeClient(trade req) { if (req.ClientId < 1) { throw new ServiceException("客户信息不存在"); } var client = DbContextFactory.GetClientDbContext(OptUser).client.FirstOrDefault(n => n.id == req.ClientId); if (client == null) { throw new ServiceException("客户信息不存在"); } if (client.ProcessStatus == "已休眠" || client.ProcessStatus == "已销户") { if (!client.ProcessOptDate.HasValue || client.ProcessOptDate.Value < req.TradeDate.Value) { var tradeDateStr = req.TradeDate.Value.ToString("yyyy-MM-dd"); var processDateStr = client.ProcessOptDate?.ToString("yyyy-MM-dd HH:mm:ss"); throw new ServiceException($"客户'{client.Name}'{client.ProcessStatus}并且处理日期{processDateStr}早于交易日期'{tradeDateStr}',无法保存交易!"); } } else if (client.ProcessStatus != "已开户") { throw new ServiceException($"客户'{client.Name}'尚未完成开户!"); } return client; } //准备交易基础信息(标的、簿记、交易日期) private void PrepareBaseInfo(trade req, underlying_manager um) { if (!req.TradeDate.HasValue) { throw new ServiceException("交易日期 必须填写"); } if (req.TradeDate.Value.Year < 2000) { throw new ServiceException("交易日期 填写错误:" + req.TradeDate.Value.ToString("yyyy-MM-dd")); } if (QdpCalendarHelper.IsHoliday(req.TradeDate.Value) && req.TradeType != "现金流交易") { throw new ServiceException("交易日期不能是假日:" + req.TradeDate.Value.ToString("yyyy-MM-dd")); } //如果起始日没有值,默认赋值交易日 if (!req.StartDate.HasValue) { req.StartDate = req.TradeDate; } //-------------------------------------- // 标的信息 //-------------------------------------- if (req.StructureType != ClientMarginTypeEnum.多空组合.ToString()) { PrepareUnderlying(req, um); } //-------------------------------------- // 簿记帐户 //-------------------------------------- if (req.AssetId < 1) { throw new ServiceException("请选择簿记账户"); } var _assetUnit = DbContext.assetunit.Where(n => n.id == req.AssetId) .Select(n => new AssetUnitDto { id = n.id, Name = n.Name, BookCategory = n.BookCategory, TraderIds = n.TraderIds, UserGroup = n.UserGroup }).FirstOrDefault(); if (_assetUnit == null) { throw new ServiceException("簿记账户不存在"); } req.AssetBookName = _assetUnit.Name; //-------------------------------------- // 交易员信息(如果没有交易员,查找簿记账户中的交易员) //-------------------------------------- if (string.IsNullOrWhiteSpace(req.TraderName)) { //有交易员id,通过id查名字 if (req.TraderId > 0) { req.TraderName = UserBLL.GetNameById(req.TraderId); } else { //当前登录用户作为交易员 req.TraderId = UserId; req.TraderName = UserName; } } req.TradeAmount = Convert.ToDouble(req.swap_positions.Sum(s => s.PosiQuantity)); } //准备交易标的信息 public underlying_manager PrepareUnderlying(trade req, underlying_manager _underlying) { var underlyingCode = req.UnderlyingCode ?? string.Empty; if (_underlying == null) { _underlying = UnderlyingDataProvider.GetUnderlying(underlyingCode); } if (_underlying == null) { throw new ServiceException("标的信息不存在:" + req.UnderlyingCode); } if (_underlying.RequiresMaturityDate() && _underlying.MaturityDate == null) { throw new ServiceException("标的到期日不存在:" + req.UnderlyingCode); } req.UnderlyingId = _underlying.id; req.UnderlyingCode = _underlying.UnderlyingCode; req.VarietyId = _underlying.UnderlyingTypeId; req.UnderlyingAssetClass = _underlying.UnderlyingType; req.UnderlyingAssetName = _underlying.UnderlyingName; req.MaturityDate = _underlying.RequiresMaturityDate() ? _underlying.MaturityDate : null; req.UnderlyingInstrumentType = _underlying.GetMainType(); req.CountRatio = _underlying.CountRatio; //权益类标的关联的交易如果没有设置分红率则取标的上的分红率,如果标的分红率为空则取0 if (!req.DividendRate.HasValue && _underlying.CalcTypeIsStock()) { req.DividendRate = _underlying.DividendRate ?? 0; } return _underlying; } /// /// 收益互换初始预付金校验 /// /// /// private string checkMarginRate(trade trade) { var errorMsg = ""; var marginValue = trade.trade_Initial_Margin.MarginValue; if (trade.trade_Initial_Margin.MarginType == (int)TradeMarginTypeEnum.固定金额) { marginValue = trade.StockEqvNotional == 0 ? 0 : trade.trade_Initial_Margin.MarginValue / Convert.ToDecimal(trade.StockEqvNotional); } //多空组合 if (ClientMarginTypeEnum.多空组合.ToString().Equals(trade.StructureType)) { if (marginValue < 0.25m) { errorMsg = $"根据监管要求权益类多空组合型收益互换预付金不得低于25%,当前交易预付金率为:{marginValue * 100}%,不符合监管要求!"; } } else { //获取标的品种 var underlingData = new UnderlyingDataProvider(); var swapposition = trade.swap_positions.FirstOrDefault(x => !string.IsNullOrEmpty(x.UnderlyingCode)); trade.UnderlyingCode = swapposition == null ? "" : swapposition.UnderlyingCode; var underlying = underlingData.GetUnderlying(trade.UnderlyingCode); var variety = underlingData.GetVariety(underlying.UnderlyingTypeId); if (variety == null) { errorMsg = $"所设置标的未对应品种,请设置对应品种后再进行交易!"; } else { //股票、窄基股票指数及其产品、信用债 初始预付金比率 必须大于等于100% if ((variety.AssetType == "个股" || (variety.AssetType == "指数" && "窄基指数".Equals(variety.IndexType))) && marginValue < 1) { errorMsg = $"所交易标的为股票或窄基指数及其产品、信用债,初始预付金比例不得低于100%!,当前交易预付金率为:{marginValue * 100}%,不符合监管要求!"; } else if ((variety.AssetType == "指数" && "宽基指数".Equals(variety.IndexType)) && Convert.ToDecimal(marginValue) < 0.5m) { //挂钩宽基股票指数及其产品,无对应期货品种的 向单一交易对手方收取的预付金比例不得低于50% errorMsg = $"所交易标的挂钩宽基股票指数及其产品,无对应期货品种的,初始预付金比例不得低于50%!,当前交易预付金率为:{marginValue * 100}%,不符合监管要求!"; } else if (marginValue < Convert.ToDecimal(variety.Margin ?? 0.0)) { //有对应期货或集中交易品种的 不得低于品种设置的最低预付金比例 errorMsg = $"所交易标的对应期货或集中交易品种的,初始预付金比例不得低于品种[{variety.VarietyName}]所设置最低预付金比例{variety.Margin * 100}%!,当前交易预付金率为:{marginValue * 100}%,不符合监管要求!"; } } } return errorMsg; } /// /// 标的校验 /// /// /// private underlying_manager checkUnderlying(trade trade) { if (trade.StructureType == ClientMarginTypeEnum.多空组合.ToString()) { return null; } var underlingData = new UnderlyingDataProvider(); var posi = trade.swap_positions.Where(x => x.PosiDirection > 0).FirstOrDefault(); if (posi == null) { throw new ServiceException("缺少标的信息"); } var underlying = underlingData.GetUnderlying(posi.UnderlyingCode); // 标的代码 if (underlying == null) { throw new ServiceException("标的代码 必须在系统中存在"); } if (underlying.UnderlyingInstrumentType == "Stock" && !underlying.IsCombined()) { var limit = LimitRangeEnum.Swap; var limitStatus = new StockBlackWhiteService(UserInfo).GetStockBlackWhiteList(limit, out var Codes); if (Codes != null) { if (limitStatus == Codes.Contains(underlying.UnderlyingCode)) { if (limitStatus) { throw new ServiceException("标的代码 填写错误,存在于黑名单中,请求值:" + underlying.UnderlyingCode); } else { throw new ServiceException("标的代码 填写错误,不存在于白名单中,请求值:" + underlying.UnderlyingCode); } } } } trade.UnderlyingCode = underlying.UnderlyingCode; trade.UnderlyingId = underlying.id; trade.UnderlyingAssetClass = underlying.UnderlyingType; trade.UnderlyingInstrumentType = underlying.UnderlyingInstrumentType; trade.UnderlyingAssetName = underlying.UnderlyingName; return underlying; } #endregion #region 互换交易查询 /// /// 互换交易列表查询 /// /// /// /// public SearchListResult SearchList(SwapTradeQueryRequest req, out tradeGridSum gsum) { var query = CreateTradeQuery(req); var retListResult = new SearchListResult(); if (req.ValueDate != null) { var dateStr = req.ValueDate?.ToString("yyyy-MM-dd"); var _query = from t in query join sp in DbContext.swap_position on t.id equals sp.SwapTradeId where sp.InterestSwapInterval.Contains(dateStr) && !sp.Invalid select t; retListResult = _query.ToSearchList(req); } else { retListResult = query.ToSearchList(req); } gsum = new tradeGridSum(); if (query.Any()) { gsum.TradePriceSum = query.Sum(q => q.TradePrice); } var tradeids = retListResult.rows.Select(r => r.id).ToList(); var list = DbContext.trade_contract_r.Where(O => tradeids.Contains(O.TradeId) && O.IsValid && O.Type == "交易确认书").AsEnumerable(); var contractCodeDict = list.GroupBy(O => O.TradeId).ToDictionary(K => K.Key, V => V.LastOrDefault().ContractCode); //获取销售提成信息 foreach (var item in retListResult.rows) { item.ContractCode = contractCodeDict.TryGetValue(item.id, out var code) ? code : ""; item.SalesCommission = new SalesModule.SalesCommissionDataService(OptUser).GetTradeCommissionInfo(item.id); } retListResult.Sum = gsum; return retListResult; } private Expression> BuildTradeQuery(TradeReq req) { var predicate = PredicateBuilder.Create(n => n.ValidState != "InValid" && n.TradeType == "收益互换"); if (req.UserAssets != null && req.UserClients != null) { predicate = predicate.And(n => req.UserAssets.Contains(n.AssetId) || req.UserClients.Contains(n.ClientId)); } if (req.AssetIdList.Any()) { predicate = predicate.And(d => req.AssetIdList.Contains(d.AssetId)); } if (!string.IsNullOrEmpty(req.TraderNames)) { predicate = predicate.And(d => req.TraderNamesList.Contains(d.TraderId)); } if (!string.IsNullOrEmpty(req.TradeNumber)) { predicate = predicate.And(d => d.TradeNumber.Contains(req.TradeNumber)); } if (req.TradeStatusList != null) { predicate = predicate.And(d => req.TradeStatusList.Contains(d.TradeStatus)); } if (req.AssetIdList.Any()) { predicate = predicate.And(d => req.AssetIdList.Contains(d.AssetId)); } if (req.ClientIdsInt.Any()) { predicate = predicate.And(d => req.ClientIdsInt.Contains(d.ClientId)); } if (req.StartDateStart != DateTime.MinValue) { predicate = predicate.And(d => d.StartDate >= req.StartDateStart); } if (req.StartDateEnd != DateTime.MinValue) { var StartDateTemp = req.StartDateEnd.AddDays(1); predicate = predicate.And(d => d.StartDate < StartDateTemp); } if (req.ExerciseDateEnd == null) { req.ExerciseDateEnd = DateTime.MaxValue; } if (req.ExerciseDateStart == null) { req.ExerciseDateStart = DateTime.MinValue; } predicate = predicate.And(d => d.ExerciseDate >= req.ExerciseDateStart && d.ExerciseDate <= req.ExerciseDateEnd); return predicate; } /// /// 获取互换交易详情 /// /// /// public trade GetSwapTrade(int intid) { var tradeObj = DbContext.trade.Find(intid); if (tradeObj == null) { return null; } //需要审批或者复核的交易都会显示行权审核提交按钮 tradeObj.MetaDic = new TradeMetaService(UserInfo).GetTradeMeta(tradeObj.id); tradeObj.trade_Initial_Margin = DbContext.trade_initial_margin.FirstOrDefault(x => x.TradeId == intid); if (tradeObj.trade_Initial_Margin == null) { tradeObj.trade_Initial_Margin = new trade_initial_margin(); } tradeObj.trade_extend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == intid); tradeObj.swap_positions = DbContext.swap_position.ActiveByTrade(intid).ToList(); tradeObj.swap_positions = tradeObj.swap_positions.Where(x => x.PosiQuantity > 0 || x.InterestDirection > 0).ToList(); var intervalPositions = tradeObj.swap_positions.Where(x => string.IsNullOrEmpty(x.UnderlyingCode) && x.IsInitial).ToList(); var intervalPositionIds = intervalPositions.Select(s => s.id).ToList(); var tradeObervations = DbContext.trade_obervation.Where(x => intervalPositionIds.Contains(x.PositionId)); foreach (var position in intervalPositions) { position.Obervation = tradeObervations.FirstOrDefault(s => s.PositionId == position.id); } var positionIds = tradeObj.swap_positions.Select(x => x.id); tradeObj.ClientCashInCashOutList = DbContext.ClientCashInCashOut.Where(x => x.ValidState != ConsGlobal.InValid && x.TradeId == intid).OrderBy(o => o.HappenDate).ToList(); List eventTyps = new List() { (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 }; var allSwapEvents = GetSwapEvents(intid, eventTyps).OrderBy(o => o.id).ToList();//历史平仓/互换记录 tradeObj.swap_Events.AddRange(allSwapEvents); if (tradeObj.swap_Events.Count > 0) { tradeObj.eod_swaps = DbContext.eod_swap.Where(x => x.SwapTradeId == intid).ToList(); tradeObj.eod_swaps.ForEach(x => { var eod = tradeObj.eod_swaps.Where(t => t.ValueDate < x.ValueDate && t.id != x.id).OrderBy(o => o.ValueDate).FirstOrDefault(); if (eod != null) { x.CloseStartDate = eod.ValueDate; } else { x.CloseStartDate = x.ValueDate; } }); } foreach (var item in tradeObj.swap_positions.Where(x => x.IsInitial)) { if (!string.IsNullOrEmpty(item.UnderlyingCode)) { item.underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(item.UnderlyingCode); if (item.underlying != null && item.underlying.IsBond() && !string.IsNullOrEmpty(item.underlying.ExJson)) { var bond = JsonHelper.Deserialize(item.underlying.ExJson); item.underlying.UnderlyingFullName = bond.UnderlyingFullName; item.underlying.UnderlyingIssuer = bond.UnderlyingIssuer; item.underlying.IssueSize = bond.IssueSize; } } } return tradeObj; } /// /// 撤回审批 /// /// public void TradeRevoke(int intid) { var tradeObj = DbContext.trade.Find(intid); if (tradeObj == null) { return; } if (!new TradeApprovalOAService(this).ArchiveActiveForTrade(tradeObj, out var errorMessage)) { throw new ServiceException(errorMessage); } var eventTypes = new List() { (int)SwapEventTypeEnum.修改交易, (int)SwapEventTypeEnum.新增交易, (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.互换 }; var lastEvent = DbContext.swap_event.Where(x => x.SwapTradeId == intid && eventTypes.Contains(x.EventType) && !x.Invalid).OrderByDescending(o => o.OptTime).FirstOrDefault(); if (tradeObj.TradeStatus == ConsTrade.平仓待复核 || tradeObj.TradeStatus == ConsTrade.互换待复核) { tradeObj.TradeStatus = ConsTrade.确认成交; } else { if (lastEvent != null && lastEvent.EventType == (int)SwapEventTypeEnum.修改交易) { tradeObj.TradeStatus = ConsTrade.修改待确认; } else { tradeObj.TradeStatus = ConsTrade.新增待确认; } } tradeObj.CheckStatus = null; tradeObj.ProcessOrderId = 0; tradeObj.IsApproval = false; if (lastEvent != null) { lastEvent.Invalid = true; } DbContext.SaveChanges(); return; } private IQueryable CreateTradeQuery(SwapTradeQueryRequest req) { var predicate = PredicateBuilder.Create(n => n.ValidState != "InValid" && n.TradeType == "收益互换"); var query = DbContext.trade.Where(predicate); if (req.ClientIds != null && req.ClientIds.Count > 0) { query = query.Where(d => req.ClientIds.Contains(d.ClientId)); } if (req.TradeIds != null && req.TradeIds.Count > 0) { query = query.Where(d => req.TradeIds.Contains(d.TraderId)); } if (req.AssetIds != null && req.AssetIds.Count > 0) { query = query.Where(d => req.AssetIds.Contains(d.AssetId)); } if (req.TradeDateStart.HasValue) { query = query.Where(d => d.TradeDate >= req.TradeDateStart); } if (req.TradeDateEnd.HasValue) { var TradeDateTemp = req.TradeDateEnd.Value.AddDays(1); query = query.Where(d => d.TradeDate < TradeDateTemp); } if (req.ExerciseDateEnd.HasValue) { query = query.Where(d => d.ExerciseDate <= req.ExerciseDateEnd); } if (req.ExerciseDateStart.HasValue) { query = query.Where(d => d.ExerciseDate >= req.ExerciseDateStart); } if (!string.IsNullOrEmpty(req.TradeNumber)) { query = query.Where(d => d.TradeNumber.Contains(req.TradeNumber)); } if (req.TradeStatus != null && req.TradeStatus.Count > 0) { query = query.Where(d => req.TradeStatus.Contains(d.TradeStatus)); } if (string.IsNullOrEmpty(req.sidx)) { req.sidx = "id"; req.sord = "desc"; }; query = query.OrderByDescending(s => s.OptDate); return query; } #endregion /// /// 获取互换交易对手方 交易编码 /// /// /// public List GetSwapTradeNumbers(int clientId) { var tradeNumbers = DbContext.trade.Where(t => t.ClientId == clientId && t.TradeType == "收益互换" && t.TradeStatus == ConsTrade.确认成交 && t.StructureType == ClientMarginTypeEnum.多空组合.ToString() && t.ValidState != "InValid").Select(s => new SelectItem { Text = s.TradeNumber, Value = s.id.ToString() }).ToList(); return tradeNumbers; } /// /// 保存交易腿信息 /// /// 持仓腿信息 /// 交易id /// 成交日期 public void SaveSwapPositions(List swap_positions, trade td) { var swapPositionList = DbContext.swap_position.Where(x => x.SwapTradeId == td.id); var swapPositionIds = swap_positions.Select(s => s.id).ToList(); var intervalPositionIds = swapPositionList.Where(x => string.IsNullOrEmpty(x.UnderlyingCode) && x.IsInitial).Select(s => s.id); var obervations = DbContext.trade_obervation.Where(o => intervalPositionIds.Contains(o.PositionId)); var originalSwapPositions = swapPositionList.Where(x => x.IsInitial); var realTimeSwapPositions = swapPositionList.Where(x => !x.IsInitial); var deleteSwapPositions = swapPositionList.Where(x => !swapPositionIds.Contains(x.id)); if (td.StructureType == "多空组合") { deleteSwapPositions = swapPositionList.Where(x => !swapPositionIds.Contains(x.id) && x.PosiDirection == 0); realTimeSwapPositions = swapPositionList.Where(x => !swapPositionIds.Contains(x.id) && x.PosiDirection == 0 && !x.IsInitial); var positions = swapPositionList.Where(x => x.PosiDirection > 0); var direction = td.trade_extend.ExtendObj.Direction; foreach (var item in positions) { item.PosiDirection = direction; } } DbContext.swap_position.RemoveRange(realTimeSwapPositions); DbContext.swap_position.RemoveRange(deleteSwapPositions); DbContext.trade_obervation.RemoveRange(obervations); foreach (var swap in swap_positions) { var position = originalSwapPositions.FirstOrDefault(x => x.id == swap.id); if (position == null) { position = new swap_position(); } position.ContractSize = swap.ContractSize; position.CountRatio = swap.CountRatio; var ratio = swap.PositionType == (int)PositionTypeFlag.Short ? -1 : 1; position.id = swap.id; position.PositionType = swap.PositionType; position.PosiTradingFee = swap.PosiTradingFee; position.PosiTradingFee=Math.Round(position.PosiTradingFee, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); position.PosiTradingFeeUnit = swap.PosiTradingFeeUnit; position.PosiFeeType = swap.PosiFeeType; position.PosiTradingFeePending = swap.PosiTradingFeePending; position.PosiTradingFeePending = Math.Round(position.PosiTradingFeePending, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); position.UnderlyingCode = swap.UnderlyingCode; position.UnderlyingInstrumentType = swap.UnderlyingInstrumentType; position.PosiDirection = swap.PosiDirection; position.PosiGrossPrice = swap.PosiGrossPrice; position.PosiNetPrice = swap.PosiQuantity == 0 ? 0 : (position.PosiGrossPrice + (position.PosiTradingFeePending / swap.PosiQuantity) * ratio); position.PosiNetPrice = Math.Round(position.PosiNetPrice, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero); position.PosiNetNoFeePrice = swap.PosiNetNoFeePrice; position.PosiNetFeePrice = swap.PosiQuantity == 0 ? 0 : (swap.PosiNetNoFeePrice + (position.PosiTradingFeePending / swap.PosiQuantity) * ratio); position.PosiNetFeePrice = Math.Round(position.PosiNetFeePrice??0, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero); position.PosiNotionalValue = Math.Round(swap.PosiNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); position.PosiQuantity = swap.PosiQuantity; position.InterestDirection = swap.InterestDirection; position.InterestMode = swap.InterestMode; position.InterestRateDefault = swap.InterestRateDefault; position.InterestPrincipalFix = Math.Round(swap.InterestPrincipalFix, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); position.InterestSwapInterval = swap.InterestSwapInterval; position.PosiStartDate = td.StartDate.Value; position.PosiMatuirityDate = td.ExerciseDate.Value; position.SwapTradeId = td.id; position.IsInitial = true; position.OptTime = DateTime.Now; position.OptId = UserInfo.UserId; position.OptName = UserInfo.UserName; position.IsAnnualized = swap.IsAnnualized; position.HappenDate = swap.HappenDate; position.Currency = swap.Currency; position.InterestType = swap.InterestType; position.FloatRate = swap.FloatRate; position.FloatRateUnderlyingCode = swap.FloatRateUnderlyingCode; position.interest_rest_days = swap.interest_rest_days; position.interest_rule = swap.interest_rule; position.category_tag = string.IsNullOrEmpty(swap.category_tag) ? "互换利率" : swap.category_tag; // R4:资金标签(fund_tag 单列)随录入保存用户逐腿选择,确认成交时系统在同列定稿 position.FundTag = string.IsNullOrWhiteSpace(swap.FundTag) ? null : swap.FundTag; position.InitYtm = swap.InitYtm; if (position.InitYtm != null && position.InitYtm > 0) { td.InitYtm = position.InitYtm; } if (position.id == 0) { DbContext.swap_position.Add(position); DbContext.SaveChanges(); position.PosiNumber = $"{td.TradeNumber}-{position.id}"; } if (swap.Obervation != null) { swap.Obervation.OptTime = DateTime.Now; swap.Obervation.OptId = UserInfo.UserId; swap.Obervation.OptName = UserInfo.UserName; swap.Obervation.PositionId = position.id; DbContext.trade_obervation.Add(swap.Obervation); } } } /// /// 修改清除互换持仓所有信息 /// /// public void ClearSwapPositions(trade td) { var tradeId = td.id; var swapFlowEvents = DbContext.swap_flow_event.Where(x => x.SwapTradeId == tradeId && x.DataState != (int)SwapFlowDateStateEnum.废弃); var eodSwapPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == tradeId); var eodSwaps = DbContext.eod_swap.Where(x => x.SwapTradeId == tradeId); var clientcashinouts = DbContext.ClientCashInCashOut.Where(x => x.TradeId == tradeId && x.Action == ClientCashInCashOut.系统操作_应付预付金); // R4:修改清除 应付预付金 资金记录时同步清理对应授信出入记录,重确认时按最新标签重写; // 追加保证金资金记录(阶段四 EOD 产生)不在本方法删除范围,其授信占用一并保留 new ClientCreditInoutService(this).RemoveByTrade(tradeId, keepAdditionalMargin: true); swapFlowEvents.ForEach(x => { x.DataState = (int)SwapFlowDateStateEnum.废弃; }); //DbContext.swap_flow_event.RemoveRange(swapFlowEvents); DbContext.eod_swap_position.RemoveRange(eodSwapPositions); DbContext.eod_swap.RemoveRange(eodSwaps); DbContext.ClientCashInCashOut.RemoveRange(clientcashinouts); } /// /// 修改展期信息,互换观察日到期日期修改 /// /// 互换交易id /// 旧到期日 /// 新到期日 public void UpdateObservationDay(int tradeId, DateTime oldExerciseDate, DateTime exerciseDate) { var dateStr = exerciseDate.ToString("yyyy-MM-dd"); var oldDateStr = oldExerciseDate.ToString("yyyy-MM-dd"); var interestPostions = DbContext.swap_position.Where(x => x.SwapTradeId == tradeId && string.IsNullOrEmpty(x.UnderlyingCode) && !x.Invalid).ToList(); interestPostions.ForEach(x => { if (x.SwapIntervalList.Count == 1 && x.SwapIntervalList.Any(t => t.Date == oldExerciseDate)) { x.InterestSwapInterval = x.InterestSwapInterval.Replace(oldDateStr, dateStr); } }); } /// /// 交易回退 /// /// /// public trade GetTrade(int tradeId) { var td = DbContext.trade.Find(tradeId); return td; } /// /// 互换交易回退 /// /// 交易id /// 回退日期 /// 回到开仓 public void TradeBack(int tradeId, DateTime valueDate, bool del = false) { var td = DbContext.trade.Find(tradeId); if (td == null) { throw new ServiceException("交易不存在"); } bool backToBegin = td.TradeDate == valueDate; var swapPositions = DbContext.swap_position.ActiveByTrade(tradeId).ToList(); td.trade_extend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == td.id); //展期 var swapEvent = DbContext.swap_event.Where(x => x.SwapTradeId == tradeId && x.EventType == (int)SwapEventTypeEnum.展期 && !x.Invalid && x.ValueDate <= valueDate).OrderByDescending(o => o.ValueDate).FirstOrDefault(); var trans = DbContext.Database.BeginTransaction(); try { var swapEventService = new SwapEventService(this); td.TradeStatus = "确认成交"; td.ProcessOrderId = 0; td.ProcessStatus = null; if (backToBegin) { td.StockEqvNotional = Math.Round(td.OriginalStockEqvNotional ?? 0, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); td.UnWindDate = null; td.HasPartialUnWind = null; SingleTradeBackToBegin(td, swapPositions); } else { // 按日期回退时只恢复 valueDate 之前最近实际收盘的快照;当天及以后数据会在 // InvalidTradeOptionDatasByDate 中清理。这样回退到除权日 D 会回到 D-1 的 // 1000 份/100 基线并让重收盘重新应用公司行为;回退到 D+1 则保留 D 的 2000 份/50。 TradeBackByDate(td, valueDate, swapPositions); } if (swapEvent != null)//展期 { swapEventService.DeleteExtensionTime(swapEvent.id); } var corporateActionEvents = DbContext.swap_event .Where(x => !x.Invalid && x.SwapTradeId == tradeId && x.EventType == (int)SwapEventTypeEnum.公司行为 && x.ValueDate >= valueDate) .OrderByDescending(x => x.id) .ToList(); InvalidTradeOptionDatasByDate(tradeId, valueDate, backToBegin); var rollbackEvent = swapEventService.AddSwapEventDate( valueDate, tradeId, (int)SwapEventTypeEnum.回退, string.Empty, 0, false, $"交易回退至{valueDate:yyyy年MM月dd日}"); // 公司行为原事件保持有效作为不可篡改审计;回退事件通过 BackId 指向本次 // 回退影响的最新公司行为事件,后续重收盘会追加新的公司行为事件。 rollbackEvent.BackId = corporateActionEvents.FirstOrDefault()?.id ?? 0; DbContext.SaveChanges(); if (del) { new TradeRiskCheckLogService(UserInfo).DeleteLog(td.TradeNumber, valueDate);//重置风控记录 } if (backToBegin) //重置开仓记录 { DeleteTradeCashInCashOut(tradeId); DeleteTradeDetials(tradeId); DbContext.SaveChanges(); if (del) { swapPositions.ForEach(x => { x.Invalid = true; x.OptTime = OptDate; }); td.ValidState = "InValid"; var tradeNumber = td.TradeNumber; td.TradeNumber = td.TradeNumber.Insert(0, "XX"); td.OptDate = OptDate; swapEventService.AddSwapEventDate(valueDate, tradeId, (int)SwapEventTypeEnum.删除, string.Empty, 0, false); DbContext.SaveChanges(); new TradeDocumentDataService(this).InnerDeleteConfirmBook(tradeId); trans.Commit(); return; } //new SwapEodPositionService(this).InitSaveEodSwapPosition(td); var happenDate = td.TradeDate.Value; if (td.trade_extend != null && !td.trade_extend.ExtendObj.NeedOpenFee) { happenDate = td.ExerciseDate.Value; } ResetMarginAmount(td, happenDate, swapPositions); } //new SwapFlowService(this).DeleteSwapFlow(tradeId, valueDate, backToBegin); trans.Commit(); } catch (Exception ex) { trans.Rollback(); throw ex; } finally { trans.Dispose(); } } /// /// 重新补录预付金记录(回退到开仓后重补,与 SwapTradeConfirm 同口径) /// /// /// /// private void ResetMarginAmount(trade td, DateTime happenDate, List swapPositions) { var cashSvc = new ClientCashInCashOutService(this); cashSvc.SaveSwapTradeClientCash(td, td.TradePrice ?? 0, happenDate, 0); // R4:重补同样走标签分配(授信占用/拆单/现金流水); // 特批标志在回退重补场景不可得,按当前剩余额度重新分配(回退即重新簿记) var fundTagSvc = new SwapFundTagService(this); var generateMarginLegs = new List(); foreach (var marginPositions in swapPositions.Where(x => x.HappenDate != null && x.IsInitial && x.InterestMode != (int)InterestModeEnum.追加预付金).GroupBy(g => g.HappenDate)) { var marginHappenDate = marginPositions.Key; foreach (var marginPosition in marginPositions) { marginPosition.Obervation = DbContext.trade_obervation.FirstOrDefault(x => x.PositionId == marginPosition.id); bool generateMargin = true; if (marginPosition.Obervation != null) { if (!marginPosition.Obervation.IsDeductPrincipal) { generateMargin = false; } } if (generateMargin) { generateMarginLegs.Add(marginPosition); } } } fundTagSvc.ApplyMarginFundTags(td, generateMarginLegs, cashSvc, false); //标签定稿(含可能的拆单)后重克隆实时持仓:TradeBack 的克隆先于定稿生成, //重克隆使实时腿继承定稿标签、拆单新拆出的现金腿也获得克隆(平仓返还分流查的是实时腿标签) InitialPosition(td); // 合约维度盯市+无预付金腿:重建交易级(positionId=0)初始预付金记录(与 SwapTradeConfirm 一致,回退重补场景)。 // 有预付金腿的互换由上面按腿重建,不在此重复生成。 // R4:资金来源是预付金腿上的录入项,无腿即无从选择授信,本交易级记录恒为现金。 var resetWatchClient = DataCacheProvider.GetClientDataSource().GetData(td.ClientId); var hasPrepayLeg = swapPositions != null && swapPositions.Any(x => x.InterestMode == (int)InterestModeEnum.初始预付金 || x.InterestMode == (int)InterestModeEnum.追加预付金); if (resetWatchClient != null && resetWatchClient.MarginWatchRule == 0 && !hasPrepayLeg) { var initMargin = cashSvc.GetInitialMarginAmount(td); if (initMargin > 0) { cashSvc.SaveSwapTradeClientCash(td, initMargin, happenDate, 0, ClientCashInCashOut.系统操作_应付预付金); } } } /// /// 获取持仓腿信息 /// /// 叫一起集合 /// 标的代码 /// public IEnumerable GetSwapPositions(List tradeIds, HashSet _underlyingCodeFilterSet) { Expression> expression = x => tradeIds.Contains(x.SwapTradeId) && !string.IsNullOrEmpty(x.UnderlyingCode) && x.IsInitial && !x.Invalid; if (_underlyingCodeFilterSet != null) { expression = expression.And(x => _underlyingCodeFilterSet.Contains(x.UnderlyingCode)); } return DbContext.swap_position.Where(expression); } /// /// 单标的回退到初始 /// /// /// private void SingleTradeBackToBegin(trade td, List swapPositions) { var positions = swapPositions.Where(x => !x.IsInitial).ToList(); var marginPositions = swapPositions.Where(x => x.IsInitial && x.InterestMode == (int)InterestModeEnum.追加预付金).ToList(); var orignPositions = swapPositions.Where(x => x.IsInitial && x.InterestMode != (int)InterestModeEnum.追加预付金).ToList(); DbContext.swap_position.RemoveRange(positions); DbContext.swap_position.RemoveRange(marginPositions); td.TradeAmount = 0; foreach (var item in orignPositions) { var interest = item.Clone(); interest.id = 0; interest.PositionId = item.id; interest.IsInitial = false; // R4:回到开仓保留 fund_tag(用户录入的选择),重新确认时按当前额度在同列定稿; // 授信出入记录已随资金记录清理,重确认时重写 td.TradeAmount = td.TradeAmount + Convert.ToDouble(interest.PosiQuantity); DbContext.swap_position.Add(interest); } } /// /// 回到某个事件之前 /// /// /// private void TradeBackByDate(trade td, DateTime valueDate, List swapPositions) { SwapEodPositionService eodPositionService = new SwapEodPositionService(this); SwapDealService swapDealService = new SwapDealService(this); // 回退基线必须是 valueDate 之前最近一个实际 EOD,而不是 valueDate-1 自然日。 // 例如周一/节假日后的 valueDate 没有周日 EOD 时,AddDays(-1) 会得到空集合, // 实时腿仍保留除权后的数量/价格。回退到除权日 D 选择 D 前基线并由下面的 // InvalidTradeOptionDatasByDate 删除 D 及以后 EOD;回退到 D+1 则会选择 D, // 保留 D 已生效的公司行为。ex_dividend_info 本身不删除,重收盘 D 会再应用一次。 var eodSwapPositionList = eodPositionService.GetLatestEodPositionsBefore(td.id, valueDate); // 没有 valueDate 之前的有效 EOD 时,eodSwapPositionList 为空;这是“没有可证明基线”的情况, // 下面不会伪造数量/价格或重算公司行为,只保留当前实时持仓并继续清理回退日之后的数据。 var swapFlowEvents = DbContext.swap_flow_event.Where(x => x.SwapTradeId == td.id && x.EventDate >= valueDate && x.DataState > (int)SwapFlowDateStateEnum.废弃).ToList(); var positions = swapPositions.Where(x => x.PosiDirection > 0 && !x.IsInitial).ToList(); foreach (var posi in positions) { var eodPosi = eodSwapPositionList.FirstOrDefault(x => x.PositionId == posi.PositionId); if (eodPosi != null) { posi.InterestAmount = eodPosi.RealizedInterest; posi.InterestFeePending = eodPosi.InterestFeePending; posi.InterestPrincipalFix = Math.Round(eodPosi.InterestPrincipalFix, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); posi.PosiTradingFeePending = eodPosi.PosiFeePending; posi.PosiDividendIncome = eodPosi.PosiDividendSum; posi.PosiQuantity = eodPosi.PosiQuantity; posi.PosiNetPrice = eodPosi.PosiNetPrice; posi.PosiGrossPrice = eodPosi.PosiGrossPrice; posi.PosiNetFeePrice = eodPosi.PosiNetFeePrice; posi.PosiNetNoFeePrice = eodPosi.PosiNetNoFeePrice; posi.PosiNotionalValue = Math.Round(eodPosi.PosiNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); if (posi.PosiDirection > 0) { td.StockEqvNotional = Math.Round(Convert.ToDouble(posi.PosiNotionalValue), ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); td.TradeAmount = Convert.ToDouble(posi.PosiQuantity); } } // eodPosi 为空时刻意不改 posi:回退只能使用已落库的历史快照,不能把缺失数据 // 猜成 0 或交易初始值,否则会把未验证的 Fund 除权数量带入后续收盘。 } td.UnWindDate = null; td.UnWindNotional = null; //DbContext.swap_position.RemoveRange(positions); //swapDealService.UpdateInitalPosition(valueDate,td, swapPositions, swapFlowEvents); DbContext.swap_flow_event.RemoveRange(swapFlowEvents); var clientCashs = DbContext.ClientCashInCashOut.Where(x => x.TradeId == td.id && x.HappenDate >= valueDate).ToList(); DbContext.ClientCashInCashOut.RemoveRange(clientCashs); DbContext.SaveChanges(); } /// /// 多空组合回到初始 /// /// private void LongshortTradeBackToBegin(List swapPositions) { var positions = swapPositions.Where(x => !x.IsInitial || x.InterestDirection == 0).ToList(); var orignPositions = swapPositions.Where(x => x.IsInitial && x.InterestDirection > 0).ToList(); foreach (var item in positions) { item.Invalid = true; } //DbContext.swap_position.RemoveRange(positions); foreach (var item in orignPositions) { var interest = item.Clone(); interest.id = 0; interest.PositionId = item.id; interest.IsInitial = false; DbContext.swap_position.Add(interest); } } /// /// 废弃或删除回退日期之后的数据 /// /// /// private void InvalidTradeOptionDatasByDate(int tradeId, DateTime valueDate, bool backToBegin) { // 所有清理条件都采用闭区间起点 [valueDate, +∞):回退到除权日 D 要删除 D 当天 // 已应用的 EOD/流水,随后重收盘 D 才会从 D-1 快照重新套一次系数;回退到 D+1 // 不会删除 D,因而保留 D 已生效的 2000 份/50。valueDate 之前的快照始终保留, // 作为 TradeBackByDate 的唯一可验证基线。 var swapEvents = DbContext.swap_event.Where(x => !x.Invalid && x.SwapTradeId == tradeId && x.ValueDate >= valueDate).ToList(); var swapEodPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == tradeId && x.ValueDate >= valueDate); var swapEods = DbContext.eod_swap.Where(x => x.SwapTradeId == tradeId && x.ValueDate >= valueDate); var swapFlowEvents = DbContext.swap_flow_event.Where(x => x.SwapTradeId == tradeId && x.EventDate >= valueDate); var firstConfirm = false; swapEvents.ForEach(x => { // 公司行为事件是不可篡改审计日志。回退只追加回退事件,不把原始公司 // 行为事件置无效;否则无法追溯交易曾经经历过的调整。 if (x.EventType == (int)SwapEventTypeEnum.公司行为) { return; } if (backToBegin && !firstConfirm && x.EventType == (int)SwapEventTypeEnum.确认交易) { firstConfirm = true; } else { x.Invalid = true; } }); swapFlowEvents.ForEach(x => { if (backToBegin && x.EventType == (int)SwapFlowEventTypeEnum.开仓) { } else { x.DataState = (int)SwapFlowDateStateEnum.废弃; } }); //DbContext.swap_flow_event.RemoveRange(swapFlowEvents); DbContext.eod_swap_position.RemoveRange(swapEodPositions); DbContext.eod_swap.RemoveRange(swapEods); } } }