Learning
VOL. VII · NO. 45 · OTC Derivatives · 01 JAN 1970

EOD 批处理:每天 8 小时的生死时速

OTC 衍生品 · 01 JAN 1970 · 11 min read · 2,580 words
· · ·

日终批处理是 OTC 交易系统的"心跳"——它把 5000+ 活跃交易从 T 日状态推进到 T+1 状态,每一步失败都可能引发连锁反应。本课拆解 10 个步骤、幂等性设计原则与真实故障案例。

必须记住
EOD 从 17:00 开跑,必须在次日 08:00 前完成全部 10 个步骤。若中途失败,第一反应是定位失败步骤、确认幂等性后重跑——而不是清空状态重新来。EodStep 接口的两个方法 execute() 执行业务逻辑、isIdempotent() 声明是否可以安全重跑,共同构成 EOD 可靠性的基石。

一、为什么 EOD 是系统的”心跳”

OTC 交易系统白天处理实时交易,EOD(End of Day)批处理负责把全量持仓和敞口推进到下一天。17:00 市场收盘后,系统开始以批处理方式完成估值、清算、报告等全部”幕后”工作。

EOD 失败的后果是系统性的:

  • 交易台在次日早上看不到最新 MTM,继续按旧数据交易——盲目交易
  • 追保通知延迟,客户错过追保窗口——违约风险上升
  • SAC 报送数据截止时间为当日 17:00,EOD 延迟直接导致报送超时

系统内有 5000+ 活跃交易(TRS、雪球、期权混合),每笔交易在 EOD 中都要经历从市场数据刷新到资金划转的完整链路。正常情况下 3 小时内完成,若某一步卡住,整个链路推迟。

系统锚点(IT 侧)
hedging-as/src/com/cicc/eod/
  EodScheduler.java — 调度器(Spring @Scheduled,cron “0 0 17 * * *”)
  EodStep.java — 步骤接口:execute(ctx) / isIdempotent()
  EodEngine.java — 执行引擎(顺序遍历步骤,处理失败/重跑)

关联 0012
EOD 第 7 步保证金计算 → MarginStepMarginCallProcessor → 追保通知。EOD 第 8 步资金划转 → CashTransferService → IPMP 划转指令。EOD 是 0012 追保与结算的触发引擎。

二、10 步详解:每个故障点都要知道

以下是 EOD 完整 10 步的耗时与失败后果——这是运维和开发都须烂熟于心的清单:

#步骤名称预计耗时失败后果
1市场数据刷新5 min用昨天数据代替,并在状态中标记”待确认”;估值仍跑但结果带警告旗
2定盘 Fixing15 min用前收盘价替代,估值结果状态置为”待审”;LIBOR/Shibor 失效时最常见
3公司行为10 min人工介入比例最高;漏掉分红/送股 → TRS 分红算错、簿记得不平
4障碍检查15 min敲出/敲入判定漏检 → 票息错误(触发后仍按未触发算 → 多付票息,或反过来)
5估值 Valuation(MTM)30 minMTM 算错 → 全台收到错误信号;交易员对冲决策失误、风险限额报表失真
6Greeks 计算20 minDV01 错 → 限额监控失灵;Delta/Gamma 错 → 对冲建议误导交易员
7保证金20 minIM/VM 算错 → 追保金额错误(多追 → 客户投诉;少追 → 风险敞口暴露)
8资金划转10 min失败 → 次日资金无法按时到账;客户收到追保但付款指令未发出
9报告生成10 minSAC 报告缺失 / 客户对账单无法发出;错过监管报送窗口则触发违规记录
10清理5 minEOD 状态归档、日志转储、临时表清理;未清理会导致次日 EOD 状态不一致
运维经验
第 3 步公司行为和第 7 步保证金是人工介入最多的两步。第 3 步依赖财务团队在 16:00 前录入分红/送股通知单;第 7 步需要运营在 EOD 后审查追保数据。系统能跑通,但"最后一公里"靠人盯着——这是 OTC 系统目前无法完全消除的摩擦点。

三、幂等性:失败后能重跑才是好设计

EodStep 接口定义了两个方法:

public interface EodStep {
    EodResult execute(EodContext ctx);      // 执行业务逻辑
    boolean isIdempotent();                 // 声明是否可以安全重跑
}

isIdempotent() 是 EOD 可靠性的核心设计理念:当某一步失败后,运维需要决定是否重跑。如果该步骤幂等,重跑不会产生副作用;如果非幂等,重跑可能导致重复写入(如重复扣款、重复记录)。

幂等性的实现方式是在写入前先检查记录是否存在(UPSERT 模式):

-- 非幂等写法(重跑会插入重复记录):
INSERT INTO eod_step_log (trade_id, step_id, status)
VALUES (?, ?, 'DONE');

-- 幂等写法(重跑安全):
INSERT INTO eod_step_log (trade_id, step_id, status)
VALUES (?, ?, 'DONE')
ON CONFLICT (trade_id, step_id) DO UPDATE SET status='DONE', run_at=NOW();
必须记住
EOD 失败后,第一反应不是"清空状态重来",而是:定位失败步骤 → 检查 isIdempotent() → 重跑该步骤。清空重来会让所有步骤重新跑一遍,浪费时间且可能引入新的不一致。
真实案例
2024 年某日 EOD 第 6 步 Greeks 计算失败(数据库连接池耗尽导致部分 Greeks 记录缺失)。运维检查发现 GreeksStep.isIdempotent() = true(Greeks 记录用 trade_id 作为唯一键,UPSERT 写入),直接重跑第 6 步,15 分钟后完成,全天 EOD 在 23:00 收口。如果当时选择清空重来,预计次日 04:00 才能完成,影响次日 08:00 的交易启动。

四、真实故障案例:两次中断的根因分析

案例一:2019 remark 字段变更截断(中断 4 小时)

数据库从 VARCHAR2(500) 扩展到 VARCHAR2(1000),某条交易记录的 remark 字段超过 500 字符但不足 1000。旧 SQL 在 INSERT 时用 SUBSTR(remark, 1, 500) 截断,但新表结构要求完整写入 1000 字符。截断后的数据拼接了错误的分隔符,导致 EOD 第 5 步估值引擎在解析该记录时报 StringIndexOutOfBoundsException,估值步骤直接崩溃。

运维起初以为是数据库连接问题,重跑了 3 次均失败。根因排查花了 2 小时,最终定位到那条超长 remark 记录,手工修正后 EOD 才跑通。

血的教训
字段扩长不是向后兼容的——旧代码里的隐式截断(SUBSTR / 固定长度数组)不会因为底层表结构改了而自动升级。上线前必须做数据迁移脚本,把旧截断数据补齐,同时升级 SQL 代码去掉截断逻辑。

案例二:2022 年市场数据同步延迟(EOD 整体延迟到次日 01:00)

当日 16:57 市场数据供应商的系统出现抖动,部分行情数据晚到 3 分钟。EOD 第 1 步市场数据刷新在 17:00 启动时,对应的价格数据尚未到达。

系统的容错设计是”超时等待 5 分钟”,但那天数据晚到 8 分钟。第 1 步在 17:05 判定”数据不可用”后,用了前一日的收盘价代替,并打上”待确认”标记。后续所有步骤均按此标记继续执行,但到第 9 步报告生成时,运营发现 MTM 数据中有 23 笔交易的价格仍是昨日数据——这批交易当天实际发生了大额盈亏,报告数字与真实情况严重不符。

SAC 报送因此推迟到次日 01:00 完成,触发了监管问询。

改进方向
数据供应商延迟 5 分钟以上时,系统应自动暂停 EOD 并发出告警,而不是静默降级到"昨日数据"。目前第 1 步的超时策略过于宽松,建议改为:超过 2 分钟未到数据 → 暂停 + 告警 + 人工确认是否继续。

五、并行化优化:从 3 小时到 2 小时

当前 EOD 是完全串行执行——每一步完成后才启动下一步。这在交易量小的时候没问题,但 5000+ 活跃交易让第 5 步估值和第 6 步 Greeks 成为明显瓶颈。

以下步骤可以安全并行化(不同标的/产品线互不依赖):

  • 障碍检查(第 4 步):按标的分组并行,不同标的的 KO/KI 状态互不影响
  • Greeks 计算(第 6 步):按产品线并行(TRS 一起算、雪球一起算、期权一起算),不同产品线共享相同的风险因子结果但计算过程独立
-- 串行(当前):
for step in [MD_REFRESH, FIXING, CORP_ACTION, BARRIER_CHECK, VALUATION, GREEKS, ...]:
    step.execute()

-- 并行优化后(障碍检查 + Greeks 可并行):
parallel([
    VALUATION,          -- 必须串行(依赖前一步)
    GREEKS_BY_PRODUCT,   -- 并行:TRS / 雪球 / 期权各自跑
    BARRIER_BY_UNDERLYING -- 并行:按标的分组
])

优化目标是总耗时从 3 小时压缩到 2 小时,为数据异常预留更多缓冲时间。

六、测验(Recall 练习)

1

EOD 批处理从每天几点开始?

2

公司行为步骤(第 3 步)失败的主要风险是?

3

EodStep 接口中的 isIdempotent() 是什么意思?

4

2019 年 EOD 中断 4 小时的根因是?

5

障碍检查(第 4 步)漏检的后果是?

6

EOD 失败后第一步应该做什么?

7

Greeks 步骤(第 6 步)算错 DV01 会导致什么?

8

资金划转(第 8 步)失败会导致?

七、错题本与复习

查看全部错题 上一课:EOD 总览

本课错题记录

八、延伸阅读

收口
EOD 批处理是 OTC 系统运营的"任督二脉"——理解 10 个步骤的依赖关系、幂等性设计原则和真实故障模式,才能在凌晨 23:00 的告警电话响起时快速定位问题、正确决策重跑策略。下一课进入对账与异常处理,继续补全运营全链路知识。

Lesson 0017 · EOD 批处理 · 上游 0016 EOD 总览 · 错题追踪 0011 错题本 · MISSION: 产品 + IT 双线精通 · 样式 base.css