风险因子数据模型:市场数据、持仓数据与参考数据

做实时风控,最怕什么?

数据模型没设计好。我见过太多团队,算法写得漂亮,结果数据模型一塌糊涂。行情来了,系统直接崩了。嗯,今天我们就聊聊这个核心问题。

一、市场数据模型:行情、利率、汇率

市场数据是实时风控的血液。它变化最快,量也最大。我个人习惯把市场数据分成三类:

  • 行情数据:股票、期货、期权的实时价格
  • 利率数据:国债收益率、Libor、Shibor等
  • 汇率数据:外汇即期、远期、掉期报价

这三类数据,更新频率完全不同。行情是毫秒级,利率是秒级,汇率介于两者之间。你想想看,如果用一个模型去套,肯定出问题。

1.1 行情数据模型

我在项目中遇到过最头疼的事:行情数据字段定义不统一。有的交易所用"last_price",有的用"close",还有的用"trade_price"。解析的时候,代码里全是if-else。

所以,我建议统一成这样的模型:

// 行情快照数据结构
struct MarketSnapshot {
    string  instrument_id;   // 合约代码
    int64   timestamp;       // 纳秒时间戳
    double  bid_price;       // 买一价
    double  ask_price;       // 卖一价
    int32   bid_volume;      // 买一量
    int32   ask_volume;      // 卖一量
    double  last_price;      // 最新成交价
    double  open_interest;   // 持仓量
    double  settlement;      // 结算价(期货特有)
};

核心原则:时间戳必须用纳秒级。别问我为什么,你试试用毫秒级做高频回测,结果全是错的。

1.2 利率与汇率模型

利率和汇率,说白了就是曲线。一条收益率曲线,包含多个期限点。我习惯用这样的结构:

// 利率曲线数据结构
struct YieldCurve {
    string  curve_id;        // 曲线ID,如"CNY_SWAP"
    int64   timestamp;       // 更新时间
    vector<CurvePoint> points; // 曲线点列表
};

struct CurvePoint {
    string  tenor;           // 期限,如"1Y","5Y"
    double  rate;            // 利率值
    double  spread;          // 利差(可选)
};

汇率模型类似,但多了一个"报价方向"字段。是Bid还是Ask?这个搞错了,计算出来的风险敞口直接翻倍。

避坑指南:我曾经因为汇率数据缺少报价方向,导致VaR计算偏差了30%。后来加了个枚举字段,问题才解决。

二、持仓数据模型:你的风险敞口

持仓数据,是风控引擎的"家底"。没有持仓,市场数据再快也没用。

持仓数据分两种:

  • 静态持仓:交易完成后就固定了,比如股票持仓
  • 动态持仓:会随时间变化,比如期权持仓的希腊字母

我建议用这样的模型:

// 持仓数据结构
struct Position {
    string  portfolio_id;    // 组合ID
    string  instrument_id;   // 合约代码
    int64   quantity;        // 持仓数量(正为多,负为空)
    double  cost_price;      // 成本价
    double  market_value;    // 市值
    double  pnl;             // 浮动盈亏
    int64   update_time;     // 更新时间
};

小技巧:持仓数量用int64,别用double。你想想看,股票持仓100股就是100股,用浮点数容易出精度问题。

对于衍生品持仓,还需要额外字段:

字段 类型 说明
delta double 期权价格对标的物价格的敏感度
gamma double delta的变化率
vega double 对波动率的敏感度
theta double 时间衰减

这些希腊字母,是实时风控的核心输入。每次行情更新,都要重新计算。所以,数据模型里必须预留这些字段。

三、参考数据模型:不变的"元数据"

参考数据,说白了就是"不会变"的数据。比如:

  • 合约基本信息(代码、名称、交易所)
  • 交易日历(开市、休市)
  • 合约乘数、最小变动价位
  • 保证金比例、涨跌停板

这些数据虽然变化少,但一旦错了,后果很严重。我记得有一次,某个合约的乘数配置错了,导致保证金计算少了10倍。还好是测试环境,不然就出大事了。

参考数据模型我建议这样设计:

// 合约参考数据
struct InstrumentRef {
    string  instrument_id;   // 合约代码
    string  exchange;        // 交易所
    string  product_type;    // 产品类型(股票/期货/期权)
    double  multiplier;      // 合约乘数
    double  tick_size;       // 最小变动价位
    double  margin_rate;     // 保证金比例
    double  upper_limit;     // 涨停价
    double  lower_limit;     // 跌停价
    int32   delivery_month;  // 交割月份
};

重要提醒:参考数据一定要做版本管理。合约乘数变了,旧数据怎么办?我习惯用"生效日期"字段来控制。

四、数据模型之间的关系

这三种数据不是孤立的。它们之间的关系,我用一张图来说明:

市场数据 行情/利率/汇率 实时更新 持仓数据 静态+动态 希腊字母 参考数据 合约信息 交易日历 价格更新 合约定义 乘数/保证金 风险指标计算 VaR / 希腊字母 / 压力测试

你看,市场数据驱动持仓数据的估值,参考数据为两者提供"骨架"。三者缺一不可。

五、实战中的设计要点

说了这么多理论,来点实际的。我在做实时风控引擎时,总结了几个要点:

  1. 数据模型要扁平化:别搞太多嵌套。实时计算讲究速度,嵌套结构解析慢。
  2. 字段类型要精确:时间戳用int64,价格用double,数量用int64。别混用。
  3. 预留扩展字段:市场在变,模型也要变。我习惯在每个结构体末尾加一个"ext"字段,存JSON。
  4. 数据版本化:参考数据尤其重要。合约乘数变了,历史数据怎么处理?加个"version"字段。

我的习惯:所有数据模型都用Protocol Buffers定义。一来跨语言兼容,二来序列化快。C++和Python都能用,省心。

嗯,数据模型就聊到这里。记住一句话:数据模型设计得好,实时风控就成功了一半。另一半,是计算引擎的优化。那个我们后面再聊。

专注资料整理