สวัสดีครับ ผมเป็นนักพัฒนาระบบเทรดอัตโนมัติมากว่า 5 ปี วันนี้จะมาแชร์ประสบการณ์การดึงข้อมูล กราฟเทียน (Candlestick/K-line) จาก Binance API และนำไปทำ การทดสอบย้อนกลับ (Backtesting) อย่างละเอียด ตั้งแต่พื้นฐานจนถึงการประยุกต์ใช้จริงในโปรเจกต์ของผม

บทความนี้เหมาะสำหรับ นักพัฒนา Python, Quant Trader, และ นักวิเคราะห์ข้อมูล ที่ต้องการสร้างระบบเทรดอัตโนมัติด้วยตัวเอง พร้อมแนะนำ เครื่องมือ AI ที่ช่วยประหยัดค่าใช้จ่ายได้ถึง 85%

ทำไมต้องดึงข้อมูลจาก Binance API

Binance เป็นผู้นำตลาดคริปโตที่มี ปริมาณซื้อขายสูงที่สุด และ API ของเขาครบถ้วนทุกฟังก์ชัน รวมถึงข้อมูลประวัติย้อนหลังที่ละเอียดมาก ตั้งแต่ timeframe 1 นาที ไปจนถึงรายเดือน ครอบคลุมทุกคู่เทรด

ข้อดีของ Binance API:

เตรียมสภาพแวดล้อมการพัฒนา

ก่อนเริ่ม ติดตั้งไลบรารีที่จำเป็น:

# ติดตั้งไลบรารีสำหรับดึงข้อมูลและวิเคราะห์
pip install python-binance pandas numpy matplotlib

ไลบรารีสำหรับ backtesting

pip install backtesting ta-lib

หากติดตั้ง ta-lib มีปัญหา ลองใช้ ta แทน

pip install ta

ดึงข้อมูลกราฟเทียนผ่าน Binance API

นี่คือโค้ดหลักสำหรับดึงข้อมูล K-line จาก Binance ซึ่งผมใช้มาหลายปีแล้ว รันได้เสถียรมาก:

import requests
import pandas as pd
from datetime import datetime

class BinanceDataFetcher:
    """
    คลาสสำหรับดึงข้อมูลกราฟเทียนจาก Binance API
    รองรับทุก timeframe และคู่เทรด
    """
    
    BASE_URL = "https://api.binance.com"
    
    def __init__(self):
        self.session = requests.Session()
    
    def get_klines(self, symbol: str, interval: str, 
                   start_str: str = None, end_str: str = None,
                   limit: int = 1000) -> pd.DataFrame:
        """
        ดึงข้อมูลกราฟเทียน
        
        Parameters:
        - symbol: คู่เทรด เช่น 'BTCUSDT', 'ETHBUSD'
        - interval: timeframe เช่น '1m', '5m', '1h', '1d'
        - start_str: วันเริ่มต้น '2023-01-01'
        - end_str: วันสิ้นสุด '2024-01-01'
        - limit: จำนวนข้อมูลสูงสุด (1-1000)
        
        Returns:
        - DataFrame พร้อมคอลัมน์ OHLCV
        """
        endpoint = "/api/v3/klines"
        params = {
            "symbol": symbol.upper(),
            "interval": interval,
            "limit": limit
        }
        
        if start_str:
            # แปลงวันที่เป็น timestamp มิลลิวินาที
            start_ts = int(pd.Timestamp(start_str).timestamp() * 1000)
            params["startTime"] = start_ts
            
        if end_str:
            end_ts = int(pd.Timestamp(end_str).timestamp() * 1000)
            params["endTime"] = end_ts
        
        response = self.session.get(
            f"{self.BASE_URL}{endpoint}",
            params=params,
            timeout=30
        )
        response.raise_for_status()
        
        # แปลงข้อมูลเป็น DataFrame
        df = pd.DataFrame(response.json())
        
        # ตั้งชื่อคอลัมน์ตามมาตรฐาน
        columns = [
            'open_time', 'open', 'high', 'low', 'close', 'volume',
            'close_time', 'quote_asset_volume', 'trades',
            'taker_buy_base', 'taker_buy_quote', 'ignore'
        ]
        df.columns = columns[:len(df.columns)]
        
        # แปลงประเภทข้อมูล
        numeric_cols = ['open', 'high', 'low', 'close', 'volume']
        for col in numeric_cols:
            df[col] = pd.to_numeric(df[col], errors='coerce')
        
        # แปลง timestamp เป็น datetime
        df['open_time'] = pd.to_datetime(df['open_time'], unit='ms')
        df['close_time'] = pd.to_datetime(df['close_time'], unit='ms')
        
        return df

ตัวอย่างการใช้งาน

fetcher = BinanceDataFetcher()

ดึงข้อมูล BTCUSDT รายชั่วโมง 1 ปีย้อนหลัง

btc_data = fetcher.get_klines( symbol='BTCUSDT', interval='1h', start_str='2023-01-01', end_str='2024-01-01', limit=1000 ) print(f"ดึงข้อมูลสำเร็จ: {len(btc_data)} แท่งเทียน") print(btc_data.head())

ดึงข้อมูลปริมาณมากด้วยการวนลูป

Binance API จำกัดการดึงข้อมูลได้ครั้งละ 1,000 แท่งเทียนเท่านั้น หากต้องการข้อมูลหลายปี ต้องแบ่งดึงเป็นช่วง:

import time

def fetch_all_klines(symbol: str, interval: str,
                     start_date: str, end_date: str) -> pd.DataFrame:
    """
    ดึงข้อมูลทั้งหมดโดยอัตโนมัติ แบ่งเป็นช่วงๆ
    """
    fetcher = BinanceDataFetcher()
    all_data = []
    
    # แปลงวันที่เป็น timestamp
    current_start = pd.Timestamp(start_date)
    final_end = pd.Timestamp(end_date)
    
    print(f"กำลังดึงข้อมูล {symbol} {interval}")
    print(f"ตั้งแต่ {start_date} ถึง {end_date}")
    
    while current_start < final_end:
        # ดึงข้อมูลช่วงละ 1000 แท่ง
        df = fetcher.get_klines(
            symbol=symbol,
            interval=interval,
            start_str=str(current_start),
            end_str=str(final_end),
            limit=1000
        )
        
        if df.empty:
            break
            
        all_data.append(df)
        
        # ขยับวันเริ่มต้นไปวันถัดไป (สำหรับ timeframe 1d)
        # หรือปรับตาม timeframe ที่ใช้
        current_start = df['close_time'].max() + pd.Timedelta(minutes=1)
        
        print(f"ดึงได้ {len(df)} แท่ง, รวม {sum(len(d) for d in all_data)} แท่ง")
        
        # หน่วงเวลาเพื่อไม่ให้ถูก rate limit
        time.sleep(0.5)
    
    # รวมข้อมูลทั้งหมด
    if all_data:
        combined_df = pd.concat(all_data, ignore_index=True)
        # ลบข้อมูลซ้ำถ้ามี
        combined_df = combined_df.drop_duplicates(subset=['open_time'])
        combined_df = combined_df.sort_values('open_time').reset_index(drop=True)
        return combined_df
    
    return pd.DataFrame()

ตัวอย่าง: ดึงข้อมูล 3 ปี

eth_daily = fetch_all_klines( symbol='ETHUSDT', interval='1d', start_date='2021-01-01', end_date='2024-01-01' )

บันทึกเป็นไฟล์ CSV

eth_daily.to_csv('ethusdt_daily.csv', index=False) print(f"บันทึกสำเร็จ: {len(eth_daily)} แท่งเทียน")

เตรียมข้อมูลสำหรับ Backtesting

ข้อมูลดิบจาก API ยังต้องผ่านการประมวลผลก่อนนำไปทดสอบระบบ:

import pandas as pd
import numpy as np

class DataPreprocessor:
    """จัดเตรียมข้อมูลสำหรับการทดสอบย้อนกลับ"""
    
    @staticmethod
    def calculate_indicators(df: pd.DataFrame) -> pd.DataFrame:
        """เพิ่ม Technical Indicators ที่จำเป็น"""
        df = df.copy()
        
        # Simple Moving Average
        df['sma_20'] = df['close'].rolling(window=20).mean()
        df['sma_50'] = df['close'].rolling(window=50).mean()
        df['sma_200'] = df['close'].rolling(window=200).mean()
        
        # Exponential Moving Average
        df['ema_12'] = df['close'].ewm(span=12, adjust=False).mean()
        df['ema_26'] = df['close'].ewm(span=26, adjust=False).mean()
        
        # MACD
        df['macd'] = df['ema_12'] - df['ema_26']
        df['macd_signal'] = df['macd'].ewm(span=9, adjust=False).mean()
        df['macd_hist'] = df['macd'] - df['macd_signal']
        
        # RSI
        delta = df['close'].diff()
        gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
        loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
        rs = gain / loss
        df['rsi'] = 100 - (100 / (1 + rs))
        
        # Bollinger Bands
        df['bb_middle'] = df['close'].rolling(window=20).mean()
        df['bb_std'] = df['close'].rolling(window=20).std()
        df['bb_upper'] = df['bb_middle'] + (df['bb_std'] * 2)
        df['bb_lower'] = df['bb_middle'] - (df['bb_std'] * 2)
        
        # Volume indicators
        df['volume_sma_20'] = df['volume'].rolling(window=20).mean()
        df['volume_ratio'] = df['volume'] / df['volume_sma_20']
        
        return df
    
    @staticmethod
    def clean_data(df: pd.DataFrame) -> pd.DataFrame:
        """ทำความสะอาดข้อมูล"""
        df = df.copy()
        
        # ลบแถวที่มีค่าว่าง
        df = df.dropna()
        
        # รีเซ็ต index
        df = df.reset_index(drop=True)
        
        # ตรวจสอบ outlier
        z_scores = np.abs((df['close'] - df['close'].mean()) / df['close'].std())
        df = df[z_scores < 5]  # ลบ outlier ที่ห่างจากค่าเฉลี่ยเกิน 5 std
        
        return df
    
    @staticmethod
    def split_train_test(df: pd.DataFrame, 
                         train_ratio: float = 0.7) -> tuple:
        """แบ่งข้อมูลสำหรับ train และ test"""
        split_idx = int(len(df) * train_ratio)
        
        train = df.iloc[:split_idx].copy()
        test = df.iloc[split_idx:].copy()
        
        print(f"Train: {len(train)} แท่ง ({train['open_time'].min()} ถึง {train['open_time'].max()})")
        print(f"Test: {len(test)} แท่ง ({test['open_time'].min()} ถึง {test['open_time'].max()})")
        
        return train, test

ใช้งาน

preprocessor = DataPreprocessor() df_with_indicators = preprocessor.calculate_indicators(eth_daily) df_clean = preprocessor.clean_data(df_with_indicators) train_data, test_data = preprocessor.split_train_test(df_clean)

สร้างระบบเทรดอัตโนมัติ

หลังจากเตรียมข้อมูลเรียบร้อย มาสร้างกลยุทธ์การซื้อขายกัน:

from dataclasses import dataclass
from typing import Optional

@dataclass
class TradeSignal:
    """สัญญาณซื้อขาย"""
    timestamp: pd.Timestamp
    action: str  # 'BUY' หรือ 'SELL'
    price: float
    reason: str
    confidence: float  # 0-1

class TradingStrategy:
    """กลยุทธ์การซื้อขายแบบ Moving Average Crossover + RSI"""
    
    def __init__(self, 
                 fast_ma: int = 20,
                 slow_ma: int = 50,
                 rsi_oversold: float = 30,
                 rsi_overbought: float = 70,
                 rsi_period: int = 14):
        self.fast_ma = fast_ma
        self.slow_ma = slow_ma
        self.rsi_oversold = rsi_oversold
        self.rsi_overbought = rsi_overbought
        self.rsi_period = rsi_period
    
    def generate_signals(self, df: pd.DataFrame) -> list:
        """สร้างสัญญาณซื้อขายจากข้อมูล"""
        signals = []
        position = 0  # 0 = ไม่มีสถานะ, 1 = ถือ long
        
        for i in range(self.slow_ma, len(df)):
            row = df.iloc[i]
            
            # เงื่อนไขซื้อ: SMA cross over + RSI oversold
            if (row['sma_20'] > row['sma_50'] and 
                df.iloc[i-1]['sma_20'] <= df.iloc[i-1]['sma_50'] and
                row['rsi'] < self.rsi_oversold and
                position == 0):
                
                signal = TradeSignal(
                    timestamp=row['open_time'],
                    action='BUY',
                    price=row['close'],
                    reason=f"SMA Crossover + RSI={row['rsi']:.1f}",
                    confidence=0.8
                )
                signals.append(signal)
                position = 1
            
            # เงื่อนไขขาย: SMA cross under + RSI overbought
            elif (row['sma_20'] < row['sma_50'] and 
                  df.iloc[i-1]['sma_20'] >= df.iloc[i-1]['sma_50'] and
                  row['rsi'] > self.rsi_overbought and
                  position == 1):
                
                signal = TradeSignal(
                    timestamp=row['open_time'],
                    action='SELL',
                    price=row['close'],
                    reason=f"SMA Cross Under + RSI={row['rsi']:.1f}",
                    confidence=0.8
                )
                signals.append(signal)
                position = 0
        
        return signals

ทดสอบกลยุทธ์

strategy = TradingStrategy( fast_ma=20, slow_ma