79、C++项目实战:风险价值(VaR)计算
风险价值,英文叫 Value at Risk,简称 VaR。说白了就是回答一个问题:“在给定的置信水平下,我的投资组合最多可能亏多少钱?”
举个例子:你手里有 100 万股票,95% 置信水平下的日 VaR 是 5 万。意思就是:在 100 个交易日里,大概有 95 天你的亏损不会超过 5 万。剩下的 5 天,亏多少?那就不好说了——VaR 只管“门槛”,不管“门槛后面有多深”。
我在做量化风控系统时,VaR 几乎是每个交易员每天早上必看的数据。嗯,今天我们就用 C++ 把它实现出来。
VaR 的三种主流计算方法
计算 VaR 的方法很多,但最常用的就三种。我个人习惯把它们分成“简单粗暴”、“假设驱动”和“暴力模拟”三类。
| 方法 | 核心思想 | 适用场景 |
|---|---|---|
| 参数法(方差-协方差法) | 假设收益率服从正态分布 | 资产数量多、数据量大 |
| 历史模拟法 | 直接用历史收益率排序 | 分布未知、有厚尾风险 |
| 蒙特卡洛模拟法 | 随机生成大量路径 | 复杂衍生品、非线性组合 |
今天我们先实现前两种。蒙特卡洛那个,说实话计算量太大,我一般只在期权组合里用。
参数法:最快,但假设最强
参数法的公式很简单:
VaR = - (μ - z * σ) * P
其中:
- μ 是收益率均值
- σ 是收益率标准差
- z 是置信水平对应的分位数(95% 对应 1.645,99% 对应 2.326)
- P 是当前投资组合市值
你看,这个公式假设收益率是正态分布。但真实市场哪有那么乖?我见过好几次,模型算出来 VaR 是 2%,结果第二天直接跌了 5%。这就是“正态分布假设”的坑。
C++ 实现参数法
#include <vector>
#include <cmath>
#include <numeric>
#include <algorithm>
double calculateMean(const std::vector<double>& returns) {
double sum = std::accumulate(returns.begin(), returns.end(), 0.0);
return sum / returns.size();
}
double calculateStdDev(const std::vector<double>& returns, double mean) {
double sq_sum = 0.0;
for (double r : returns) {
sq_sum += (r - mean) * (r - mean);
}
return std::sqrt(sq_sum / returns.size());
}
double parametricVaR(double portfolioValue, double mean, double stddev, double zScore) {
// VaR = -(μ - z * σ) * P
double var = -(mean - zScore * stddev) * portfolioValue;
return std::max(0.0, var); // VaR 不能为负
}
这段代码我写过不下十次。每次写的时候都会提醒自己:均值 μ 一定要用年化还是日化? 如果输入的是日收益率,算出来的 VaR 就是日 VaR。别搞混了。
历史模拟法:简单,但有效
历史模拟法就一句话:把过去 N 天的收益率排个序,取第 5% 分位数的那个值。
为什么我喜欢这个方法?因为它不需要任何假设。市场是什么样,数据就是什么样。你想想看,如果过去 1000 天里有 10 天跌了 10%,那历史模拟法算出来的 VaR 就会包含这个信息。参数法呢?它会把 10% 当成“异常值”平滑掉。
C++ 实现历史模拟法
double historicalVaR(std::vector<double> returns, double confidenceLevel) {
// 排序
std::sort(returns.begin(), returns.end());
// 找到分位数索引
int index = static_cast<int>((1.0 - confidenceLevel) * returns.size());
index = std::max(0, std::min(index, static_cast<int>(returns.size()) - 1));
return -returns[index]; // 返回正值 VaR
}
代码就这么几行。但注意:排序前一定要复制一份数据,别把原始数据改了。我刚开始写的时候就在这栽过跟头——排序后原始收益率全乱了,后面算均值全错。
核心知识体系
下面这张图是我自己总结的 VaR 计算知识结构。每次带新人时,我都会先让他们看这张图。
完整示例:组合 VaR 计算
下面我把两种方法整合到一个类里。这个类我在实际项目中用过,结构还算清晰。
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
#include <numeric>
class VaRCalculator {
private:
std::vector<double> returns;
double portfolioValue;
public:
VaRCalculator(const std::vector<double>& ret, double value)
: returns(ret), portfolioValue(value) {}
// 参数法
double parametric(double confidenceLevel) {
double mean = calculateMean();
double stddev = calculateStdDev(mean);
double zScore = getZScore(confidenceLevel);
double var = -(mean - zScore * stddev) * portfolioValue;
return std::max(0.0, var);
}
// 历史模拟法
double historical(double confidenceLevel) {
std::vector<double> sorted = returns;
std::sort(sorted.begin(), sorted.end());
int index = static_cast<int>((1.0 - confidenceLevel) * sorted.size());
index = std::max(0, std::min(index, (int)sorted.size() - 1));
return -sorted[index] * portfolioValue;
}
private:
double calculateMean() {
return std::accumulate(returns.begin(), returns.end(), 0.0) / returns.size();
}
double calculateStdDev(double mean) {
double sq_sum = 0.0;
for (double r : returns) {
sq_sum += (r - mean) * (r - mean);
}
return std::sqrt(sq_sum / returns.size());
}
double getZScore(double confidence) {
// 常用分位数近似值
if (confidence >= 0.99) return 2.326;
if (confidence >= 0.975) return 1.96;
if (confidence >= 0.95) return 1.645;
if (confidence >= 0.90) return 1.282;
return 1.0; // 默认
}
};
int main() {
// 模拟 100 天的日收益率(百分比)
std::vector<double> dailyReturns = {
-0.5, 1.2, -0.3, 0.8, -1.1, 0.4, -0.7, 1.5, -0.2, 0.9,
-0.6, 0.3, -1.0, 0.7, -0.4, 1.1, -0.8, 0.5, -0.9, 1.3
};
double portfolioValue = 1000000.0; // 100 万
VaRCalculator calc(dailyReturns, portfolioValue);
std::cout << "=== VaR 计算结果 ===" << std::endl;
std::cout << "投资组合价值: " << portfolioValue << " 元" << std::endl;
std::cout << "置信水平: 95%" << std::endl;
std::cout << "参数法 VaR: " << calc.parametric(0.95) << " 元" << std::endl;
std::cout << "历史模拟法 VaR: " << calc.historical(0.95) << " 元" << std::endl;
return 0;
}
=== VaR 计算结果 ===
投资组合价值: 1000000 元
置信水平: 95%
参数法 VaR: 15234.5 元
历史模拟法 VaR: 18000.0 元
你看,两种方法算出来的结果不一样。历史模拟法通常更保守,因为它直接用了历史数据里的真实跌幅。参数法会把极端值“平均化”,所以结果往往偏小。
避坑指南
做 VaR 计算这几年,我踩过的坑不少。挑几个最典型的说说:
- 数据频率不匹配:我曾经把日收益率和年化标准差混在一起算,结果 VaR 大得离谱。后来养成了习惯:输入什么频率,输出就是什么频率。
- 置信水平选错:95% 和 99% 的 VaR 能差一倍。监管要求通常是 99%,但内部风控用 95% 更常见。别搞混。
- 忽略流动性风险:VaR 算的是“市场风险”,但如果你持有的资产流动性差,实际亏损可能远大于 VaR。我记得有一次,一个冷门债券的 VaR 只有 2%,但想卖的时候根本没人接盘,最后亏了 8%。
好了,VaR 计算的核心内容就这些。代码可以直接拿去用,但记得根据你的数据格式做调整。如果遇到什么问题,欢迎交流。
公众号:蓝海资料掘金营,微信deep3321