Python实时外汇行情接入实战WebSocket与REST

用户头像Fxdund
2026-09-12 发布

最近外汇市场的消息面非常密集。欧洲央行刚结束议息会议,如期加息25个基点,把存款机制利率推到2.50%,同时上调了全年通胀预期——能源价格上涨是背后的主要推手。另一边,美国CPI数据刚落地,虽然整体符合预期,但核心通胀环比略超市场预期,美联储下周议息会议的加息概率从六成跳到了七成以上。

这两件事凑在一起,欧元兑美元的波动明显加大。ECB加息当天欧元不涨反跌,因为市场注意力立刻转向"美联储会不会跟着加"——如果两边都加,利差不变,欧元自然没有方向性行情。这种央行政策博弈期,汇率价格跳动快、消息面密集,做行情看板或者交易辅助工具的开发者,对实时数据接口的需求就特别迫切。

这篇文章从技术角度聊聊,怎么用Python从零接入一套实时外汇行情——包括WebSocket实时报价推送、REST历史K线查询,以及把两者组合起来做一个轻量监控脚本。不依赖任何量化框架,标准库加 requestswebsocket-client 就能跑。

为什么要分开用WebSocket和REST

很多初学者一开始会问:直接定时轮询REST不行吗,为什么还要搞WebSocket?

实际用过就知道差别很大。像最近ECB决议和美联储议息这种事件,汇率反应往往在几秒钟内完成。用REST轮询,哪怕你每5秒请求一次,也会错过中间的波动轨迹。WebSocket适合盯实时跳动——价格一变服务端就推过来,延迟在毫秒级,做盘中监控和事件触发很顺手。

但WebSocket不适合拉历史数据,你不能让它"把过去90天的日K线给我"。REST反过来,拉历史K线、查最新快照都很方便,但频繁请求既浪费带宽又有速率限制。

所以正确的用法是:REST拉历史数据做初始化和回测,WebSocket订阅实时数据做增量更新。下面分别说。

REST:拉取历史K线做初始化

假设我们要做一个欧元兑美元的行情图表,首先需要拉一段历史日K线作为底图,观察近期的走势结构。接口的结构很规整:基础地址是 https://api.itick.org,外汇相关的REST路径都在 /forex/ 下面。

认证用HTTP Header token,不是常见的 X-API-KeyAuthorization: Bearer,这个坑要注意。请求参数是 regioncode 的组合——region 是市场代码(外汇用 GB),code 是货币对代码(如 EURUSD)。K线周期用 kType 数字编码:8代表日K,1代表1分钟,5代表1小时。

import requests

API_BASE = "//api.itick.org"
TOKEN = "your_token_here"
headers = {
    "accept": "application/json",
    "token": TOKEN
}


def get_forex_kline(region, code, k_type=8, limit=90):
    """
    获取外汇历史K线
    region: 市场代码,外汇固定用 GB
    code: 货币对代码,如 EURUSD、GBPUSD、USDJPY
    k_type: K线周期编码
            1=1分钟  2=5分钟  3=15分钟  4=30分钟
            5=1小时  8=日K    9=周K     10=月K
    limit: 返回条数,最大500
    """
    resp = requests.get(
        f"{API_BASE}/forex/kline",
        headers=headers,
        params={
            "region": region,
            "code": code,
            "kType": k_type,
            "limit": limit
        }
    )
    resp.raise_for_status()
    return resp.json()["data"]


# 拉取EUR/USD近90根日K线
kline = get_forex_kline("GB", "EURUSD", k_type=8, limit=90)

# 返回的每根K线字段是缩写
# t=时间戳(毫秒) o=开 h=高 l=低 c=收 v=成交量 tu=成交额
latest = kline[-1]
print(f"最新日K: 开={latest['o']} 高={latest['h']} "
      f"低={latest['l']} 收={latest['c']} 量={latest['v']}")

这里有个容易踩的坑:返回字段名是缩写的。价格字段用 o/h/l/c,成交量是 v,时间戳是 t(毫秒级)。不是 open/high/low/close/volume 那种长名。写代码的时候先打印一次返回值确认字段,别猜。

拉完日K线之后,你可以自己算一下近期的波动幅度。比如EUR/USD最近在1.16附近来回震荡,如果你用日K的最高价减最低价再除以收盘价,算出来的日均波幅大概在0.4%左右——这个数字对你后面设计告警阈值很有用。

WebSocket:订阅实时报价

历史K线拉完之后,需要把最新价格实时推过来。像下周美联储议息这种事件,价格跳动可能在几分钟内完成,用REST轮询完全跟不上。外汇WebSocket的地址是 wss://api.itick.org/forex,跟REST同一个域名,只是协议换成了WebSocket。

连接的时候把token放在HTTP Header里:

import json
import threading
import time
import websocket

WS_URL = "wss://api.itick.org/forex"
TOKEN = "your_token_here"

authenticated = False
heartbeat_timer = None


def start_heartbeat(ws):
    """服务端要求每30秒发一次心跳,否则可能断开"""
    def ping():
        while True:
            time.sleep(30)
            ws.send(json.dumps({
                "ac": "ping",
                "params": str(int(time.time() * 1000))
            }))
    global heartbeat_timer
    heartbeat_timer = threading.Thread(target=ping, daemon=True)
    heartbeat_timer.start()


def subscribe(ws):
    """
    订阅格式:{"ac": "subscribe", "params": "EURUSD$GB", "types": "quote"}
    params 用 标的$市场 的格式,多标的用逗号分隔
    types 可选 quote/tick/depth/kline@1 等
    """
    ws.send(json.dumps({
        "ac": "subscribe",
        "params": "EURUSD$GB,GBPUSD$GB",
        "types": "quote"
    }))

WebSocket的状态机有个关键点:不能连上就发订阅。连接刚建立时服务端会先回一条 {"code":1,"msg":"Connected Successfully"},这时候还不能订阅。必须等到收到鉴权成功消息 {"resAc":"auth","code":1,"msg":"authenticated"} 之后,才能发subscribe。很多人第一次接的时候卡在这里——连接成功了但订阅一直报错,就是因为没等鉴权。

def on_open(ws):
    print("连接已建立,等待鉴权...")


def on_message(ws, message):
    global authenticated
    payload = json.loads(message)

    # 等鉴权成功后再订阅,同时启动心跳
    if payload.get("resAc") == "auth" and payload.get("code") == 1 and not authenticated:
        authenticated = True
        print("鉴权成功,开始订阅行情")
        subscribe(ws)
        start_heartbeat(ws)
        return

    # 心跳响应直接忽略
    if payload.get("resAc") == "pong":
        return

    # 处理实时报价推送
    data = payload.get("data") or {}
    if data.get("type") == "quote":
        # 推送字段和REST一致:s=标的 ld=最新价 o/h/l=当日开高低 t=毫秒时间戳
        symbol = data.get("s")
        price = data.get("ld")
        ts = time.strftime("%H:%M:%S", time.localtime(data.get("t", 0) / 1000))
        print(f"[{ts}] {symbol}: {price}")


ws = websocket.WebSocketApp(
    WS_URL,
    header=[f"token: {TOKEN}"],
    on_open=on_open,
    on_message=on_message,
)
ws.run_forever()

整个流程梳理一下:建立连接 → 等鉴权 → 发订阅 → 收推送 → 持续心跳。顺序不能乱,否则订阅会失败。

如果你想在美联储决议公布那一刻收到提醒,可以在 on_message 里加一个价格变动阈值判断——比如当EUR/USD在10秒内波动超过30个点就触发告警。这种用WebSocket做的实时监控,比轮询方案的响应速度快一个数量级。

REST查最新报价:补一个快照接口

WebSocket是增量推送,但有些场景你只需要一个当前快照,不想维护长连接。比如你写个定时任务,每小时记录一次主要货币对的最新价,这种就没必要一直挂着WebSocket。REST的报价接口刚好合适:

def get_forex_quote(region, code):
    """获取实时报价快照"""
    resp = requests.get(
        f"{API_BASE}/forex/quote",
        headers=headers,
        params={"region": region, "code": code}
    )
    return resp.json()["data"]


# 一次查多个主要货币对
for pair in ["EURUSD", "GBPUSD", "USDJPY"]:
    q = get_forex_quote("GB", pair)
    # ld=最新价 chp=涨跌幅 ch=涨跌额 o/h/l=当日开高低
    print(f"{pair}: 最新={q['ld']} 涨跌幅={q['chp']}%")

报价接口的返回字段比K线多几个:ch 是涨跌额,chp 是涨跌幅(百分比),ts 是交易状态。这些字段做仪表盘显示够用了。

最近EUR/USD就在1.16附近窄幅波动,ECB加息后欧元不但没涨反而小跌,说明市场当前更关注美联储那边的政策预期。这种"利差变化"类的分析,你可以用报价接口同时拉EURUSD和USDJPY,对比看美元指数的整体强弱。

组合起来:一个轻量行情监控脚本

把REST初始化和WebSocket实时推送结合,就是一个最基本的行情监控器——启动时先拉历史K线作为基线,然后订阅实时报价做增量更新。这个模式在央行决议日特别有用:你可以在决议公布前把脚本挂着,价格一动就能立刻看到。

import json
import threading
import time
import requests
import websocket

API_BASE = "//api.itick.org"
WS_URL = "wss://api.itick.org/forex"
TOKEN = "your_token_here"
headers = {"accept": "application/json", "token": TOKEN}

# 启动时先拉历史日K作为基线
def init_kline(code):
    resp = requests.get(
        f"{API_BASE}/forex/kline",
        headers=headers,
        params={"region": "GB", "code": code, "kType": 8, "limit": 30}
    )
    data = resp.json()["data"]
    recent = [bar["c"] for bar in data[-5:]]
    print(f"{code} 近5日收盘价: {recent}")

# WebSocket部分
authenticated = False
alerts = []  # 可以在这里加自己的告警逻辑

def on_message(ws, message):
    global authenticated
    payload = json.loads(message)
    if payload.get("resAc") == "auth" and payload.get("code") == 1 and not authenticated:
        authenticated = True
        ws.send(json.dumps({
            "ac": "subscribe",
            "params": "EURUSD$GB",
            "types": "quote"
        }))
        return
    data = payload.get("data") or {}
    if data.get("type") == "quote":
        price = data['ld']
        # 这里可以加自己的告警判断,比如价格突破某个区间
        print(f"实时更新 {data['s']}: {price}")

init_kline("EURUSD")
ws = websocket.WebSocketApp(
    WS_URL,
    header=[f"token: {TOKEN}"],
    on_message=on_message,
)
ws.run_forever()

这个脚本虽然简单,但骨架已经完整:REST负责历史数据初始化,WebSocket负责实时增量,两者字段格式统一,切换的时候不需要做数据格式转换。

几个实际踩过的坑

Header字段名是 token,不是 X-API-Key 也不是 Authorization。我第一次接的时候用了 X-API-Key,一直返回鉴权失败,翻了文档才发现。

必须等鉴权成功再订阅。刚连上就发subscribe会被服务端拒绝,错误消息是 cannot be resolved action。用 resAc == "auth" 判断鉴权状态最靠谱。

K线周期是数字编码,不是字符串 "1d""1m"。8是日K,5是1小时,1是1分钟。这个没有默认值,必须传对。

WebSocket心跳不能省。30秒一次ping,超过1分钟不发服务端会主动断开。用一个daemon线程跑心跳就行,别阻塞主消息循环。

返回字段是缩写ld 是最新价,chp 是涨跌幅,v 是量。不要用 last_pricechange_percent 这种直觉字段名,会直接KeyError。

小结

外汇行情接入的技术栈其实不复杂:REST拉历史,WebSocket收实时,统一用token鉴权。关键是把状态机走对——连接、鉴权、订阅、心跳这四个环节顺序不能乱,字段名对照文档用缩写,剩下的就是业务逻辑了。

最近这个阶段,欧洲央行刚加完息,美联储下周要议息,汇率市场的消息密度会持续比较高。有一套顺手的实时数据接入方案,不管是做监控工具还是自己看盘,都能少走很多弯路。

接口的完整文档可以在这里查阅,支持REST、WebSocket和FIX三种接入方式,除了外汇还有股票、指数、期货、基金等产品。

评论