-- ============================================================================ -- 互换分红损益:数据导出与重复计算验证 SQL 工具箱 -- ---------------------------------------------------------------------------- -- ⚠ 本脚本所有列名均取自真实库 DDL(glms_yltrs_ylcms.sql),非实体类属性名。 -- 实体类属性名与库列名存在差异(如 swap_flow_event.EventDate、 -- bond_payment_info.paying_interest、swap_position.IsInitial 等)。 -- -- 目的: -- 1) 定位并导出一笔"带分红的互换交易"完整数据,作 golden source 种子; -- 2) 用真实库数据验证"分红被重复计算 2 次"; -- 3) 生命周期守恒校验。 -- -- 涉及表与关键列(取自 DDL,列名以反引号为准): -- swap_event 事件主表(窄列):id,SwapTradeId,ValueDate,EventType, -- EventReason,EventData(json),Invalid,BackId,OptId,OptTime,ClientCashId -- ※ 无 SwapTradeNo/UnderlyingCode/金额,这些在 EventData 或流水表 -- swap_position 持仓:PositionId,SwapTradeId,UnderlyingCode,PosiQuantity, -- PosiNotionalValue,PosiNetPrice,PosiGrossPrice,IsInitial(0实时/1期初), -- PosiDividendIncome,PosiTradingFeePending,Invalid -- swap_flow_event 流水(分红核心):EventDate(非ValueDate!),SwapTradeId,SwapTradeNo, -- EventType,PositionId,Quantity,MarkClosePnl,DividendIn,DividendPending, -- CloseFee,DataState(0废弃/1等待/100完成) -- eod_swap_position 日终归档:ValueDate,SwapTradeId,PositionId,TdCloseMtmPnl,TdCloseDividend, -- RealizedMtmPnL,RealizedDividend,RealizedFee,RealizedInterest, -- RealizedInterestFee,RealizedPnl,DV01(大写),PosiDividendSum,Invalid -- eod_swap 日终汇总:ValueDate,SwapTradeId,SwapTradeNo,TdRealizedPnL,RealizedPnL, -- PostionValue -- bond_payment_info 债券付息:underlying_code,pay_date_PL,paying_interest(非payment_interest!), -- paying_principal,paying_price -- -- EventType 枚举(注意 swap_event 与 swap_flow_event 取值不同!): -- swap_event.EventType (SwapEventTypeEnum): 展期1/平仓2/互换3/自动互换4/回退5/合成持仓6... -- swap_flow_event.EventType (SwapFlowEventTypeEnum): 开仓1/平仓2/互换3/自动互换4 -- → 分红型互换在两表均为 EventType IN (3,4) -- ============================================================================ -- ============================================================================ -- 第 0 步:定位一笔"带分红的互换交易"作为样本 -- ============================================================================ -- 思路:互换/自动互换事件(EventType in 3,4) 的 DividendIn != 0,即发生过分红型互换。 -- swap_flow_event 自带 SwapTradeNo(人类可读),无需关联 swap_event。 -- DataState=100 仅取已完成流水。 SELECT SwapTradeId, MAX(SwapTradeNo) AS SwapTradeNo, COUNT(*) AS 互换事件数, SUM(DividendIn) AS 互换事件分红合计, SUM(MarkClosePnl) AS 互换事件盯市合计, MAX(EventDate) AS 最近事件日 FROM swap_flow_event WHERE EventType IN (3, 4) -- 互换 / 自动互换 AND DividendIn <> 0 -- 真正发生过分红 AND DataState = 100 -- 仅完成的 GROUP BY SwapTradeId ORDER BY SUM(DividendIn) DESC, MAX(EventDate) DESC LIMIT 20; -- 选定其中一行 SwapTradeId,填入下面 @TargetTradeId。 -- ============================================================================ -- 第 1 步:单笔交易完整数据导出(golden source 种子) -- ============================================================================ -- 用法:把 @TargetTradeId 改为第 0 步选出的值,逐段执行。 SET @TargetTradeId := 1874; -- ← 替换为实际样本 SwapTradeId -- 1.1 互换事件主表(窄列;EventData 是 json,含详细快照) SELECT id, SwapTradeId, ValueDate, EventType, EventReason, Invalid, BackId, OptId, OptName, OptTime, ClientCashId FROM swap_event WHERE SwapTradeId = @TargetTradeId ORDER BY ValueDate, id; -- 1.2 互换持仓(区分期初/实时:IsInitial 1=期初, 0=实时) SELECT PositionId, SwapTradeId, UnderlyingCode, PosiDirection, PositionType, PosiQuantity, PosiNotionalValue, PosiNetPrice, PosiGrossPrice, ContractSize, CountRatio, PosiTradingFee, PosiTradingFeePending, PosiDividendIncome, IsInitial, Invalid FROM swap_position WHERE SwapTradeId = @TargetTradeId ORDER BY IsInitial DESC, PositionId; -- 说明:IsInitial=1 是期初开仓腿;=0 是实时持仓(会随平仓/互换变动)。 -- 1.3 流水事件(分红核心表,导出全字段便于复盘) SELECT id, EventDate, SwapTradeId, SwapTradeNo, EventType, EventReason, PositionId, PayDirection, PositionType, UnderlyingCode, Quantity, TradingAmount, TradingAmountAvg, TradingAmountFeeAvg, TradingFee, TradingFeePending, DividendPending, MarkClosePnl, DividendIn, CloseFee, DataState, ClientCashId FROM swap_flow_event WHERE SwapTradeId = @TargetTradeId ORDER BY EventDate, EventType, id; -- 1.4 日终持仓归档(按日快照,含所有 TdClose* / Realized* 字段) SELECT id, ValueDate, SwapTradeId, PositionId, PosiDirection, PositionType, UnderlyingCode, PosiQuantity, PosiNotionalValue, PosiNetPrice, PosiGrossPrice, UnderlyingPrice, UnderlyingMarketValue, TdPosiDividend, PosiMtmPnL, PosiDividendSum, PosiFeePending, PosiProfitSum, TdCloseQty, TdCloseMtmPnl, TdCloseDividend, TdCloseFee, RealizedMtmPnL, RealizedDividend, RealizedFee, RealizedInterest, RealizedInterestFee, RealizedPnl, DV01, PosiStatus, Invalid FROM eod_swap_position WHERE SwapTradeId = @TargetTradeId ORDER BY PositionId, ValueDate; -- 1.5 日终互换层汇总 SELECT id, ValueDate, SwapTradeId, SwapTradeNo, TdCloseQty, TdRealizedPnL, RealizedPnL, PostionValue FROM eod_swap WHERE SwapTradeId = @TargetTradeId ORDER BY ValueDate; -- 1.6 债券付息明细(理论应付分红来源) -- 先从 swap_position 取该交易挂钩的标的代码: SELECT DISTINCT UnderlyingCode FROM swap_position WHERE SwapTradeId = @TargetTradeId AND UnderlyingCode IS NOT NULL; -- 再用取到的 UnderlyingCode 查付息明细(替换 @BondCode): SET @BondCode := 'PUT_UNDERLYING_CODE_HERE'; SELECT id, underlying_code, inner_code, pay_date_PL, pay_date_act, paying_interest, paying_principal, paying_price, interest_tax_rate, event_type, info_source, insert_time FROM bond_payment_info WHERE underlying_code = @BondCode ORDER BY pay_date_PL; -- ============================================================================ -- 第 2 步:分红重复计算验证(证明"分红被算 2 次") -- ============================================================================ -- 根因链路(SwapEodPositionService.cs): -- SetPriceInfoByFlowEvent:1610 TdCloseMtmPnl = Σ unwindEvents.MarkClosePnl -- (互换/平仓事件的 MarkClosePnl 已含分红) -- UpdateEodPosition:1486 RealizedMtmPnL += TdCloseMtmPnl ← 分红第1次进"盯市"列 -- UpdateEodPosition:1488/1607 TdCloseDividend = Σ DividendIn -- UpdateEodPosition:1494 RealizedDividend += TdCloseDividend ← 分红第2次进"分红"列 -- SaveEodSwap:1869 eod_swap.RealizedPnL = Σ(RealizedMtmPnL + RealizedDividend + ...) -- → 分红在盯市列和分红列各计一次 = 2 次 -- -- 验证思路:若 MarkClosePnl 含分红,则同一事件日同一持仓满足: -- 该日"盯市列中扣除纯平仓价差后的余额" ≈ "分红列",且两者都进了 RealizedPnL → 重复。 SET @TargetTradeId := 1874; -- ← 替换为实际样本 -- 2.1 逐日核对:盯市列 vs 分红列 -- 关键比对:盯市列里扣除"纯平仓事件(EventType=2)的价差"后,剩余是否≈分红列。 -- 若是,说明互换/自动互换事件(EventType in 3,4)的 MarkClosePnl 含分红。 SELECT esp.ValueDate, esp.PositionId, esp.TdCloseMtmPnl AS 当日盯市列, esp.TdCloseDividend AS 当日分红列, -- 当日纯平仓事件(EventType=2)的盯市价差合计(理论上=纯价差,不含分红) (SELECT COALESCE(SUM(sfe2.MarkClosePnl), 0) FROM swap_flow_event sfe2 WHERE sfe2.SwapTradeId = esp.SwapTradeId AND sfe2.PositionId = esp.PositionId AND sfe2.EventType = 2 AND sfe2.DataState = 100 AND sfe2.EventDate = esp.ValueDate) AS 纯平仓盯市价差, -- 盯市列 - 纯平仓价差 = 互换/自动互换事件贡献的盯市成分(若≈分红列→含分红) (esp.TdCloseMtmPnl - ( SELECT COALESCE(SUM(sfe2.MarkClosePnl), 0) FROM swap_flow_event sfe2 WHERE sfe2.SwapTradeId = esp.SwapTradeId AND sfe2.PositionId = esp.PositionId AND sfe2.EventType = 2 AND sfe2.DataState = 100 AND sfe2.EventDate = esp.ValueDate )) AS 盯市列扣除纯平仓后余额, esp.TdCloseDividend AS 分红列, esp.RealizedMtmPnL AS 累计盯市已实现, esp.RealizedDividend AS 累计分红已实现, esp.RealizedPnl AS 持仓层累计已实现 FROM eod_swap_position esp WHERE esp.SwapTradeId = @TargetTradeId AND (esp.TdCloseDividend <> 0 OR esp.TdCloseMtmPnl <> 0) ORDER BY esp.PositionId, esp.ValueDate; -- 2.2 全生命周期汇总:盯市列累计 + 分红列累计 vs RealizedPnL -- 若 MarkClosePnl 含分红:盯市累计里多算了一份分红,导致 -- RealizedMtmPnL + RealizedDividend > 真实盯市价差 + 分红 (多出 ≈ 分红金额) SELECT esp.PositionId, MAX(esp.RealizedMtmPnL) AS 最终累计盯市已实现, MAX(esp.RealizedDividend) AS 最终累计分红已实现, MAX(esp.RealizedPnl) AS 最终持仓层累计已实现, -- 互换层汇总公式(SaveEodSwap:1869)的口径: (MAX(esp.RealizedMtmPnL) + MAX(esp.RealizedDividend) + COALESCE(MAX(esp.RealizedFee),0) + COALESCE(MAX(esp.RealizedInterest),0) + COALESCE(MAX(esp.RealizedInterestFee),0)) AS 按互换层公式重算, -- 理论上不含费的纯盯市价差(用纯平仓 EventType=2 的 MarkClosePnl 估算): (SELECT COALESCE(SUM(sfe.MarkClosePnl), 0) FROM swap_flow_event sfe WHERE sfe.SwapTradeId = esp.SwapTradeId AND sfe.PositionId = esp.PositionId AND sfe.EventType = 2 AND sfe.DataState = 100) AS 纯平仓盯市价差合计 FROM eod_swap_position esp WHERE esp.SwapTradeId = @TargetTradeId GROUP BY esp.PositionId; -- 2.3 一句话诊断:互换/自动互换分红事件的 MarkClosePnl 是否含分红 -- 对每个 EventType in (3,4) 且 DividendIn<>0 的事件日, -- 检查当日 eod 盯市列是否也包含了等额成分。 SELECT sfe.EventDate, sfe.PositionId, sfe.EventType, sfe.DividendIn AS 事件分红流入, sfe.MarkClosePnl AS 事件盯市含费, esp.TdCloseMtmPnl AS 当日盯市列, esp.TdCloseDividend AS 当日分红列, CASE -- 同一互换事件自身:若 MarkClosePnl≈DividendIn,则该事件盯市就含分红 WHEN ABS(sfe.MarkClosePnl - sfe.DividendIn) < 0.01 AND sfe.DividendIn <> 0 THEN '⚠该事件MarkClosePnl≈DividendIn→盯市含分红(根因)' -- 当日整列:盯市列≈分红列 WHEN esp.TdCloseMtmPnl <> 0 AND ABS(esp.TdCloseMtmPnl - esp.TdCloseDividend) < 0.01 THEN '⚠当日盯市列≈分红列→重复' ELSE '需人工核对' END AS 诊断 FROM swap_flow_event sfe JOIN eod_swap_position esp ON esp.SwapTradeId = sfe.SwapTradeId AND esp.PositionId = sfe.PositionId AND esp.ValueDate = sfe.EventDate WHERE sfe.SwapTradeId = @TargetTradeId AND sfe.EventType IN (3, 4) AND sfe.DividendIn <> 0 AND sfe.DataState = 100 ORDER BY sfe.EventDate; -- ============================================================================ -- 第 3 步:生命周期守恒校验(累计已实现分红 vs 理论应付分红) -- ============================================================================ -- 含义:一笔互换交易从开仓到全部平仓,"已实现分红收益总额"应等于持仓期间 -- 该债券应付分红(税后)的累加。任何偏差说明核算有误。 -- 若存在第 2 步的重复计算,累计已实现分红会被放大,本步量化偏差作修复后回归基线。 SET @TargetTradeId := 1874; -- 3.1 实际已实现分红(从事件流水 DividendIn 累加) SELECT SUM(CASE WHEN EventType IN (2,3,4) THEN DividendIn ELSE 0 END) AS 事件流水已实现分红合计, SUM(CASE WHEN EventType IN (3,4) THEN DividendIn ELSE 0 END) AS 其中互换事件分红, SUM(CASE WHEN EventType = 2 THEN DividendIn ELSE 0 END) AS 其中平仓事件分红 FROM swap_flow_event WHERE SwapTradeId = @TargetTradeId AND DataState = 100; -- 3.2 日终表口径的最终累计已实现分红(应与 3.1 一致) SELECT PositionId, MAX(RealizedDividend) AS 日终表累计已实现分红, MAX(RealizedPnl) AS 日终表累计已实现盈亏 FROM eod_swap_position WHERE SwapTradeId = @TargetTradeId GROUP BY PositionId; -- 3.3 理论应付分红(税后)—— 需人工带入持仓区间与标的 -- 业务口径(BondPaymentService.CalcPayment): -- totalPayment = CalcPayment(UnderlyingCode, StartDate, EndDate, Qty, shortRatio, dirRatio) -- 理论税后分红 = totalPayment / (1 + tax) * (1 - tax) -- 这里给出从 bond_payment_info 直接估算的简化版(仅供量级对照): SET @BondCode := 'PUT_UNDERLYING_CODE_HERE'; SET @StartDate := '2024-01-01'; SET @EndDate := '2024-12-31'; SELECT underlying_code, SUM(COALESCE(paying_interest, 0)) * 0.01 AS 区间每张利息合计_相对值, COUNT(*) AS 付息次数 FROM bond_payment_info WHERE underlying_code = @BondCode AND pay_date_PL BETWEEN @StartDate AND @EndDate GROUP BY underlying_code; -- 说明:paying_interest 为"每张兑付利息额",×0.01 转相对价后还需 ×持仓数量 ×方向, -- 再做税后调整,才能与 3.1/3.2 对齐。精确口径见 BondPaymentService.CalcPayment。 -- ============================================================================ -- 附录:导出为 json / csv 的方式 -- ============================================================================ -- 【方式A:MySQL 客户端导出(推荐,最简单)】 -- 在 Navicat / DBeaver / MySQL Workbench 中执行上述任一 SELECT,结果区右键 -- "导出" → 选 JSON / CSV / Excel。推荐把 1.1~1.6 各导一份,按表名命名: -- swap_event.json / swap_position.json / swap_flow_event.json / -- eod_swap_position.json / eod_swap.json / bond_payment_info.json -- -- 【方式B:命令行 mysqldump(整表+DDL,含 CREATE)】 -- mysqldump -h -u -p swap_flow_event \ -- --where="SwapTradeId=1874 AND DataState=100" \ -- --skip-add-drop-table --no-create-info > sfe_1874.sql -- -- 【方式C:SELECT ... INTO OUTFILE(服务端导 csv,需 FILE 权限)】 -- SELECT ... FROM swap_flow_event WHERE SwapTradeId=1874 -- INTO OUTFILE '/tmp/sfe_1874.csv' -- FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\r\n'; -- -- golden source 种子建议:用方式A导出 1.1~1.6 共 6 个 json, -- 连同 trade 主记录,作为"一笔带分红互换交易"的完整快照纳入版本库。