外匯行情 API、股票、貴金屬、加密貨幣多源接入:該怎樣處理異質行情數據?
一、跨市場行情整合常見痛點
過去開發一套多資產監控原型系統時,曾經同時接入三家外部數據源,分別負責外匯與貴金屬、加密貨幣、股票行情。各家服務的介面各自獨立,驗證規則、商品代碼、時間戳、回傳欄位互不相容,大量開發時間耗費在數據轉換與適配,反而壓縮了核心業務邏輯的開發時程。主要問題可以歸納為四點:
1. 商品代碼體系不一致
同一個交易標的,不同服務商的編寫格式會有差異。例如歐元兌美元會寫成 EURUSD 或是 EUR/USD;加密貨幣交易對有 BTC‑USDT、BTCUSDT 兩種寫法;股票代碼還需要加上交易所後綴。直接沿用另一個數據源的代碼查詢,往往會出現查無資料、數據缺失的狀況。
2. 時間戳與欄位資料型態異質
時間戳有秒、毫秒、時間字串等多種形式;價格、成交量欄位,部分介面回傳數值,部分則回傳字串。若沒有統一轉換型態,在時間序列統計、指標運算時很容易拋出例外,影響系統穩定性與數據可信度。
3. 推送機制、心跳與存取限制各不相同
部分 API 支援 WebSocket 長連線即時推送,部分僅能透過 HTTP 輪詢取得資料。各家的心跳保活、斷線重連、存取頻率限制規則皆為自訂,難以撰寫一套通用的客戶端,維護多個數據來源的成本會隨著市場數量上升。
4. 各市場交易時間模型存在差異
加密貨幣為全年無休 24 小時交易;外匯於週末休市;股票則有開盤、午休、例假日休市的規則。如果直接使用固定逾時時間做故障判斷,休市期間會產生大量無效告警,無法分辨是市場正常休市,還是數據來源真的發生故障,干擾系統監控作業。
二、多資產行情 API 的評估維度
選型的核心目標,是降低上層業務的適配負擔,減少異質數據源帶來的數據不一致風險,建議從以下四個面向進行評估:
驗證機制:不同資產類別是否可以使用同一組 Token 完成身份驗證,減少多組憑證的管理負擔。
協定一致性:不同商品的訂閱請求、推送回傳欄位的命名是否穩定、具備共通性。
文件完備度:是否提供完整可查閱的商品代碼清單與欄位說明。
執行約束說明:明確標示存取配額、限流規範,文件也需說明心跳、斷線復原的處理邏輯。
必須客觀理解:世上不存在欄位 100% 統一的第三方 API。就算是同一個服務商,不同資產也可能分屬不同接入端點,依然會存在細部欄位差異。工程上務實的做法,優先挑選協定標準化程度高的服務,再於應用層建構適配器,消化剩餘的格式差異。
在原型驗證階段,可以使用 AllTick API 進行測試。外匯、貴金屬、加密貨幣共用同一組 WebSocket 接入端點,美股、港股、A 股市場則使用另一組端點,兩套連線的訂閱協定維持一致,能夠有效減少適配層的程式開發量。
三、架構設計:透過內部 Quote 模型隔離外部數據來源差異
設計價值:把外部介面的變化封閉在適配層,確保量化運算、回測、上層業務邏輯不會因為第三方介面調整而受到影響。
無論外部 API 回傳的格式如何,在應用內部定義專屬的行情實體 Quote,只保留業務真正需要的核心欄位:數據來源標示、商品代碼、價格、成交量、毫秒等級時間戳。
所有外部原始行情數據流入系統之後,第一步就執行正規化轉換,映射為統一的Quote物件。上層模組,包含策略運算、指標統計、回測分析、系統監控,全部使用這套內部模型,完全不感知第三方原始 JSON 結構。
這個架構可以帶來兩項實質工程效益:
切換底層數據來源時,只需要修改數據轉換函式,上層業務、回測邏輯不需要更動;
新增某一個交易市場,僅補充數據來源設定,不需要大規模重構現有程式。
過去的專案便是依靠這套抽象設計,新增港股、美股行情的接入,僅耗費半天就完成測試上線。開發上建議一條原則:業務與回測邏輯,不要直接依賴第三方 API 的原始回傳結構。
四、原型實作:單一程序多路 WebSocket 行情接收
下方提供可除錯的原型程式碼,同一支程序維護多條獨立 WebSocket 連線,各市場的連線互相隔離,單一連線發生故障,不會影響其他數據流。適合前期技術驗證,若要上線生產環境,還需要加強容錯、資料持久化相關能力。
import asyncio
import json
import os
import uuid
from dataclasses import dataclass
import websockets
BASE = "wss://quote.alltick.co"
FEEDS = {
"multi": { # 外匯、貴金屬、加密貨幣
"path": "/quote-b-ws-api",
"codes": ["EURUSD", "GOLD", "BTCUSDT"],
},
"stock": { # 美股、港股、A股
"path": "/quote-stock-b-ws-api",
"codes": ["AAPL.US", "700.HK", "600519.SH"],
},
}
heartbeat = {"cmd_id": 22000, "seq_id": 1, "trace": "heartbeat", "data": {}}
@dataclass
class Quote:
feed: str
code: str
price: float
volume: float
ts_ms: int
def normalize(feed, tick):
"""將外部原始tick資料正規化為內部Quote模型,供業務、回測模組呼叫"""
return Quote(
feed=feed,
code=tick["code"],
price=float(tick["price"]),
volume=float(tick["volume"]),
ts_ms=int(tick["tick_time"]),
)
async def run_feed(name, cfg, queue):
token = "填入你申請取得的AllTick_token"
uri = f"{BASE}{cfg['path']}?token={token}"
subscribe = {
"cmd_id": 22004,
"seq_id": 1,
"trace": str(uuid.uuid4()),
"data": {"symbol_list": [{"code": c} for c in cfg["codes"]]},
}
while True: # 斷線自動重新連線
try:
async with websockets.connect(uri) as ws:
await ws.send(json.dumps(subscribe))
async def beat():
while True:
await asyncio.sleep(10)
await ws.send(json.dumps(heartbeat))
task = asyncio.create_task(beat())
try:
async for raw in ws:
msg = json.loads(raw)
if msg.get("cmd_id") == 22998:
await queue.put(normalize(name, msg["data"]))
finally:
task.cancel()
except (websockets.ConnectionClosed, OSError):
await asyncio.sleep(3)
async def consumer(queue):
while True:
q = await queue.get()
# 可在此擴充:寫入資料庫、推送業務模組、回測資料前置處理
print(q.feed, q.code, q.price, q.ts_ms)
async def main():
queue = asyncio.Queue()
await asyncio.gather(
consumer(queue),
*[run_feed(n, c, queue) for n, c in FEEDS.items()],
)
asyncio.run(main())開發注意事項
各市場使用獨立 WebSocket 連線,分別處理訂閱、心跳保活、斷線重試;解析完畢的資料傳送到共用非同步佇列統一消費。
同一個 WebSocket 工作階段當中,重複發送訂閱指令會覆蓋原本的訂閱清單。動態新增商品時,需要把完整的商品清單重新送出。
接入新的數據來源,務必列印原始封包確認時間戳單位,時間單位錯誤,會直接造成時間序列、回測結果全部錯亂。
五、完成數據正規化之後仍要留意的工程細節
就算完成內部模型轉換,不等於整條數據鏈路已經完備,有幾點會直接影響數據可靠度,開發階段必須處理:
依不同市場設計故障告警邏輯
不要使用全域單一的逾時門檻。加密貨幣的監控逾時可以設定較短;外匯、股票則要搭配交易日曆判斷。外匯週末沒有行情屬於正常休市,不應該觸發故障告警。
留意浮點數運算的精度問題
外匯報價普遍是五位小數,A 股兩位小數,加密貨幣最高可到八位小數。牽涉金額、淨值計算場景,建議使用Decimal型別運算,避免浮點數累積誤差,導致業務計算、回測結果出現偏移。
歷史 K 線資料同樣需要執行正規化
即時行情透過 WebSocket 取得,歷史 K 線大多呼叫 REST 介面,兩套介面回傳結構往往不一樣。取得歷史資料之後,同樣轉換成Quote內部模型再寫入資料庫。讓歷史回測、即時業務可以使用同一套資料物件,縮小實盤與回測之間的邏輯落差。
六、結語
多資產行情整合並沒有捷徑。選用數據來源時,優先挑選協定規範、文件完善的服務;架構層面上透過適配層隔離第三方介面的異質差異,保障上層業務、量化回測模組的穩定性。
開發多資產相關系統的時候,優先定義統一的內部行情模型,能夠有效降低後續迭代、切換數據來源的改造成本,提升整條數據鏈路的可維護性。在原型開發階段,也可以參考如 AllTick API 這類協定一致性較佳的行情服務,減少適配層的開發負荷。
免責聲明:本文為技術實務分享,範例程式僅適用原型驗證。部署至生產系統,必須補齊例外捕捉、數據校驗、持久化儲存、監控告警、權限安全等機制。