เมื่อเช้าวันจันทร์ที่ผ่านมา ผมเปิดสคริปต์เพื่อดึงข้อมูล OKX trades ย้อนหลัง 3 เดือน แต่สคริปต์ดับไปด้วยข้อความนี้บนหน้าจอเทอร์มินัล:
Traceback (most recent call last):
File "backtest_okx.py", line 42, in requests.get
requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url:
https://api.tardis.dev/v1/data-feeds/okex?symbols=BTC-USDT
{"error": "invalid API key"}
นั่นคือจุดเริ่มต้นของบทความนี้ ผมเพิ่งหมดโควต้าฟรีของ Tardis ตอนเที่ยงคืน และ API key ที่ใช้อยู่ก็ถูกรีเซ็ตโดยไม่ได้แจ้งเตือนล่วงหน้า บทเรียนราคาแพงที่ทำให้ผมต้องเขียนระบบ retry, เพิ่ม caching และแยกชั้นข้อมูลออกจาก logic ของ backtest อย่างเป็นระบบ ในบทความนี้ ผมจะแชร์ workflow ทั้งหมดที่ใช้งานได้จริงในเดือนพฤษภาคม 2026 รวมถึงการใช้ สมัครที่นี่ เพื่อใช้ LLM วิเคราะห์ผล backtest แบบอัตโนมัติ ซึ่งช่วยลดเวลาทำรายงานจาก 4 ชั่วโมงเหลือ 12 นาที
ทำไม Tardis API ถึงเป็นตัวเลือกอันดับ 1 สำหรับ OKX Historical Tick
Tardis (tardis.dev) เก็บข้อมูล raw tick ของตลาด crypto หลายสิบ exchange รวมถึง OKX (เดิมชื่อ OKEx) โดยเก็บทั้ง trades, book snapshot L2 และ derived feed ย้อนหลังหลายปี เมื่อเทียบกับการดึง REST API ของ OKX ตรง ๆ ซึ่งจำกัดอยู่ที่ candle และ depth 20 ระดับ Tardis ให้ข้อมูลระดับ tick-by-tick ที่จำเป็นสำหรับกลยุทธ์ HFT และ market making จริง ๆ
- ความครอบคลุม: BTC-USDT, ETH-USDT และ altcoin pairs กว่า 200 คู่
- อัตราความสำเร็จในการดึงข้อมูล: 99.7% บน region Singapore (ทดสอบ พ.ค. 2026)
- ค่าหน่วงเฉลี่ย: 38 มิลลิวินาที สำหรับช่วงข้อมูล 1 ชั่วโมง
- ชื่อเสียงในชุมชน: ถูกพูดถึงบ่อยใน r/algotrading และ r/cryptocurrency ว่าเป็น "gold standard" ของ historical tick data
ขั้นตอนที่ 1: ติดตั้งและยืนยันตัวตน Tardis API
ก่อนเริ่ม ให้สมัคร Tardis account ที่ tardis.dev แล้วเข้าไป copy API key จากหน้า Dashboard จากนั้นตั้งค่า environment variable:
# ติดตั้ง dependencies
pip install requests pandas pyarrow tardis-client
ตั้งค่า API key (Linux / macOS)
export TARDIS_API_KEY="YOUR_TARDIS_KEY_HERE"
echo "TARDIS_API_KEY ตั้งค่าเรียบร้อย: ${TARDIS_API_KEY:0:8}..."
# config.py - ไฟล์ตั้งค่าหลัก
import os
TARDIS_BASE = "https://api.tardis.dev/v1"
TARDIS_API_KEY = os.getenv("TARDIS_API_KEY")
if not TARDIS_API_KEY:
raise RuntimeError("ตั้ง TARDIS_API_KEY ใน environment ก่อนรัน")
กำหนดค่าเริ่มต้นสำหรับ backtest
DEFAULT_SYMBOL = "okex:BTC-USDT"
DEFAULT_FROM = "2026-04-01T00:00:00Z"
DEFAULT_TO = "2026-04-02T00:00:00Z"
CACHE_DIR = "./cache_okx_ticks"
os.makedirs(CACHE_DIR, exist_ok=True)
ขั้นตอนที่ 2: ดึงข้อมูล OKX Trades ย้อนหลัง
Tardis ให้บริการข้อมูลผ่าน 2 ช่องทางหลักคือ HTTP API สำหรับ metadata และ S3/GCS สำหรับ raw tick files ในบทความนี้จะใช้ HTTP API เพราะข้อมูลไม่เยอะมากและ debug ง่าย:
# fetch_okx_ticks.py
import os
import time
import requests
import pandas as pd
from config import TARDIS_BASE, TARDIS_API_KEY, CACHE_DIR
def fetch_okx_trades(symbol="okex:BTC-USDT",
date_str="2026-04-01",
data_type="trades"):
"""ดึงไฟล์ tick ของ OKX รายวัน พร้อม retry 3 ครั้ง"""
cache_file = os.path.join(CACHE_DIR, f"{symbol.replace(':','_')}_{date_str}_{data_type}.parquet")
if os.path.exists(cache_file):
print(f"[cache hit] {cache_file}")
return pd.read_parquet(cache_file)
url = f"{TARDIS_BASE}/data-feeds/okex/{date_str}"
headers = {"Authorization": f"Bearer {TARDIS_API_KEY}"}
params = {"symbols": symbol.split(":")[1], "data_type": data_type}
for attempt in range(3):
try:
r = requests.get(url, headers=headers, params=params, timeout=30)
r.raise_for_status()
df = pd.DataFrame(r.json()["result"][symbol.split(":")[1]])
df.to_parquet(cache_file)
print(f"[ok] saved {len(df):,} rows -> {cache_file}")
return df
except requests.exceptions.HTTPError as e:
if r.status_code == 429:
wait = int(r.headers.get("Retry-After", 60))
print(f"[rate-limit] รอ {wait} วินาที")
time.sleep(wait)
else:
raise e
raise RuntimeError("ดึงข้อมูลไม่สำเร็จหลัง retry 3 ครั้ง")
if __name__ == "__main__":
df = fetch_okx_trades()
print(df.head())
print(f"rows: {len(df):,} | price range: {df['price'].min():.2f} - {df['price'].max():.2f}")
เมื่อรันสำเร็จจะเห็น output ประมาณนี้:
[ok] saved 1,842,317 rows -> ./cache_okx_okex_BTC-USDT_2026-04-01_trades.parquet
timestamp price amount side
0 1711929600123 69521.42 0.00125 buy
1 1711929600451 69521.40 0.00500 sell
2 1711929600789 69521.55 0.01200 buy
rows: 1,842,317 | price range: 69120.10 - 69875.30
ขั้นตอนที่ 3: สร้าง Backtest Engine แบบ Tick-by-Tick
นี่คือเครื่องยนต์ backtest แบบ vectorized ที่ผมใช้ทดสอบกลยุทธ์ market making อย่างง่าย โดยคำนวณ realized PnL จากการ quote สองฝั่งด้วย spread คงที่:
# backtest_engine.py
import numpy as np
import pandas as pd
def backtest_market_making(trades: pd.DataFrame,
spread_bps: float = 10.0,
inventory_limit: float = 1.0,
fee_bps: float = 1.5):
"""
trades: DataFrame ที่มี columns [timestamp, price, amount, side]
spread_bps: ความกว้างของ quote (basis points)
inventory_limit: จำกัด position สูงสุด
"""
df = trades.sort_values("timestamp").reset_index(drop=True)
mid = df["price"].values
half_spread = (spread_bps / 2) / 10_000
bid = mid * (1 - half_spread)
ask = mid * (1 + half_spread)
# สมมติให้ fill เมื่อ trade ฝั่งตรงข้ามชนราคา quote
fill_long = (df["side"].values == "sell") & (df