using BaseOUDAL; using Newtonsoft.Json; using NPOI.POIFS.Properties; using System.Linq.Expressions; using YLErp.DBModels; using YLErp.DBModels.Consts; using YLErp.DBModels.Enums; using YLErp.Helpers; using YLErp.Model; using YLErp.Model.Enum; using YLErp.Models; using YLErp.Modules.DataProviderModule; using YLErp.Modules.EodModule; using YLErp.QdpModule; namespace YLErp.Modules.SwapModule { /// /// 互换流水日终归档服务 /// public class SwapEodPositionService : SwapTradeBaseService { private List marginTypes = new List() { (int)InterestModeEnum.追加预付金, (int)InterestModeEnum.初始预付金 }; private static readonly IYcLogger Log = LogFactory.GetLogger(typeof(SwapEodPositionService).FullName); public SwapEodPositionService(OptUserInfo optUser) : base(optUser) { } public SwapEodPositionService(YLBaseService baseService) : base(baseService) { } #region 可测试化接缝(Seams)——override 这些虚方法可在测试中替换 DB/外部调用,生产代码行为不变 /// 持久化 eod 持仓记录(生产: DbContext.Add;测试: 收集到列表) protected virtual void PersistEodSwapPosition(eod_swap_position position) { if (position.id == 0) { DbContext.eod_swap_position.Add(position); } else { UpdateDbOption(position); } } /// 保存所有变更(生产: DbContext.SaveChanges;测试: 计数) protected virtual void SaveAllChanges() { DbContext.SaveChanges(); } /// 获取汇率(生产: EodCurrencyRateService;测试: 返回固定值) protected virtual double GetCurrencyRate(string quoteCurrency, string settlementCurrency, DateTime valueDate, bool seekPreday, CurrencyRateType currencyRateType) { return new EodCurrencyRateService(UserInfo).GetCurrencyRate(quoteCurrency, settlementCurrency, valueDate, seekPreday, currencyRateType); } /// /// 计算利息腿利息明细(生产: new SwapDealService(this).GetInterests;测试: 用StubSwapDealService内存算) /// 参数与 SwapDealService.GetInterests 完全一致,保证行为不变。 /// protected virtual List CalcSwapInterests( trade td, trade_extend tradeExtend, DateTime valueDate, DateTime unwindDate, List eodPositions, List positions, decimal posiNotionalValue, decimal posiLongNotionalValue, decimal posiShortNotionalValue, decimal closePosiNotionalValue, decimal closePrecent, int eventType, bool tdClose, bool needPrice, decimal grossPrice, decimal orginPv, bool add = false, bool settment = true, bool newCalcLast = false, List closeList = null) { return new SwapDealService(this).GetInterests(td, tradeExtend, valueDate, unwindDate, eodPositions, positions, posiNotionalValue, posiLongNotionalValue, posiShortNotionalValue, closePosiNotionalValue, closePrecent, eventType, tdClose, needPrice, grossPrice, orginPv, add, settment, newCalcLast, closeList); } // FindTrade 已上提到基类 SwapTradeBaseService(三子类实现一致,消除重复) /// 查找交易扩展(生产: DbContext.trade_extend;测试: 内存字典) protected virtual trade_extend FindTradeExtend(int tradeId) { return DbContext.trade_extend.FirstOrDefault(x => x.TradeId == tradeId); } /// 查找指定日期范围的 eod 持仓(生产: DbContext.eod_swap_position.Where;测试: 内存列表) protected virtual List FindEodSwapPositions(int swapTradeId, DateTime preSettleDate) { return DbContext.eod_swap_position.Where(x => x.SwapTradeId == swapTradeId && !x.Invalid && x.ValueDate >= preSettleDate).ToList(); } /// 查找交易持仓(生产: DbContext.swap_position.Where;测试: 内存列表) protected virtual List FindSwapPositions(int swapTradeId) { return DbContext.swap_position.Where(x => x.SwapTradeId == swapTradeId && !x.Invalid).ToList(); } /// 查找框架合约日终汇总(生产: DbContext.eod_swap.FirstOrDefault;测试: 内存字典) protected virtual eod_swap FindEodSwap(int swapTradeId, DateTime valueDate) { return DbContext.eod_swap.FirstOrDefault(x => x.SwapTradeId == swapTradeId && x.ValueDate == valueDate); } /// 添加互换事件(生产: new SwapEventService(this).AddSwapEventDate;测试: 收集到列表) protected virtual swap_event AddSwapEvent(DateTime tradeDate, int swapTradeId, int eventType, string data, int clientCashId, bool save, string reason) { return new SwapEventService(this).AddSwapEventDate(tradeDate, swapTradeId, eventType, data, clientCashId, save, reason); } /// 在事务中执行(生产: BeginTransaction/Commit/Rollback;测试: 直接执行不包事务) protected virtual void ExecuteInTransaction(Action action) { var trans = DbContext.Database.BeginTransaction(); try { action(); trans.Commit(); } catch { trans.Rollback(); throw; } finally { trans.Dispose(); } } /// 添加资金记录(生产: AddClientCashInCashOut;测试: 收集到计数器) protected virtual int AddClientCash(trade td, double amount, string action, DateTime valueDate) { return AddClientCashInCashOut(td, amount, action, valueDate); } /// 保存框架合约日终汇总(生产: SaveEodSwap私有方法;测试: 收集到列表) protected virtual void SaveEodSwapRecord(trade td, DateTime settleDate, DateTime preSettleDate) { SaveEodSwap(td, settleDate, preSettleDate); } /// 清理旧持仓事件(生产: ClearSwapPositions;测试: 空操作) protected virtual void ClearSwapPositionsForCompose(trade td, DateTime tradeDate, List eventTypes) { ClearSwapPositions(td, tradeDate, eventTypes, false); } /// 获取标的市场价格(生产: UnderlyingCodePrice查缓存+中债估值;测试: 返回固定值) protected virtual decimal GetUnderlyingPrice(string code, DateTime settleDate, out decimal vobp) { return UnderlyingCodePrice(code, settleDate, out vobp); } /// 获取标的缓存数据(生产: DataCacheProvider;测试: 返回内存对象) protected virtual underlying_manager GetUnderlyingData(string underlyingCode) { return DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode); } /// 计算债券付息(生产: BondPaymentService;测试: 返回固定值) protected virtual decimal CalcBondPayment(string underlyingCode, DateTime fromDate, DateTime toDate, decimal qty, int shortRatio, int directionRatio) { return new BondPaymentService(UserInfo).CalcPayment(underlyingCode, fromDate, toDate, qty, shortRatio, directionRatio); } // ---- SwapPositionCompose 路径专用 seam(借鉴 testable 分支)---- /// 查找收盘所需的活跃互换交易(生产: DbContext.trade.Where;测试: 内存列表) protected virtual List FindActiveSwapTrades(DateTime settleDate, IEnumerable clientIds) { var tradePredicate = PredicateBuilder.Create(n => n.ValidState != ConsGlobal.InValid && n.TradeType == "收益互换" && n.TradeDate <= settleDate && n.ExerciseDate >= settleDate && (n.TradeStatus == ConsTrade.确认成交 || n.UnWindDate >= settleDate) ); if (clientIds != null && clientIds.Any()) { tradePredicate = tradePredicate.And(x => clientIds.Contains(x.ClientId)); } return DbContext.trade.Where(tradePredicate).ToList(); } /// 查找交易的所有持仓(含初始+实际,生产: DbContext.swap_position;测试: 内存列表) protected virtual List FindAllSwapPositions(List tradeIds) { return DbContext.swap_position.Where(t => tradeIds.Contains(t.SwapTradeId) && !t.Invalid).ToList(); } /// 批量查找交易扩展(生产: DbContext.trade_extend;测试: 内存列表) protected virtual List FindTradeExtends(List tradeIds) { return DbContext.trade_extend.Where(x => tradeIds.Contains(x.TradeId)).ToList(); } /// 查找指定日期的日终汇总(生产: DbContext.eod_swap;测试: 内存列表) protected virtual List FindEodSwapsByDate(DateTime valueDate) { return DbContext.eod_swap.Where(x => x.ValueDate == valueDate).ToList(); } /// 查找交易在指定日期的完成流水事件(生产: DbContext.swap_flow_event;测试: 内存列表) protected virtual List FindFlowEvents(int swapTradeId, DateTime settleDate) { Expression> eventExpression = x => x.SwapTradeId == swapTradeId && x.DataState == (int)SwapFlowDateStateEnum.完成 && x.EventDate == settleDate; return DbContext.swap_flow_event.Where(eventExpression).ToList(); } #endregion /// /// 多空组合 互换流水合成持仓 /// /// 清算日期 public void SwapFlowEventCompose(DateTime tradeDate) { new SysJobService(UserInfo).UpdateJob("互换流水合成持仓", 41, "互换流水合成持仓进行中"); var eventQueryGroup = DbContext.swap_flow_event.Where(n => n.DataState == (int)SwapFlowDateStateEnum.等待完成 && n.EventDate == tradeDate).AsEnumerable().GroupBy(g => g.SwapTradeId); foreach (var eventQueryGroupItem in eventQueryGroup) { ComposePage(eventQueryGroupItem.Key, eventQueryGroupItem.ToList(), tradeDate); } new SysJobService(UserInfo).UpdateJob("互换流水合成持仓", 42, "互换流水合成持仓完成"); } /// /// 多空组合 互换流水合成持仓 /// /// 清算日期 public void SwapFlowEventCompose(List flowEventIds, DateTime tradeDate) { new SysJobService(UserInfo).UpdateJob("互换流水合成持仓", 41, "互换流水合成持仓进行中"); var eventQueryGroup = DbContext.swap_flow_event.Where(n => flowEventIds.Contains(n.id)).ToList().GroupBy(g => g.SwapTradeId); foreach (var eventQueryGroupItem in eventQueryGroup) { ComposePage(eventQueryGroupItem.Key, eventQueryGroupItem.ToList(), tradeDate, false); } new SysJobService(UserInfo).UpdateJob("互换流水合成持仓", 42, "互换流水合成持仓完成"); } /// /// 收盘生成归档信息 /// /// 结算日期 public void SwapPositionCompose(DateTime settleDate, DateTime preSettleDate, IEnumerable ClientIds) { var dateStr = settleDate.ToString("yyyy-MM-dd"); Log.Info("SwapPositionCompose:" + "settleDate:" + settleDate + " preSettleDate:" + preSettleDate + " ClientIds:" + JsonHelper.Serialize(ClientIds)); var tradeQueryList = FindActiveSwapTrades(settleDate, ClientIds); var tradeIds = tradeQueryList.Select(s => s.id).ToList(); var allTradePositionList = FindAllSwapPositions(tradeIds); var tradePositionList = allTradePositionList.Where(t => t.IsInitial).ToList(); var tradeRealPositionList = allTradePositionList.Where(t => !t.IsInitial).ToList(); var tradeExtendList = FindTradeExtends(tradeIds); var eodSwapList = FindEodSwapsByDate(preSettleDate); List eventTyps = new List() { (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 }; foreach (var td in tradeQueryList) { ExecuteInTransaction(() => { List removeEventTyps = new List() { (int)SwapEventTypeEnum.自动互换 }; bool longShort = td.StructureType == ClientMarginTypeEnum.多空组合.ToString(); ClearSwapPositions(td, settleDate, removeEventTyps, true); var positions = tradePositionList.Where(x => x.SwapTradeId == td.id); var realPositions = tradeRealPositionList.Where(s => s.SwapTradeId == td.id); var posiList = positions.Where(x => x.PosiQuantity > 0).ToList(); var realPosiList = realPositions.ToList(); var interestList = positions.Where(x => x.InterestDirection > 0).ToList(); DateTime posiDate = td.TradeDate.Value;//交易日期 var lastEodSwap = eodSwapList.FirstOrDefault(x => x.SwapTradeId == td.id); //上一交易日无日终归档,且不是交易日期,且当前收盘日期不是交易日期,报错 if (lastEodSwap == null && settleDate > posiDate) { throw new Exception($"交易{td.TradeNumber}在上一交易日【{preSettleDate:yyyy-MM-dd}】未收盘"); } var allEodPositions = FindEodSwapPositions(td.id, preSettleDate); var eodPositions = allEodPositions.Where(x => x.ValueDate == preSettleDate).ToList();//上一日终持仓信息 var tradeExtend = tradeExtendList.FirstOrDefault(x => x.TradeId == td.id); td.trade_extend = tradeExtend; var todyEodPositions = allEodPositions.Where(x => x.ValueDate == settleDate).ToList(); var allPositionQty = realPositions.Sum(x => x.PosiQuantity);//总剩余持仓数量 var orginPv = eodPositions.Sum(s => s.PosiNotionalValue); if (longShort && td.ExerciseDate.Value == settleDate && allPositionQty != 0)//多空组合判断是否已到到期日且无持仓信息 { throw new Exception($"交易【{td.TradeNumber}】到期扔有持仓信息"); } var flowEvents = FindFlowEvents(td.id, settleDate); var preDealDate = GetPreDealDate(td.id, settleDate, eventTyps);//上一次平仓/互换/自动互换处理日期 List autoInterests = new List();//自动互换利息腿信息 //处理浮动腿 var curEodPosis = DealFloatPositions(posiList, realPosiList, eodPositions, todyEodPositions, settleDate, td, preSettleDate, flowEvents); var posiLongNotional = curEodPosis.Where(s => s.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue); var posiShortNotional = curEodPosis.Where(s => s.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue); var closePosiNotional = curEodPosis.Where(s => s.TdCloseQty > 0).Sum(s => s.TdCloseQty * s.ContractSize * s.PosiGrossPrice); var grossPrice = curEodPosis.Where(x => x.PosiDirection > 0).FirstOrDefault()?.PosiGrossPrice ?? 0; //处理利息腿 DealInterests(interestList, eodPositions, todyEodPositions, settleDate, td, flowEvents, autoInterests, lastEodSwap, posiLongNotional, posiShortNotional, closePosiNotional, grossPrice, orginPv); //获取自动互换的 interval 信息,用于确定结算日期 IntervalModel autoInterval = null; foreach (var interest in interestList) { autoInterval = interest.SwapIntervalList.FirstOrDefault(x => x.Date == settleDate && x.Settlement == 1); if (autoInterval != null) break; } // 自动互换(仅利息/预付金,不含分红) DealAutoInterests(autoInterests, td, settleDate, preDealDate, posiLongNotional + posiShortNotional, autoInterval); // 分红独立处理:只要当天有债券需要分红,则生成分红自动互换,与利息互换无关 DealDividends(curEodPosis, td, settleDate, tradeExtend); //多空组合判断是否已到到期日且无持仓信息 if (longShort && td.ExerciseDate.Value == settleDate && allPositionQty == 0) { td.TradeStatus = "已到期"; td.UnWindDate = settleDate; } SaveAllChanges(); }); } } /// /// 框架合约汇总 /// /// /// /// public void SwapEodCompose(DateTime settleDate, DateTime preSettleDate, IEnumerable ClientIds) { Log.Info("SwapEodCompose:" + "settleDate:" + settleDate + " preSettleDate:" + preSettleDate + " ClientIds:" + JsonHelper.Serialize(ClientIds)); var tradePredicate = PredicateBuilder.Create(n => n.ValidState != ConsGlobal.InValid && n.TradeType == "收益互换" && n.TradeDate <= settleDate && n.ExerciseDate >= settleDate && (n.TradeStatus == ConsTrade.确认成交 || n.UnWindDate >= settleDate) ); if (ClientIds != null && ClientIds.Any()) { tradePredicate = tradePredicate.And(x => ClientIds.Contains(x.ClientId)); } var tradeQueryList = DbContext.trade.Where(tradePredicate).ToList(); foreach (var td in tradeQueryList) { Log.Info("【框架合约汇总】处理合约ID为:[" + td.id + "]的数据,收盘时间为:[" + settleDate + "]。开始"); SaveEodSwap(td, settleDate, preSettleDate); Log.Info("【框架合约汇总】处理合约ID为:[" + td.id + "]的数据,收盘时间为:[" + settleDate + "]。结束"); } DbContext.SaveChanges(); } /// /// 处理利息腿归档 /// /// 利息腿持仓信息 /// 上一日终归档持仓信息 /// 当日归档持仓信息 /// 当前结算日期 /// 互换交易主信息 /// 当日平仓/互换信息 /// 自动互换集合 /// 多空组合平仓利息腿信息 /// 上一日终框架合约 protected void DealInterests(List interestList, List eodPositions, List todyEodPositions, DateTime settleDate, trade td, List flowEvents, List autoInterests, eod_swap lastEodSwap, decimal posiLongNational, decimal posiShortNational, decimal closeNational, decimal grossPrice, decimal orginPv) { Log.Info("===================处理利息腿归档===================="); // 添加详细的参数验证日志 Log.Info($"[DealInterests] 参数验证 - settleDate: {settleDate:yyyy-MM-dd}, td.id: {td?.id}, td.TradeNumber: {td?.TradeNumber}"); Log.Info($"[DealInterests] 参数验证 - interestList.Count: {interestList?.Count ?? 0}, eodPositions.Count: {eodPositions?.Count ?? 0}, todyEodPositions.Count: {todyEodPositions?.Count ?? 0}"); Log.Info($"[DealInterests] 参数验证 - flowEvents.Count: {flowEvents?.Count ?? 0}, autoInterests.Count: {autoInterests?.Count ?? 0}"); Log.Info($"[DealInterests] 参数验证 - posiLongNational: {posiLongNational}, posiShortNational: {posiShortNational}, closeNational: {closeNational}, grossPrice: {grossPrice}, orginPv: {orginPv}"); // 验证关键参数 if (td == null) { Log.Info("[DealInterests] 参数验证: td (trade) 为 null"); throw new ArgumentNullException(nameof(td), "交易对象不能为null"); } if (interestList == null) { Log.Info($"[DealInterests] 参数验证: interestList 为 null, td.id: {td.id}"); throw new ArgumentNullException(nameof(interestList), "利息腿列表不能为null"); } if (flowEvents == null) { Log.Info($"[DealInterests] 参数验证: flowEvents 为 null, td.id: {td.id}"); throw new ArgumentNullException(nameof(flowEvents), "流水事件列表不能为null"); } if (autoInterests == null) { Log.Info($"[DealInterests] 参数验证: autoInterests 为 null, td.id: {td.id}"); throw new ArgumentNullException(nameof(autoInterests), "自动互换列表不能为null"); } var hasClose = flowEvents.Any(x => x.EventType == (int)SwapEventTypeEnum.平仓); var hasSwap = flowEvents.Any(x => x.EventType == (int)SwapEventTypeEnum.互换); foreach (var interest in interestList) { Log.Info($"InterestMode is {interest.InterestMode},HappenDate is {interest.HappenDate},settleDate is {settleDate}"); if (interest.InterestMode == (int)InterestModeEnum.追加预付金 && interest.HappenDate > settleDate) { continue; } var eodPosition = eodPositions.FirstOrDefault(x => x.PositionId == interest.id);//上一日日终利息信息 可能不存在 var tdEodPosition = todyEodPositions.FirstOrDefault(x => x.PositionId == interest.id);//当前结算日日终利息信息 var insterval = interest.SwapIntervalList.FirstOrDefault(x => x.Date == settleDate && x.Settlement == 1);//自动互换观察日信息 List dealInterests = new List(); dealInterests.AddRange(flowEvents); var dealInterest = dealInterests.FirstOrDefault(n => n.PositionId == interest.id);//当日是否做过互换或平仓 var swapEvents = flowEvents.Where(x => (x.EventType == (int)SwapEventTypeEnum.互换 || x.EventType == (int)SwapEventTypeEnum.平仓) && x.PositionId == interest.id).ToList(); //如果当日有互换/当日有平仓 不再重新生成或更新 Log.Info($"insterval is {insterval},hasSwap is {hasSwap},hasClose is {hasClose}"); if (insterval != null && !hasSwap) { if (!hasClose)//当日无平仓 { var _autoInterests = SaveAutoEodInterestPosition(eodPosition, tdEodPosition, interest, td, settleDate, insterval, lastEodSwap, posiLongNational, posiShortNational, grossPrice, orginPv); if (_autoInterests.Count > 0) { autoInterests.AddRange(_autoInterests); } } else { var _autoInterests = SaveAutoEodWithCloseInterestPosition(eodPosition, tdEodPosition, interest, td, settleDate, insterval, posiLongNational, posiShortNational, swapEvents, closeNational, true, grossPrice, orginPv); if (_autoInterests.Count > 0) { autoInterests.AddRange(_autoInterests); } } } else if (hasSwap)//当日有互换,根据互换事件重新生成 { SaveEodInterestPosition(eodPosition, tdEodPosition, interest, td, settleDate, swapEvents); } else if (hasClose) { SaveAutoEodWithCloseInterestPosition(eodPosition, tdEodPosition, interest, td, settleDate, insterval, posiLongNational, posiShortNational, swapEvents, closeNational, false, grossPrice, orginPv); } else//无自动互换、互换/平仓,复制上一日终信息,并计算当日新增利息 { SaveEodInterestPositionCopy(eodPosition, tdEodPosition, settleDate, td, interest, lastEodSwap, true, posiLongNational, posiShortNational, grossPrice, orginPv); } } } /// /// 处理浮动腿归档 /// /// /// /// /// /// /// /// /// protected List DealFloatPositions(List posiList, List realPosiList, List eodPositions, List todyEodPositions, DateTime settleDate, trade td, DateTime preSettleDate, List flowEvents) { string settleDateStr = settleDate.ToString("yyyy-MM-dd"); string preSettleDateStr = preSettleDate.ToString("yyyy-MM-dd"); Log.Info($"================开始处理{settleDateStr}浮动腿归档=================="); List list = new List(); Log.Info($"浮动腿归档各项参数如下:\n " + $"settleDate为:{settleDateStr} \n" + $"preSettleDate为:{preSettleDateStr} \n " + $"td为:{td.id} \n " + $"posiList为:{JsonHelper.Serialize(posiList)} \n " + $"realPosiList为:{JsonHelper.Serialize(realPosiList)} \n " + $"eodPositions为:{JsonHelper.Serialize(eodPositions)} \n " + $"todyEodPositions为:{JsonHelper.Serialize(todyEodPositions)} \n " + $"flowEvents为:{JsonHelper.Serialize(flowEvents)} \n "); foreach (var posi in posiList) { Log.Info($"posi为:{JsonHelper.Serialize(posi, false)}"); var eodPosition = eodPositions.Where(x => x.PositionId == posi.id).FirstOrDefault();//上一日日终持仓信息 var tdEodPosition = todyEodPositions.FirstOrDefault(x => x.PositionId == posi.id);//当前结算日日终持仓信息 var unwindEvents = flowEvents.Where(x => x.PositionId == posi.id).ToList();//当前日平仓信息 var realPosition = realPosiList.FirstOrDefault(s => s.PositionId == posi.id); eod_swap_position eodPosi = new eod_swap_position(); if (eodPosition == null) { eodPosi = SaveCurrentEodInitalPosi(posi, td, settleDate, preSettleDate, unwindEvents); } else if (unwindEvents.Count() == 0) { eodPosi = CopyEodPosition(eodPosition, tdEodPosition, td, settleDate, preSettleDate); } else { eodPosi = UpdateEodPosition(posi, eodPosition, tdEodPosition, td, settleDate, preSettleDate, unwindEvents); } Log.Info($"eodPosi为:{JsonHelper.Serialize(eodPosi, false)}"); list.Add(eodPosi); } Log.Info($"================{settleDateStr}浮动腿归档结束=================="); return list; } /// /// 处理自动互换数据 /// /// /// /// /// /// 自动互换观察日信息,用于获取结算日期 /// 当日浮动端EOD持仓 /// 交易扩展信息 /// /// 自动互换(仅利息/预付金,不含分红) /// private void DealAutoInterests(List autoInterests, trade td, DateTime settleDate, DateTime? preDealDate, decimal StockEqvNotional, IntervalModel interval) { if (autoInterests.Count == 0) return; UnwindData unwindData = new UnwindData(); unwindData.SwapTradeId = td.id; unwindData.ValueDate = settleDate; unwindData.StartDate = preDealDate ?? td.StartDate.Value; unwindData.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0); unwindData.PosiNotionalValue = StockEqvNotional; unwindData.PayDate = settleDate; autoInterests.ForEach(x => x.PayDate = settleDate); var premiumModes = new List() { (int)InterestModeEnum.初始预付金, (int)InterestModeEnum.追加预付金 }; var premiumInterests = autoInterests.Where(x => premiumModes.Contains(x.InterestMode)).ToList(); var interestLegs = autoInterests.Where(x => !premiumModes.Contains(x.InterestMode)).ToList(); decimal premiumTotal = 0; premiumInterests.ForEach(x => { var ratio = x.InterestDirection == (int)SwapDirectionEnum.收取 ? -1 : 1; premiumTotal += x.InterestClosePnL * ratio; }); unwindData.SwapMarginRebatePnl = premiumTotal; decimal interestTotal = 0; interestLegs.ForEach(x => { var ratio = x.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1; interestTotal += x.InterestClosePnL * ratio; }); unwindData.SwapCloseAmount = interestTotal ; unwindData.SwapDividendPnl = 0; unwindData.SwapRealizedPnL = unwindData.SwapCloseAmount + unwindData.SwapMarginRebatePnl; SaveAutoSwapDeal(td, autoInterests, unwindData, interval); } /// /// 分红独立处理:当天有债券需要分红时,生成独立的分红自动互换事件 /// private void DealDividends(List curEodPositions, trade td, DateTime settleDate, trade_extend tradeExtend) { if (curEodPositions == null) return; var hasDividend = curEodPositions.Any(x => x.PosiDividendSum != 0); if (!hasDividend) return; var dividendPayDateOffset = tradeExtend?.ExtendObj?.DividendPayDate ?? 1; if (dividendPayDateOffset <= 0) return; var payDays = dividendPayDateOffset - 1; var dividendPayDate = QdpCalendarHelper.GetNonHoliday(settleDate.AddDays(payDays)); List dividendEvents = new List(); decimal dividendTotal = 0; foreach (var eodPosi in curEodPositions.Where(x => x.PosiDividendSum != 0)) { var dividendEvent = new swap_flow_event { SwapTradeId = td.id, SwapTradeNo = td.TradeNumber, EventType = (int)SwapEventTypeEnum.自动互换, EventReason = "系统操作-分红", EventDate = settleDate, UnwindDate = settleDate, PayDate = dividendPayDate, PositionId = eodPosi.PositionId, UnderlyingCode = eodPosi.UnderlyingCode, UnderlyingInstrumentType = eodPosi.UnderlyingInstrumentType, PayDirection = eodPosi.PosiDirection, PositionType = eodPosi.PositionType, PositionQty = eodPosi.PosiQuantity, Quantity = 0, ContractSize = eodPosi.ContractSize, TradingAmountAvg = eodPosi.PosiGrossPrice, TradingAmountNetAvg = eodPosi.PosiNetNoFeePrice, PosiGrossPrice = eodPosi.PosiGrossPrice, PosiNetPrice = eodPosi.PosiNetPrice, MarkClosePnl = 0,//当日盯市不要计算分红 DividendIn = eodPosi.PosiDividendSum, CloseFee = 0, TradingFee = 0, TradingFeePending = 0, ClientId = td.ClientId, DataState = (int)SwapFlowDateStateEnum.完成, }; dividendEvents.Add(dividendEvent); dividendTotal += eodPosi.PosiDividendSum; eodPosi.TdCloseDividend += eodPosi.PosiDividendSum; //当日也要展示 eodPosi.TdPosiDividend = 0; eodPosi.RealizedDividend += eodPosi.PosiDividendSum; //互换持仓价值要去掉已实现的 eodPosi.SwapPositionValue -= eodPosi.PosiDividendSum; //已实现盈亏要加上已实现的 eodPosi.RealizedPnl += eodPosi.PosiDividendSum; eodPosi.PosiDividendSum = 0; eodPosi.PosiProfitSum -= eodPosi.TdCloseDividend; } UnwindData unwindData = new UnwindData(); unwindData.SwapTradeId = td.id; unwindData.ValueDate = settleDate; unwindData.StartDate = td.StartDate.Value; unwindData.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0); unwindData.PosiNotionalValue = curEodPositions.Sum(x => x.PosiNotionalValue); unwindData.PayDate = dividendPayDate; unwindData.SwapDividendPnl = dividendTotal; unwindData.SwapCloseAmount = 0; unwindData.SwapMarginRebatePnl = 0; unwindData.SwapRealizedPnL = dividendTotal; SaveAutoSwapDeal(td,null , unwindData,null, dividendEvents:dividendEvents); } /// /// 保存自动互换数据信息 /// /// /// /// 自动互换观察日信息,用于获取结算日期 private long SaveAutoSwapDeal(trade td, List flowEvents, UnwindData unwindData, IntervalModel interval, List dividendEvents = null) { //td.UnWindDate = unwindData.ValueDate; //优先使用 interval.SettlementDate 作为资金记录发生日期,如果没有则使用 ValueDate var cashHappenDate = interval?.SettlementDate ?? unwindData.ValueDate; int clientCashId = 0; var clientCashIds = new List(); // 利息腿:插入资金记录(使用系统操作_互换) if (unwindData.SwapCloseAmount != 0) { clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapCloseAmount), ClientCashInCashOut.系统操作_互换, cashHappenDate); clientCashIds.Add(clientCashId); } // 预付金腿:单独插入一条资金记录(系统操作_预付金返息) if (unwindData.SwapMarginRebatePnl != 0) { clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapMarginRebatePnl), ClientCashInCashOut.系统操作_预付金返息, unwindData.ValueDate); clientCashIds.Add(clientCashId); } unwindData.SwapCloseAmount = unwindData.SwapRealizedPnL;//需要算上预付金利息 和 分红; 只是不算预付金返还 // 分红:使用派息支付日偏移记录资金记录 if (unwindData.SwapDividendPnl != 0) { var dividendPayDate = (dividendEvents != null && dividendEvents.Count > 0) ? dividendEvents.First().PayDate.Value : unwindData.ValueDate; clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapDividendPnl), ClientCashInCashOut.系统操作_互换, dividendPayDate); clientCashIds.Add(clientCashId); } unwindData.ClientCashIds = clientCashIds; string data = JsonConvert.SerializeObject(unwindData); var swapEvent = new SwapEventService(this).AddSwapEventDate(unwindData.ValueDate, unwindData.SwapTradeId, (int)SwapEventTypeEnum.自动互换, data, clientCashId, true, "系统操作-自动互换");//将互换总额存入事件 if (flowEvents!=null) { flowEvents.ForEach(x => { x.EventId = swapEvent.id; DbContext.swap_flow_event.Add(x); }); UpdateInitalPostion(flowEvents, td.id); } // 保存分红事件 if (dividendEvents != null) { dividendEvents.ForEach(x => { x.EventId = swapEvent.id; DbContext.swap_flow_event.Add(x); }); UpdateInitalPostion(dividendEvents, td.id); } return swapEvent.id; } /// /// 互换更新实时持仓信息 /// /// private void UpdateInitalPostion(List flowEvents, int swapTradeId) { var positions = DbContext.swap_position.Where(x => !x.IsInitial && x.SwapTradeId == swapTradeId && !x.Invalid); foreach (var position in positions) { var interest = flowEvents.FirstOrDefault(x => x.PositionId == position.id); if (interest != null) { position.InterestAmount += interest.InterestAmount; UpdateDbOption(position); } } } /// /// 分页合成互换流水开平仓事件,暂时只按加权平均处理 /// /// 互换交易id /// 清算日期 protected void ComposePage(int swapTradeId, List flowEvents, DateTime tradeDate, bool needTrans = true) { // 同一标的 事件编码一致 var eventQuery = flowEvents.OrderBy(o => o.PositionId).ToList(); if (eventQuery.Count == 0) { return; } SwapTradeService swapTradeService = new SwapTradeService(this); var trans = needTrans ? DbContext.Database.BeginTransaction() : null; try { UnwindData unwindData = new UnwindData(); unwindData.SwapTradeId = swapTradeId; var swapEvent = AddSwapEvent(tradeDate, swapTradeId, (int)SwapEventTypeEnum.合成持仓, string.Empty, 0, true, "系统操作-自动合成持仓"); var td = FindTrade(swapTradeId); var preSettleDate = GetPreValueDate(tradeDate);//上一交易日期 List eventTyps = new List() { (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 }; List removeEventTyps = new List() { (int)SwapEventTypeEnum.平仓, (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 }; ClearSwapPositionsForCompose(td, tradeDate, removeEventTyps); td.trade_extend = FindTradeExtend(swapTradeId); var allEodPositions = FindEodSwapPositions(swapTradeId, preSettleDate); var eodPositions = allEodPositions.Where(x => x.ValueDate == preSettleDate).ToList();//上一日终持仓信息 var positions = FindSwapPositions(swapTradeId); var oriPositions = positions.Where(x => x.IsInitial).ToList(); var realPositions = positions.Where(x => !x.IsInitial).ToList(); var fpositions = positions.Where(x => x.PosiDirection > 0).ToList(); decimal tdCloseQty = 0; decimal totalPosiNotionalValue = 0;//总剩余名义本金 decimal tdCloseNotionalValue = 0;//当日平仓名义本金 var preDealDate = GetPreDealDate(swapTradeId, tradeDate, eventTyps);//上一次平仓/互换/自动互换事件日期 var lastEodSwap = FindEodSwap(swapTradeId, preSettleDate); decimal stockEqvNotional = lastEodSwap == null ? Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0) : lastEodSwap.NotionalValue;//上一日名义本金 unwindData.NotionalValue = stockEqvNotional; List longshortCloseInterests = new List(); decimal tradePrice = 0;//开仓费 decimal allPosiNotionalValue = 0; decimal longNotionalValue = realPositions.Where(s => s.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue);//剩余多头名义本金规模 decimal shortNotionalValue = realPositions.Where(s => s.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue);//剩余空头名义本金规模 foreach (var eventGroup in eventQuery.GroupBy(g => g.PositionId))//持仓标的腿合成持仓 { var eventList = eventGroup.ToList(); var eventFlow = eventGroup.Last(); var fposition = fpositions.FirstOrDefault(n => n.PositionId == eventGroup.Key); var position = fpositions.FirstOrDefault(n => n.id == eventGroup.Key); var eodPayPosition = allEodPositions.Where(x => x.PositionId == eventFlow.PositionId).OrderByDescending(o => o.ValueDate).FirstOrDefault();//浮动腿 日终持仓信息 decimal netPrice = fposition == null ? 0 : fposition.PosiNetPrice;//持仓均价 decimal grossPrice = fposition == null ? 0 : fposition.PosiGrossPrice;//持仓均价-不含费 decimal netFeePrice = fposition == null ? 0 : fposition.PosiNetFeePrice ?? 0;//持仓净价-含费 decimal netNoFeePrice = fposition == null ? 0 : fposition.PosiNetNoFeePrice ?? 0;//持仓净价-不含费 decimal tradingFee = fposition == null ? 0 : fposition.PosiTradingFeePending;//持仓交易费用 decimal payQty = fposition == null ? 0 : fposition.PosiQuantity;//持仓数量 decimal posiNotionalValue = fposition == null ? 0 : fposition.PosiNotionalValue;//剩余名义本金 decimal dividendIn = 0;//当日浮动端分红 decimal tdDividendIn = 0;//当日浮动端平仓盈亏分红 decimal openQty = fposition == null ? 0 : fposition.PosiQuantity;//开仓数量 decimal openAmount = fposition == null ? 0 : openQty * grossPrice;//开仓累计成交金额不含费 decimal openAmountFee = fposition == null ? 0 : openQty * netPrice;//开仓累计成交金额含费 decimal openAmountNetFee = fposition == null ? 0 : openQty * netFeePrice;//开仓累计成交净价金额含费 decimal openAmountNet = fposition == null ? 0 : openQty * netNoFeePrice;//开仓累计成交净价金额不含费 decimal closeQty = 0;//当日平仓数量 decimal closeFee = 0;//当日平仓费用 decimal closeMtmPnl = 0;//当日平仓盈亏 var posiType = fposition == null ? 0 : fposition.PositionType; var swapInsertEnum = SwapPushDataEnum.收益互换交易新增; var swapPosiEnum = SwapPushDataEnum.互换资产交易新增; if (fposition != null) { swapInsertEnum = SwapPushDataEnum.收益互换交易修改; swapPosiEnum = SwapPushDataEnum.互换资产交易修改; } eventList.ForEach(x => { x.EventId = swapEvent.id; decimal ratio = x.EventType == 1 ? 1 : -1;//开仓为加法,平仓为减法 tradingFee = tradingFee + x.TradingFeePending;//开仓累计 if (x.EventType == 1) { openAmountFee = openAmountFee + x.TradingAmountFeeAvg * x.Quantity; openAmount = openAmount + x.TradingAmountAvg * x.Quantity; openAmountNetFee = openAmountNetFee + (x.TradingAmountNetFeeAvg * x.Quantity) ?? 0; openAmountNet = openAmountNet + (x.TradingAmountNetAvg * x.Quantity) ?? 0; openQty = openQty + x.Quantity; if (posiType != x.PositionType) { payQty = x.Quantity; posiType = x.PositionType; } else { payQty = payQty + x.Quantity; } } else { decimal amount = x.MarkClosePnl + x.CloseFee + x.DividendIn; //记录资金记录 if (amount != 0) { int clientCashId = AddClientCash(td, Convert.ToDouble(x.MarkClosePnl), ClientCashInCashOut.系统操作_平仓费, x.UnwindDate.Value); x.ClientCashId = clientCashId; } payQty = payQty - x.Quantity; closeQty = closeQty + x.Quantity; closeFee = closeFee + x.CloseFee; tdDividendIn = tdDividendIn + x.DividendIn; tdCloseNotionalValue = tdCloseNotionalValue + x.TradingAmount; } closeMtmPnl = closeMtmPnl + x.MarkClosePnl; x.PositionQty = payQty; dividendIn = dividendIn + x.DividendIn; x.DataState = (int)SwapFlowDateStateEnum.完成; }); tdCloseQty += closeQty; if (eventFlow.EventType == 1)//最后一条是开仓 { payQty = eventFlow.Quantity; } bool newOpen = openQty != 0 && closeQty == 0; if (openQty != 0 && closeQty == 0)//只有开仓,价格加权平均 { netPrice = openAmountFee / openQty;//持仓均价=((上一日持仓含费均价*上一日持仓数量)+(开仓成交均价*开仓数量))/所有开仓数量 grossPrice = openAmount / openQty;//持仓均价-不含费=((上一日持仓不含费均价*上一日持仓数量)+(开仓成交均价-不含费*开仓数量))/所有开仓数量 netFeePrice = openAmountNetFee / openQty; netNoFeePrice = openAmountNet / openQty; } else if (posiType != fposition?.PositionType)//平仓完新开仓 { netPrice = eventFlow.TradingAmountFeeAvg; grossPrice = eventFlow.TradingAmountAvg; netFeePrice = eventFlow.TradingAmountNetFeeAvg ?? 0; netNoFeePrice = eventFlow.TradingAmountNetAvg ?? 0; } if (eodPayPosition == null)//无日终持仓 { eodPayPosition = new eod_swap_position(); eodPayPosition.PosiStartDate = eventFlow.PayDate.Value; eodPayPosition.ClientId = td.ClientId; eodPayPosition.SwapTradeId = td.id; eodPayPosition.ContractSize = eventFlow.ContractSize; } posiNotionalValue = eventFlow.ContractSize * netPrice * Math.Abs(payQty); totalPosiNotionalValue = totalPosiNotionalValue + posiNotionalValue; allPosiNotionalValue += posiNotionalValue; tradePrice += SaveEodPosition(eodPayPosition, td, eventFlow, netPrice, grossPrice, netFeePrice, netNoFeePrice, payQty, tradingFee, posiNotionalValue, dividendIn, tdDividendIn, closeQty, closeFee, closeMtmPnl, posiType, fposition == null); } if (tdCloseQty != 0) { td.HasPartialUnWind = 1; } // td.StockEqvNotional += Convert.ToDouble(totalPosiNotionalValue); td.TradePrice += Convert.ToDouble(tradePrice); unwindData.PosiNotionalValue = allPosiNotionalValue; unwindData.CloseNotionalValue = tdCloseNotionalValue; swapEvent.EventData = JsonHelper.Serialize(unwindData); SaveAllChanges(); SaveEodSwapRecord(td, tradeDate, preSettleDate); SaveAllChanges(); trans?.Commit(); } catch (Exception ex) { trans?.Rollback(); throw new Exception(ex.Message, ex); } finally { trans?.Dispose(); } } /// /// 产生互换用 /// /// 上一日日终归档 /// 当日归档 /// 持仓腿 /// 主体交易 /// 计息开始日 /// 计息结束日 /// 平仓金额 protected void SaveEodInterestPosition(eod_swap_position eodPayPosition, eod_swap_position newEodPayPosition, swap_position position, trade td, DateTime valueDate, List flowEvents) { Log.Info($"eodPayPosition is {JsonHelper.Serialize(eodPayPosition, false)},newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}"); if (eodPayPosition == null) { eodPayPosition = new eod_swap_position(); eodPayPosition.ClientId = td.ClientId; eodPayPosition.SwapTradeId = td.id; eodPayPosition.PosiStartDate = position.PosiStartDate; eodPayPosition.PosiMatuirityDate = td.ExerciseDate.Value; } var tradeExtend = td.trade_extend.ExtendObj; decimal ratio = position.InterestDirection == (int)SwapDirectionEnum.收取 ? 1m : -1m;//收取为正,支付为负 if (marginTypes.Contains(position.InterestMode)) { ratio = -ratio; } if (newEodPayPosition == null) { newEodPayPosition = new eod_swap_position(); newEodPayPosition.ClientId = td.ClientId; newEodPayPosition.SwapTradeId = td.id; newEodPayPosition.PosiStartDate = position.PosiStartDate; eodPayPosition.PosiMatuirityDate = td.ExerciseDate.Value; } newEodPayPosition.ValueDate = valueDate; newEodPayPosition.PositionId = position.id; newEodPayPosition.ClientId = td.ClientId; newEodPayPosition.SwapTradeId = td.id; UpdateDbOption(newEodPayPosition); newEodPayPosition.PosiStatus = 0; newEodPayPosition.Invalid = false; //持仓内容-利息腿 newEodPayPosition.InterestDirection = position.InterestDirection; newEodPayPosition.InterestMode = position.InterestMode; newEodPayPosition.InterestPrincipalFix = position.InterestPrincipalFix; newEodPayPosition.InterestRateDefault = position.InterestRateDefault; newEodPayPosition.InterestSwapInterval = position.InterestSwapInterval; newEodPayPosition.IsAnnualized = position.IsAnnualized; newEodPayPosition.HappenDate = position.HappenDate; newEodPayPosition.Currency = position.Currency; newEodPayPosition.InterestType = position.InterestType; newEodPayPosition.interest_rest_days = position.interest_rest_days; newEodPayPosition.interest_rule = position.interest_rule; newEodPayPosition.FloatRate = flowEvents.FirstOrDefault()?.FloatRate ?? 0; newEodPayPosition.FloatRateUnderlyingCode = position.FloatRateUnderlyingCode; newEodPayPosition.InterestFeePending = 0; //利息端估值用信息 newEodPayPosition.TdInterestPrincipal = flowEvents.FirstOrDefault()?.InterestPrincipal ?? 0; newEodPayPosition.TdInterestRate = flowEvents.FirstOrDefault()?.InterestRate ?? 0; //当日已实现 newEodPayPosition.TdInterestFee = flowEvents.Sum(x => x.InterestFee); newEodPayPosition.TdCloseInterest = flowEvents.Sum(x => x.InterestAmount); newEodPayPosition.TdCloseInterestFee = newEodPayPosition.TdInterestFee; //持仓内容-利息腿-损益统计(本方视角) var intersetAcmount = newEodPayPosition.TdInterestPrincipal * (newEodPayPosition.TdInterestRate + newEodPayPosition.FloatRate); if (position.IsAnnualized) { intersetAcmount /= tradeExtend.AnnualDays; } newEodPayPosition.TdInterestIncome = intersetAcmount;// 要算一下当天产生的利息 newEodPayPosition.InterestIncomeSum = eodPayPosition.InterestIncomeSum + newEodPayPosition.TdInterestIncome - newEodPayPosition.TdCloseInterest; //上一天待实现 + 当天产生的利息 - flowEvents的利息 newEodPayPosition.InterestFeeSum = eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee - newEodPayPosition.TdCloseInterestFee; newEodPayPosition.InterestProfitSum = newEodPayPosition.InterestIncomeSum + newEodPayPosition.InterestFeeSum; //持仓价值 newEodPayPosition.SwapPositionValue = newEodPayPosition.InterestProfitSum * ratio + newEodPayPosition.PosiProfitSum; //累计已实现 newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio; newEodPayPosition.RealizedInterestFee = eodPayPosition.RealizedInterestFee + newEodPayPosition.TdCloseInterestFee; var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true, position.InterestDirection == (int)SwapDirectionEnum.收取 ? CurrencyRateType.Buy : CurrencyRateType.Sell); newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate); PersistEodSwapPosition(newEodPayPosition); } /// /// 自动互换用,当日无互换,当日无平仓 /// /// 上一日日终持仓 /// 当前收盘日日终持仓 /// 利息腿信息 /// 框架合约 /// 当前收盘日 /// 当前观察日 /// 上一平仓/互换日期 /// 当日平仓金额 /// 上一日终框架合约估值 protected List SaveAutoEodInterestPosition(eod_swap_position eodPayPosition, eod_swap_position newEodPayPosition, swap_position position, trade td, DateTime valueDate, IntervalModel interval, eod_swap lastEodSwap, decimal posiLongNotional, decimal posiShortNational, decimal grossPrice, decimal orginPv) { Log.Info($"[SaveAutoEodInterestPosition] 开始执行 - valueDate: {valueDate:yyyy-MM-dd}, td.id: {td?.id}, position.id: {position?.id}"); // 详细的参数验证 if (td == null) { Log.Info("[SaveAutoEodInterestPosition] 参数验证: td (trade) 为 null"); throw new ArgumentNullException(nameof(td), "交易对象不能为null"); } if (position == null) { Log.Info($"[SaveAutoEodInterestPosition] 参数验证: position 为 null, td.id: {td.id}"); throw new ArgumentNullException(nameof(position), "持仓对象不能为null"); } if (interval == null) { Log.Info($"[SaveAutoEodInterestPosition] 参数验证: interval 为 null, td.id: {td.id}, position.id: {position.id}"); throw new ArgumentNullException(nameof(interval), "观察日信息不能为null"); } if (td.trade_extend == null) { Log.Info($"[SaveAutoEodInterestPosition] 参数验证: td.trade_extend 为 null, td.id: {td.id}, td.TradeNumber: {td.TradeNumber}"); throw new ArgumentNullException("td.trade_extend", "交易扩展信息不能为null"); } Log.Info($"eodPayPosition is {JsonHelper.Serialize(eodPayPosition, false)},newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}"); // 验证 ExtendObj if (td.trade_extend.ExtendObj == null) { Log.Info($"[SaveAutoEodInterestPosition] 参数验证: td.trade_extend.ExtendObj 为 null, td.id: {td.id}"); throw new ArgumentNullException("td.trade_extend.ExtendObj", "交易扩展对象不能为null"); } var tradeExtend = td.trade_extend.ExtendObj; decimal oriPosiNotionalValue = posiLongNotional + posiShortNational; decimal posiNotionalValue = oriPosiNotionalValue; bool longShort = td.StructureType == ClientMarginTypeEnum.多空组合.ToString(); if (lastEodSwap != null) { posiNotionalValue = lastEodSwap.NotionalValue; } decimal closePercent = 1; decimal ratio = position.InterestDirection == (int)SwapDirectionEnum.收取 ? 1m : -1m;//收取为正,支付为负 if (marginTypes.Contains(position.InterestMode)) { ratio = -ratio; } if (eodPayPosition == null) { eodPayPosition = new eod_swap_position(); eodPayPosition.ClientId = td.ClientId; eodPayPosition.SwapTradeId = td.id; eodPayPosition.PosiStartDate = td.StartDate.Value; eodPayPosition.PosiMatuirityDate = td.ExerciseDate.Value; } if (newEodPayPosition == null) { newEodPayPosition = eodPayPosition.Clone(); newEodPayPosition.id = 0; } List positions = new List(); positions.Add(position); List preEodPositions = new List(); preEodPositions.Add(eodPayPosition); var interestModes = new List() { (int)InterestModeEnum.固定值, (int)InterestModeEnum.初始预付金, (int)InterestModeEnum.追加预付金 }; if (interestModes.Contains(position.InterestMode)) { orginPv = eodPayPosition.InterestPrincipalFix; } var interests = CalcSwapInterests(td, td.trade_extend, valueDate, valueDate, preEodPositions, positions, posiNotionalValue, posiLongNotional, posiShortNational, posiNotionalValue, closePercent, (int)SwapEventTypeEnum.自动互换, false, true, grossPrice, orginPv, true); decimal InterestAmount = interests.Sum(x => x.InterestAmount); decimal TdInterestAmount = interests.Sum(x => x.TdInterestAmount); newEodPayPosition.ValueDate = valueDate; newEodPayPosition.PositionId = position.id; UpdateDbOption(newEodPayPosition); newEodPayPosition.Invalid = false; //持仓内容-利息腿 newEodPayPosition.InterestDirection = position.InterestDirection; newEodPayPosition.InterestMode = position.InterestMode; newEodPayPosition.InterestPrincipalFix = position.InterestPrincipalFix; newEodPayPosition.InterestRateDefault = position.InterestRateDefault; newEodPayPosition.InterestSwapInterval = position.InterestSwapInterval; newEodPayPosition.IsAnnualized = position.IsAnnualized; newEodPayPosition.HappenDate = position.HappenDate; newEodPayPosition.Currency = position.Currency; newEodPayPosition.InterestType = position.InterestType; newEodPayPosition.interest_rest_days = position.interest_rest_days; newEodPayPosition.interest_rule = position.interest_rule; newEodPayPosition.FloatRate = interests.Count > 0 ? interests.First().FloatRate ?? 0 : 0; newEodPayPosition.FloatRateUnderlyingCode = position.FloatRateUnderlyingCode; newEodPayPosition.InterestFeePending = 0; //利息端估值用信息 newEodPayPosition.TdInterestPrincipal = interestModes.Contains(position.InterestMode) ? eodPayPosition.InterestPrincipalFix : posiNotionalValue; newEodPayPosition.TdInterestRate = interval.Rate; //当日已实现 //newEodPayPosition.TdInterestFee = 0; newEodPayPosition.TdCloseInterest = InterestAmount; // newEodPayPosition.TdCloseInterestFee = newEodPayPosition.TdInterestFee; //持仓内容-利息腿-损益统计(本方视角) newEodPayPosition.TdInterestIncome = TdInterestAmount; Log.Info($"InterestFeeSum is {eodPayPosition.InterestFeeSum},TdInterestFee is {newEodPayPosition.TdInterestFee},TdCloseInterestFee is {newEodPayPosition.TdCloseInterestFee}"); newEodPayPosition.InterestIncomeSum = 0; newEodPayPosition.InterestFeeSum = eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee - newEodPayPosition.TdCloseInterestFee; newEodPayPosition.InterestProfitSum = newEodPayPosition.InterestIncomeSum + newEodPayPosition.InterestFeeSum; //持仓价值 newEodPayPosition.SwapPositionValue = newEodPayPosition.InterestProfitSum * ratio + newEodPayPosition.PosiProfitSum; //累计已实现 newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio; newEodPayPosition.RealizedInterestFee = eodPayPosition.RealizedInterestFee + newEodPayPosition.TdCloseInterestFee; newEodPayPosition.RealizedPnl = newEodPayPosition.RealizedInterest + newEodPayPosition.RealizedInterestFee; var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true, position.InterestDirection == (int)SwapDirectionEnum.收取 ? CurrencyRateType.Buy : CurrencyRateType.Sell); newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate); PersistEodSwapPosition(newEodPayPosition); Log.Info($"the last newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}"); return interests; } /// /// 自动互换用,当日无互换,当日有平仓 /// /// 上一日日终持仓 /// 当前收盘日日终持仓 不可能为空 /// 利息腿信息 /// 框架合约 /// 当前收盘日 /// 当前观察日 /// 上一平仓/互换日期 /// 当日平仓金额 /// 上一日终框架合约估值 /// 平仓主信息 protected List SaveAutoEodWithCloseInterestPosition(eod_swap_position eodPayPosition, eod_swap_position newEodPayPosition, swap_position position, trade td, DateTime valueDate, IntervalModel interval, decimal posiLongNotional, decimal posiShortNational, List flowEvents, decimal closeNational, bool autoSwap, decimal grossPrice, decimal orginPv) { Log.Info($"eodPayPosition is {JsonHelper.Serialize(eodPayPosition, false)},newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}"); var tradeExtend = td.trade_extend.ExtendObj; decimal oriPosiNotionalValue = posiLongNotional + posiShortNational + closeNational; decimal posiNotionalValue = posiLongNotional + posiShortNational; decimal ratio = position.InterestDirection == (int)SwapDirectionEnum.收取 ? 1m : -1m;//收取为正,支付为负 if (marginTypes.Contains(position.InterestMode)) { ratio = -ratio; } var lastInterestIncomeSum = eodPayPosition.InterestIncomeSum; eodPayPosition = new eod_swap_position(); eodPayPosition.ClientId = td.ClientId; eodPayPosition.SwapTradeId = td.id; eodPayPosition.PosiStartDate = td.StartDate.Value; eodPayPosition.PosiMatuirityDate = td.ExerciseDate.Value; if (newEodPayPosition == null) { newEodPayPosition = eodPayPosition.Clone(); newEodPayPosition.id = 0; } var interestModes = new List() { (int)InterestModeEnum.固定值, (int)InterestModeEnum.初始预付金, (int)InterestModeEnum.追加预付金 }; if (interestModes.Contains(position.InterestMode)) { orginPv = eodPayPosition.InterestPrincipalFix; } else { orginPv = posiNotionalValue; } decimal closePercent = oriPosiNotionalValue == 0 ? 0 : closeNational / oriPosiNotionalValue; var eventType = autoSwap ? (int)SwapEventTypeEnum.自动互换 : (int)SwapEventTypeEnum.平仓; bool longShort = td.StructureType == ClientMarginTypeEnum.多空组合.ToString(); if (!autoSwap) { closePercent = oriPosiNotionalValue == 0 ? 0 : closeNational / oriPosiNotionalValue; if (longShort) { closePercent = 0; } } List positions = new List(); positions.Add(position); List preEodPositions = new List(); preEodPositions.Add(eodPayPosition); var interests = CalcSwapInterests(td, td.trade_extend, valueDate, valueDate, preEodPositions, positions, posiNotionalValue, posiLongNotional, posiShortNational, closeNational, 1, eventType, false, true, grossPrice, orginPv, true, settment: false, newCalcLast: true); decimal TdInterestAmount = interests.Sum(x => x.TdInterestAmount); decimal InterestAmount = interests.Sum(x => x.InterestAmount); newEodPayPosition.ValueDate = valueDate; newEodPayPosition.PositionId = position.id; UpdateDbOption(newEodPayPosition); newEodPayPosition.Invalid = false; //持仓内容-利息腿 newEodPayPosition.InterestDirection = position.InterestDirection; newEodPayPosition.InterestMode = position.InterestMode; newEodPayPosition.InterestPrincipalFix = position.InterestPrincipalFix; newEodPayPosition.InterestPrincipalFix *= (1 - closePercent); newEodPayPosition.InterestRateDefault = position.InterestRateDefault; newEodPayPosition.InterestSwapInterval = position.InterestSwapInterval; newEodPayPosition.IsAnnualized = position.IsAnnualized; newEodPayPosition.HappenDate = position.HappenDate; newEodPayPosition.Currency = position.Currency; newEodPayPosition.InterestType = position.InterestType; newEodPayPosition.FloatRate = interests.Count > 0 ? interests.First().FloatRate ?? 0 : 0; newEodPayPosition.FloatRateUnderlyingCode = position.FloatRateUnderlyingCode; newEodPayPosition.interest_rest_days = position.interest_rest_days; newEodPayPosition.interest_rule = position.interest_rule; //利息端估值用信息 newEodPayPosition.TdInterestPrincipal = interests.Count > 0 ? interests.First().InterestPrincipal : 0; if (interval != null) { newEodPayPosition.TdInterestRate = interval.Rate; } else { newEodPayPosition.TdInterestRate = flowEvents.FirstOrDefault()?.InterestRate ?? 0; } //当日已实现,平仓时已处理 newEodPayPosition.TdInterestFee = flowEvents.Sum(s => s.InterestFee); newEodPayPosition.TdCloseInterestFee = newEodPayPosition.TdInterestFee; newEodPayPosition.TdCloseInterest = flowEvents.Sum(s => s.InterestClosePnL); var intersetAcmount = newEodPayPosition.TdInterestPrincipal * (newEodPayPosition.TdInterestRate + newEodPayPosition.FloatRate); if (position.IsAnnualized) { intersetAcmount /= tradeExtend.AnnualDays; } newEodPayPosition.TdInterestIncome = intersetAcmount; Log.Info($"InterestIncomeSum is {lastInterestIncomeSum},TdInterestIncome is {newEodPayPosition.TdInterestIncome}" + $",TdCloseInterest is {newEodPayPosition.TdCloseInterest}"); Log.Info($"InterestFeeSum is {eodPayPosition.InterestFeeSum},TdInterestFee is {newEodPayPosition.TdInterestFee}" + $",TdCloseInterestFee is {newEodPayPosition.TdCloseInterestFee}"); if (closePercent == 1) { newEodPayPosition.InterestIncomeSum = 0; } else { newEodPayPosition.InterestIncomeSum = InterestAmount; } //持仓内容-利息腿-损益统计(本方视角) newEodPayPosition.InterestFeeSum = eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee - newEodPayPosition.TdCloseInterestFee; newEodPayPosition.InterestProfitSum = newEodPayPosition.InterestIncomeSum + newEodPayPosition.InterestFeeSum; //持仓价值 newEodPayPosition.SwapPositionValue = newEodPayPosition.InterestProfitSum * ratio + newEodPayPosition.PosiProfitSum; Log.Info($"InterestIncomeSum is {eodPayPosition.InterestIncomeSum},TdInterestIncome is {newEodPayPosition.TdInterestIncome}" + $",TdCloseInterest is {newEodPayPosition.TdCloseInterest}"); Log.Info($"InterestFeeSum is {eodPayPosition.InterestFeeSum},TdInterestFee is {newEodPayPosition.TdInterestFee}" + $",TdCloseInterestFee is {newEodPayPosition.TdCloseInterestFee}"); //累计已实现 newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio; newEodPayPosition.RealizedInterestFee = eodPayPosition.RealizedInterestFee + newEodPayPosition.TdCloseInterestFee; newEodPayPosition.RealizedPnl = newEodPayPosition.RealizedInterest + newEodPayPosition.RealizedInterestFee; ; var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true, position.InterestDirection == (int)SwapDirectionEnum.收取 ? CurrencyRateType.Buy : CurrencyRateType.Sell); newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate); Log.Info($"即将插入数据库的 newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}"); PersistEodSwapPosition(newEodPayPosition); return interests; } /// /// 利息腿复制上一日终归档信息 /// /// 上一日终利息腿持仓信息 /// 利息腿信息 /// 上一交易日 /// 当前结算日 /// 互换交易主干 protected void SaveEodInterestPositionCopy(eod_swap_position eodPayPosition, eod_swap_position newEodPayPosition, DateTime valueDate, trade td, swap_position position, eod_swap lastEodSwap, bool needPrice, decimal posiLongNational, decimal posiShortNational, decimal grossPrice, decimal orginPv) { Log.Info($"eodPayPosition is {JsonHelper.Serialize(eodPayPosition, false)},newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}"); List intervals = position.SwapIntervalList; var tradeExtend = td.trade_extend.ExtendObj; var interestModes = new List() { (int)InterestModeEnum.固定值, (int)InterestModeEnum.初始预付金, (int)InterestModeEnum.追加预付金 }; if (eodPayPosition == null) { //if (position.PosiStartDate > valueDate) //{ // return; //} eodPayPosition = new eod_swap_position(); eodPayPosition.InterestDirection = position.InterestDirection; eodPayPosition.ClientId = td.ClientId; eodPayPosition.SwapTradeId = td.id; //eodPayPosition.PositionId = position.id; 为了算利息时找不到给日期重新赋值 eodPayPosition.InterestMode = position.InterestMode; eodPayPosition.InterestPrincipalFix = position.InterestPrincipalFix; eodPayPosition.InterestRateDefault = position.InterestRateDefault; eodPayPosition.InterestSwapInterval = position.InterestSwapInterval; eodPayPosition.TdInterestPrincipal = interestModes.Contains(position.InterestMode) ? eodPayPosition.InterestPrincipalFix : posiLongNational + posiShortNational; eodPayPosition.PosiStartDate = td.StartDate.Value; eodPayPosition.PosiMatuirityDate = td.ExerciseDate.Value; eodPayPosition.IsAnnualized = position.IsAnnualized; eodPayPosition.HappenDate = position.HappenDate; eodPayPosition.Currency = position.Currency; eodPayPosition.InterestType = position.InterestType; eodPayPosition.FloatRate = position.FloatRate; eodPayPosition.FloatRateUnderlyingCode = position.FloatRateUnderlyingCode; eodPayPosition.interest_rest_days = position.interest_rest_days; eodPayPosition.interest_rule = position.interest_rule; } if (newEodPayPosition == null) { newEodPayPosition = eodPayPosition.Clone(); newEodPayPosition.id = 0; newEodPayPosition.PositionId = position.id; } if (interestModes.Contains(position.InterestMode)) { orginPv = eodPayPosition.InterestPrincipalFix; } bool longShort = td.StructureType == ClientMarginTypeEnum.多空组合.ToString(); decimal oriPosiNotionalValue = posiLongNational + posiShortNational; decimal posiNotionalValue = oriPosiNotionalValue; if (lastEodSwap == null) { lastEodSwap = new eod_swap(); } decimal closePercent = oriPosiNotionalValue == 0 ? 0 : posiNotionalValue / oriPosiNotionalValue; if (longShort) { closePercent = 0; } if (td.ExerciseDate == valueDate) { closePercent = 1; } decimal ratio = eodPayPosition.InterestDirection == (int)SwapDirectionEnum.收取 ? 1m : -1m;//收取为正,支付为负 if (marginTypes.Contains(position.InterestMode)) { ratio = -ratio; } List positions = new List { position }; List preEodPositions = new List(); if (eodPayPosition.id != 0) { preEodPositions.Add(eodPayPosition); } var interests = CalcSwapInterests(td, td.trade_extend, valueDate, valueDate, preEodPositions, positions, posiNotionalValue, posiLongNational, posiShortNational, posiNotionalValue, closePercent, 0, false, needPrice, grossPrice, orginPv); UpdateDbOption(newEodPayPosition); newEodPayPosition.PosiStatus = 0; newEodPayPosition.Invalid = false; newEodPayPosition.ValueDate = valueDate; decimal InterestAmount = interests.Sum(s => s.InterestAmount); decimal TdInterestAmount = interests.Sum(x => x.TdInterestAmount); //利息端估值用信息 newEodPayPosition.TdInterestPrincipal = interests.Count > 0 ? interests.First().InterestPrincipal : 0; newEodPayPosition.TdInterestRate = interests.Count > 0 ? interests.First().InterestRate : 0; newEodPayPosition.FloatRate = interests.Count > 0 ? interests.First().FloatRate ?? 0 : 0; //当日已实现 newEodPayPosition.TdCloseInterest = 0; newEodPayPosition.TdCloseInterestFee = 0; //持仓内容-利息腿-损益统计(本方视角) newEodPayPosition.TdInterestIncome = TdInterestAmount; newEodPayPosition.TdInterestFee = 0; Log.Info($"InterestIncomeSum is {eodPayPosition.InterestIncomeSum},TdInterestIncome is {newEodPayPosition.TdInterestIncome}" + $",TdCloseInterest is {newEodPayPosition.TdCloseInterest}"); Log.Info($"InterestFeeSum is {eodPayPosition.InterestFeeSum},TdInterestFee is {newEodPayPosition.TdInterestFee}" + $",TdCloseInterestFee is {newEodPayPosition.TdCloseInterestFee}"); newEodPayPosition.InterestIncomeSum = eodPayPosition.InterestIncomeSum + newEodPayPosition.TdInterestIncome - newEodPayPosition.TdCloseInterest; newEodPayPosition.InterestFeeSum = eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee - newEodPayPosition.TdCloseInterestFee; newEodPayPosition.InterestProfitSum = newEodPayPosition.InterestIncomeSum + newEodPayPosition.InterestFeeSum; //持仓价值 newEodPayPosition.SwapPositionValue = newEodPayPosition.InterestProfitSum * ratio + newEodPayPosition.PosiProfitSum; //累计已实现 newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio; newEodPayPosition.RealizedInterestFee = eodPayPosition.RealizedInterestFee + newEodPayPosition.TdCloseInterestFee; newEodPayPosition.RealizedPnl = newEodPayPosition.RealizedInterest + newEodPayPosition.RealizedInterestFee; var currencyRate = GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate, true, eodPayPosition.InterestDirection == (int)SwapDirectionEnum.收取 ? CurrencyRateType.Buy : CurrencyRateType.Sell); newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate); Log.Info($"the last newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}"); PersistEodSwapPosition(newEodPayPosition); } /// /// 持仓腿生成期初持仓及归档信息 /// /// 上一日终持仓信息 /// 合约框架 /// 最后一条事件流水 /// 期初价格 /// 期初价格不含费 /// 剩余持仓数量 /// 开仓交易佣金费用 /// 当前剩余名义本金 /// 当日浮动端分红 /// 当日浮动端平仓盈亏分红 /// 当日平仓数量 /// 当日平仓费用 /// 当日浮动盈亏 protected virtual decimal SaveEodPosition(eod_swap_position newEodPayPosition, trade td, swap_flow_event eventFlow, decimal netPrice, decimal grossPrice, decimal netFeePrice, decimal netNoFeePrice, decimal payQty, decimal tradingFee, decimal posiNotionalValue, decimal dividendIn, decimal tdDividendIn, decimal closeQty, decimal closeFee, decimal closeMtmPnl, int posiType, bool open) { payQty = Math.Abs(payQty); decimal ratio = eventFlow.PayDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;//收取为正,支付为负 decimal shortRatio = newEodPayPosition.PositionType == (int)PositionTypeFlag.Long ? 1 : -1;//多空方向 newEodPayPosition.ValueDate = eventFlow.PayDate.Value; newEodPayPosition.PositionId = eventFlow.PositionId; newEodPayPosition.ClientId = td.ClientId; newEodPayPosition.SwapTradeId = td.id; //持仓内容-浮动收益腿 newEodPayPosition.PosiDirection = eventFlow.PayDirection; newEodPayPosition.PositionType = posiType; newEodPayPosition.UnderlyingCode = eventFlow.UnderlyingCode; newEodPayPosition.UnderlyingInstrumentType = eventFlow.UnderlyingInstrumentType; newEodPayPosition.ContractSize = eventFlow.ContractSize; newEodPayPosition.CountRatio = eventFlow.CountRatio; newEodPayPosition.PosiNetPrice = netPrice; newEodPayPosition.PosiGrossPrice = grossPrice; newEodPayPosition.PosiNetFeePrice = netFeePrice; newEodPayPosition.PosiNetNoFeePrice = netNoFeePrice; newEodPayPosition.PosiQuantity = payQty; newEodPayPosition.PosiNotionalValue = posiNotionalValue; newEodPayPosition.PosiMatuirityDate = td.ExerciseDate.Value; if (newEodPayPosition.PosiQuantity == 0) { newEodPayPosition.PosiMatuirityDate = eventFlow.PayDate.Value; } //else //{ // newEodPayPosition.PosiMatuirityDate = td.ExerciseDate.Value; //} newEodPayPosition.PosiFeePending = tradingFee; //浮动端估值用信息 newEodPayPosition.UnderlyingPrice = UnderlyingCodePrice(newEodPayPosition.UnderlyingCode, eventFlow.EventDate, out decimal vobp); newEodPayPosition.dv01 = Dv01Helper.CalcDv01(newEodPayPosition.UnderlyingCode, newEodPayPosition.PosiQuantity, newEodPayPosition.PosiDirection, newEodPayPosition.PositionType, vobp); newEodPayPosition.UnderlyingMarketValue = newEodPayPosition.UnderlyingPrice * newEodPayPosition.PosiQuantity * newEodPayPosition.ContractSize * shortRatio; //当日已实现 newEodPayPosition.TdCloseQty = closeQty; newEodPayPosition.TdChangedQty = 0; newEodPayPosition.TdCloseMtmPnl = closeMtmPnl * ratio; newEodPayPosition.TdCloseDividend = tdDividendIn * ratio; newEodPayPosition.TdCloseFee = closeFee * ratio; //持仓内容-浮动收益腿-损益统计(本方视角 newEodPayPosition.TdPosiDividend = Math.Round(dividendIn * ratio, 2); newEodPayPosition.PosiMtmPnL = (newEodPayPosition.UnderlyingPrice - newEodPayPosition.PosiGrossPrice) * newEodPayPosition.PosiQuantity * newEodPayPosition.ContractSize * shortRatio * ratio; newEodPayPosition.PosiDividendSum = Math.Round(newEodPayPosition.TdPosiDividend - newEodPayPosition.TdCloseDividend, 2); newEodPayPosition.PosiProfitSum = newEodPayPosition.PosiMtmPnL + newEodPayPosition.PosiDividendSum + newEodPayPosition.PosiFeePending; //持仓价值 newEodPayPosition.SwapPositionValue = newEodPayPosition.InterestProfitSum + newEodPayPosition.PosiProfitSum; //累计已实现 newEodPayPosition.RealizedFee = closeFee; newEodPayPosition.RealizedMtmPnL = newEodPayPosition.TdCloseMtmPnl; newEodPayPosition.RealizedDividend = newEodPayPosition.TdCloseDividend; newEodPayPosition.RealizedPnl = newEodPayPosition.TdCloseMtmPnl; newEodPayPosition.PosiStatus = payQty == 0 ? 1 : 0; UpdateDbOption(newEodPayPosition); newEodPayPosition.Invalid = false; var currencyRate = new EodCurrencyRateService(UserInfo).GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, eventFlow.EventDate , seekPreday: true, currencyRateType: posiNotionalValue < 0 ? CurrencyRateType.Buy : CurrencyRateType.Sell); newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate); decimal posiTradingFee = 0; if (open)//更新新开仓持仓腿信息,因为在生成开仓事件时,先生成了空的持仓腿信息 { if (td.trade_extend.ExtendObj.NeedOpenFee && td.TradeDate == eventFlow.EventDate)//开仓 { posiTradingFee = Math.Abs(newEodPayPosition.PosiTradingFee) * Convert.ToDecimal(currencyRate); } UpdateSwapPosition(newEodPayPosition, td.TradeNumber); } UpdateSwapPositionWithRealTime(newEodPayPosition); PersistEodSwapPosition(newEodPayPosition); return posiTradingFee; } /// ///当日无平仓,无互换,生成持仓腿日终归档,适用于上一日终存在 /// /// 上一日日终归档信息 /// 当日日终归档信息 /// 当日平仓/互换事件信息 /// 交易信息 protected eod_swap_position CopyEodPosition(eod_swap_position eod, eod_swap_position curretEod, trade td, DateTime valueDate, DateTime preSettleDate) { if (curretEod == null) { curretEod = eod.Clone(); curretEod.id = 0; curretEod.ValueDate = valueDate; } var um = GetUnderlyingData(eod.UnderlyingCode); if (um == null) { return curretEod; } var dealDate = curretEod.ValueDate; int shortRatio = eod.PositionType == (int)PositionTypeFlag.Long ? 1 : -1; int directionRatio = eod.PosiDirection == (int)SwapDirectionEnum.收取 ? 1 : -1; curretEod.PosiStatus = curretEod.PosiQuantity == 0 ? 1 : 0; var price = GetUnderlyingPrice(eod.UnderlyingCode, dealDate, out decimal vobp); curretEod.dv01 = Dv01Helper.CalcDv01(eod.UnderlyingCode, curretEod.PosiQuantity, eod.PosiDirection, eod.PositionType, vobp); decimal tax = um.ValueAddedTax ?? 0; if (valueDate > td.StartDate.Value && curretEod.PosiQuantity > 0) { decimal payment = CalcBondPayment(curretEod.UnderlyingCode, eod.ValueDate, valueDate, curretEod.PosiQuantity, shortRatio, directionRatio); // 考虑增值税 curretEod.TdPosiDividend = Math.Round(payment / (1 + tax) * (1 - tax), 2); } curretEod.PosiDividendSum = eod.PosiQuantity > 0 ? Math.Round(eod.PosiDividendSum + curretEod.TdPosiDividend, 2) : 0; curretEod.PosiQuantity = eod.PosiQuantity; if (curretEod.PosiStatus == 1) { curretEod.PosiNotionalValue = 0; } curretEod.UnderlyingPrice = price; curretEod.UnderlyingMarketValue = curretEod.UnderlyingPrice * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio; curretEod.PosiMtmPnL = (curretEod.UnderlyingPrice - curretEod.PosiGrossPrice) * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio * directionRatio; //curretEod.TdPosiDividend = 0; //curretEod.PosiDividendSum = eod.PosiDividendSum + curretEod.TdPosiDividend; curretEod.PosiProfitSum = curretEod.PosiMtmPnL + curretEod.PosiDividendSum + curretEod.PosiFeePending; curretEod.TdCloseFee = 0; curretEod.TdCloseQty = 0; curretEod.TdCloseMtmPnl = 0; // 需要计算平仓盈亏分红 curretEod.TdCloseDividend = 0; curretEod.RealizedMtmPnL = eod.RealizedMtmPnL + curretEod.TdCloseMtmPnl; curretEod.RealizedDividend = eod.RealizedDividend + curretEod.TdCloseDividend; curretEod.RealizedFee = eod.RealizedFee + curretEod.TdCloseFee; curretEod.RealizedPnl = eod.RealizedPnl + curretEod.TdCloseMtmPnl; var currencyRate = new EodCurrencyRateService(UserInfo).GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, td.StartDate.Value , seekPreday: true, currencyRateType: curretEod.PosiDirection == (int)SwapDirectionEnum.收取 ? CurrencyRateType.Buy : CurrencyRateType.Sell); curretEod.TdCurrency = Convert.ToDecimal(currencyRate); //持仓价值 curretEod.SwapPositionValue = curretEod.InterestProfitSum + curretEod.PosiProfitSum; UpdateDbOption(curretEod); curretEod.Invalid = false; if (curretEod.id == 0) { PersistEodSwapPosition(curretEod); } return curretEod; } /// /// 更新虚拟交易费用 /// /// private void UpdateVTradingFee(eod_swap_position curretEod) { //int ratio = curretEod.PositionType == (int)PositionTypeFlag.Long ? -1 : 1; //int shortRatio = curretEod.PositionType == (int)PositionTypeFlag.Long ? 1 : -1; //var priceFee = curretEod.PosiQuantity == 0 ? 0 : curretEod.UnderlyingPrice + curretEod.PosiFeePending / curretEod.PosiQuantity * ratio; //curretEod.VTradingFee = -(priceFee - curretEod.PosiNetPrice - curretEod.UnderlyingPrice + curretEod.PosiGrossPrice) * curretEod.PosiNotionalValue * shortRatio; curretEod.VTradingFee = curretEod.PosiFeePending * 2; } /// /// 当日有平仓/互换,生成持仓腿日终归档,适用于上一日终存在 /// /// /// /// /// protected eod_swap_position UpdateEodPosition(swap_position swapPosition, eod_swap_position eod, eod_swap_position curretEod, trade td, DateTime valueDate, DateTime preSettleDate, List unwindEvents) { if (curretEod == null) { curretEod = eod.Clone(); curretEod.TdPosiDividend = 0; curretEod.id = 0; curretEod.ValueDate = valueDate; } var um = GetUnderlyingData(eod.UnderlyingCode); if (um == null) { return curretEod; } var dealDate = curretEod.ValueDate; int shortRatio = eod.PositionType == (int)PositionTypeFlag.Long ? 1 : -1; int directionRatio = eod.PosiDirection == (int)SwapDirectionEnum.收取 ? 1 : -1; var price = GetUnderlyingPrice(eod.UnderlyingCode, dealDate, out decimal vobp); var todayConsumedDividend = CalcConsumedDividend(curretEod, unwindEvents); var originNotional = (decimal)td.OriginalStockEqvNotional / swapPosition.PosiNetPrice; decimal totalPayment = CalcBondPayment(curretEod.UnderlyingCode, td.StartDate.Value, valueDate, (decimal)originNotional, shortRatio, directionRatio); decimal tax = um.ValueAddedTax ?? 0; decimal totalInterest = totalPayment / (1 + tax) * (1 - tax); SetPriceInfoByFlowEvent(eod, curretEod, unwindEvents, swapPosition); curretEod.dv01 = Dv01Helper.CalcDv01(eod.UnderlyingCode, curretEod.PosiQuantity, eod.PosiDirection, eod.PositionType, vobp); curretEod.UnderlyingPrice = price; curretEod.UnderlyingMarketValue = curretEod.UnderlyingPrice * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio; curretEod.PosiMtmPnL = (curretEod.UnderlyingPrice - curretEod.PosiGrossPrice) * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio * directionRatio; curretEod.TdPosiDividend = 0; // 分红与互换无关,只要持仓>0且起始日早于当前日,正常计算当日分红 // 修改,互换事件会影响待实现的分红的,现在要算上 if (valueDate > td.StartDate.Value && (curretEod.PosiQuantity > 0)) { decimal payment = CalcBondPayment(curretEod.UnderlyingCode, eod.ValueDate, valueDate, curretEod.PosiQuantity, shortRatio, directionRatio); curretEod.TdPosiDividend = Math.Round(payment / (1 + tax) * (1 - tax), 2); } curretEod.RealizedMtmPnL = eod.RealizedMtmPnL + curretEod.TdCloseMtmPnl; // 当日浮动端平仓盈亏·分红(仅来自平仓事件 和 互换 中已实现的分红) curretEod.TdCloseDividend = unwindEvents.Sum(e => e.DividendIn); curretEod.RealizedFee = eod.RealizedFee + curretEod.TdCloseFee; curretEod.RealizedPnl = eod.RealizedPnl + curretEod.TdCloseMtmPnl; curretEod.PosiStatus = curretEod.PosiQuantity == 0 ? 1 : 0; var closeQty = unwindEvents.Where(x => x.EventType == (int)SwapFlowEventTypeEnum.平仓).ToList().Sum(s => s.Quantity); curretEod.RealizedDividend = curretEod.RealizedDividend + curretEod.TdCloseDividend; // 分红与互换解耦:持仓>0时待实现分红用递增模式(前日+当天新计-当天实现), // 与 CopyEodPosition 的逐天递增口径一致,避免从头重算的舍入累积差异。 if (curretEod.PosiQuantity > 0) { curretEod.PosiDividendSum = eod.PosiDividendSum + curretEod.TdPosiDividend - curretEod.TdCloseDividend; } else { curretEod.PosiDividendSum = 0; } curretEod.RealizedPnl += curretEod.TdCloseDividend; curretEod.SwapPositionValue -= curretEod.TdCloseDividend; curretEod.PosiProfitSum = curretEod.PosiMtmPnL + curretEod.PosiDividendSum + curretEod.PosiFeePending; if (curretEod.PosiStatus == 1) { curretEod.PosiNotionalValue = 0; } var currencyRate = new EodCurrencyRateService(UserInfo).GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, td.StartDate.Value , seekPreday: true, currencyRateType: curretEod.PosiDirection == (int)SwapDirectionEnum.收取 ? CurrencyRateType.Buy : CurrencyRateType.Sell); curretEod.TdCurrency = Convert.ToDecimal(currencyRate); //持仓价值 curretEod.SwapPositionValue = curretEod.InterestProfitSum + curretEod.PosiProfitSum; UpdateDbOption(curretEod); curretEod.Invalid = false; if (curretEod.id == 0) { PersistEodSwapPosition(curretEod); } return curretEod; } private decimal CalcConsumedDividend(eod_swap_position curretEod, List events) { decimal consumedDividend = 0; List swapEventTypes = new List() { (int)SwapEventTypeEnum.互换, (int)SwapEventTypeEnum.自动互换 }; //这里要剔除掉平仓产生的分红 consumedDividend = events .Where(x => x.SwapTradeId == curretEod.SwapTradeId && swapEventTypes.Contains(x.EventType) && x.DataState == (int)SwapFlowDateStateEnum.完成) .Sum(s => s.DividendIn); return consumedDividend; } /// /// 根据开平仓事件算价格及后付费用 /// /// /// /// public void SetPriceInfoByFlowEvent(eod_swap_position eod, eod_swap_position curretEod, List unwindEvents, swap_position position) { if (eod.PosiDirection == 0) { return; } int shortRatio = eod.PositionType == (int)PositionTypeFlag.Long ? 1 : -1; int directionRatio = eod.PosiDirection == (int)SwapDirectionEnum.收取 ? 1 : -1; var unwindFlowEvents = unwindEvents.Where(x => x.EventType == (int)SwapFlowEventTypeEnum.平仓).ToList(); var openFlowEvents = unwindEvents.Where(x => x.EventType == (int)SwapFlowEventTypeEnum.开仓).ToList(); decimal unwindQty = unwindFlowEvents.Sum(s => s.Quantity); decimal openQty = openFlowEvents.Sum(s => s.Quantity); var qty = eod.PosiQuantity + openQty - unwindQty; curretEod.PosiQuantity = qty < 0 ? 0 : Math.Abs(qty); if (unwindEvents.Count == 0) { curretEod.PosiNetPrice = position.PosiNetPrice; curretEod.PosiGrossPrice = position.PosiGrossPrice; curretEod.PosiNetFeePrice = position.PosiNetFeePrice; curretEod.PosiNetNoFeePrice = position.PosiNetNoFeePrice; curretEod.PosiQuantity = position.PosiQuantity; curretEod.PosiFeePending = -position.PosiTradingFeePending * directionRatio; } else { var eventTradingFee = unwindEvents.Where(x => x.EventType == (int)SwapFlowEventTypeEnum.开仓 || x.EventType == (int)SwapFlowEventTypeEnum.平仓).Sum(s => s.TradingFeePending * (s.EventType == (int)SwapFlowEventTypeEnum.开仓 ? 1m : -1m)); curretEod.PosiFeePending = eod.PosiFeePending + eventTradingFee; if (openFlowEvents.Count() == 0) { curretEod.PosiNetPrice = eod.PosiNetPrice; curretEod.PosiGrossPrice = eod.PosiGrossPrice; curretEod.PosiNetFeePrice = eod.PosiNetFeePrice; curretEod.PosiNetNoFeePrice = eod.PosiNetNoFeePrice; } else //平仓数量一定<持仓数量 { var posiQty = eod.PosiQuantity - unwindQty; if (posiQty < 0) { posiQty = 0; } curretEod.PosiGrossPrice = (eod.PosiGrossPrice * eod.PosiQuantity + openFlowEvents.Sum(a => a.Quantity * a.TradingAmountAvg)) / (eod.PosiQuantity + openQty); curretEod.PosiGrossPrice = Math.Round(curretEod.PosiGrossPrice, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero); curretEod.PosiNetPrice = (eod.PosiNetPrice * eod.PosiQuantity + openFlowEvents.Sum(a => a.Quantity * a.TradingAmountFeeAvg)) / (eod.PosiQuantity + openQty); curretEod.PosiNetPrice = Math.Round(curretEod.PosiNetPrice, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero); curretEod.PosiNetNoFeePrice = (eod.PosiNetNoFeePrice * eod.PosiQuantity + openFlowEvents.Sum(a => a.Quantity * a.TradingAmountNetAvg)) / (eod.PosiQuantity + openQty); curretEod.PosiNetNoFeePrice = Math.Round(curretEod.PosiNetNoFeePrice ?? 0, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero); curretEod.PosiNetFeePrice = (eod.PosiNetFeePrice * eod.PosiQuantity + openFlowEvents.Sum(a => a.Quantity * a.TradingAmountNetFeeAvg)) / (eod.PosiQuantity + openQty); curretEod.PosiNetFeePrice = Math.Round(curretEod.PosiNetFeePrice ?? 0, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero); } curretEod.PosiNotionalValue = curretEod.PosiGrossPrice * curretEod.PosiQuantity * curretEod.ContractSize; curretEod.PosiNotionalValue = Math.Round(curretEod.PosiNotionalValue, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); curretEod.TdCloseDividend = unwindEvents.Sum(x => x.DividendIn); curretEod.TdCloseFee = unwindFlowEvents.Sum(x => x.TradingFee + x.TradingFeePending); curretEod.TdCloseQty = unwindQty; curretEod.TdCloseMtmPnl = unwindEvents.Sum(x => x.MarkClosePnl); } if (curretEod.PosiQuantity == 0) { curretEod.PosiFeePending = 0; } UpdateVTradingFee(curretEod); } /// /// 单标的浮动腿 首次归档 /// /// /// /// protected eod_swap_position SaveCurrentEodInitalPosi(swap_position position, trade td, DateTime settleDate, DateTime preSettleDate, List unwindEvents) { eod_swap_position curretEod = new eod_swap_position(); var um = GetUnderlyingData(position.UnderlyingCode); if (um == null) { return curretEod; } var dealDate = settleDate; curretEod.ValueDate = settleDate; curretEod.PosiStartDate = position.PosiStartDate; curretEod.PosiMatuirityDate = td.ExerciseDate.Value; curretEod.SwapTradeId = td.id; curretEod.PositionId = position.id; curretEod.ClientId = td.ClientId; int shortRatio = position.PositionType == (int)PositionTypeFlag.Long ? 1 : -1; int directionRatio = position.PosiDirection == (int)SwapDirectionEnum.收取 ? 1 : -1; curretEod.PositionType = position.PositionType; var eod = new eod_swap_position() { ContractSize = position.ContractSize, PositionType = position.PositionType, PosiDirection = position.PosiDirection, PosiFeePending = 0, PosiNetPrice = position.PosiNetPrice, PosiGrossPrice = position.PosiGrossPrice, PosiNetFeePrice = position.PosiNetFeePrice, PosiNetNoFeePrice = position.PosiNetNoFeePrice, }; curretEod.PosiDirection = position.PosiDirection; curretEod.UnderlyingCode = position.UnderlyingCode; curretEod.UnderlyingInstrumentType = position.UnderlyingInstrumentType; curretEod.SwapTradeId = position.SwapTradeId; curretEod.ContractSize = position.ContractSize; curretEod.CountRatio = position.CountRatio; curretEod.PosiTradingFee = position.PosiTradingFee; curretEod.UnderlyingPrice = GetUnderlyingPrice(position.UnderlyingCode, dealDate, out decimal vobp); SetPriceInfoByFlowEvent(eod, curretEod, unwindEvents, position); curretEod.dv01 = Dv01Helper.CalcDv01(curretEod.UnderlyingCode, curretEod.PosiQuantity, curretEod.PosiDirection, curretEod.PositionType, vobp); //if (settleDate == td.TradeDate) //{ // curretEod.UnderlyingPrice = curretEod.PosiGrossPrice; // //curretEod.TdCloseMtmPnl = 0; // //curretEod.TdCloseFee = 0; //} // TdCloseDividend 已由 SetPriceInfoByFlowEvent 设置 // 当日新增分红及待实现分红(有互换全量归0,开仓首日两者相同) curretEod.TdPosiDividend = 0; var hasSwapEvent = unwindEvents.Any(e => e.EventType == (int)SwapFlowEventTypeEnum.互换 || e.EventType == (int)SwapFlowEventTypeEnum.自动互换); if (!hasSwapEvent && settleDate > td.StartDate.Value && curretEod.PosiQuantity > 0) { decimal tax = um.ValueAddedTax ?? 0; decimal payment = CalcBondPayment(curretEod.UnderlyingCode, td.StartDate.Value, settleDate, curretEod.PosiQuantity, shortRatio, directionRatio); payment = Math.Round(payment / (1 + tax) * (1 - tax), 2); //var consumedDividend = CalcConsumedDividend(curretEod, unwindEvents); 首日应该没有分红 curretEod.TdPosiDividend = payment; curretEod.PosiDividendSum = payment; } curretEod.UnderlyingMarketValue = curretEod.UnderlyingPrice * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio; curretEod.PosiMtmPnL = (curretEod.UnderlyingPrice - curretEod.PosiGrossPrice) * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio * directionRatio; curretEod.PosiProfitSum = curretEod.PosiMtmPnL + curretEod.PosiDividendSum + curretEod.PosiFeePending; curretEod.RealizedMtmPnL = curretEod.TdCloseMtmPnl; curretEod.RealizedDividend = curretEod.TdCloseDividend; curretEod.RealizedFee = curretEod.TdCloseFee; curretEod.RealizedPnl = curretEod.TdCloseMtmPnl; curretEod.PosiStatus = curretEod.PosiQuantity == 0 ? 1 : 0; if (curretEod.PosiStatus == 1) { curretEod.PosiNotionalValue = 0; } //持仓价值 curretEod.SwapPositionValue = curretEod.InterestProfitSum + curretEod.PosiProfitSum; var currencyRate = new EodCurrencyRateService(UserInfo).GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, td.StartDate.Value , seekPreday: true, currencyRateType: curretEod.PosiDirection == (int)SwapDirectionEnum.收取 ? CurrencyRateType.Buy : CurrencyRateType.Sell); curretEod.TdCurrency = Convert.ToDecimal(currencyRate); UpdateDbOption(curretEod); curretEod.Invalid = false; PersistEodSwapPosition(curretEod); return curretEod; } /// /// 获取标的收盘价格 /// /// 标的代码 /// 收盘日 /// public decimal UnderlyingCodePrice(string code, DateTime settleDate, out decimal vobp) { vobp = 0; var data = DataCacheProvider.GetUnderlyingDataSource().GetData(code); if (data == null) { return 0; } if (data.IsBond()) { return BondPrice(data, settleDate, out vobp); } var price = data.Price ?? 0; if (EodPriceQueryService.TryGetEodPrice(settleDate, code, out var eodPrice)) { price = eodPrice.GetPrice(SettlementTypeEnum.ClosePrice); } return Convert.ToDecimal(price); } /// /// 获取债券收盘价格 /// /// /// /// public decimal BondPrice(underlying_manager data, DateTime settleDate, out decimal vobp) { vobp = 0; var price = data.Price ?? 0; if (EodPriceQueryService.TryGetBondEodPrice(settleDate, data.UnderlyingCode, out var eodPrice)) { price = eodPrice.GetPrice(SettlementTypeEnum.ClosePrice); vobp = eodPrice.Vobp ?? 0; } else { price = price * Convert.ToDouble(ConsGlobal.bondPriceMultiple); } return Convert.ToDecimal(price); } /// /// 框架合约估值 /// /// 互换交易 /// 收盘日 private void SaveEodSwap(trade td, DateTime settleDate, DateTime preSettleDate) { var eod_Swaps = DbContext.eod_swap.Where(x => x.SwapTradeId == td.id && x.ValueDate >= preSettleDate && x.ValueDate <= settleDate).ToList(); var eod_Swap = eod_Swaps.FirstOrDefault(x => x.ValueDate == settleDate); var preEodSwap = eod_Swaps.FirstOrDefault(x => x.ValueDate == preSettleDate); if (eod_Swap == null) { eod_Swap = new eod_swap(); } var tradeSpan = DbContext.trade_span.FirstOrDefault(x => x.TradeId == td.id && x.ValueDate == settleDate); var eodSwapPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == td.id && x.ValueDate == settleDate && !x.Invalid).ToList(); var interestPositions = eodSwapPositions.Where(x => string.IsNullOrEmpty(x.UnderlyingCode)).ToList();//利息腿 var positions = eodSwapPositions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode)).ToList();//持仓腿 eod_Swap.NotionalValueLong = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue); eod_Swap.NotionalValueShort = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue); eod_Swap.NotionalValue = eod_Swap.NotionalValueLong + eod_Swap.NotionalValueShort; eod_Swap.SwapTradeId = td.id; eod_Swap.SwapTradeNo = td.TradeNumber; eod_Swap.ClientId = td.ClientId; eod_Swap.BookId = td.AssetId; eod_Swap.ValueDate = settleDate; eod_Swap.StructureType = td.StructureType; eod_Swap.MarketValueLong = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.UnderlyingMarketValue); eod_Swap.MarketValueShort = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.UnderlyingMarketValue); eod_Swap.FloatingPnL = positions.Sum(s => s.PosiProfitSum); eod_Swap.dv01 = positions.Sum(s => s.dv01 ?? 0); decimal interestPnL = 0; interestPositions.ForEach(x => { decimal ratio = x.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;//收取为正,支付为负 if (marginTypes.Contains(x.InterestMode)) { ratio = -ratio; } interestPnL += x.InterestProfitSum * ratio; }); eod_Swap.InterestPnL = interestPnL; eod_Swap.PostionValue = eodSwapPositions.Sum(s => s.SwapPositionValue); eod_Swap.RealizedPnL = eodSwapPositions.Sum(s => s.RealizedPnl); eod_Swap.TdRealizedPnL = eod_Swap.RealizedPnL - (preEodSwap?.RealizedPnL ?? 0); eod_Swap.TdCloseQty = positions.Sum(s => s.TdCloseQty); var initMargin = Convert.ToDecimal(tradeSpan?.InitialMargin ?? 0); var maintainMargin = Convert.ToDecimal(tradeSpan?.WorstCastClientPayable ?? 0); if (initMargin < 0) { eod_Swap.InitMarginLoss = Math.Abs(initMargin); } else { eod_Swap.InitMarginGain = Math.Abs(initMargin); } if (maintainMargin < 0) { eod_Swap.PostionMarginLoss = Math.Abs(maintainMargin); } else { eod_Swap.PostionMarginGain = Math.Abs(maintainMargin); } UpdateDbOption(eod_Swap); if (eod_Swap.id == 0) { DbContext.eod_swap.Add(eod_Swap); } } /// /// 单标的修改当天 框架合约信息 /// /// /// private void UpdateEodSwap(trade td, DateTime settleDate) { eod_swap eod_Swap = DbContext.eod_swap.FirstOrDefault(x => x.SwapTradeId == td.id && x.ValueDate == settleDate); if (eod_Swap == null) { eod_Swap = new eod_swap(); eod_Swap.SwapTradeId = td.id; eod_Swap.ValueDate = settleDate; DbContext.eod_swap.Add(eod_Swap); } var eodSwapPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == td.id && x.ValueDate == settleDate && !x.Invalid).ToList(); var interestPositions = eodSwapPositions.Where(x => string.IsNullOrEmpty(x.UnderlyingCode)).ToList();//利息腿 var positions = eodSwapPositions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode)).ToList();//持仓腿 eod_Swap.NotionalValue = Convert.ToDecimal(td.StockEqvNotional); eod_Swap.NotionalValueLong = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue); eod_Swap.NotionalValueShort = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue); eod_Swap.MarketValueLong = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.UnderlyingMarketValue); eod_Swap.MarketValueShort = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.UnderlyingMarketValue); eod_Swap.FloatingPnL = positions.Sum(s => s.PosiProfitSum); eod_Swap.dv01 = positions.Sum(s => s.dv01 ?? 0); interestPositions.ForEach(x => { decimal ratio = x.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;//收取为正,支付为负 if (marginTypes.Contains(x.InterestMode)) { ratio = -ratio; } eod_Swap.InterestPnL += x.InterestProfitSum * ratio; }); eodSwapPositions.ForEach(x => { decimal ratio = x.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;//收取为正,支付为负 if (marginTypes.Contains(x.InterestMode)) { ratio = -ratio; } eod_Swap.TdRealizedPnL += x.TdCloseMtmPnl + x.TdCloseDividend + x.TdCloseFee + x.TdCloseInterest * ratio + x.TdCloseInterestFee; }); eod_Swap.PostionValue = eodSwapPositions.Sum(s => s.SwapPositionValue); eod_Swap.RealizedPnL = eodSwapPositions.Sum(s => s.RealizedMtmPnL + s.RealizedDividend + s.RealizedFee + s.RealizedInterest + s.RealizedInterestFee); eod_Swap.TdCloseQty = positions.Sum(s => s.TdCloseQty); var tradeInitMarginObj = DbContext.trade_initial_margin.FirstOrDefault(x => x.TradeId == td.id); var initMarginList = interestPositions.Where(x => x.InterestMode == (int)InterestModeEnum.初始预付金 && x.HappenDate == settleDate).ToList(); var addMarginList = interestPositions.Where(x => x.InterestMode == (int)InterestModeEnum.追加预付金 && x.HappenDate == settleDate).ToList(); eod_Swap.InitMarginGain += initMarginList.Where(s => s.InterestDirection == (int)SwapDirectionEnum.收取).ToList().Sum(s => s.InterestPrincipalFix); eod_Swap.InitMarginLoss += initMarginList.Where(s => s.InterestDirection == (int)SwapDirectionEnum.支付).ToList().Sum(s => s.InterestPrincipalFix); eod_Swap.PostionMarginGain += addMarginList.Where(s => s.InterestDirection == (int)SwapDirectionEnum.收取).ToList().Sum(s => s.InterestPrincipalFix); eod_Swap.PostionMarginLoss += addMarginList.Where(s => s.InterestDirection == (int)SwapDirectionEnum.支付).ToList().Sum(s => s.InterestPrincipalFix); UpdateDbOption(eod_Swap); DbContext.SaveChanges(); } /// /// 获取多空组合 平仓详细 /// /// /// /// public SwapLongShortCloseModel GetCloseDetails(int tradeId, DateTime valueDate) { SwapLongShortCloseModel closeModel = new SwapLongShortCloseModel(); var eodPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == tradeId && x.ValueDate == valueDate && !x.Invalid).ToList(); var flowEvents = DbContext.swap_flow_event.Where(x => x.SwapTradeId == tradeId && x.EventDate == valueDate && x.DataState == (int)SwapFlowDateStateEnum.完成 && x.EventType == (int)SwapEventTypeEnum.平仓 && string.IsNullOrEmpty(x.UnderlyingCode)).ToList(); closeModel.DealPositions = eodPositions.Where(x => x.TdCloseQty != 0).ToList(); closeModel.DealInterests = flowEvents; return closeModel; } /// /// 查询互换日终持仓风险-明细 /// /// /// public SearchListResult SearchEodPositionList(EodSwapPositionQueryRequest req) { var predicate = PredicateBuilder.Create(n => !n.Invalid); var tradePredicate = PredicateBuilder.Create(n => n.TradeType == "收益互换" && n.ValidState != "InValid"); if (req.ValueDate.HasValue) { predicate = predicate.And(n => n.ValueDate == req.ValueDate); //tradePredicate = tradePredicate.And(n => n.StartDate <= req.ValueDate); } if (!string.IsNullOrEmpty(req.TradeNumber)) { tradePredicate = tradePredicate.And(n => n.TradeNumber.Contains(req.TradeNumber.Trim())); } if (req.UserAssets != null || req.UserClients != null) { tradePredicate = tradePredicate.And(q => req.UserAssets.Contains(q.AssetId) || req.UserClients.Contains(q.ClientId)); } if (req.ClientIds != null && req.ClientIds.Any(x => x > 0)) { tradePredicate = tradePredicate.And(q => req.ClientIds.Contains(q.ClientId)); } if (req.BookIds != null && req.BookIds.Any(x => x > 0)) { tradePredicate = tradePredicate.And(q => req.BookIds.Contains(q.AssetId)); } if (req.UnderlyingIds != null) { var underlyingCodes = new List(); foreach (var id in req.UnderlyingIds) { var un = UnderlyingDataProvider.GetUnderlying(id); if (un != null) { underlyingCodes.Add(un.UnderlyingCode); } } predicate = predicate.And(n => underlyingCodes.Contains(n.UnderlyingCode)); } var positionQuery = DbContext.eod_swap_position.Where(predicate); var tradeQuery = DbContext.trade.Where(tradePredicate); var query = from position in positionQuery join td in tradeQuery on position.SwapTradeId equals td.id select new SwapPositionResponse { eodPosition = position, TradeDate = td.StartDate.Value, SwapTradeNo = td.TradeNumber, StructureType = td.StructureType, ClientName = td.ClientName, ClientId = td.ClientId, }; if (string.IsNullOrEmpty(req.sidx)) { req.sidx = "SwapTradeNo,eodPosition.id"; req.sord = "asc"; } var retListResult = query.ToSearchList(req); foreach (var item in retListResult.rows) { var client = DataCacheProvider.GetClientDataSource().GetData(item.ClientId); item.SwapTradeTypeStr = client?.SwapTradeTypeStr; var multiplier = ConsGlobal.InstrumentType.IsBond(item.eodPosition.UnderlyingInstrumentType) ? 100 : 1; item.eodPosition.PosiNetPrice *= multiplier; item.eodPosition.PosiGrossPrice *= multiplier; item.eodPosition.PosiNetFeePrice *= multiplier; item.eodPosition.PosiNetNoFeePrice *= multiplier; item.eodPosition.UnderlyingPrice *= multiplier; } return retListResult; } /// /// 查询互换日终持仓风险-框架合约 /// /// /// public SearchListResult SearchEodSwapList(EodSwapQueryRequest req) { var predicate = PredicateBuilder.Create(n => 1 == 1); var tradePredicate = PredicateBuilder.Create(n => n.TradeType == "收益互换" && n.ValidState != "InValid"); if (req.ValueDate.HasValue) { predicate = predicate.And(n => n.ValueDate == req.ValueDate); // tradePredicate = tradePredicate.And(n=>n.StartDate<=req.ValueDate); } if (!string.IsNullOrEmpty(req.TradeNumber)) { tradePredicate = tradePredicate.And(n => n.TradeNumber.Contains(req.TradeNumber.Trim())); } if (req.UserAssets != null || req.UserClients != null) { tradePredicate = tradePredicate.And(q => req.UserAssets.Contains(q.AssetId) || req.UserClients.Contains(q.ClientId)); } if (req.ClientIds != null && req.ClientIds.Any(x => x > 0)) { tradePredicate = tradePredicate.And(q => req.ClientIds.Contains(q.ClientId)); } if (req.BookIds != null && req.BookIds.Any(x => x > 0)) { tradePredicate = tradePredicate.And(q => req.BookIds.Contains(q.AssetId)); } var positionQuery = DbContext.eod_swap.Where(predicate); var tradeQuery = DbContext.trade.Where(tradePredicate); var query = from position in positionQuery join td in tradeQuery on position.SwapTradeId equals td.id select new EodSwapResponse { position = position, TradeDate = td.StartDate.Value, SwapTradeNo = td.TradeNumber, StructureType = td.StructureType, ClientName = td.ClientName, AssetBookName = td.AssetBookName, ClientId = td.ClientId }; if (string.IsNullOrEmpty(req.sidx)) { req.sidx = "SwapTradeNo,position.id"; req.sord = "asc"; } DbContext.SetDebugLog(); var retListResult = query.ToSearchList(req); foreach (var item in retListResult.rows) { var client = DataCacheProvider.GetClientDataSource().GetData(item.ClientId); item.SwapTradeTypeStr = client?.SwapTradeTypeStr; } var dv01 = query.Sum(O => O.position.dv01??0); retListResult.Sum = new {DV = dv01 }; return retListResult; } /// /// 获取互换交易日终持仓数据 /// /// /// /// public List GetPreEodPositions(int tradeId, DateTime valueDate) { return DbContext.eod_swap_position.Where(x => x.SwapTradeId == tradeId && x.ValueDate == valueDate && !x.Invalid).ToList(); } /// /// 获取互换交易日终持仓数据集合 /// /// /// /// public List GetEodSwaps(List tradeIds, DateTime valueDate) { return DbContext.eod_swap.Where(x => tradeIds.Contains(x.SwapTradeId) && x.ValueDate == valueDate).ToList(); } /// /// 获取互换交易某交易日区间框架合约数据 /// /// /// /// /// public List GetEodSwaps(List tradeIds, DateTime valueDate, DateTime preValueDate) { return DbContext.eod_swap.Where(x => tradeIds.Contains(x.SwapTradeId) && x.ValueDate <= valueDate && x.ValueDate >= preValueDate).ToList(); } /// /// 获取互换交易某日终持仓数据 /// /// /// /// /// public List GetEodPositions(List tradeIds, DateTime valueDate, DateTime preValueDate) { return DbContext.eod_swap_position.Where(x => tradeIds.Contains(x.SwapTradeId) && !string.IsNullOrEmpty(x.UnderlyingCode) && x.ValueDate <= valueDate && x.ValueDate >= preValueDate && !x.Invalid).ToList(); } /// /// 获取互换交易某区间日终持仓估值-按产品要求 /// /// /// public SearchListResult SearchPositionList(ClientSwapPositionRequest req) { var retListResult = GetSearchPositionList(req); var clientDataSource = DataCacheProvider.GetClientDataSource(); var underlyDataSource = DataCacheProvider.GetUnderlyingDataSource(); foreach (var item in retListResult.rows) { var client = clientDataSource.GetData(item.ClientId); item.ClientNumber = client.Number; if (!string.IsNullOrEmpty(item.eodPosition.UnderlyingCode)) { var underly = underlyDataSource.GetData(item.eodPosition.UnderlyingCode); if (underly != null) { item.eodPosition.UnderlyingName = underly.UnderlyingName; } } } return retListResult; } /// /// 获取互换交易某区间日终持仓估值-按山证要求 /// /// /// public SearchListResult SearchEodPositionList(ClientSwapPositionRequest req) { var retListResult = GetSearchEodPositionList(req); return retListResult; } /// /// 互换持仓明细查询 /// /// /// private SearchListResult GetSearchPositionList(ClientSwapPositionRequest req) { var predicate = PredicateBuilder.Create(n => !n.Invalid && n.PositionType > 0 && n.PosiStatus == 0); var tradePredicate = PredicateBuilder.Create(n => n.TradeType == "收益互换" && n.ValidState != "InValid"); if (req.ClientId > 0) { predicate = predicate.And(x => x.ClientId == req.ClientId); } if (req.ValueDate != null) { predicate = predicate.And(x => x.ValueDate == req.ValueDate); tradePredicate = tradePredicate.And(n => n.StartDate <= req.ValueDate); } var positionQuery = DbContext.eod_swap_position.Where(predicate); var tradeQuery = DbContext.trade.Where(tradePredicate); var query = from position in positionQuery join td in tradeQuery on position.SwapTradeId equals td.id select new SwapPositionResponse { eodPosition = position, TradeDate = td.StartDate.Value, SwapTradeNo = td.TradeNumber, StructureType = td.StructureType, ClientName = td.ClientName, ClientId = td.ClientId, InitialMarginDirection = position.PosiDirection }; if (string.IsNullOrEmpty(req.sidx)) { req.sidx = "SwapTradeNo,eodPosition.id"; req.sord = "asc"; } var retListResult = query.ToSearchList(req); foreach (var item in retListResult.rows) { SetClientEodPosition(item.eodPosition); SetPosiPrice(item.eodPosition); } return retListResult; } private SearchListResult GetSearchEodPositionList(ClientSwapPositionRequest req) { var predicate = PredicateBuilder.Create(n => !n.Invalid && n.PosiQuantity > 0); var interestPredicate = PredicateBuilder.Create(n => !n.Invalid && n.InterestDirection > 0); var tradePredicate = PredicateBuilder.Create(n => n.ValidState != "InValid"); // TODO 这里暂时忽略前端传的值 暂时使用临时方案:同时查普通债券类收益互换和普通收益互换 tradePredicate = tradePredicate.And(n => n.StructureType == "普通债券类收益互换" || n.StructureType == "普通收益互换"); if (req.ClientId > 0) { predicate = predicate.And(x => x.ClientId == req.ClientId); } if (req.ValueDateFrom != null) { predicate = predicate.And(x => x.ValueDate >= req.ValueDateFrom); } if (req.ValueDate != null) { predicate = predicate.And(x => x.ValueDate == req.ValueDate); tradePredicate = tradePredicate.And(x => req.ValueDate >= x.TradeDate); } var positionQuery = DbContext.eod_swap_position.Where(predicate); var tradeQuery = DbContext.trade.Where(tradePredicate); var query = from position in positionQuery join td in tradeQuery on position.SwapTradeId equals td.id join tcrConfirm in DbContext.trade_contract_r.Where(O => O.Type == ContractTypeEnum.Trade && O.IsValid) on td.id equals tcrConfirm.TradeId into tcrConfirms from tcrConfirm in tcrConfirms.DefaultIfEmpty() select new EodSwapPositionResponse { position = position, ClientName = td.ClientName, ConfrimNo = tcrConfirm.ContractCode, TradeNumber = td.TradeNumber, StructureType = td.StructureType, UnwindDate = td.UnWindDate, TradeStatus = td.TradeStatus, InitYtm = td.InitYtm }; if (string.IsNullOrEmpty(req.sidx)) { req.sidx = "position.id"; req.sord = "asc"; } var retListResult = query.ToSearchList(req); var tradeIds = retListResult.rows.Select(s => s.position.SwapTradeId).ToList(); interestPredicate = interestPredicate.And(x => tradeIds.Contains(x.SwapTradeId)); var valueDates = retListResult.rows.Select(s => s.position.ValueDate).Distinct().ToList(); var eodPositions = DbContext.eod_swap_position.Where(interestPredicate).ToList(); var positions = DbContext.swap_position.Where(x => tradeIds.Contains(x.SwapTradeId) && x.InterestMode == (int)InterestModeEnum.初始预付金 && x.IsInitial && !x.Invalid).ToList(); var tradeExtends = DbContext.trade_extend.Where(x => tradeIds.Contains(x.TradeId)).ToList(); Dictionary tradeDic = new Dictionary(); foreach (var item in retListResult.rows) { var tradeExtend = tradeExtends.FirstOrDefault(x => x.TradeId == item.position.SwapTradeId); var eventDate = item.position.ValueDate; if (tradeExtend != null) { eventDate = QdpCalendarHelper.GetNonHoliday(eventDate.AddDays(tradeExtend.ExtendObj.SettlementRules)); } item.DayCount = Math.Max(0, (eventDate - item.position.PosiStartDate).Days + 1); //item.position.PosiProfitSum += item.position.VTradingFee-item.position.PosiFeePending; SetClientEodPosition(item.position); //item.position.PosiProfitSum += item.TradingFee; var posiProfitSum = item.position.PosiProfitSum; //item.position.PosiProfitSum 不需要加交易费用 item.position.PosiProfitSum = item.position.PosiProfitSum - item.position.PosiFeePending - item.position.PosiDividendSum; item.NetSettmentAmount = item.position.PosiProfitSum + item.position.PosiDividendSum + item.position.PosiFeePending; item.PeriodAmount = item.position.PosiDividendSum; var margins = positions.Where(x => x.SwapTradeId == item.position.SwapTradeId); var interests = eodPositions.Where(x => x.SwapTradeId == item.position.SwapTradeId && x.ValueDate == item.position.ValueDate); var eodMargins = interests.Where(x => marginTypes.Contains(x.InterestMode)); var eodInterests = interests.Where(x => !marginTypes.Contains(x.InterestMode)); var floatRateInterest = eodInterests.Where(x => !string.IsNullOrEmpty(x.FloatRateUnderlyingCode)).FirstOrDefault(); item.position.FloatRateUnderlyingCode = floatRateInterest?.FloatRateUnderlyingCode; item.position.FloatRate = floatRateInterest?.FloatRate ?? 0; item.OpenMarginAmount = margins.Sum(s => s.InterestPrincipalFix * (s.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1)); item.OpenMarginRate = margins.Sum(s => s.InterestRateDefault * (s.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1)); item.MarginInterestAmount = eodMargins.Sum(s => s.InterestIncomeSum * (s.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1)); item.InterestAmount = eodInterests.Sum(s => s.InterestIncomeSum * (s.InterestDirection == (int)SwapDirectionEnum.收取 ? -1 : 1)); item.InterestRate = eodInterests.Sum(s => s.InterestRateDefault); item.NetSettmentAmount += item.InterestAmount + item.MarginInterestAmount + eodMargins.Sum(s => s.InterestPrincipalFix * (s.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1)); item.NetSettmentAmount = Math.Round(item.NetSettmentAmount, ConsGlobal.MoneyRound, MidpointRounding.AwayFromZero); if (item.position.PosiNotionalValue != 0 && item.position.PosiNetPrice != 0) { item.FloatRateAbs = item.position.PosiNotionalValue == 0 ? 0 : item.InterestAmount / item.position.PosiNotionalValue; } SetPosiPrice(item.position); } return retListResult; } /// /// 设置客户视角 /// /// private void SetClientEodPosition(eod_swap_position position) { position.TdCloseDividend = -position.TdCloseDividend; position.TdCloseMtmPnl = -position.TdCloseMtmPnl; position.TdCloseFee = -position.TdCloseFee; position.TdCloseInterest = -position.TdCloseInterest; position.TdCloseInterestFee = -position.TdCloseInterestFee; position.RealizedMtmPnL = -position.RealizedMtmPnL; position.RealizedDividend = -position.RealizedDividend; position.RealizedFee = -position.RealizedFee; position.RealizedInterest = -position.RealizedInterest; position.RealizedInterestFee = -position.RealizedInterestFee; position.RealizedPnl = -position.RealizedPnl; position.InterestProfitSum = -position.InterestProfitSum; position.PosiProfitSum = -position.PosiProfitSum; position.VTradingFee = -position.VTradingFee; position.PosiFeePending = -position.PosiFeePending; position.SwapPositionValue = -position.SwapPositionValue; position.PosiDividendSum = -position.PosiDividendSum; } private void SetPosiPrice(eod_swap_position position) { var um = DataCacheProvider.GetUnderlyingDataSource().GetData(position.UnderlyingCode); if (um != null && um.IsBond()) { position.PosiNetPrice *= 100; position.UnderlyingPrice *= 100; position.PosiGrossPrice *= 100; position.PosiNetFeePrice *= 100; position.PosiNetNoFeePrice *= 100; return; } } /// /// 获取客户互换持仓信息 /// /// /// /// public List GetSwapPositions(int clientId, DateTime valueDate) { var trades = DbContext.trade.Where(x => x.TradeType == "收益互换" && x.ClientId == clientId && ConsTrade.LiveTradeStatusList.Contains(x.TradeStatus) && x.ValidState != "InValid").ToList(); var tradeIds = trades.Select(s => s.id).ToList(); var eodSwaps = DbContext.eod_swap_position.Where(x => tradeIds.Contains(x.SwapTradeId) && x.ValueDate == valueDate).ToList(); var tradeExtends = DbContext.trade_extend.Where(x => tradeIds.Contains(x.TradeId)).ToList(); return ConvertEodPnl(eodSwaps, trades, tradeExtends); } private List ConvertEodPnl(List eodSwaps, List trades, List tradeExtends) { List list = new List(); foreach (var item in eodSwaps) { var tradeOrigin = trades.First(x => x.id == item.SwapTradeId); var realizedPnL = item.RealizedMtmPnL + item.RealizedInterest; var tdExtend = tradeExtends.First(x => x.TradeId == item.SwapTradeId); eod_position model = new eod_position() { TradeId = tradeOrigin.id, TradeType = tradeOrigin.TradeType, ClientId = tradeOrigin.ClientId, TradeNumber = tradeOrigin.TradeNumber, TradeDate = tradeOrigin.TradeDate, ExerciseDate = tradeOrigin.ExerciseDate, PrincipalRate = tradeOrigin.PrincipalRate ?? 0, BasisUnderlyingCode = tradeOrigin.BasisUnderlyingCode, UnderlyingCode = item.UnderlyingCode, BasisGap = tradeOrigin.BasisGap ?? 0, Lots = Convert.ToDouble(item.PosiQuantity), ParticipationRate = tradeOrigin.ParticipationRate ?? 0, NoRiskRate = tradeOrigin.NoRiskRate ?? 0, UnderlyingPrice = Convert.ToDouble(item.UnderlyingPrice), Pv = Convert.ToDouble(item.UnderlyingMarketValue) * -1, RoundedPv = Math.Round(Convert.ToDouble(item.UnderlyingMarketValue), 2) * -1, Pnl = Convert.ToDouble(realizedPnL) * -1, RoundedPnl = Math.Round(Convert.ToDouble(realizedPnL), 2) * -1, ValueDate = item.ValueDate, PvDouble = Convert.ToDouble(item.UnderlyingMarketValue), PnlDouble = Convert.ToDouble(realizedPnL), PositionRelizedAmount = Convert.ToDouble(realizedPnL) * -1, InstrumentType = tradeOrigin.UnderlyingInstrumentType, IsGroup = tradeOrigin.IsGroup, SettlementType = tradeOrigin.SettlementType, SettlementFlag = tradeOrigin.SettlementFlag, tradeOrigin = tradeOrigin.Clone(), Vol = 0, Delta = 0, Gamma = 0, Theta = 0, Vega = 0, Rho = 0, GammaCash = 0 }; SetDicValue(model, item, tdExtend.ExtendObj.AnnualDays); list.Add(model); } return list; } /// /// 设置持仓导出字典信息 /// /// /// /// private void SetDicValue(eod_position model, eod_swap_position item, int annualDays) { var extDic = model.trade.MetaDic; if (!string.IsNullOrEmpty(item.UnderlyingCode)) { var underlyingAssetName = DataCacheProvider.GetUnderlyingDataSource().GetData(item.UnderlyingCode)?.UnderlyingName; decimal posiTradingFeeUnit = 0; if (item.PosiQuantity != 0) { posiTradingFeeUnit = item.PosiTradingFee / item.PosiQuantity; } if (item.PosiDirection == (int)SwapDirectionEnum.支付) { extDic["互换_支付方标的代码"] = item.UnderlyingCode; extDic["互换_支付方标的名称"] = underlyingAssetName; extDic["互换_支付方期初标的价格"] = item.PosiGrossPrice.OtcFormatUmPrice(); extDic["互换_支付方交易数量"] = item.PosiQuantity.OtcFormatNotional(); extDic["互换_支付方到期标的价格"] = item.UnderlyingPrice.OtcFormatUmPrice(); extDic["互换_支付方单位交易费用"] = posiTradingFeeUnit.OtcFormatUmPrice(); extDic["互换_支付方初始预付金"] = item.RealizedFee.OtcFormatPercent(); extDic["互换_支付方交易费用"] = item.PosiTradingFee.OtcFormatUmPrice(); extDic["互换_支付方多空方向"] = item.PositionType == (int)PositionTypeFlag.Long ? "多头" : "空头"; } else { extDic["互换_收取方标的代码"] = item.UnderlyingCode; extDic["互换_收取方标的名称"] = underlyingAssetName; extDic["互换_收取方期初标的价格"] = item.PosiGrossPrice.OtcFormatUmPrice(); extDic["互换_收取方交易数量"] = item.PosiQuantity.OtcFormatNotional(); extDic["互换_收取方到期标的价格"] = item.UnderlyingPrice.OtcFormatUmPrice(); extDic["互换_收取方单位交易费用"] = posiTradingFeeUnit.OtcFormatUmPrice(); extDic["互换_收取方初始预付金"] = ""; extDic["互换_收取方交易费用"] = item.PosiTradingFee.OtcFormatUmPrice(); extDic["互换_收取方多空方向"] = item.PositionType == (int)PositionTypeFlag.Long ? "多头" : "空头"; } } else { if (item.InterestDirection == (int)SwapDirectionEnum.支付) { extDic["互换_支付方互换利率"] = item.InterestRateDefault.OtcFormatPercent(); extDic["互换_支付方固定收益"] = ""; } else { extDic["互换_收取方互换利率"] = item.InterestRateDefault.OtcFormatPercent(); extDic["互换_收取方固定收益"] = ""; } } extDic["互换_互换日期"] = ""; extDic["年化天数"] = annualDays.ToString(); } } }