交易信号数据类型与量化:定点数表示、浮点数处理、数据位宽选择策略
各位同学,今天我们来聊聊FPGA处理交易信号时最基础、也最容易被忽视的问题——数据怎么表示。
你可能觉得,不就是数字吗?用整数还是小数,有啥好纠结的?
嗯,我当年也是这么想的。直到有一次,我做的量化加速卡在回测时表现完美,一上实盘就崩了。查了三天,最后发现是浮点数精度问题,导致一个很小的价差信号被截断成了零。那笔交易没触发,直接亏了六位数。
从那以后,我对数据类型的敬畏心就上来了。今天这堂课,我把这些坑和经验都摊开来讲。
一、定点数表示:FPGA的“母语”
FPGA不像CPU那样天生擅长浮点运算。它最拿手的,其实是定点数。
什么叫定点数?说白了,就是小数点位置固定不变的二进制数。比如我们用16位表示一个数,约定高8位是整数部分,低8位是小数部分。这就是一个Q8.8格式的定点数。
我个人习惯用Qm.n这种记法。m是整数位宽,n是小数位宽。总位宽就是m+n,再加一个符号位(如果需要的话)。
举个例子:
// Q8.8格式,16位有符号定点数
// 表示范围:-128.0 到 +127.99609375
// 精度:1/256 ≈ 0.0039
wire signed [15:0] price_q88;
// 将浮点数 123.456 转为 Q8.8
// 123.456 * 256 = 31604.736 → 取整 31605
assign price_q88 = 16'd31605; // 实际表示 123.457
你看,定点数的好处很明显:运算就是整数加减乘除,一个时钟周期就能搞定。没有浮点单元那种动辄几十个周期的延迟。
核心要点:在FPGA里做高频交易信号处理,90%的场景用定点数就够了。浮点数?那是给CPU和GPU准备的。
二、浮点数处理:什么时候不得不用?
那浮点数在FPGA里就完全没用吗?也不是。
我记得有个项目是做期权定价的二叉树模型。中间要算很多次exp()和sqrt(),用定点数实现,精度损失大到模型直接发散。最后还是上了浮点IP核。
FPGA里的浮点数主要有两种:
- 单精度(32位):1位符号 + 8位指数 + 23位尾数
- 半精度(16位):1位符号 + 5位指数 + 10位尾数
半精度是最近几年才火起来的。为什么?因为AI推理需要大量矩阵运算,半精度够用,功耗还低。交易信号处理也一样——很多场景下,半精度比单精度更实用。
我的建议:能用定点数就别用浮点数。实在要用浮点数,优先考虑半精度。单精度IP核占用的LUT和DSP资源,够你实现三个定点数乘法器了。
三、数据位宽选择策略:多宽才够用?
这个问题,我每次带新人都会被问到。答案其实就一句话:够用就行,别贪多。
为什么?因为位宽每增加1位,你消耗的LUT和布线资源就翻倍。在FPGA这种资源受限的环境里,位宽就是钱。
那怎么确定“够用”的位宽?我一般分三步走:
- 分析动态范围:你的信号最大可能到多少?最小呢?比如沪深300股指期货,价格在3000-6000之间波动。整数部分用13位(2^13=8192)就够。
- 确定精度需求:交易信号的最小价格变动是多少?A股是0.01元,期货可能是0.2点。小数部分至少要有能分辨这个最小单位的精度。
- 留余量:我习惯在计算出的位宽上再加2-3位。防止极端行情下溢出。2020年原油期货跌到负值那次,很多人的定点数直接炸了,就是因为没留余量。
| 交易品种 | 价格范围 | 最小变动 | 推荐位宽 | 格式 |
|---|---|---|---|---|
| A股个股 | 0.01 - 1000 | 0.01 | 24位 | Q10.14 |
| 股指期货 | 2000 - 8000 | 0.2 | 20位 | Q13.7 |
| 加密货币 | 0.0001 - 100000 | 0.00001 | 32位 | Q17.15 |
| 外汇 | 0.01 - 200 | 0.00001 | 28位 | Q8.20 |
避坑指南:我曾经在一个CTA策略里,用32位定点数处理螺纹钢期货的价差信号。回测跑了三年都没问题。结果实盘时,遇到一次极端行情,价差瞬间拉大到正常值的100倍,直接溢出变成了负数,触发了反向开仓。那一单亏了40万。
从那以后,我所有涉及溢出的地方都加了饱和截位逻辑。宁可精度损失,也不能让数据翻转。
四、知识体系总览
下面这张图,是我整理的数据类型选择决策流程。你照着走一遍,基本不会出错。
这张图的核心逻辑其实很简单:先问自己要不要做复杂数学运算。要,就上浮点。不要,再看动态范围清不清楚。清楚就自定义定点数,不清楚就根据资源情况二选一。
你想想看,这个决策树走下来,是不是比凭感觉选数据类型靠谱多了?
五、实战中的位宽优化技巧
最后分享几个我在项目中常用的优化手法:
- 多精度混合:同一个模块里,不同路径用不同位宽。比如价格信号用24位,成交量用32位,时间戳用48位。没必要统一成一个位宽。
- 动态截位:在乘法运算后,根据输入数据的实际范围动态调整截位位置。比固定截位能多保留1-2位有效精度。
- 饱和截位 vs 舍入截位:交易信号处理我强烈建议用饱和截位。宁可数值卡在最大值,也不能让它翻转。舍入截位虽然精度高一点,但遇到极端值就完蛋。
一个小技巧:在仿真阶段,我会在testbench里故意注入超出正常范围10倍的数据,看看模块会不会崩溃。这个习惯帮我发现了至少5个潜在的溢出bug。
好了,关于数据类型和位宽选择,今天就聊这么多。记住一句话:FPGA里没有最好的数据类型,只有最合适的。下一节课,我们会把这些知识用到实际的信号处理流水线设计中去。
公众号:蓝海数据掘金营,微信deep3321