-- ============================================================================
-- 互换分红损益：数据导出与重复计算验证 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<host> -u<user> -p<db> 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 主记录，作为"一笔带分红互换交易"的完整快照纳入版本库。
