🐍 Pillar Code-Heavy

Python + Deriv API: Cách Tạo Trading Bot Đầu Tiên Của Bạn

Bởi Dan Machado · 25 phút đọc · Code production-ready

Đây là tutorial complete Python bot với Deriv API official. WebSocket connection, OAuth authentication, RSI strategy, và RiskManager class. Code production-ready, có thể deploy trực tiếp lên VPS.

📋 Prerequisites

1. Python 3.9+ installed
2. Tài khoản Deriv (demo enough) — sign up here
3. API token từ Deriv (tạo trong Settings)
4. Basic Python knowledge (variables, functions, async/await)
5. VS Code hoặc PyCharm editor

Tại Sao Chọn Python?

  • Free và open-source: Không cần license
  • Rich library ecosystem: NumPy, Pandas, TA-Lib cho analysis
  • ML/AI integration: Pakai TensorFlow, scikit-learn cho predictions
  • Cross-platform: Run trên Windows, Mac, Linux, VPS
  • Active community: Stack Overflow, GitHub examples
  • Deriv official support: SDK Python được Deriv maintain

Setup Step-by-Step

1

Install Python & Tools

Download Python 3.9+. Install VS Code. Verify với: python --version trong terminal.

2

Get Deriv API Token

Login Deriv → Account Settings → API Token. Create token với scopes: read, trade, payments. Copy token và save securely. Đây là “password” cho bot — không share.

3

Install Deriv Python SDK

Mở terminal trong project folder, run:

▸ bash · install dependencies
pip install python_deriv_api websocket-client pandas numpy
4

Create Project Structure

Tạo folder mới, files:
bot.py — main script
risk_manager.py — risk management class
.env — store API token (không commit Git)

Code #1: WebSocket Connection Basic

Bước đầu — connect đến Deriv API qua WebSocket:

▸ python · connection_test.py COPY ↗
"""
Deriv API WebSocket Connection Test
Author: IA Trader Pro
"""
import asyncio
import json
import websockets

# === CONFIG ===
DERIV_WS_URL = "wss://ws.binaryws.com/websockets/v3?app_id=1089"
API_TOKEN = "YOUR_API_TOKEN_HERE"  # Replace with your token

async def test_connection():
    """Test basic WebSocket connection to Deriv API."""
    async with websockets.connect(DERIV_WS_URL) as ws:
        # Authorize
        auth_request = {
            "authorize": API_TOKEN
        }
        await ws.send(json.dumps(auth_request))
        response = await ws.recv()
        data = json.loads(response)
        
        if "error" in data:
            print(f"✗ Auth failed: {data['error']['message']}")
            return
        
        print(f"✓ Connected as: {data['authorize']['loginid']}")
        print(f"  Balance: ${data['authorize']['balance']} {data['authorize']['currency']}")
        print(f"  Account type: {data['authorize']['account_type']}")

# Run
if __name__ == "__main__":
    asyncio.run(test_connection())

Run: python connection_test.py

Expected output:

▸ terminal output
✓ Connected as: VRTC1234567 (Virtual demo account)
  Balance: $10000.00 USD
  Account type: virtual

Code #2: RiskManager Class

Heart của bot — class quản lý risk:

▸ python · risk_manager.py COPY ↗
"""
Risk Management Class
Tracks daily losses, enforces limits, sizes positions.
"""
from datetime import datetime, date
from dataclasses import dataclass, field

@dataclass
class RiskManager:
    """Risk management for trading bot."""
    
    # === CONFIGURATION ===
    initial_balance: float = 1000.0
    risk_per_trade_pct: float = 2.0  # 2% of balance
    daily_loss_limit_pct: float = 5.0  # 5% daily stop
    weekly_loss_limit_pct: float = 10.0  # 10% weekly stop
    max_concurrent_positions: int = 3
    max_trades_per_day: int = 10
    cooldown_after_losses: int = 3  # consecutive losses
    cooldown_minutes: int = 60
    
    # === STATE TRACKING ===
    current_balance: float = field(init=False)
    daily_pnl: float = 0.0
    weekly_pnl: float = 0.0
    consecutive_losses: int = 0
    trades_today: int = 0
    open_positions: int = 0
    cooldown_until: datetime = None
    today_date: date = field(default_factory=date.today)
    
    def __post_init__(self):
        self.current_balance = self.initial_balance
    
    # === CHECKS ===
    
    def can_trade(self) -> tuple[bool, str]:
        """Check if bot is allowed to trade now."""
        # Daily reset check
        if date.today() != self.today_date:
            self._reset_daily()
        
        # Cooldown check
        if self.cooldown_until and datetime.now() < self.cooldown_until:
            mins_left = (self.cooldown_until - datetime.now()).seconds // 60
            return False, f"Cooldown active ({mins_left} min left)"
        
        # Daily loss limit
        daily_loss_pct = abs(min(self.daily_pnl, 0)) / self.current_balance * 100
        if daily_loss_pct >= self.daily_loss_limit_pct:
            return False, f"Daily loss limit hit ({daily_loss_pct:.1f}%)"
        
        # Weekly loss limit
        weekly_loss_pct = abs(min(self.weekly_pnl, 0)) / self.current_balance * 100
        if weekly_loss_pct >= self.weekly_loss_limit_pct:
            return False, f"Weekly loss limit hit ({weekly_loss_pct:.1f}%)"
        
        # Max trades per day
        if self.trades_today >= self.max_trades_per_day:
            return False, f"Max trades reached ({self.max_trades_per_day})"
        
        # Max concurrent positions
        if self.open_positions >= self.max_concurrent_positions:
            return False, f"Max positions open ({self.open_positions})"
        
        return True, "OK to trade"
    
    def calculate_stake(self) -> float:
        """Calculate stake size based on 2% rule."""
        return round(self.current_balance * self.risk_per_trade_pct / 100, 2)
    
    # === UPDATES ===
    
    def on_trade_result(self, pnl: float):
        """Update state after trade result."""
        self.current_balance += pnl
        self.daily_pnl += pnl
        self.weekly_pnl += pnl
        self.trades_today += 1
        self.open_positions = max(0, self.open_positions - 1)
        
        if pnl < 0:
            self.consecutive_losses += 1
            # Check cooldown trigger
            if self.consecutive_losses >= self.cooldown_after_losses:
                from datetime import timedelta
                self.cooldown_until = datetime.now() + timedelta(minutes=self.cooldown_minutes)
                print(f"⚠ Cooldown triggered: {self.cooldown_minutes} min")
                self.consecutive_losses = 0  # Reset counter
        else:
            self.consecutive_losses = 0
    
    def on_trade_open(self):
        """Track new open position."""
        self.open_positions += 1
    
    def _reset_daily(self):
        """Reset daily counters."""
        self.daily_pnl = 0.0
        self.trades_today = 0
        self.today_date = date.today()
        print(f"📅 Daily reset: {self.today_date}")
    
    # === REPORTING ===
    
    def status_summary(self) -> str:
        """Get current status summary."""
        return f"""
═══════════════════════════════════════
  Balance: ${self.current_balance:.2f}
  Daily P/L: ${self.daily_pnl:+.2f} ({self.daily_pnl/self.current_balance*100:+.1f}%)
  Weekly P/L: ${self.weekly_pnl:+.2f}
  Trades today: {self.trades_today}/{self.max_trades_per_day}
  Open positions: {self.open_positions}/{self.max_concurrent_positions}
  Consecutive losses: {self.consecutive_losses}
  Cooldown: {self.cooldown_until.strftime('%H:%M') if self.cooldown_until else 'None'}
═══════════════════════════════════════
"""

Code #3: RSI Bot Main Script

Main bot kết hợp WebSocket + RSI strategy + RiskManager:

▸ python · bot.py COPY ↗
"""
Deriv V75 RSI Trading Bot — Production Ready
Strategy: BUY CALL when RSI < 30, BUY PUT when RSI > 70
Risk: 2% stake, 5% daily loss limit
"""
import asyncio
import json
import websockets
import pandas as pd
import numpy as np
from datetime import datetime
from risk_manager import RiskManager

# === CONFIG ===
DERIV_WS_URL = "wss://ws.binaryws.com/websockets/v3?app_id=1089"
API_TOKEN = "YOUR_API_TOKEN_HERE"  # From Deriv account settings
ASSET = "R_75"  # Volatility 75 Index
DURATION = 5  # ticks
RSI_PERIOD = 14
RSI_OVERSOLD = 30
RSI_OVERBOUGHT = 70

# === INDICATORS ===
def calculate_rsi(prices: pd.Series, period: int = 14) -> float:
    """Calculate RSI from price series."""
    delta = prices.diff()
    gain = delta.where(delta > 0, 0).rolling(window=period).mean()
    loss = -delta.where(delta < 0, 0).rolling(window=period).mean()
    rs = gain / loss
    rsi = 100 - (100 / (1 + rs))
    return rsi.iloc[-1] if not rsi.empty else 50.0

# === BOT CLASS ===
class DerivBot:
    def __init__(self, api_token: str):
        self.api_token = api_token
        self.ws = None
        self.risk_mgr = RiskManager(initial_balance=10000.0)
        self.candles = []
        self.req_id = 0
    
    async def connect(self):
        """Connect and authorize."""
        self.ws = await websockets.connect(DERIV_WS_URL)
        await self._send({"authorize": self.api_token})
        response = await self._recv()
        
        if "error" in response:
            raise Exception(f"Auth failed: {response['error']['message']}")
        
        self.risk_mgr.current_balance = float(response["authorize"]["balance"])
        self.risk_mgr.initial_balance = self.risk_mgr.current_balance
        print(f"✓ Connected: {response['authorize']['loginid']}")
        print(f"  Balance: ${self.risk_mgr.current_balance:.2f}")
    
    async def subscribe_candles(self):
        """Subscribe to candle updates."""
        await self._send({
            "ticks_history": ASSET,
            "adjust_start_time": 1,
            "count": 100,
            "end": "latest",
            "start": 1,
            "style": "candles",
            "granularity": 60,  # 1 minute candles
            "subscribe": 1
        })
        # Get initial history
        response = await self._recv()
        if "candles" in response:
            self.candles = [
                {"close": c["close"]} for c in response["candles"]
            ]
            print(f"📊 Loaded {len(self.candles)} historical candles")
    
    async def place_trade(self, direction: str, stake: float):
        """Place CALL or PUT trade."""
        contract_type = "CALL" if direction == "BUY" else "PUT"
        
        await self._send({
            "buy": 1,
            "subscribe": 1,
            "price": stake,
            "parameters": {
                "amount": stake,
                "basis": "stake",
                "contract_type": contract_type,
                "currency": "USD",
                "duration": DURATION,
                "duration_unit": "t",  # ticks
                "symbol": ASSET
            }
        })
        
        response = await self._recv()
        if "error" in response:
            print(f"  ✗ Trade error: {response['error']['message']}")
            return None
        
        contract_id = response["buy"]["contract_id"]
        self.risk_mgr.on_trade_open()
        print(f"  📈 Trade placed: {contract_type} ID {contract_id} stake ${stake}")
        
        # Wait for result
        return await self._wait_for_result(contract_id, stake)
    
    async def _wait_for_result(self, contract_id: int, stake: float):
        """Wait for trade to complete."""
        while True:
            response = await self._recv()
            if response.get("proposal_open_contract", {}).get("contract_id") == contract_id:
                contract = response["proposal_open_contract"]
                if contract.get("is_sold"):
                    payout = float(contract["payout"])
                    pnl = payout - stake if payout > stake else -stake
                    self.risk_mgr.on_trade_result(pnl)
                    result = "WIN" if pnl > 0 else "LOSS"
                    print(f"  {'✓' if pnl > 0 else '✗'} {result}: ${pnl:+.2f} | Balance: ${self.risk_mgr.current_balance:.2f}")
                    return pnl
    
    async def run_strategy(self):
        """Main strategy loop."""
        print(f"\n🤖 Bot started: V75 RSI Strategy")
        print(self.risk_mgr.status_summary())
        
        while True:
            try:
                response = await self._recv()
                
                # New candle received
                if "ohlc" in response:
                    candle = response["ohlc"]
                    self.candles.append({"close": float(candle["close"])})
                    if len(self.candles) > 100:
                        self.candles.pop(0)
                    
                    # Need at least RSI_PERIOD candles
                    if len(self.candles) < RSI_PERIOD:
                        continue
                    
                    # Risk check
                    can_trade, reason = self.risk_mgr.can_trade()
                    if not can_trade:
                        print(f"⏸ {reason}")
                        await asyncio.sleep(60)
                        continue
                    
                    # Calculate RSI
                    prices = pd.Series([c["close"] for c in self.candles])
                    rsi = calculate_rsi(prices, RSI_PERIOD)
                    
                    timestamp = datetime.now().strftime("%H:%M:%S")
                    print(f"\n[{timestamp}] RSI: {rsi:.1f} | Price: {prices.iloc[-1]:.2f}")
                    
                    # Trading logic
                    stake = self.risk_mgr.calculate_stake()
                    
                    if rsi < RSI_OVERSOLD:
                        print(f"  🟢 RSI oversold ({rsi:.1f} < {RSI_OVERSOLD}) — BUY CALL")
                        await self.place_trade("BUY", stake)
                    
                    elif rsi > RSI_OVERBOUGHT:
                        print(f"  🔴 RSI overbought ({rsi:.1f} > {RSI_OVERBOUGHT}) — BUY PUT")
                        await self.place_trade("SELL", stake)
                    
                    else:
                        print(f"  ⏹ RSI neutral — skip")
            
            except websockets.ConnectionClosed:
                print("⚠ Connection lost, reconnecting...")
                await asyncio.sleep(5)
                await self.connect()
                await self.subscribe_candles()
            
            except Exception as e:
                print(f"✗ Error: {e}")
                await asyncio.sleep(5)
    
    # === HELPERS ===
    async def _send(self, request: dict):
        """Send request with auto req_id."""
        self.req_id += 1
        request["req_id"] = self.req_id
        await self.ws.send(json.dumps(request))
    
    async def _recv(self) -> dict:
        """Receive response."""
        return json.loads(await self.ws.recv())

# === MAIN ===
async def main():
    bot = DerivBot(API_TOKEN)
    await bot.connect()
    await bot.subscribe_candles()
    await bot.run_strategy()

if __name__ == "__main__":
    print("🚀 Starting Deriv V75 RSI Bot...")
    print("   Press Ctrl+C to stop\n")
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\n\n⏹ Bot stopped by user")

Cách Run Bot

  1. Save 2 files (bot.py + risk_manager.py) trong cùng folder
  2. Replace YOUR_API_TOKEN_HERE với token thực của bạn
  3. Open terminal trong folder
  4. Run: python bot.py
  5. Bot sẽ connect, load 100 historical candles, và bắt đầu trade khi có signal
  6. Press Ctrl+C để stop bot

Expected Output

▸ terminal output
🚀 Starting Deriv V75 RSI Bot...
   Press Ctrl+C to stop

✓ Connected: VRTC1234567
  Balance: $10000.00
📊 Loaded 100 historical candles

🤖 Bot started: V75 RSI Strategy
═══════════════════════════════════════
  Balance: $10000.00
  Daily P/L: $+0.00 (+0.0%)
  Weekly P/L: $+0.00
  Trades today: 0/10
  Open positions: 0/3
  Consecutive losses: 0
  Cooldown: None
═══════════════════════════════════════

[14:23:15] RSI: 28.4 | Price: 458.32
  🟢 RSI oversold (28.4 < 30) — BUY CALL
  📈 Trade placed: CALL ID 178234982 stake $200.00
  ✓ WIN: $+181.00 | Balance: $10181.00

[14:24:30] RSI: 52.1 | Price: 459.41
  ⏹ RSI neutral — skip

[14:25:45] RSI: 73.8 | Price: 461.55
  🔴 RSI overbought (73.8 > 70) — BUY PUT
  📈 Trade placed: PUT ID 178234999 stake $200.00
  ✗ LOSS: $-200.00 | Balance: $9981.00

Deploy lên VPS

Để bot run 24/7, deploy lên VPS:

  1. Rent VPS: DigitalOcean ($6/month), AWS EC2 free tier, hoặc Hetzner Cloud
  2. SSH vào server qua Terminal hoặc PuTTY
  3. Install Python: sudo apt install python3 python3-pip
  4. Install dependencies: pip3 install python_deriv_api websocket-client pandas numpy
  5. Upload code: SCP hoặc git clone
  6. Run với screen/tmux để bot continue khi disconnect:
▸ bash · run on VPS
# Install screen
sudo apt install screen

# Start new screen session
screen -S derivbot

# Run bot
python3 bot.py

# Detach: Ctrl+A then D
# Re-attach later: screen -r derivbot

Improvements & Next Steps

Add Logging

  • Save mỗi trade vào CSV file cho analysis sau
  • Log errors riêng để debug
  • Use Python’s logging module

Add Notifications

  • Telegram bot cho alerts khi big wins/losses
  • Email notifications khi daily limit hit
  • Discord webhook cho community sharing

Multi-Asset Trading

  • Run multiple symbols concurrent (V75 + V100 + Boom 500)
  • Diversification reduces correlation risk
  • Need separate RiskManager per asset

ML Integration

  • scikit-learn cho price prediction
  • TensorFlow LSTM cho sequence modeling
  • Sentiment analysis từ news APIs

Common Errors & Fixes

🐛 Troubleshooting

“Authorization failed”: Token expired or invalid. Generate new từ Deriv settings.
“Connection closed unexpectedly”: Internet issue or rate limit. Bot tự reconnect.
“Insufficient balance”: Stake quá lớn cho balance hiện tại. Check 2% rule.
“Contract not found”: Trade duration expired before result. Increase wait time.
“RSI not computed”: Cần ít nhất 14 candles trước khi compute RSI.

Security Best Practices

🔐 Bảo Mật

1. Never hardcode API token trong code committed Git
2. Use environment variables hoặc .env file (gitignored)
3. Restrict token scopes: chỉ enable read + trade (không enable admin)
4. Rotate tokens monthly cho security
5. Monitor account activity daily — unusual trades = compromised
6. VPS firewall: chỉ allow SSH từ IP của bạn

🚀 Cần Deriv API token? Mở demo account miễn phí (instant access):

Mở Demo Deriv Miễn Phí

Bài Liên Quan

DM

Dan Machado

Founder IA Trader Pro · Python developer · 5+ năm với Deriv API

⚠️ Disclaimer: Code production-ready nhưng cần test trên demo trước. Trading binary options có rủi ro mất vốn cao. Deriv không được cấp phép bởi SBV. Bài viết này có chứa affiliate link Deriv. Disclaimer đầy đủ.