4. 现金流建模:到期日阶梯、合同现金流与行为化调整
现金流建模,说白了就是回答一个问题:钱什么时候进来,什么时候出去。
我在做流动性风险压力测试的头两年,踩过最大的坑就是——以为合同现金流就是实际现金流。后来被监管问询过一次,才真正明白:合同是合同,现实是现实。你想想看,客户会老老实实按合同还款吗?当然不会。
所以这一章,我们重点聊三件事:到期日阶梯怎么搭、合同现金流怎么算、行为化调整怎么做。
4.1 到期日阶梯:流动性分析的骨架
到期日阶梯,是流动性风险建模的起点。没有它,你连钱什么时候到期都搞不清楚。
我个人习惯把到期日阶梯分成三个维度:
- 时间维度:隔夜、7天、14天、1个月、3个月、6个月、1年、3年、5年、5年以上
- 产品维度:存款、贷款、同业存单、债券、衍生品、结构性产品
- 币种维度:本币、外币(必要时分币种)
举个例子,一张简单的到期日阶梯表长这样:
| 时间区间 | 资产到期流入 | 负债到期流出 | 净头寸 | 累计净头寸 |
|---|---|---|---|---|
| 隔夜 | 50亿 | 80亿 | -30亿 | -30亿 |
| 7天 | 120亿 | 90亿 | +30亿 | 0亿 |
| 14天 | 80亿 | 60亿 | +20亿 | +20亿 |
| 1个月 | 200亿 | 150亿 | +50亿 | +70亿 |
| 3个月 | 300亿 | 250亿 | +50亿 | +120亿 |
看到没?隔夜是负的30亿,说明明天就缺钱。这时候你就要问自己:流动性储备够不够补这个缺口?
核心要点:到期日阶梯不是静态的。每次压力测试,都要重新跑一遍。因为资产负债结构每天都在变。
4.2 合同现金流:按合同条款算出来的“理想状态”
合同现金流,就是假设所有交易对手都按合同办事。存款到期才取、贷款到期才还、债券到期才兑付。
听起来很简单对吧?但实际操作中,有四个细节特别容易出错:
- 本金和利息要分开算——利息的到期日往往和本金不同
- 浮动利率产品——未来现金流不确定,得用远期利率曲线估算
- 提前还款/提前支取条款——合同里写了可以提前,但合同现金流不考虑
- 展期条款——有些债券有展期权,合同现金流按原始到期日算
我在项目中遇到过一家银行,把一笔10亿的浮动利率贷款按固定利率算了现金流。结果压力测试时,利率上升导致实际利息支出比预期多了2000万。嗯,这个坑我记了很久。
下面是一段Python代码,用来计算一笔固定利率贷款的合同现金流:
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
def calc_contract_cashflows(principal, rate, start_date, maturity_date, freq='M'):
"""
计算固定利率贷款的合同现金流
principal: 本金
rate: 年化利率
start_date: 起息日
maturity_date: 到期日
freq: 付息频率,'M'=月,'Q'=季,'Y'=年
"""
# 生成付息日期序列
dates = pd.date_range(start=start_date, end=maturity_date, freq=freq)
# 计算每期利息
if freq == 'M':
period_rate = rate / 12
elif freq == 'Q':
period_rate = rate / 4
else:
period_rate = rate
interest = principal * period_rate
# 构建现金流表
cf = pd.DataFrame({
'date': dates,
'interest': interest,
'principal': 0
})
# 最后一期还本
cf.iloc[-1, cf.columns.get_loc('principal')] = principal
cf['total'] = cf['interest'] + cf['principal']
return cf
# 示例:1亿贷款,年利率4.5%,1年期,按月付息
cf_table = calc_contract_cashflows(
principal=100_000_000,
rate=0.045,
start_date='2024-01-01',
maturity_date='2024-12-31',
freq='M'
)
print(cf_table.head(3))
小提示:实际工作中,合同现金流通常从核心系统直接导出。但做压力测试时,我建议自己写一套计算逻辑,方便做敏感性分析。
4.3 行为化调整:从“合同”到“现实”的关键一步
合同现金流是理想状态。行为化调整,就是把理想拉回现实。
为什么要做行为化调整?因为客户的行为和合同写的不一样:
- 活期存款——合同上没到期日,但实际有沉淀率。我见过一家银行,活期存款的沉淀率高达60%,也就是说只有40%会在短期内被取走
- 定期存款——合同上写1年,但很多人会提前支取。压力情景下,提前支取率可能从5%飙升到30%
- 贷款——合同上写3年,但客户可能提前还款。利率下行时,提前还款率会明显上升
- 信用卡——合同上写的是循环额度,实际用款率只有30%-50%
行为化调整的核心,就是给每个产品定义一个行为化系数:
# 行为化调整示例:定期存款提前支取模型
def behavioral_adjustment(contract_cf, product_type, scenario='baseline'):
"""
对合同现金流进行行为化调整
product_type: 'demand_deposit', 'time_deposit', 'loan', 'credit_card'
scenario: 'baseline', 'adverse', 'severely_adverse'
"""
# 定义不同情景下的行为系数
behavior_params = {
'demand_deposit': {
'baseline': {'stable_rate': 0.65, 'runoff_rate': 0.35},
'adverse': {'stable_rate': 0.50, 'runoff_rate': 0.50},
'severely_adverse': {'stable_rate': 0.30, 'runoff_rate': 0.70}
},
'time_deposit': {
'baseline': {'early_withdrawal': 0.05},
'adverse': {'early_withdrawal': 0.15},
'severely_adverse': {'early_withdrawal': 0.30}
}
}
params = behavior_params[product_type][scenario]
# 这里只是示意逻辑,实际需要更复杂的模型
if product_type == 'demand_deposit':
# 活期存款:稳定部分视为长期,流失部分视为短期
stable = contract_cf * params['stable_rate']
runoff = contract_cf * params['runoff_rate']
return {'stable': stable, 'runoff': runoff}
elif product_type == 'time_deposit':
# 定期存款:提前支取部分提前到期
early = contract_cf * params['early_withdrawal']
normal = contract_cf * (1 - params['early_withdrawal'])
return {'early': early, 'normal': normal}
return contract_cf
⚠️ 曾经踩过的坑:我曾经把行为化系数设成固定值,结果压力测试结果怎么都对不上历史数据。后来才发现,行为化系数必须随压力情景动态调整。基线情景和严重压力情景下,客户的反应完全不同。
4.4 行为化调整的实战框架
做行为化调整,我建议按以下步骤来:
- 历史数据回测——至少取3年数据,分析每个产品的实际现金流与合同现金流的偏差
- 定义行为模式——沉淀率、提前支取率、提前还款率、用款率
- 情景映射——每个压力情景对应一套行为系数
- 压力测试执行——用行为化系数调整合同现金流,生成行为化现金流
- 结果验证——和历史压力事件对比,看模型是否合理
下面这张图,是我自己总结的现金流建模核心逻辑:
你看,整个流程其实不复杂。但难点在于:行为化系数怎么定?
我个人经验是:
- 先做历史均值,这是底线
- 再做压力情景下的极端值,参考2008年、2020年等危机时期的数据
- 最后做敏感性分析,看看系数变动10%对流动性缺口的影响有多大
一句话总结:合同现金流是起点,行为化调整是灵魂。没有行为化调整的压力测试,就是纸上谈兵。
嗯,这一章的内容就到这里。记住:做现金流建模,别只看合同,多想想客户会怎么动。你想想看,如果所有客户都按合同办事,那还要风险管理干嘛?
公众号:蓝海资料掘金营,微信deep3321