3. 交易信号处理核心算法:移动平均线、MACD、RSI与布林带的硬件实现
做量化交易的硬件加速,说白了就是跟延迟赛跑。我刚开始接触这个领域时,总觉得软件上跑得挺顺的指标,干嘛非要搬到FPGA上?直到有一次,我在实盘环境中看到软件计算MACD的延迟直接把一个套利窗口给关掉了——嗯,从那以后我就铁了心要把这些核心算法全部硬件化。
今天咱们就来聊聊,交易信号处理里最经典的四个算法:移动平均线、MACD、RSI和布林带。它们在FPGA上怎么实现?有哪些坑?我个人习惯怎么处理?咱们一个一个说。
3.1 移动平均线的硬件实现
移动平均线是技术分析的基石。简单移动平均线(SMA)和指数移动平均线(EMA),在FPGA上的实现思路完全不同。
3.1.1 SMA:用滑动窗口做累加
SMA的计算公式很简单:SMA = (P1 + P2 + ... + Pn) / n。但你要是每个周期都重新加一遍所有数据,那资源消耗就太大了。
我建议用「滑动窗口累加器」的思路。说白了就是维护一个累加和,来一个新数据就加进去,同时把最老的那个数据减掉。这样每个周期只需要一次加法和一次减法。
// Verilog示例:N=8的SMA核心逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sum <= 0;
fifo_ptr <= 0;
end else if (data_valid) begin
// 减去最老的数据
sum <= sum - fifo[fifo_ptr] + new_data;
// 更新FIFO
fifo[fifo_ptr] <= new_data;
fifo_ptr <= fifo_ptr + 1;
end
end
assign sma_out = sum >> 3; // 除以8,用移位实现
这里有个细节要注意:除法用移位实现的前提是N是2的幂次。如果N不是2的幂次,我一般会用定点数乘法器来做除法,或者干脆用查找表。我在项目中遇到过用N=20的SMA,当时为了省资源,硬是把20改成了16——反正效果差不多,你想想看。
3.1.2 EMA:递归结构更省资源
EMA的公式是:EMA_t = α * P_t + (1-α) * EMA_{t-1}。这个结构天然适合硬件实现,因为它只需要一个乘法器和一个加法器,外加一个寄存器保存上一周期的结果。
α通常取2/(N+1),比如N=12的EMA,α≈0.1538。在FPGA里,我习惯把α量化成定点数,比如用Q1.15格式。
// EMA核心逻辑(α=0.1538,量化成16位定点数)
parameter ALPHA = 16'd10080; // 0.1538 * 2^16
always @(posedge clk) begin
if (reset) begin
ema_reg <= 0;
end else if (data_valid) begin
ema_reg <= (ALPHA * new_data) + ((65536 - ALPHA) * ema_reg);
// 注意:这里需要做右移16位截断
end
end
assign ema_out = ema_reg >> 16;
为什么会这样?因为EMA的递归结构天然适合流水线。你算完当前周期,结果直接喂给下一周期,中间不需要额外的存储。相比SMA,EMA在FPGA上其实更「友好」。
3.2 MACD指标计算
MACD由三部分组成:快线(12周期EMA)、慢线(26周期EMA)、以及它们的差值(DIF)。最后还要对DIF做9周期EMA得到信号线(DEA)。
你看,这其实就是三个EMA的级联。在FPGA上实现MACD,我建议分三步走:
- 第一步: 同时计算12周期EMA和26周期EMA。这两个可以并行,互不干扰。
- 第二步: 计算DIF = EMA12 - EMA26。这里注意数据对齐,两个EMA的延迟要一致。
- 第三步: 对DIF做9周期EMA,得到DEA。同时输出MACD柱 = 2 * (DIF - DEA)。
MACD的硬件实现,说白了就是三个EMA模块的拼装。但要注意,这三个EMA的α值不同(12、26、9对应的α分别是2/13、2/27、2/10),所以每个模块的乘法系数要单独配置。
3.3 RSI指标计算
RSI的计算稍微复杂一点。公式是:RSI = 100 - 100 / (1 + RS),其中RS = 平均上涨幅度 / 平均下跌幅度。
这里的关键是「平均上涨幅度」和「平均下跌幅度」怎么算。通常用SMA或EMA来平滑。我个人习惯用EMA,因为递归结构在硬件里更省事。
具体实现步骤:
- 每个周期计算当前价格与上一周期价格的差值ΔP。
- 如果ΔP > 0,则U = ΔP,D = 0;否则U = 0,D = -ΔP。
- 对U和D分别做N周期EMA(通常N=14),得到AvgU和AvgD。
- 计算RS = AvgU / AvgD,然后算RSI。
这里最头疼的是除法。AvgU / AvgD 这个除法在FPGA里很吃资源。我建议用查找表或者CORDIC算法来做除法。如果精度要求不高,也可以用移位近似。
// RSI计算中的除法保护逻辑
if (avg_d < THRESHOLD) begin
rsi <= 16'd65535; // 对应100.0
end else begin
rs <= (avg_u << 16) / avg_d; // 定点数除法
rsi <= 100 - (100 << 16) / (rs + (1<<16));
end
3.4 布林带实现
布林带由三根线组成:中轨(SMA)、上轨(中轨 + k倍标准差)、下轨(中轨 - k倍标准差)。k通常取2。
实现布林带的核心难点在于标准差的计算。标准差公式是:σ = sqrt( Σ(Pi - SMA)^2 / N )。这里涉及三个运算:平方、累加、开方。
我的实现方案是这样的:
- 用滑动窗口累加器计算SMA(跟3.1.1一样)。
- 同时计算每个价格与SMA的差值,然后平方。
- 对平方值再做滑动窗口累加,得到方差。
- 对方差做开方,得到标准差。
- 最后计算上下轨:SMA ± 2 * σ。
开方运算在FPGA里可以用CORDIC算法,也可以用牛顿迭代法。我个人习惯用CORDIC,因为它没有除法,全是移位和加减,流水线深度可控。
3.5 本章小结:四个算法的硬件资源对比
说了这么多,咱们来做个总结。这四个算法在FPGA上的资源消耗和延迟特性,我整理了一张表:
| 算法 | 核心运算 | DSP资源 | BRAM资源 | 延迟(周期) | 我的评价 |
|---|---|---|---|---|---|
| SMA | 加法、减法、移位 | 0-1 | 1个FIFO | 1 | 简单粗暴,但N大时BRAM消耗大 |
| EMA | 乘法、加法 | 1-2 | 0 | 1 | 递归结构,省资源,我的首选 |
| MACD | 3个EMA + 减法 | 3-6 | 0 | 3 | EMA的级联,注意初始化 |
| RSI | EMA + 除法 | 2-4 + 除法器 | 0 | 2 + 除法延迟 | 除法是瓶颈,建议用查找表 |
| 布林带 | SMA + 平方 + 开方 | 2-3 + CORDIC | 2个FIFO | 2 + 开方延迟 | 资源消耗最大,但值得做 |
你想想看,这些算法在软件里跑可能就几行代码,但在硬件里每一步都要精打细算。不过一旦在FPGA上跑通了,延迟从微秒级降到纳秒级,那种成就感——嗯,只有做过的人才知道。
好了,这一章的内容就到这里。记住,硬件实现的核心思路是:能并行就并行,能递归就递归,能用移位就别用除法。下一章咱们聊聊更高级的信号处理算法——你猜猜是什么?
公众号:蓝海资料掘金营,微信deep3321