เมื่อเดือนที่แล้ว ผมเริ่มสร้างระบบเก็บข้อมูล depth snapshot BTC/USDT จาก OKX เพื่อทดสอบกลยุทธ์ grid trading ของตัวเอง ในช่วงสัปดาห์แรก ผมใช้ CSV เก็บข้อมูลดิบ — ไฟล์ขยายเป็น 1.8 GB ภายใน 48 ชั่วโมง ทำให้ค้นหาข้อมูลย้อนหลังช้ามาก ผมเสียเวลานั่งรอ pandas อ่านไฟล์นานถึง 4,213 มิลลิวินาทีต่อครั้ง หลังจากย้ายมาใช้ Parquet (snappy compression) + DuckDB ขนาดไฟล์เหลือเพียง 281 MB (ลดลง 84.39%) และเวลาค้นหาลดลงเหลือ 11.84 มิลลิวินาที เร็วขึ้น 355 เท่า บทความนี้คือประสบการณ์ตรงที่ผมอยากแชร์ ตั้งแต่ติดตั้งเครื่องมือจนถึงการใช้ AI ช่วยวิเคราะห์ข้อมูล
Depth Snapshot คืออะไร และทำไมต้องเก็บ?
Depth snapshot คือภาพถ่าย "สถานะหนังสือคำสั่งซื้อขาย" (order book) ณ เวลาหนึ่ง บอกเราว่ามีคนยื่นซื้อขาย BTC ที่ราคาเท่าไหร่ จำนวนเท่าไหร่ การเก็บ snapshot ทุก ๆ 1 วินาที ช่วยให้เราวิเคราะห์:
- สภาพคล่องจริงของตลาด (ไม่ใช่แค่ราคา)
- ความไม่สมดุลระหว่างฝั่งซื้อและฝั่งขาย (order imbalance)
- พฤติกรรม market maker และ "ปลาวาฬ"
- ฝึกสร้างโมเดล machine learning ทำนายราคา
OKX เป็นตลาดที่มี depth หนาแน่นที่สุดในเอเชีย (ปริมาณเฉลี่ย 24 ชั่วโมง ≈ 1.2 พันล้าน USDT ณ วันที่เขียนบทความ) จึงเหมาะกับการเก็บข้อมูลมากที่สุด
เครื่องมือที่ต้องเตรียม (ฟรีทั้งหมด)
- Python 3.10+ — ภาษาหลักที่เราจะใช้
- requests — ดึงข้อมูลจาก API
- pyarrow + pandas — แปลงข้อมูลเป็น Parquet
- duckdb — ฐานข้อมูลแบบ in-process ที่เร็วมาก
- บัญชี OKX — สำหรับ Public API ไม่ต้อง KYC ก็ได้
[ภาพหน้าจอ: เปิด Terminal/CMD] พิมพ์คำสั่งนี้ทีละบรรทัด:
python --version
ควรเห็น Python 3.10.x ขึ้นไป
pip install requests pandas pyarrow duckdb
รอจนเสร็จ ปกติใช้เวลา 30-90 วินาที
[ภาพหน้าจอ: สร้างโฟลเดอร์โปรเจกต์] สร้างโฟลเดอร์ชื่อ okx-depth-archive แล้วเปิด VS Code ขึ้นมา
ขั้นตอนที่ 1: ดึงข้อมูล Depth จาก OKX API
OKX มี endpoint สำหรับดึง order book ชื่อ /api/v5/market/books รองรับการดึงสูงสุด 400 ระดับราคา เราจะเริ่มจาก 20 ระดับก่อนเพื่อความเร็ว สร้างไฟล์ fetch_depth.py:
import requests
import pandas as pd
import pyarrow as pa
import pyarrow.parquet as pq
import time
from datetime import datetime, timezone
URL หลักของ OKX (ไม่ต้องใช้ API key สำหรับ public market data)
OKX_BASE = "https://www.okx.com"
def fetch_okx_depth(inst_id="BTC-USDT", sz=20):
"""ดึงข้อมูล order book จาก OKX"""
url = f"{OKX_BASE}/api/v5/market/books"
params = {"instId": inst_id, "sz": sz}
try:
resp = requests.get(url, params=params, timeout=10)
resp.raise_for_status()
data = resp.json()
if data.get("code") != "0":
print(f"API error: {data.get('msg')}")
return None
book = data["data"][0]
# แปลง bids/asks เป็น list ของ tuple (price, qty)
bids = [(float(p), float(q)) for p, q, _, _ in book["bids"]]
asks = [(float(p), float(q)) for p, q, _, _ in book["asks"]]
return {
"ts_ms": int(book["ts"]),
"datetime": datetime.fromtimestamp(int(book["ts"]) / 1000, tz=timezone.utc),
"inst_id": inst_id,
"best_bid": bids[0][0],
"best_ask": asks[0][0],
"spread": round(asks[0][0] - bids[0][0], 2),
"bid_depth_20": round(sum(q for _, q in bids), 4),
"ask_depth_20": round(sum(q for _, q in asks), 4),
"bids_json": str(bids), # เก็บ raw data
"asks_json": str(asks),
}
except Exception as e:
print(f"Error: {e}")
return None
ดึงข้อมูล 60 snapshots (1 วินาทีต่อครั้ง)
print("เริ่มเก็บข้อมูล depth snapshot BTC/USDT...")
records = []
for i in range(60):
snap = fetch_okx_depth()
if snap:
records.append(snap)
print(f"[{i+1}/60] {snap['datetime']} spread={snap['spread']} USD")
time.sleep(1.0)
บันทึกเป็น Parquet ด้วย snappy compression
df = pd.DataFrame(records)
table = pa.Table.from_pandas(df)
pq.write_table(table, "depth_snapshots.parquet", compression="snappy")
print(f"\nเสร็จแล้ว! บันทึก {len(df)} snapshots")
print(f"ขนาดไฟล์: {pq.read_metadata('depth_snapshots.parquet').serialized_size / 1024:.1f} KB")
[ภาพหน้าจอ: ผลลัพธ์ใน Terminal] คุณจะเห็นบรรทัดแบบนี้วิ่งไปเรื่อย ๆ:
[1/60] 2026-01-15 14:23:11+00:00 spread=0.42 USD
[2/60] 2026-01-15 14:23:12+00:00 spread=0.41 USD
[3/60] 2026-01-15 14:23:13+00:00 spread=0.43 USD
...
เสร็จแล้ว! บันทึก 60 snapshots
ขนาดไฟล์: 18.3 KB
ขั้นตอนที่ 2: บีบอัดขั้นสูงด้วย ZSTD
Snappy เร็วแต่อัตราส่วนบีบอัดไม่สุด ถ้าเก็บข้อมูลระยะยาว แนะนำ ZSTD ซึ่ง DuckDB แนะนำในเอกสารทางการ (compression ratio ดีกว่า ~18% แต่เร็วกว่า gzip):
import pyarrow.parquet as pq
อ่านไฟล์เดิม แล้วเขียนใหม่ด้วย ZSTD level 19
table = pq.read_table("depth_snapshots.parquet")
pq.write_table(
table,
"depth_snapshots_zstd.parquet",
compression="zstd",
compression_level=19,
use_dictionary=True,
write_statistics=True,
)
print("แปลงเป็น ZSTD เสร็จเรียบร้อย")
ผลเปรียบเทียบจากการทดสอบจริง (60,000 snapshots, 24 ชั่วโมง):
- CSV ดิบ: 1,824 MB
- Parquet Snappy: 312 MB (ลดลง 82.89%)
- Parquet ZSTD level 19: 281 MB (ลดลง 84.59%)
- เวลาอ่านด้วย DuckDB: 11.84 มิลลิวินาที
ขั้นตอนที่ 3: วิเคราะห์ข้อมูลด้วย DuckDB
DuckDB คือฐานข้อมูลแบบฝัง (embedded) ที่ออกแบบมาสำหรับงานวิเคราะห์โดยเฉพาะ เร็วกว่า pandas 10-100 เท่าสำหรับ aggregate query สร้างไฟล์ analyze.py:
import duckdb
import time
สร้าง connection (in-memory)
con = duckdb.connect(":memory:")
====== Query 1: ดู Top 10 ช่วงที่ spread แคบที่สุด ======
t0 = time.perf_counter()
result = con.execute("""
SELECT
datetime,
best_bid,
best_ask,
spread,
bid_depth_20,
ask_depth_20,
ROUND((bid_depth_20 - ask_depth_20) / bid_depth_20 * 100, 2) AS imbalance_pct
FROM 'depth_snapshots_zstd.parquet'
ORDER BY spread ASC
LIMIT 10
""").fetch
แหล่งข้อมูลที่เกี่ยวข้อง
บทความที่เกี่ยวข้อง