Learning
VOL. VII · NO. 67 · OTC Derivatives · 19 JUL 2026

Eod Batch Deep Dive

OTC 衍生品 · 19 JUL 2026 · 10 min read · 1,283 words
· · ·

ODTS 18 — EOD 批处理:系统每晚的心跳

业务问题

每晚收盘后,系统必须处理交易日发生的一切:

收盘 → 系统有 8 小时(下午 4 点到午夜):
  1. 获取所有资产的收盘价
  2. 处理所有分红和公司行为
  3. 检查所有障碍条件 (KO/KI)
  4. 重估 5000+ 笔活跃交易
  5. 计算所有 Greeks (delta, gamma, vega, theta, rho)
  6. 计算所有保证金 (IM + VM)
  7. 生成资金划转指令
  8. 生成报告(SAC、客户、内部)
  
如果完成晚了 → 交易台第二天早上没有风险数据
如果失败了 → 交易台第二天盲目交易

EOD 批处理架构

~/odts1/hedging-as/src/com/cicc/action/eod/
├── EodScheduler.java           — 主编排
├── EodStep.java                — 每个步骤的接口
└── EodContext.java             — 共享上下文(交易、日期、参数)

~/odts1/hedging-as/src/com/cicc/service/eod/
├── MarketDataRefreshStep.java  — 第 1 步
├── FixingStep.java             — 第 2 步
├── CorporateActionStep.java    — 第 3 步
├── BarrierCheckStep.java       — 第 4 步
├── ValuationStep.java          — 第 5 步
├── GreeksStep.java             — 第 6 步
├── MarginStep.java             — 第 7 步
├── CashMovementStep.java       — 第 8 步
├── ReportingStep.java          — 第 9 步
└── CleanupStep.java            — 第 10 步

每个步骤实现 EodStep 接口,只有一个方法:

public interface EodStep {
    boolean execute(EodContext context);
    String getStepName();
    int getStepOrder();
    boolean isIdempotent();
}

逐步骤详解

第 1 步:市场数据刷新

做什么:获取最新价格、波动率曲面、利率曲线
时间:约 5 分钟
数据依赖:Bloomberg/Reuters 连接
失败模式:没有数据 → 下游全是陈旧数据
恢复:重试 3 次。仍失败则用昨天数据(带标记)

代码中,它对每个活跃标的调用价格服务。如果获取失败,重试两次,仍失败则用前收盘价并记录警告。整个过程对波动率曲面和利率曲线同样处理。

第 2 步:定盘 (Fixing)

做什么:处理所有浮动端的定盘
时间:约 15 分钟
数据依赖:来自市场数据步骤的定盘数据
失败模式:某端的定盘不可用 → 无法计算付款
恢复:用该端的先前定盘利率,标记待人工审核

FixingStep.javaContractFixingEvent 表读取计划定盘,从 FixingData 表读取已发布的定盘利率,并更新 ContractCashFlow 表中的计算金额。

第 3 步:公司行为

做什么:应用分红、拆股、配股
时间:约 10 分钟
复杂度:每个公司行为必须与每笔 TRS 核对
失败模式:漏掉公司行为 → TRS 分红算错
修复:通过 CorporateActionService 手动录入

公司行为步骤是最人工的步骤。并非所有公司行为都通过数据推送送达。运营团队手动录入并购、退市和特别分红。

第 4 步:障碍检查

做什么:对每个雪球和障碍期权,检查障碍是否被触发
时间:约 15 分钟(并行化)
失败模式:KO/KI 漏检 → 错误票息、错误终止
修复:对受影响合约重新运行该步骤

障碍检查将当日收盘价与每个障碍水平比较。如果敲出,合约状态设为 KNOCKED_OUT 并生成敲出事件和最终票息。如果敲入,雪球开始追踪亏损。

历史表现:

2021:约 20 个障碍检查/晚(轻松)
2022(市场暴跌):约 200 个检查/晚(EOD 耗时 4 小时)
2023:约 150 个检查/晚(并行化后 30 分钟)

第 5 步:估值

做什么:重估所有活跃合约 (5000+)
时间:约 30–45 分钟
复杂度:最长的步骤
失败模式:某合约估值错误 → 该交易的 P&L 错误
修复:识别失败合约,单独运行,修补结果

这是 EOD 批处理的核心。它为每笔活跃合约调用定价引擎:

// ValuationStep.java (简化)
for (Contract contract : activeContracts) {
    try {
        ValuationResult result = pricingService.valuate(
            contract.getId(),
            context.getMarketData(),
            context.getDate()
        );
        valuationDao.saveResult(contract.getId(), result, context.getDate());
    } catch (Exception e) {
        log.error("Valuation failed for " + contract.getId(), e);
        errors.add(contract.getId());
    }
}

并行化: 估值步骤使用 Java parallel streams,每个合约在自己线程上运行,线程池大小 = 可用 CPU 核心数。

第 6 步:Greeks

做什么:计算每笔合约的 delta、gamma、vega、theta、rho
时间:约 20 分钟
方法:bump-and-reval(每个输入 bump 1%,重估,计算变化)
失败模式:Greeks 不匹配 → 错误对冲建议
修复:对特定合约重跑 Greeks

第 7 步:保证金

做什么:计算每个交易对手的 IM 和 VM
时间:约 20 分钟
复杂度:VM 简单(MTM 变化),IM 难(Monte Carlo)
失败模式:错误追保金额 → 客户争议
修复:手动覆盖保证金金额

第 8 步:资金流转

做什么:为结算、追保、费用生成付款指令
时间:约 10 分钟
失败模式:错误金额 → 错误付款 → 操作损失
修复:在 CashTransfer 表中手动覆盖

第 9 步:报告

做什么:生成 SAC 报告、客户对账单、内部风险报告
时间:约 20 分钟
失败模式:错误报告格式 → 监管合规问题
修复:重新生成特定报告

第 10 步:清理

做什么:归档临时表、更新状态、发送完成通知
时间:约 5 分钟
失败模式:无关键的

EOD 应急手册

当 EOD 批处理失败时(确实会发生,约每月 1–2 次):

如果 EOD 在第 N 步失败:
  1. 检查日志找出错误
  2. 修复根因(可能是缺失价格、坏合约、数据库连接问题)
  3. 从第 N 步重跑(不是从头)
  4. 如果第 N 步是估值(第 5 步),可以只重跑失败合约

系统支持部分重跑,因为每个步骤是幂等的:
  第 2 步:如果定盘已完成,跳过
  第 5 步:如果已估值,检查时间戳 → 只重估过期的

EOD 仪表盘

运营团队通过内部仪表盘监控 EOD:

EOD 状态:     RUNNING (第 5/10 步 — 估值)
开始时间:     18:05:22
已耗时:       45:38
预计:         01:15:00

第 1 步: ✅ 市场数据      (00:04:12)
第 2 步: ✅ 定盘          (00:12:45)
第 3 步: ✅ 公司行为      (00:08:33)
第 4 步: ✅ 障碍检查      (00:15:22)
第 5 步: ⏳ 估值          (00:45:38)
第 6 步: ⏳ Greeks        (等待中)
第 7 步: ⏳ 保证金        (等待中)
第 8 步: ⏳ 资金流转      (等待中)
第 9 步: ⏳ 报告          (等待中)
第 10 步:⏳ 清理          (等待中)

错误:0
警告:3(均为非流动股票的陈旧价格)

仪表盘位置: 位于 new-edsweb/src/views/eod/,是一个 Vue 页面。

当 EOD 批处理完成晚了

EOD 晚一小时,不是一个”系统问题”——它是一个业务事件。不同的人损失不同的东西:

角色如果 EOD 在 23:00 完成实际后果
运营团队22:00 开始审查追保 → 23:30 才能开始匆忙中漏掉争议或金额错误 → 第二天客户投诉
交易员22:00 收到对冲建议 → 市场已收盘,无法执行第二天开盘承担隔夜风险。一晚 gamma 裸露可能值 50–200 万
风险管理早会(08:30)用前一天数据汇总风险指标过时 12 小时 → 错误的风险判断
财务/结算无法生成次日资金划转 → 次日早上才处理追保付款延迟 → 客户投诉,或更糟——交易台自己因付款迟到被对手方罚款

如果批处理在晚上 10 点后完成,下游消费者(风险团队、运营、保证金团队)无法完成工作。最坏情况:

18:00 — EOD 开始
21:00 — EOD 完成(预期)
21:30 — 运营审核追保
22:00 — 报告生成
22:30 — 夜间维护

如果 EOD 在 23:00 完成:
  23:30 — 运营审核追保(仓促,可能漏过错误)
  00:00 — 报告生成(早会来不及)
  00:30 — 维护窗口缩短

2023 年 EOD 超时事件:

一个复杂的 10 年期雪球,每天需要检查障碍,单笔估值花了 20 分钟。估值步骤对每笔合约设置了 timeout = 5 分钟。这个雪球超过了 5 分钟。估值步骤对该雪球抛出了 TimeoutException 并继续。但错误处理发现得太晚——批处理已经在它身上浪费了 20 分钟。

修复:每笔合约的 timeout = 60 秒,并为有问题的交易设置死信队列 (dead-letter queue)。如果一笔交易耗时超过 60 秒,跳过它并标记为需要人工估值。

EOD 失败的实际成本: 每月 1–2 次 EOD 失败看起来不多,但每次失败都会产生连锁反应。运营团队每月多花 4–8 小时手动修复(重跑步骤、核对数据)。这本身只是一两千块的人力成本。真正的问题是被遗漏的障碍事件。2022 年 10 月,一个雪球在 EOD 步骤 4(障碍检查)中因为定价引擎超时被跳过,但 BarrierCheck 步骤没有在之前检查过的合约列表中排除该合约——它从另一个数据源读了一遍,得出”没有触发”的结论。这个雪球的敲出(KO)被漏掉了。客户多收了一个月票息(25 万),直到次月运营对账才发现。修复: BarrierCheck 步骤改为从 ValuationStep 的结果读取”已估值合约列表”,所有未估值的合约标记为”人工检查”。

EOD 的事件响应成本: 系统有一个”EOD 告警”——如果 EOD 在 22:00 前未完成,会自动给 IT 值班人员发短信。IT 值班每月排班一次,每次值班一周。值班人员在 EOD 失败时需要远程登录检查日志、决定是否需要重跑、通知运营团队。每次值班在一周内平均被叫醒 1–2 次。这种”救火”模式是系统稳定的最后一个防线的成本:一个人的睡眠被中断 + 第二天生产力下降(人的成本),加上被叫醒后 30–60 分钟的诊断时间。2023 年,团队花了 3 个月时间逐步优化 EOD 步骤的容错能力,才把月均失败降到 1 次以下。这段优化工作的投入 = 3 人月 × 8 万 = 24 万的研发成本——但换来的是交易员每天早上不再焦虑地看 EOD 报告。