ในโลกของ DeFi และการซื้อขายสินทรัพย์ดิจิทัล การทำ Backtest กลยุทธ์การทำตลาด (Market Making) เป็นหัวใจสำคัญในการพัฒนาระบบที่ทำกำไรได้จริง Hyperliquid ในฐานะ L2 blockchain ที่เน้นประสิทธิภาพสูง นำเสนอ Orderbook ระดับ Granular ที่เหมาะอย่างยิ่งสำหรับงาน Backtest ในบทความนี้ เราจะพาคุณเจาะลึกถึงวิธีการดึงข้อมูล L2 Orderbook ของ Hyperliquid และนำไปใช้ในการทำ Backtest กลยุทธ์การทำตลาดด้วยโค้ดระดับ Production พร้อม Benchmark จริง
ทำความรู้จัก Hyperliquid L2 Architecture
Hyperliquid เป็น Layer 2 blockchain ที่สร้างบน Arbitrum Nitro โดยมีจุดเด่นที่ความเร็วในการประมวลผลธุรกรรมและการจัดการ Orderbook แบบ On-chain ทำให้ข้อมูล Orderbook มีความถูกต้องและโปร่งใส สถาปัตยกรรมนี้ประกอบด้วย:
- Orderbook State — เก็บข้อมูลราคา Bid/Ask พร้อม Volume ณ แต่ละระดับราคา
- Trade Events — บันทึกทุกธุรกรรมที่เกิดขึ้นพร้อม Timestamp ความละเอียดระดับ Millisecond
- Funding Rate Updates — ข้อมูลการจ่าย Funding ที่ส่งผลต่อต้นทุนการถือ Position
- Insurance Fund — ข้อมูลสถานะกองทุนประกันความเสี่ยง
การดึงข้อมูล L2 Orderbook History
สำหรับการทำ Backtest ที่มีคุณภาพสูง เราต้องการข้อมูล Orderbook ที่มีความละเอียดมากพอที่จะจำลองสถานการณ์การซื้อขายที่สมจริง โดยเราจะใช้ Hyperliquid Python SDK ร่วมกับ HolySheep AI สำหรับการประมวลผลข้อมูล
import requests
import time
from dataclasses import dataclass
from typing import List, Dict, Optional
import json
การตั้งค่า HolySheep AI API สำหรับการวิเคราะห์ข้อมูล
HOLYSHEEP_API_KEY = "YOUR_HOLYSHEEP_API_KEY"
HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1"
@dataclass
class OrderbookLevel:
"""โครงสร้างข้อมูลระดับราคาใน Orderbook"""
price: float
size: float
side: str # 'bid' หรือ 'ask'
timestamp: int
@dataclass
class Trade:
"""โครงสร้างข้อมูลธุรกรรมการซื้อขาย"""
trade_id: str
price: float
size: float
side: str
timestamp: int
fee: float
class HyperliquidHistoryClient:
"""Client สำหรับดึงข้อมูลประวัติ Orderbook จาก Hyperliquid"""
def __init__(self, base_url: str = "https://api.hyperliquid.xyz/info"):
self.base_url = base_url
self.session = requests.Session()
self.session.headers.update({
'Content-Type': 'application/json',
'User-Agent': 'HyperliquidBacktest/1.0'
})
def _make_request(self, method: str, params: dict) -> dict:
"""ส่ง request ไปยัง Hyperliquid Info API"""
payload = {
"type": method,
"payload": params
}
for attempt in range(3):
try:
response = self.session.post(self.base_url, json=payload, timeout=30)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
if attempt < 2:
time.sleep(2 ** attempt)
else:
raise RuntimeError(f"Request failed after 3 attempts: {e}")
def get_orderbook_snapshot(self, coin: str) -> Dict[str, List[OrderbookLevel]]:
"""
ดึง Orderbook snapshot ณ ปัจจุบัน
Args:
coin: ชื่อเหรียญ เช่น 'BTC', 'ETH'
Returns:
Dict ที่มี 'bids' และ 'asks' เป็น List ของ OrderbookLevel
"""
data = self._make_request("getL2Book", {"coin": coin})
bids = [
OrderbookLevel(
price=float(level[0]),
size=float(level[1]),
side='bid',
timestamp=int(time.time() * 1000)
)
for level in data.get('l2Book', [])[0].get('bids', [])
]
asks = [
OrderbookLevel(
price=float(level[0]),
size=float(level[1]),
side='ask',
timestamp=int(time.time() * 1000)
)
for level in data.get('l2Book', [])[0].get('asks', [])
]
return {'bids': bids, 'asks': asks}
def get_trades_history(self, coin: str, start_time: int, end_time: int) -> List[Trade]:
"""
ดึงประวัติธุรกรรมในช่วงเวลาที่กำหนด
Args:
coin: ชื่อเหรียญ
start_time: Unix timestamp ในหน่วย Milliseconds
end_time: Unix timestamp ในหน่วย Milliseconds
Returns:
List ของ Trade objects
"""
all_trades = []
current_start = start_time
while current_start < end_time:
data = self._make_request("getTradeHistory", {
"coin": coin,
"startTime": current_start,
"endTime": end_time
})
trades = [
Trade(
trade_id=t.get('hash', ''),
price=float(t['px']),
size=float(t['sz']),
side=t['side'],
timestamp=int(t['time']),
fee=float(t.get('fee', 0))
)
for t in data
]
all_trades.extend(trades)
if len(trades) < 100:
break
current_start = trades[-1].timestamp + 1
time.sleep(0.1) # Rate limiting
return all_trades
def analyze_with_holysheep(orderbook_data: dict) -> dict:
"""
ใช้ HolySheep AI ในการวิเคราะห์ Orderbook Pattern
ราคาประหยัดมาก: GPT-4.1 $8/MTok, Claude Sonnet 4.5 $15/MTok
Gemini 2.5 Flash เพียง $2.50/MTok - เหมาะสำหรับการประมวลผลข้อมูลจำนวนมาก
"""
payload = {
"model": "gpt-4.1",
"messages": [
{
"role": "system",
"content": """คุณเป็นผู้เชี่ยวชาญด้าน Market Making วิเคราะห์ Orderbook data ต่อไปนี้
และระบุ:
1. Spread ที่เหมาะสม
2. ระดับข้อมูลที่ขาดหาย (Market Depth)
3. คำแนะนำสำหรับ Bid/Ask Sizing"""
},
{
"role": "user",
"content": f"Orderbook Data:\n{json.dumps(orderbook_data, indent=2)}"
}
],
"temperature": 0.3
}
response = requests.post(
f"{HOLYSHEEP_BASE_URL}/chat/completions",
headers={
"Authorization": f"Bearer {HOLYSHEEP_API_KEY}",
"Content-Type": "application/json"
},
json=payload,
timeout=60
)
return response.json()
ตัวอย่างการใช้งาน
if __name__ == "__main__":
client = HyperliquidHistoryClient()
# ดึง Orderbook snapshot
btc_orderbook = client.get_orderbook_snapshot("BTC")
print(f"BTC Best Bid: {btc_orderbook['bids'][0].price}")
print(f"BTC Best Ask: {btc_orderbook['asks'][0].price}")
print(f"Spread: {(btc_orderbook['asks'][0].price - btc_orderbook['bids'][0].price):.2f}")
# ดึงประวัติการซื้อขาย 7 วัน
end_time = int(time.time() * 1000)
start_time = end_time - (7 * 24 * 60 * 60 * 1000)
trades = client.get_trades_history("BTC", start_time, end_time)
print(f"Total trades fetched: {len(trades)}")
ระบบ Backtest Engine สำหรับ Market Making Strategy
หลังจากได้ข้อมูล Orderbook History แล้ว ขั้นตอนต่อไปคือการสร้าง Backtest Engine ที่จำลองการทำตลาดอย่างแม่นยำ ระบบนี้ต้องรองรับการจัดการ Position, PnL Calculation, และการคำนวณค่า Fee อย่างครบถ้วน
import numpy as np
from datetime import datetime, timedelta
from collections import deque
from dataclasses import dataclass, field
from typing import Deque, Dict, List, Tuple
import asyncio
from concurrent.futures import ThreadPoolExecutor
import statistics
@dataclass
class Position:
"""โครงสร้างข้อมูล Position ของ Market Maker"""
size: float = 0.0
entry_price: float = 0.0
unrealized_pnl: float = 0.0
realized_pnl: float = 0.0
fees_paid: float = 0.0
def update_pnl(self, current_price: float):
if self.size != 0:
self.unrealized_pnl = self.size * (current_price - self.entry_price)
@dataclass
class Order:
"""โครงสร้างข้อมูล Order ที่รอการจับคู่"""
order_id: str
side: str # 'buy' หรือ 'sell'
price: float
size: float
filled_size: float = 0.0
timestamp: int = 0
fee_rate: float = 0.00035 # Hyperliquid Maker Fee
@property
def is_fully_filled(self) -> bool:
return abs(self.filled_size - self.size) < 1e-9
class MarketMakingBacktest:
"""
Backtest Engine สำหรับกลยุทธ์ Market Making
รองรับการประมวลผลแบบ Vectorized สำหรับประสิทธิภาพสูง
"""
def __init__(
self,
initial_balance: float = 100_000.0,
maker_fee: float = 0.00035,
taker_fee: float = 0.0006,
funding_rate: float = 0.0001 # รายชั่วโมง
):
self.initial_balance = initial_balance
self.balance = initial_balance
self.maker_fee = maker_fee
self.taker_fee = taker_fee
self.funding_rate = funding_rate
self.position = Position()
# ประวัติผลลัพธ์
self.trade_history: List[dict] = []
self.equity_curve: List[float] = []
self.order_history: List[Order] = []
# สถิติ
self.stats = {
'total_trades': 0,
'winning_trades': 0,
'losing_trades': 0,
'total_fees': 0.0,
'max_drawdown': 0.0,
'sharpe_ratio': 0.0
}
def calculate_spread(
self,
mid_price: float,
volatility: float,
inventory_bias: float = 0.0
) -> Tuple[float, float]:
"""
คำนวณ Bid/Ask Price ตามกลยุทธ์ Market Making
Args:
mid_price: ราคากลางปัจจุบัน
volatility: ความผันผวนของราคา
inventory_bias: ความเอนเอียงของ Inventory (-1 ถึง 1)
Returns:
(bid_price, ask_price)
"""
# Base spread ตามความผันผวน
base_spread = volatility * 0.5
# ปรับ spread ตาม inventory bias
# ถ้า inventory > 0 (long) ขยาย spread ฝั่ง sell เพื่อระบายสินค้า
if inventory_bias > 0:
bid_spread = base_spread * (1 + inventory_bias * 0.5)
ask_spread = base_spread * (1 - inventory_bias * 0.3)
else:
bid_spread = base_spread * (1 - inventory_bias * 0.3)
ask_spread = base_spread * (1 + abs(inventory_bias) * 0.5)
return (
mid_price * (1 - bid_spread),
mid_price * (1 + ask_spread)
)
def simulate_fill(
self,
order: Order,
trade_price: float,
trade_size: float,
timestamp: int
) -> dict:
"""
จำลองการ Fill Order
Returns:
dict ที่มีข้อมูลการ Fill
"""
fill_size = min(order.size - order.filled_size, trade_size)
fill_value = fill_size * trade_price
fee = fill_value * self.maker_fee
# อัพเดท Position
if order.side == 'buy':
# ซื้อ = เพิ่ม Long Position
if self.position.size == 0:
self.position.entry_price = trade_price
self.position.size = fill_size
else:
# คำนวณ average price
total_value = self.position.size * self.position.entry_price + fill_value
self.position.size += fill_size
self.position.entry_price = total_value / self.position.size
else:
# ขาย = ลด Long Position หรือเพิ่ม Short Position
if self.position.size >= fill_size:
# ปิด Long Position บางส่วน
pnl = fill_size * (trade_price - self.position.entry_price)
self.position.realized_pnl += pnl
self.position.size -= fill_size
if self.position.size == 0:
self.position.entry_price = 0.0
else:
# เปิด Short Position
self.position.size -= fill_size
if self.position.size < 0:
# คำนวณ average short entry
pnl = (self.position.size + fill_size) * (
self.position.entry_price - trade_price
)
self.position.realized_pnl += pnl
self.position.size = fill_size
self.position.entry_price = trade_price
# หักค่าธรรมเนียม
self.balance -= fee
self.position.fees_paid += fee
self.stats['total_fees'] += fee
# อัพเดท Order
order.filled_size += fill_size
return {
'timestamp': timestamp,
'order_id': order.order_id,
'side': order.side,
'price': trade_price,
'size': fill_size,
'fee': fee,
'balance': self.balance,
'position_size': self.position.size
}
def run_backtest(
self,
orderbook_data: List[dict],
trades_data: List[dict],
params: dict
) -> dict:
"""
รัน Backtest บนข้อมูล History
Args:
orderbook_data: ข้อมูล Orderbook ตามเวลา
trades_data: ข้อมูลธุรกรรม
params: พารามิเตอร์กลยุทธ์
"""
window_volatility = deque(maxlen=20)
for i, ob_snapshot in enumerate(orderbook_data):
mid_price = ob_snapshot['mid_price']
timestamp = ob_snapshot['timestamp']
# คำนวณ Volatility
if len(window_volatility) > 1:
returns = np.diff(list(window_volatility)) / list(window_volatility)[:-1]
volatility = float(np.std(returns)) if len(returns) > 0 else 0.001
else:
volatility = 0.001
window_volatility.append(mid_price)
# คำนวณ Inventory Bias
if self.balance + self.position.size * mid_price > 0:
total_equity = self.balance + self.position.size * mid_price
inventory_value = self.position.size * mid_price
inventory_bias = inventory_value / total_equity
else:
inventory_bias = 0.0
# คำนวณ Spread
bid_price, ask_price = self.calculate_spread(
mid_price, volatility, inventory_bias
)
# วาง Orders
bid_order = Order(
order_id=f"backtest_bid_{i}",
side='buy',
price=bid_price,
size=params['order_size'],
timestamp=timestamp
)
ask_order = Order(
order_id=f"backtest_ask_{i}",
side='sell',
price=ask_price,
size=params['order_size'],
timestamp=timestamp
)
self.order_history.extend([bid_order, ask_order])
# จำลองการ Fill จาก Trades
relevant_trades = [
t for t in trades_data
if t['timestamp'] == timestamp
]
for trade in relevant_trades:
trade_price = trade['price']
trade_size = trade['size']
# ตรวจสอบการ Fill ของ Bid Order
if bid_price >= trade_price and bid_order.size > bid_order.filled_size:
fill = self.simulate_fill(bid_order, trade_price, trade_size, timestamp)
self.trade_history.append(fill)
# ตรวจสอบการ Fill ของ Ask Order
if ask_price <= trade_price and ask_order.size > ask_order.filled_size:
fill = self.simulate_fill(ask_order, trade_price, trade_size, timestamp)
self.trade_history.append(fill)
# คำนวณ Unrealized PnL
self.position.update_pnl(mid_price)
# อัพเดท Equity Curve
total_equity = (
self.balance +
self.position.size * mid_price +
self.position.realized_pnl
)
self.equity_curve.append(total_equity)
# คำนวณสถิติสุดท้าย
return self._calculate_final_stats()
def _calculate_final_stats(self) -> dict:
"""คำนวณสถิติผลลัพธ์สุดท้าย"""
equity = np.array(self.equity_curve)
returns = np.diff(equity) / equity[:-1]
total_pnl = self.equity_curve[-1] - self.initial_balance
total_return = total_pnl / self.initial_balance * 100
# Max Drawdown
running_max = np.maximum.accumulate(equity)
drawdowns = (equity - running_max) / running_max
max_drawdown = float(np.min(drawdowns) * 100)
# Sharpe Ratio (annualized, assuming 24/7 markets)
if len(returns) > 1 and np.std(returns) > 0:
sharpe = (np.mean(returns) / np.std(returns)) * np.sqrt(24 * 365)
else:
sharpe = 0.0
# Win Rate
realized = [t['price'] * t['size'] for t in self.trade_history if t['side'] == 'sell']
winning = sum(1 for pnl in self.trade_history if pnl.get('pnl', 0) > 0)
total_trades = len(self.trade_history)
return {
'total_pnl': total_pnl,
'total_return_pct': total_return,
'final_equity': self.equity_curve[-1],
'max_drawdown_pct': max_drawdown,
'sharpe_ratio': sharpe,
'total_trades': total_trades,
'win_rate': winning / total_trades if total_trades > 0 else 0,
'total_fees': self.stats['total_fees'],
'equity_curve': self.equity_curve
}
Benchmark: ทดสอบประสิทธิภาพ
if __name__ == "__main__":
import time
# สร้างข้อมูล Dummy สำหรับทดสอบ
np.random.seed(42)
n_periods = 10000
orderbook_data = []
trades_data = []
price = 50_000.0
for i in range(n_periods):
price *= (1 + np.random.normal(0, 0.001))
orderbook_data.append({
'mid_price': price,
'timestamp': i * 1000
})
if np.random.random() > 0.5:
trades_data.append({
'price': price * (1 + np.random.uniform(-0.0001, 0.0001)),
'size': np.random.uniform(0.1, 1.0),
'timestamp': i * 1000
})
# รัน Backtest
backtest = MarketMakingBacktest(
initial_balance=100_000.0,
maker_fee=0.00035
)
start_time = time.perf_counter()
result = backtest.run_backtest(
orderbook_data,
trades_data,
params={'order_size': 0.5}
)
elapsed = time.perf_counter() - start_time
print("=" * 50)
print("BENCHMARK RESULTS")
print("=" * 50)
print(f"Periods processed: {n_periods:,}")
print(f"Execution time: {elapsed:.4f} seconds")
print(f"Throughput: {n_periods/elapsed:,.0f} periods/sec")
print("-" * 50)
print(f"Total PnL: ${result['total_pnl']:,.2f}")
print(f"Return: {result['total_return_pct']:.2f}%")
print(f"Sharpe Ratio: {result['sharpe_ratio']:.3f}")
print(f"Max Drawdown: {result['max_drawdown_pct']:.2f}%")
print(f"Total Trades: {result['total_trades']:,}")
print(f"Win Rate: {result['win_rate']*100:.1f}%")
print(f"Total Fees: ${result['total_fees']:.2f}")
การเพิ่มประสิทธิภาพด้วย Async Processing และ Caching
สำหรับการทำ Backtest บนข้อมูลจำนวนมาก ประสิทธิภาพในการประมวลผลเป็นสิ่งสำคัญมาก เราจะใช้เทคนิค Async Processing ร่วมกับ In-Memory Caching เพื่อเร่งความเร็ว
import asyncio
import aiohttp
from functools import lru_cache
from typing import Optional
import hashlib
import pickle
from pathlib import Path
class AsyncHyperliquidFetcher:
"""
Async Client สำหรับดึงข้อมูล Hyperliquid
พร้อมระบบ Caching แบบ Multi-Level
"""
def __init__(
self,
cache_dir: str = "./cache",
max_cache_size_mb: int = 500,
rate_limit: int = 10 # requests per second
):
self.cache_dir = Path(cache_dir)
self.cache_dir.mkdir(parents=True, exist_ok=True)
self.max_cache_size = max_cache_size_mb * 1024 * 1024
self._semaphore = asyncio.Semaphore(rate_limit)
self._session: Optional[aiohttp.ClientSession] = None
self._memory_cache: dict = {}
self._cache_hits = 0
self._cache_misses = 0
async def _get_session(self) -> aiohttp.ClientSession:
"""Lazy initialization ของ aiohttp Session"""
if self._session is None or self._session.closed:
timeout = aiohttp.ClientTimeout(total=60, connect=10)
connector = aiohttp.TCPConnector(
limit=100,
limit_per_host=30,
enable_cleanup_closed=True
)
self._session = aiohttp.ClientSession(
timeout=timeout,
connector=connector
)
return self._session
def _get_cache_key(self, endpoint: str, params: dict) -> str:
"""สร้าง Cache Key จาก Endpoint และ Parameters"""
content = f"{endpoint}:{sorted(params.items())}"
return hashlib.sha256(content.encode()).hexdigest()
def _get_cache_path(self, cache_key: str) -> Path:
"""กำหนด Path สำหรับ Disk Cache"""
return self.cache_dir / f"{cache_key}.pkl"
def _check_disk_cache(self, cache_key: str) -> Optional[any]:
"""ตรวจสอบ Disk Cache"""
cache_path = self._get_cache_path(cache_key)
if cache_path.exists():
try:
with open(cache_path, 'rb') as f:
return pickle.load(f)
except Exception:
return None
return None
def _write_disk_cache(self, cache_key: str, data: any):
"""เขียนข้อมูลลง Disk Cache"""
cache_path = self._get_cache_path(cache_key)
try:
with open(cache_path, 'wb') as f:
pickle.dump(data, f)
except Exception:
pass
async def fetch_orderbook_batch(
self,
coins: List[str],
semaphore: Optional[asyncio.Semaphore] = None
) -> Dict[str, dict]:
"""
ดึง Orderbook ของหลายเหรียญพร้อมกัน
Args:
coins: List ของชื่อเหรียญ
semaphore: Semaphore สำหรับควบคุม concurrency
Returns:
Dict แมประหว่างชื่อเหรียญกับข้อมูล Orderbook
"""
sem = semaphore or self._semaphore
async def fetch_single(coin: str) -> Tuple[str, dict]:
cache_key = self._get_cache_key("orderbook", {"coin": coin})
# ตรวจสอบ Memory Cache
if cache_key in self._memory_cache:
self._cache_hits += 1
return coin, self._memory_cache[cache_key]
# ตรวจสอบ Disk Cache
disk_data = self._check_disk_cache(cache_key)
if disk_data