此刻向五个不同的服务查询 EUR/USD 汇率,你会得到五个略有差异的数字。差得不算离谱——但在小数点后第四位不同,有时是第三位。如果你的财务团队曾因为结账页显示 1.0847、而银行对账单写着 1.0821 而提过工单,你就明白这不是一个学术问题。
那么,汇率 API 的数据到底从哪里来? 诚实的答案是:没有任何一个 API "知道"汇率,因为根本不存在唯一一个汇率可供知晓。外汇是场外市场,没有中央交易所,也没有收盘钟——只有全球成千上万家机构互相报价。你能调用的每一个 API,都是一条对这个市场进行采样、清洗,然后交给你一个数字的流水线。本文逐层拆解这条流水线,解释两家提供商为何会出现分歧,并演示如何在把计费逻辑建立在某个数据源之上以前先对它做审计。
简短答案:市场与你的 JSON 之间有三层
每一个汇率 API——无论免费还是付费,包括我们自己的——都由相同的三层构成:
- 采集。 原始价格从上游来源拉取:机构级 FX 数据流、央行发布的数据,以及经纪商或零售报价。
- 归一化。 这些原始价格被校验,异常值被剔除,多个来源被融合,并为每个货币对推导出唯一的参考汇率。
- 交付。 推导出的汇率按固定节奏做快照、缓存,并带着时间戳通过 HTTP 提供出去。
这三层中任何一层出现差异,你的响应体里就会出现不同的数字。大多数开发者以为分歧来自第 1 层。实际上,第 2 层和第 3 层造成的分歧同样多。
第 1 层 —— 原始价格究竟来自哪里
银行间与机构级数据流
最接近"真实"汇率的是银行间市场:大型银行与流动性提供商彼此成交的价格。这些价格以持续的 bid 与 ask 报价流的形式,来自交易场所、主经纪商和市场数据供应商。
这类数据流是可获得的保真度最高的来源,同时也是最昂贵的——这正是免费 API 很少将其作为主要来源的原因。当某个提供商宣称提供"实时"或"亚分钟级"汇率时,几乎总是因为其流水线顶端接入了机构级数据流。
请注意,银行间数据流给你的是两个价格而非一个——一个 bid、一个 ask。你在 API 响应里看到的那个单一汇率,通常是两者的中点。如果这个区分对你来说是新的,我们关于货币兑换中的买卖价差的指南做了详细说明。
央行参考汇率
第二大来源是央行的官方发布。最著名的例子是欧洲央行,它在每个 TARGET 工作日约 16:00 CET,基于欧洲各国央行之间的协商程序,发布欧元外汇参考汇率。另有数十家央行为本国货币发布同类的每日汇率。
央行汇率有两个巨大优势:免费,且具有权威性。许多税务机关和会计准则明确接受它们用于申报。这正是免费 API 生态很大一部分建立其上的原因。Frankfurter 是该领域被广泛使用的开源项目,它追踪 84 家央行、覆盖 201 种货币的每日汇率,历史可回溯至 1948 年——全部是公开数据的再分发。
它们也有两个严重局限:
- 它们是每日快照,不是实时价格。 16:00 CET 的参考汇率,无法告诉你 09:00 或 22:00 发生了什么。
- 周末与节假日会停止。 如果你的 API 在周六不返回数据,或者重复周五的数字,通常的解释就是它源自欧洲央行。
零售与经纪商报价
第三类来源是面向终端客户的定价:银行、卡组织、支付处理商或汇款服务实际给客户的价格。这些汇率已经包含加价——在市场汇率之上嵌入价格里的利润。
这就是为什么你在消费者比价网站看到的汇率与银行对账单对不上。两边都没错,它们衡量的是不同的东西。面向消费者的网站通常展示中间市场汇率,而银行报给你的是中间市场汇率加上它自己的价差。对绝大多数软件场景而言,你要的是中间市场那个数字,并且应当显式地施加你自己的加价——放在你看得见、审得清的地方。
第 2 层 —— 提供商如何把数据流变成一个汇率
原始价格到位后,提供商必须决定发布哪个数字。这里会做四个决定,而每一个都是提供商彼此分道扬镳的地方。
融合(blending)。 大多数商业 API 并不依赖单一上游来源。例如 Open Exchange Rates 将其数据描述为从多家提供商收集、以算法方式融合。融合能抹平单个坏跳价,但融合权重是专有的——这恰恰是两个融合数据流永远不会完全一致的原因。
异常值剔除。 来自某个场所的错误报价可能偏离一个数量级。提供商会应用过滤器,剔除落在共识值容差带之外的价格。激进过滤意味着汇率稳定,但对真实波动反应更慢;宽松过滤意味着反应快,但偶尔有噪声。
中间价推导。 如果上游数据流是 bid/ask,提供商会发布一个中间价。简单中点 (bid + ask) / 2 是标准做法,但按成交量加权的方法会得出略有不同的结果。
交叉汇率三角推算。 没有任何提供商会直接获取全部 30,000 多个可能的货币对。绝大多数货币对是通过一个枢纽货币计算出来的——通常是 USD 或 EUR:
GBP/JPY = (USD/JPY) / (USD/GBP)这意味着你拿到的冷门货币对汇率,继承了另外两个货币对的舍入与时点。以 USD 为枢纽的提供商与以 EUR 为枢纽的提供商,对同一个交叉盘会得出不同的数字。相关机制见交叉汇率详解。
第 3 层 —— 汇率如何抵达你的代码
最后一层是开发者掌控最多、思考最少的一层。
更新频率是提供商之间、以及不同价格档位之间最大的单一差异点。免费套餐通常一天刷新一到两次;付费档位每小时、每十分钟或每 60 秒刷新一次。两个使用相同数据的 API 会产生分歧,仅仅因为一个在 14:00 拍了快照,另一个在 14:47。
缓存会放大这一点。多数 API 位于 CDN 之后,而构建良好的客户端又会在其之上做本地缓存。给 10 分钟的刷新周期叠加 15 分钟的边缘缓存,你的应用可能正在使用 25 分钟前的汇率。用来展示价格没问题,用来结算交易则不可接受——真正的实务问题永远是对这个具体操作而言,多旧算太旧。我们关于货币 API 的缓存与错误处理的指南讲解了如何设定这些窗口。
时间戳是你的防线。任何严肃的 API 都会返回汇率被采集的时刻。请读取它。不要假设你收到响应的时刻就是该汇率成立的时刻:
const MAX_AGE_SECONDS = 900; // 15 minutes
async function getRate(base, symbol) {
const res = await fetch(
`https://api.finexly.com/v1/latest?base=${base}&symbols=${symbol}`,
{ headers: { Authorization: `Bearer ${process.env.FINEXLY_API_KEY}` } }
);
const data = await res.json();
const ageSeconds = Math.floor(Date.now() / 1000) - data.timestamp;
if (ageSeconds > MAX_AGE_SECONDS) {
throw new Error(`Rate is ${ageSeconds}s old — refusing to price on stale data`);
}
return { rate: data.rates[symbol], ageSeconds };
}下面是底层请求与一个具有代表性的响应结构:
curl "https://api.finexly.com/v1/latest?base=USD&symbols=EUR,GBP,JPY" \
-H "Authorization: Bearer YOUR_API_KEY"{
"success": true,
"base": "USD",
"timestamp": 1755244800,
"rates": {
"EUR": 0.9241,
"GBP": 0.7863,
"JPY": 147.2150
}
}完整的参数与端点说明见 Finexly API 文档。
为什么两个 API 对同一货币对返回不同数字
把三层合起来看,分歧有六个成因,大致按危害程度排序:
- 快照时点不同。 迄今为止最常见的原因。两个数据流都没毛病;它们只是看的时刻不同。
- 来源组合不同。 源自央行的汇率与源自银行间市场的汇率,按定义衡量的就是两回事。
- 中间市场价 vs 含加价。 一家给你市场中点,另一家给你的是价差已经嵌进去的客户价。
- 交叉盘的枢纽货币不同。 以 USD 为枢纽和以 EUR 为枢纽的三角推算,对同一个非美元货币对会得出不同结果。
- 精度与舍入。 六位小数截断为四位,或者以反向货币对发布再被重新求倒数,两者都会引入漂移。
- 你忘掉的缓存层。 你的 CDN、框架的 HTTP 缓存和你自己的 Redis 层,每一个都在叠加数据的"年龄"。
一条实用经验法则:对主要货币对而言,两个可靠的中间市场来源之间相差几个基点(0.01% = 1 bp)是正常且预期之内的。相差 50 bp 或以上,说明其中一个要么陈旧、要么含加价、要么坏了——上线前你应该查清是哪一个。
在信任一个汇率 API 之前如何审计它
不要仅凭信任接受提供商关于准确度的说法。用一周时间,针对你财务团队认可的权威来源跑一下这个检查:
import os
import requests
from datetime import datetime, timezone
FINEXLY_URL = "https://api.finexly.com/v1/latest"
HEADERS = {"Authorization": f"Bearer {os.environ['FINEXLY_API_KEY']}"}
def get_rate(base: str, symbol: str) -> dict:
r = requests.get(
FINEXLY_URL,
headers=HEADERS,
params={"base": base, "symbols": symbol},
timeout=5,
)
r.raise_for_status()
data = r.json()
return {
"rate": data["rates"][symbol],
"captured_at": datetime.fromtimestamp(data["timestamp"], tz=timezone.utc),
}
def basis_points(a: float, b: float) -> float:
"""Difference between two rates, in basis points."""
return abs(a - b) / ((a + b) / 2) * 10_000
primary = get_rate("EUR", "USD")
reference = 1.0839 # whatever your accounting source published
diff = basis_points(primary["rate"], reference)
print(f"Finexly: {primary['rate']} captured {primary['captured_at']:%H:%M UTC}")
print(f"Reference: {reference}")
print(f"Delta: {diff:.1f} bp -> {'OK' if diff < 25 else 'INVESTIGATE'}")在结果中重点看三件事:
- 差值是稳定的还是在漂移? 恒定的偏移暗示存在系统性加价;随机的偏移暗示时点问题。
- 差值是否在特定时段跳升? 这指向快照时点,通常出现在某家央行的发布窗口附近。
- 周末会怎样? 如果你的来源周五下午冻结、周一才恢复,那它源自央行——请据此安排周一的对账。
你也可以验证三角推算:直接取一个交叉盘,再通过 USD 计算一遍,两者应当在一到两个基点内吻合。
按使用场景选择数据源
不存在放之四海皆"最好"的来源——只有适合你正在构建之物的来源。
| 使用场景 | 你需要什么 | 可接受的数据陈旧度 |
|---|---|---|
| 向购物者展示价格 | 中间市场汇率,之上叠加你自己的加价 | 小时级 |
| SaaS 订阅计费 | 中间市场价,每个计费批次一个快照,随发票一并保存 | 小时级,但必须留痕 |
| 会计与税务申报 | 特定日期的央行参考汇率 | 按定义即每日 |
| 分析与仪表盘 | 来自单一来源的一致历史时间序列 | 每日 |
| 付款与汇款 | 新鲜的中间市场价,并设定明确的容差带 | 分钟级 |
| 交易与对冲 | 来自机构级数据流的真实 bid/ask | 秒级 |
如果你还在评估选项,我们对免费与付费货币 API的比较拆解了升级档位后有哪些变化,价格方案页面则展示了刷新频率与请求上限的位置。若只想对某个货币对做一次快速人工核对,货币转换器使用与 API 相同的底层数据流。
常见问题
免费货币 API 的数据从哪里来?
几乎总是来自央行发布,最常见的是欧洲央行的每日欧元参考汇率,有时会与少数其他公开来源融合。这正是免费档位通常一天只刷新一次、跳过周末,且覆盖的冷门货币比付费档位少的原因。
为什么我的 API 汇率和 Google 上的不一样?
Google 显示的是中间市场参考汇率,它是快照而非连续的实时价格,且未必与你调用 API 的时刻同步采样。小幅差异很正常;大幅差异通常意味着两者之一是含加价的零售汇率,而非中间市场汇率。
会计和税务申报应该使用哪个汇率?
使用相关央行针对交易日期发布的官方参考汇率——这是大多数税务机关的预期。请通过带明确日期的历史端点获取,而不是复用实时汇率,并把它与交易记录一并保存。
实时汇率 API 真的是实时的吗?
严格意义上很少是。"实时"通常意味着提供商以较短间隔刷新——最高档位常见 60 秒——而不是逐笔推送。请看响应中的时间戳和文档写明的刷新间隔,而不是营销文案。
我能直接爬取汇率而不用 API 吗?
可以,但你会继承所爬页面的全部失败模式:版面变更、速率限制、没有时间戳、没有历史回填,并且经常违反服务条款。完整权衡见货币 API 与网页爬取对比。
建立在你能审计的数据流之上
清楚你的汇率数据从哪里来,决定了你面对的是一个一句话就能解释的货币 bug,还是一个吞掉一周工程时间的 bug。集成之前向任何提供商提三个问题:数据来源是什么、多久刷新一次、每个响应是否带采集时间戳。
准备好接入你真正能审计的汇率数据了吗?免费获取 Finexly API 密钥——无需信用卡。从每月 1,000 次免费请求起步,覆盖 170+ 种货币,从第一天起就提供带时间戳的响应与历史数据。
Explore More
Vlado Grigirov
Senior Currency Markets Analyst & Financial Strategist
Vlado Grigirov is a senior currency markets analyst and financial strategist with over 14 years of experience in foreign exchange markets, cross-border finance, and currency risk management. He has wo...
View full profile →