เมื่อเช้าวันจันทร์ที่ผ่านมา ผมเปิดสคริปต์เพื่อดึงข้อมูล 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 จริง ๆ

ขั้นตอนที่ 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