Risk Management Framework
ODTS 24 — 风险管理框架:安全网
业务问题
场外衍生品交易台承担风险。每笔交易都有风险。问题不是”我们有没有风险?“而是”我们知道自己的风险是什么吗?“风险管理是以下实践的循环:衡量风险(我们可能损失多少?)、设定限额(允许我们损失多少?)、监控风险(我们在限额内吗?)、缓释风险(对冲、保证金、提前终止)。
风险类型
| 风险类型 | 含义 | 衡量方式 | 归属谁 |
|---|---|---|---|
| 市场风险 (Market Risk) | 市场波动的 P&L 影响 | Greeks, VaR, 压力测试 | 交易台 |
| 交易对手风险 (Counterparty Risk) | 客户违约 | PFE, CVA, 信用限额 | 信用风险 |
| 流动性风险 (Liquidity Risk) | 无法平仓 | 买卖价差、持仓规模 | 交易台 |
| 操作风险 (Operational Risk) | 系统故障、人为错误 | 事故数、损失事件 | 运营 |
| 模型风险 (Model Risk) | 错误的定价模型 | 模型验证、储备金 | 量化团队 |
| 法律风险 (Legal Risk) | 合同不可执行 | 法律审查、ISDA 争议 | 法律团队 |
对一个从事交易系统开发的人来说,理解这些风险类别很重要——因为你在 EOD 批处理中加一个字段,可能影响的是市场风险计量;你改一个结算逻辑,可能影响的是交易对手风险。
市场风险:Greeks
Greeks 是市场风险衡量的核心。每个希腊值是一个敏感度指标,告诉你当市场波动时 P&L 会发生什么。
对于一笔香草看涨期权——标的为贵州茅台 (600519.SH),名义本金 1000 万,行权价 200,期限 6 个月——EOD 快照可能显示:Delta 为 +45,230 元(茅台涨 1 元 → P&L 增加 4.5 万),Gamma 为 3,450 元(标的每波动 1 元,Delta 变化 3,450),Vega 为 1,200 元(波动率 +1% → P&L +1,200),Theta 为 -850 元(每天时间价值损耗 850 元),Rho 为 500 元(利率 +1% → P&L +500)。
Delta 对冲的实际操作: 交易员的持仓 Delta 为 +45,230(多头),为了对冲需要卖出价值 45,230 元的茅台股票,使净 Delta 归零。第二天,Gamma 使 Delta 变化了 +3,450,所以需要再卖出价值 3,450 元的茅台股票。每次再对冲都有交易费用(印花税、佣金)。高 Gamma 的交易需要频繁再对冲,这会侵蚀 P&L。这就是为什么交易员在报价时会把 Gamma 成本算进价格里。
头寸限额
交易台对每个标的有硬性限额。Delta 限额为每个标的 ±5000 万(超限必须立即对冲),Gamma 限额为 ±500 万(必须减少头寸),Vega 限额为 ±200 万(必须买入/卖出期权对冲),名义本金限额为每个交易对手 5 亿(不得有新交易),集中度限额为不超过日均成交量的 10%。
限额超限有分级协议:达到限额的 80% 时系统记录警告;90% 时系统邮件通知风险团队;100% 时通知首席交易员,必须在 1 小时内批准;120% 时该标的暂停交易。
代码中限额检查通过 RiskCheckThresholdLevel 枚举实现——四个级别(NORMAL, WARNING, BREACH, CRITICAL),每个级别关联对应的通知策略。检查引擎位于 eds-utility/src/.../risk/model/RiskCheckDetailResult.java,结果结构包含 thresholdLevel、checkStatus(通过/失败)、exceededAmount 和 approvedBy。
VaR(风险价值)
VaR 回答”在 99% 置信水平下,我们一天内的最大可能损失是多少?“比如说,每日 VaR 为 500 万,意味着有 1% 的概率一天损失超过 500 万,相当于每年约 2.5 个交易日。
历史模拟法
系统的计算方式使用历史模拟法 (Historical Simulation):取过去 500 个交易日的市场数据(即期价格、波动率、利率、汇率的历史日变化),用每天的市场波动重新为整个投资组合定价,计算每个情景的 P&L。然后从最差到最好排序,取倒数第 5 差的结果(500 的 1% = 5)。这个过程在 hedging-as 的 EOD 风险批处理中执行,关键输入来自 EDS_CTRCONTRACTEOD 表(前一日的 Greeks 缓存)和每日市场快照。
历史模拟步骤:
第 1 步:取每个风险因子过去 500 天的日收益率
第 2 步:对每个风险因子,生成 500 个"明日情景"(当前值 × 历史收益率)
第 3 步:对每个情景,用一阶近似重新估值组合:
new_NPV ≈ old_NPV + Σ(Greek × Δrisk_factor)
(注意:这里用了一阶近似,假设 Greeks 在市场小幅波动下近似线性)
第 4 步:按 P&L 变化从小到大排序
第 5 步:第 5 小的值 = 99% VaR
VaR 的统计噪声
VaR 的精度取决于历史窗口长度。窗口越长,样本代表性越好,但过长的窗口包含过时的市场制度。500 天是一个折中:
窗口长度 优点 缺点
───────── ────────────── ─────────────────────
250 天 反应快,捕捉新 regime 尾部事件不够
500 天 较好的尾部估计 可能包含过时 regime
1000 天 尾部最稳定 包含 4 年前的制度,可能已不适用
ODTS 选择的 500 天窗口意味着 2008 危机数据在 2010 年后已经被踢出窗口——但 2015 年股灾的影响会保留到 2017 年。
Expected Shortfall(期望损失)
VaR 有局限性:它告诉你第 99 百分位水平,但不告诉你那 1% 的尾部会发生什么。一个组合可能 VaR = 500 万且最大损失 = 600 万(尾部紧凑,安全),也可能 VaR = 500 万但最大损失 = 5000 万(肥尾,危险)。
系统也计算期望损失 (Expected Shortfall)——最差 1% 情景的平均值——来解决这个问题。如果最差 5 个情景的 P&L 分别是 -500 万、-520 万、-550 万、-600 万、-5000 万,VaR = 500 万,但 ES = (-500 + -520 + -550 + -600 + -5000)/5 = -1434 万。ES 比 VaR 大 3 倍,表明尾部存在极端风险——这本身就是个预警信号。
潜在未来敞口 (PFE)
PFE 估算”如果市场朝不利方向波动,这笔交易在未来某个日期可能值多少钱?“它通过 Monte Carlo 模拟生成 10,000 条价格路径,计算每条路径上的未来盯市价值 (MTM),取第 95 百分位值。PFE 决定信用额度使用(占用了客户多少信用额度)、初始保证金(最低抵押品要求)和资本占用(为该交易持有的监管资本)。
PFE 计算细节
PFE 计算流程:
第 1 步:为每个风险因子(即期价格、利率、汇率)拟合几何布朗运动参数
μ = 历史漂移率,σ = 历史波动率
第 2 步:生成 10,000 条未来路径,每条路径向前模拟到交易到期日
第 3 步:在每条路径上的每个未来估值日,重新定价交易
第 4 步:对每个未来估值日,取 10,000 个 MTM 值的第 95 百分位
第 5 步:整条 PFE 曲线 = 各估值日的第 95 百分位 MTM 值序列
(通常在交易前半段上升,后半段下降——因为时间价值衰减)
PFE 曲线的业务解释
PFE 曲线形状:
期限较短: 曲线陡峭上升、平缓下降——市场有足够时间移动但到期日又近
期限较长: 曲线先升后降,峰值在约 1/3 到期处
看涨期权 PFE: 峰值在约 30-40% 期限处,然后下降
互换 PFE: 对称,峰值在约 40-50% 期限处
TRS PFE: 持续上升(名义价值不变,但不确定性增加)
信用额度检查
每个交易对手有一个信用额度。代码中风险检查 (RiskCheckDetailResult) 在交易簿记时触发:
招行:额度 5 亿,当前占用 3.5 亿(占 70%),可用 1.5 亿
如果新交易 PFE = 8000 万:
检查:3.5 亿 + 0.8 亿 = 4.3 亿 < 5 亿 → 通过 ✓
如果新交易 PFE = 2 亿:
检查:3.5 亿 + 2 亿 = 5.5 亿 > 5 亿 → 拒绝 ✗
可否覆盖?可以,首席交易员操作 override 并记录原因
2021 年发生过一笔交易簿记后超过了交易对手信用额度 5000 万的案例。系统没有阻止,因为系统在应用汇率转换之前检查了限额。新交易以美元计,限额以人民币计。7000 万美元按 6.8 汇率折合 4.76 亿人民币,但系统比较了 4.8 亿 + 7000 万 = 5.5 亿(把美元名义本金当作人民币),交易通过了检查并被簿记。结果信用额度超出 2600 万人民币,2 天后才被 EOD 风险报告发现。
根本原因: RiskCheckDetailResult 中的敞口计算没有对非本币交易进行汇率转换,限额比较在金额和货币之间不匹配时仍然通过了。
修复及后续: 这个 bug 在事后被标记为”关键修复”。解决方案不是简单的加汇率转换——因为限额检查是一个通用的 RiskCheckDetailResult 类,支持多种风险类别(Delta、Vega、信用额度等),而汇率转换只适用于信用额度检查。最终方案是在信用额度检查分支中增加 ExchangeRateUtil.convert(amount, fromCcy, toCcy) 调用,但这花了 3 周——因为需要确保不影响其他风险类别的检查逻辑。
ActiveMQ 延迟的实际后果
风险规则引擎通过 ActiveMQ 消息队列异步执行。正常情况下延迟 200-500ms,但在高峰时期(如 EOD 附近或市场大幅波动时),队列积压可能导致几分钟的延迟。
2022 年某事件:
交易员在 14:55 簿记一笔大额 TRS(名义 2 亿)
→ ActiveMQ 消息发送到 RiskRuleNotification 队列
→ 队列积压(同期有 EOD 批处理消息也在队列中)
→ 风险检查在 15:03 才完成(8 分钟后!)
→ 检查结果:Delta 限额超限 1200 万
问题:
这 8 分钟里交易员认为"系统没报错,说明通过了"
→ 继续簿记了另一笔关联交易
→ 实际上这两笔交易加起来的 Delta 限额超了 2500 万
→ 等到 EOD 风险报告出来才发现
教训: 异步风险检查的延迟意味着”没报错”不等于”通过了”。交易员和运营需要理解这个架构约束——不能把 ActiveMQ 的沉默等同于风险通过的确认。
压力测试
交易台定期运行压力情景。ODTS 的压力测试通过 RiskRulePublisher 机制触发——一个 ActiveMQ 消息驱动的风险规则引擎:当市场数据变动超过阈值时,消息被发布到 RiskRuleNotification 队列,触发对应的风险规则评估。
标准压力情景
| 情景 | 市场冲击 | 适用场景 |
|---|---|---|
| 2008 危机 | 股票 -50%,波动率 +300%,信用价差 +500bp | 系统性危机 |
| 2015 中国股灾 | 股票 -30%,波动率 +200%,流动性半衰期 3 天 | 中国市场危机 |
| 利率冲击 | 利率 +2%,汇率 -5%,债券收益率 +150bp | 货币政策突变 |
| 信用事件 | 指定交易对手违约,回收率 40%,剩余头寸价差 +100bp | 交易对手违约 |
| 相关性破裂 | 所有相关性变为 1,分散化失效 | 系统性风险 |
| 人民币贬值 | USDCNY +10%,离岸/在岸价差扩大至 500bp | 汇率危机 |
压力测试的计算过程
压力测试不是简单的因子冲击——它需要一致地重新定价整个投资组合。ODTS 的做法是:
步骤 1: 加载当前持仓的全部 Greeks(来自 EDS_CTRCONTRACTEOD)
步骤 2: 对每个情景,计算 Greeks × 市场冲击 = P&L 变化
步骤 3: 汇总所有交易得到组合级别的 P&L 影响
步骤 4: 将结果存入风险报表表
限制:一阶近似(Greeks 在压力情景下可能高度非线性,
但完全重新定价数千笔交易在时间上不可行)
对于尾部风险较大的奇异期权,一阶近似可能严重低估损失。风险团队对这些产品维护额外的模型储备金 (model reserve),在压力测试结果之上扣减。
压力测试的频率
每日自动: 5 个标准情景全量运行
每周深度: 对奇异期权组合进行完全重新定价(非一阶近似)
每月附加: 中国证监会 (CSRC) 规定的情景
季度: 反向压力测试 — "什么场景会导致我们损失 1 亿?"
风险规则引擎
ODTS 的风险检查不是硬编码的,而是通过规则引擎实现的。eds-utility 中的 Drools 规则文件(如 Risky.drl)定义了风险规则:
规则文件路径:
eds-utility/resources/rules/nqaGrossPrice/Risky.drl
eds-utility/resources/rules/grossPrice/Risky.drl
eds-utility/resources/rules/grossPrice/Risky_Hedge.drl
规则触发机制:
交易簿记 → RiskRulePublisher 发送 ActiveMQ 消息 →
RiskRuleNotification 被消费 → Drools 规则引擎评估 →
RiskCheckDetailResult 记录检查结果
这里有一个架构上的权衡:规则引擎提供了灵活性(规则修改不需要重新部署代码),但也引入了延迟(ActiveMQ 消息传递意味着风险检查不是完全的实时——有一个几百毫秒的滞后)。对于大交易量时期,消息队列可能出现积压,风险检查结果延迟几分钟才出现。
VaR 的欺骗性——一个压力测试案例
VaR = 500 万听起来很安全——如果公司资本金是 50 亿,0.1% 的风险敞口。但 VaR 的欺骗性在于:它假设历史会重演。
2022 年某真实情景:
正常市场下 VaR = 500 万
2022 年 3 月(俄乌冲突 + 美联储加息叠加):
实际最大日损失 = 2300 万(VaR 的 4.6 倍!)
为什么 VaR 这么不准?
- 500 天历史窗口中大部分是 2021 年低波动的数据
- 2022 年 3 月的波动率水平是历史均值的 3 倍
- VaR 模型直到波动率数据进入窗口(约 3 个月后)才"学会"了新的波动率水平
后来补充的压力测试显示(如果当时跑了"2008 危机"情景):
压力损失 = 3500 万——更准确地反映了极端损失可能性
但当天 EOD 风险报告中的 VaR = 500 万(基于历史模拟)
业务含义: VaR 是滞后指标——它反映的是过去 500 天的市场,而不是明天的市场。压力测试是前瞻性的,但只在管理团队查看时才有用。风险管理的最大危险不是 VaR 水平高,而是 VaR 看起来安全但实际风险已经变了。
首席交易员每天早上看到的风险报告
─────────────────────────────────────────
每日风险报告 | Date: 2025-07-17
─────────────────────────────────────────
P&L 摘要:
MTM P&L: +1,234,567.89
已实现 P&L: -234,567.89
未实现 P&L: +1,469,135.78
风险指标:
VaR (99%, 1d): 5,200,000.00
期望损失: 8,100,000.00
压力损失 (2008): 12,300,000.00
希腊值敞口(前 5 大标的):
600519.SH (茅台): Δ=+2.3M Γ=+0.5M ν=+0.8M
000858.SZ (五粮液): Δ=-1.1M Γ=+0.3M ν=+0.4M
300750.SZ (宁德时代): Δ=+0.8M Γ=-0.2M ν=+0.3M
限额检查:
所有限额在容忍范围内 ✓
交易对手风险:
信用额度使用率: 67%
最大敞口: 招商银行 (120M)
未付保证金催缴: 15M (今天到期)
关键文件
| 组件 | 路径 |
|---|---|
| 风险检查结果模型 | eds-utility/src/.../risk/model/RiskCheckDetailResult.java |
| 风险检查状态枚举 | eds-utility/src/.../risk/enums/PbMessageEnum.java(RiskCheckStatus, RiskCheckThresholdLevel) |
| 风险规则类别 | eds-utility/src/.../risk/enums/RiskCategory.java |
| 风险规则类型 | eds-utility/src/.../risk/enums/RiskRuleType.java |
| 规则引擎 DRL | eds-utility/resources/rules/grossPrice/Risky.drl |
| 对冲规则 | eds-utility/resources/rules/grossPrice/Risky_Hedge.drl |
| ActiveMQ 风险规则发布 | eds-utility/src/.../activeMQ/RiskRule/RiskRulePublisher.java |
| 风险规则通知 | eds-utility/src/.../activeMQ/RiskRule/RiskRuleNotification.java |
| 风险常数 | eds-utility/src/.../risk/util/RiskConstant.java |
| VaR 计算 | hedging-as/.../risk/VarCalculationAction.java |
| 风险计算入口 | hedging-as/.../risk/RiskAction.java |
| 风险规则 DAO | hedging-as/.../risk/dao/RiskRuleDaoImpl.java |
| EOD 风险快照表 | eds-web-app/db/EDS/V1.8/EDS20171107170003_CREATE_CTCONTRACTEOD.sql |
| 限额配置视图 | eds-web-app/db/EDS/code/views/v_limit_config.sql |
风险管理是一个由限额、VaR、PFE 和情景分析组成的系统,每天运行——它不能防止损失,但它确保交易台确切知道可能损失多少,并且永远不会超过公司愿意容忍的金额。代码中的风险规则引擎通过 ActiveMQ 消息驱动,这带来灵活性但也引入了风险检查的异步延迟——一个需要交易台公认的架构决策。