| Chiến Lược Momentum Bot Auto Trading

Được viết bởi thanhdt vào ngày 17/11/2025 lúc 12:36 | 57 lượt xem

Chiến Lược Momentum Trading bằng Python

Momentum Trading là một trong những chiến lược giao dịch phổ biến nhất, dựa trên nguyên tắc “xu hướng là bạn của bạn” (trend is your friend). Chiến lược này giả định rằng các tài sản đang tăng giá sẽ tiếp tục tăng, và các tài sản đang giảm giá sẽ tiếp tục giảm. Trong bài viết này, chúng ta sẽ xây dựng một bot giao dịch tự động sử dụng chiến lược Momentum Trading với Python.

Tổng quan về Momentum Trading

Momentum Trading là gì?

Momentum Trading là phương pháp giao dịch dựa trên giả định rằng xu hướng hiện tại sẽ tiếp tục trong tương lai. Nguyên tắc cơ bản:

  • Mua khi giá đang tăng mạnh (momentum tăng)
  • Bán khi giá đang giảm mạnh (momentum giảm)
  • Nắm giữ vị thế cho đến khi momentum yếu đi
  • Thoát lệnh khi momentum đảo chiều

Tại sao Momentum Trading hiệu quả?

  1. Xu hướng có tính bền vững: Xu hướng mạnh thường tiếp tục trong một khoảng thời gian
  2. Phù hợp thị trường trending: Hoạt động tốt khi có xu hướng rõ ràng
  3. Lợi nhuận cao: Có thể bắt được toàn bộ xu hướng
  4. Tín hiệu rõ ràng: Các chỉ báo momentum dễ nhận diện
  5. Có thể tự động hóa: Dễ dàng lập trình thành bot

Các chỉ báo Momentum phổ biến

  1. MACD (Moving Average Convergence Divergence): Đo lường sự thay đổi momentum
  2. RSI (Relative Strength Index): Xác định momentum và quá mua/quá bán
  3. Rate of Change (ROC): Tốc độ thay đổi giá
  4. Momentum Indicator: Chênh lệch giá giữa hai thời điểm
  5. Stochastic Oscillator: So sánh giá đóng cửa với phạm vi giá
  6. ADX (Average Directional Index): Đo lường sức mạnh xu hướng

Cài đặt Môi trường

Thư viện cần thiết

# requirements.txt
pandas==2.1.0
numpy==1.24.3
ccxt==4.0.0
python-binance==1.0.19
matplotlib==3.7.2
plotly==5.17.0
ta-lib==0.4.28
schedule==1.2.0
python-dotenv==1.0.0
scipy==1.11.0

Cài đặt

pip install pandas numpy ccxt python-binance matplotlib plotly schedule python-dotenv scipy

Lưu ý:

  • TA-Lib yêu cầu cài đặt thư viện C trước. Trên Windows, tải file .whl từ đây.
  • Đối với Linux/Mac: sudo apt-get install ta-lib hoặc brew install ta-lib

Xây dựng các Chỉ báo Momentum

Tính toán MACD

import pandas as pd
import numpy as np
from typing import Tuple

class MACDIndicator:
    """
    Lớp tính toán MACD
    """
    
    def __init__(self, fast_period: int = 12, slow_period: int = 26, signal_period: int = 9):
        """
        Khởi tạo MACD Indicator
        
        Args:
            fast_period: Chu kỳ EMA nhanh (mặc định: 12)
            slow_period: Chu kỳ EMA chậm (mặc định: 26)
            signal_period: Chu kỳ EMA cho Signal Line (mặc định: 9)
        """
        self.fast_period = fast_period
        self.slow_period = slow_period
        self.signal_period = signal_period
    
    def calculate_ema(self, data: pd.Series, period: int) -> pd.Series:
        """
        Tính toán Exponential Moving Average
        
        Args:
            data: Series chứa giá
            period: Chu kỳ EMA
            
        Returns:
            Series chứa giá trị EMA
        """
        return data.ewm(span=period, adjust=False).mean()
    
    def calculate(self, data: pd.Series) -> pd.DataFrame:
        """
        Tính toán MACD, Signal và Histogram
        
        Args:
            data: Series chứa giá (thường là close price)
            
        Returns:
            DataFrame với các cột: macd, signal, histogram
        """
        # Tính EMA nhanh và chậm
        ema_fast = self.calculate_ema(data, self.fast_period)
        ema_slow = self.calculate_ema(data, self.slow_period)
        
        # Tính MACD Line
        macd_line = ema_fast - ema_slow
        
        # Tính Signal Line
        signal_line = self.calculate_ema(macd_line, self.signal_period)
        
        # Tính Histogram
        histogram = macd_line - signal_line
        
        return pd.DataFrame({
            'macd': macd_line,
            'signal': signal_line,
            'histogram': histogram
        })
    
    def get_signal(self, macd: float, signal: float, histogram: float) -> int:
        """
        Xác định tín hiệu từ MACD
        
        Args:
            macd: Giá trị MACD
            signal: Giá trị Signal
            histogram: Giá trị Histogram
            
        Returns:
            1: Bullish, -1: Bearish, 0: Neutral
        """
        # Bullish: MACD cắt lên Signal và Histogram > 0
        if macd > signal and histogram > 0:
            return 1
        # Bearish: MACD cắt xuống Signal và Histogram < 0
        elif macd < signal and histogram < 0:
            return -1
        return 0

Tính toán Rate of Change (ROC)

class ROCIndicator:
    """
    Lớp tính toán Rate of Change
    """
    
    def __init__(self, period: int = 10):
        """
        Khởi tạo ROC Indicator
        
        Args:
            period: Chu kỳ ROC (mặc định: 10)
        """
        self.period = period
    
    def calculate(self, data: pd.Series) -> pd.Series:
        """
        Tính toán Rate of Change
        
        ROC = ((Price(today) - Price(n periods ago)) / Price(n periods ago)) × 100
        
        Args:
            data: Series chứa giá
            
        Returns:
            Series chứa giá trị ROC (%)
        """
        roc = ((data - data.shift(self.period)) / data.shift(self.period)) * 100
        return roc
    
    def is_strong_momentum(self, roc: float, threshold: float = 2.0) -> bool:
        """
        Kiểm tra momentum có mạnh không
        
        Args:
            roc: Giá trị ROC
            threshold: Ngưỡng momentum mạnh (%)
            
        Returns:
            True nếu momentum mạnh
        """
        return abs(roc) >= threshold

Tính toán Momentum Indicator

class MomentumIndicator:
    """
    Lớp tính toán Momentum Indicator
    """
    
    def __init__(self, period: int = 10):
        """
        Khởi tạo Momentum Indicator
        
        Args:
            period: Chu kỳ Momentum (mặc định: 10)
        """
        self.period = period
    
    def calculate(self, data: pd.Series) -> pd.Series:
        """
        Tính toán Momentum
        
        Momentum = Price(today) - Price(n periods ago)
        
        Args:
            data: Series chứa giá
            
        Returns:
            Series chứa giá trị Momentum
        """
        momentum = data - data.shift(self.period)
        return momentum
    
    def get_signal(self, momentum: float, prev_momentum: float) -> int:
        """
        Xác định tín hiệu từ Momentum
        
        Args:
            momentum: Giá trị Momentum hiện tại
            prev_momentum: Giá trị Momentum trước đó
            
        Returns:
            1: Bullish, -1: Bearish, 0: Neutral
        """
        # Momentum tăng và dương
        if momentum > 0 and momentum > prev_momentum:
            return 1
        # Momentum giảm và âm
        elif momentum < 0 and momentum < prev_momentum:
            return -1
        return 0

Tính toán ADX (Average Directional Index)

class ADXIndicator:
    """
    Lớp tính toán ADX (Average Directional Index)
    """
    
    def __init__(self, period: int = 14):
        """
        Khởi tạo ADX Indicator
        
        Args:
            period: Chu kỳ ADX (mặc định: 14)
        """
        self.period = period
    
    def calculate_true_range(self, df: pd.DataFrame) -> pd.Series:
        """Tính True Range"""
        high = df['high']
        low = df['low']
        close = df['close']
        prev_close = close.shift(1)
        
        tr1 = high - low
        tr2 = abs(high - prev_close)
        tr3 = abs(low - prev_close)
        
        true_range = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
        return true_range
    
    def calculate_directional_movement(self, df: pd.DataFrame) -> Tuple[pd.Series, pd.Series]:
        """Tính Directional Movement"""
        high = df['high']
        low = df['low']
        prev_high = high.shift(1)
        prev_low = low.shift(1)
        
        # Plus Directional Movement (+DM)
        plus_dm = high - prev_high
        plus_dm[plus_dm < 0] = 0
        plus_dm[(high - prev_high) < (prev_low - low)] = 0
        
        # Minus Directional Movement (-DM)
        minus_dm = prev_low - low
        minus_dm[minus_dm < 0] = 0
        minus_dm[(prev_low - low) < (high - prev_high)] = 0
        
        return plus_dm, minus_dm
    
    def calculate(self, df: pd.DataFrame) -> pd.DataFrame:
        """
        Tính toán ADX, +DI, -DI
        
        Args:
            df: DataFrame OHLCV
            
        Returns:
            DataFrame với các cột: adx, plus_di, minus_di
        """
        # Tính True Range
        tr = self.calculate_true_range(df)
        atr = tr.rolling(window=self.period).mean()
        
        # Tính Directional Movement
        plus_dm, minus_dm = self.calculate_directional_movement(df)
        
        # Tính Directional Indicators
        plus_di = 100 * (plus_dm.rolling(window=self.period).mean() / atr)
        minus_di = 100 * (minus_dm.rolling(window=self.period).mean() / atr)
        
        # Tính DX
        dx = 100 * abs(plus_di - minus_di) / (plus_di + minus_di)
        
        # Tính ADX (SMA của DX)
        adx = dx.rolling(window=self.period).mean()
        
        return pd.DataFrame({
            'adx': adx,
            'plus_di': plus_di,
            'minus_di': minus_di
        })
    
    def is_strong_trend(self, adx: float, threshold: float = 25.0) -> bool:
        """
        Kiểm tra xu hướng có mạnh không
        
        Args:
            adx: Giá trị ADX
            threshold: Ngưỡng xu hướng mạnh (mặc định: 25)
            
        Returns:
            True nếu xu hướng mạnh
        """
        return adx >= threshold

Tính toán RSI

class RSIIndicator:
    """
    Lớp tính toán RSI (Relative Strength Index)
    """
    
    def __init__(self, period: int = 14):
        """
        Khởi tạo RSI Indicator
        
        Args:
            period: Chu kỳ RSI (mặc định: 14)
        """
        self.period = period
    
    def calculate(self, data: pd.Series) -> pd.Series:
        """
        Tính toán RSI
        
        Args:
            data: Series chứa giá
            
        Returns:
            Series chứa giá trị RSI (0-100)
        """
        delta = data.diff()
        
        gain = (delta.where(delta > 0, 0)).rolling(window=self.period).mean()
        loss = (-delta.where(delta < 0, 0)).rolling(window=self.period).mean()
        
        rs = gain / loss
        rsi = 100 - (100 / (1 + rs))
        
        return rsi

Tính toán ATR

class ATRIndicator:
    """
    Lớp tính toán ATR (Average True Range)
    """
    
    def __init__(self, period: int = 14):
        """
        Khởi tạo ATR Indicator
        
        Args:
            period: Chu kỳ ATR (mặc định: 14)
        """
        self.period = period
    
    def calculate_true_range(self, df: pd.DataFrame) -> pd.Series:
        """Tính True Range"""
        high = df['high']
        low = df['low']
        close = df['close']
        prev_close = close.shift(1)
        
        tr1 = high - low
        tr2 = abs(high - prev_close)
        tr3 = abs(low - prev_close)
        
        true_range = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
        return true_range
    
    def calculate_smma_atr(self, df: pd.DataFrame) -> pd.Series:
        """
        Tính ATR sử dụng SMMA (Wilder's smoothing)
        
        Args:
            df: DataFrame chứa OHLC data
            
        Returns:
            Series chứa giá trị ATR
        """
        true_range = self.calculate_true_range(df)
        
        # Sử dụng SMMA để tính ATR
        atr = true_range.ewm(alpha=1.0/self.period, adjust=False).mean()
        
        return atr

Chiến lược Momentum Trading

Nguyên lý Chiến lược

  1. Xác định xu hướng: Sử dụng ADX để xác định xu hướng mạnh
  2. Xác định hướng: Sử dụng MACD, RSI để xác định hướng momentum
  3. Tín hiệu vào lệnh:
    • BUY: MACD bullish, RSI > 50, ROC > 0, ADX > 25
    • SELL: MACD bearish, RSI < 50, ROC < 0, ADX > 25
  4. Quản lý rủi ro: Stop Loss và Take Profit dựa trên ATR
  5. Thoát lệnh: Khi momentum yếu đi hoặc đảo chiều

Lớp Chiến lược Momentum Trading

class MomentumTradingStrategy:
    """
    Chiến lược Momentum Trading
    """
    
    def __init__(
        self,
        macd_fast: int = 12,
        macd_slow: int = 26,
        macd_signal: int = 9,
        rsi_period: int = 14,
        roc_period: int = 10,
        adx_period: int = 14,
        adx_threshold: float = 25.0,
        require_all_confirmations: bool = True
    ):
        """
        Khởi tạo chiến lược
        
        Args:
            macd_fast: Chu kỳ MACD fast
            macd_slow: Chu kỳ MACD slow
            macd_signal: Chu kỳ MACD signal
            rsi_period: Chu kỳ RSI
            roc_period: Chu kỳ ROC
            adx_period: Chu kỳ ADX
            adx_threshold: Ngưỡng ADX cho xu hướng mạnh
            require_all_confirmations: Yêu cầu tất cả chỉ báo xác nhận
        """
        self.macd_fast = macd_fast
        self.macd_slow = macd_slow
        self.macd_signal = macd_signal
        self.rsi_period = rsi_period
        self.roc_period = roc_period
        self.adx_period = adx_period
        self.adx_threshold = adx_threshold
        self.require_all_confirmations = require_all_confirmations
        
        self.macd = MACDIndicator(fast_period=macd_fast, slow_period=macd_slow, signal_period=macd_signal)
        self.roc = ROCIndicator(period=roc_period)
        self.momentum = MomentumIndicator(period=10)
        self.adx = ADXIndicator(period=adx_period)
        self.rsi = RSIIndicator(period=rsi_period)
    
    def calculate_indicators(self, df: pd.DataFrame) -> pd.DataFrame:
        """
        Tính toán tất cả các chỉ báo
        
        Args:
            df: DataFrame OHLCV
            
        Returns:
            DataFrame với các chỉ báo đã tính
        """
        result = df.copy()
        
        # MACD
        macd_data = self.macd.calculate(result['close'])
        result['macd'] = macd_data['macd']
        result['macd_signal'] = macd_data['signal']
        result['macd_histogram'] = macd_data['histogram']
        
        # RSI
        result['rsi'] = self.rsi.calculate(result['close'])
        
        # ROC
        result['roc'] = self.roc.calculate(result['close'])
        
        # Momentum
        result['momentum'] = self.momentum.calculate(result['close'])
        
        # ADX
        adx_data = self.adx.calculate(result)
        result['adx'] = adx_data['adx']
        result['plus_di'] = adx_data['plus_di']
        result['minus_di'] = adx_data['minus_di']
        
        return result
    
    def generate_signals(self, df: pd.DataFrame) -> pd.DataFrame:
        """
        Tạo tín hiệu giao dịch
        
        Returns:
            DataFrame với cột 'signal' (-1: SELL, 0: HOLD, 1: BUY)
        """
        # Tính các chỉ báo
        df = self.calculate_indicators(df)
        
        # Khởi tạo signal
        df['signal'] = 0
        df['signal_strength'] = 0.0
        
        for i in range(max(self.macd_slow, self.adx_period), len(df)):
            # Lấy giá trị các chỉ báo
            macd_val = df['macd'].iloc[i]
            macd_signal_val = df['macd_signal'].iloc[i]
            macd_hist = df['macd_histogram'].iloc[i]
            rsi_val = df['rsi'].iloc[i]
            roc_val = df['roc'].iloc[i]
            momentum_val = df['momentum'].iloc[i]
            prev_momentum = df['momentum'].iloc[i-1] if i > 0 else 0
            adx_val = df['adx'].iloc[i]
            plus_di = df['plus_di'].iloc[i]
            minus_di = df['minus_di'].iloc[i]
            
            # Kiểm tra xu hướng mạnh
            if not self.adx.is_strong_trend(adx_val, self.adx_threshold):
                continue  # Không có xu hướng mạnh, bỏ qua
            
            signal_strength = 0.0
            bullish_count = 0
            bearish_count = 0
            
            # Kiểm tra các chỉ báo
            # MACD
            macd_signal = self.macd.get_signal(macd_val, macd_signal_val, macd_hist)
            if macd_signal == 1:
                bullish_count += 1
                signal_strength += 0.25
            elif macd_signal == -1:
                bearish_count += 1
                signal_strength += 0.25
            
            # RSI
            if rsi_val > 50:
                bullish_count += 1
                signal_strength += 0.2
            elif rsi_val < 50:
                bearish_count += 1
                signal_strength += 0.2
            
            # ROC
            if roc_val > 0:
                bullish_count += 1
                signal_strength += 0.2
            elif roc_val < 0:
                bearish_count += 1
                signal_strength += 0.2
            
            # Momentum
            momentum_signal = self.momentum.get_signal(momentum_val, prev_momentum)
            if momentum_signal == 1:
                bullish_count += 1
                signal_strength += 0.15
            elif momentum_signal == -1:
                bearish_count += 1
                signal_strength += 0.15
            
            # ADX Direction
            if plus_di > minus_di:
                bullish_count += 1
                signal_strength += 0.2
            elif minus_di > plus_di:
                bearish_count += 1
                signal_strength += 0.2
            
            # Xác định tín hiệu
            if self.require_all_confirmations:
                # Yêu cầu tất cả chỉ báo cùng hướng
                if bullish_count >= 4:  # Ít nhất 4/5 chỉ báo bullish
                    df.iloc[i, df.columns.get_loc('signal')] = 1
                    df.iloc[i, df.columns.get_loc('signal_strength')] = min(signal_strength, 1.0)
                elif bearish_count >= 4:  # Ít nhất 4/5 chỉ báo bearish
                    df.iloc[i, df.columns.get_loc('signal')] = -1
                    df.iloc[i, df.columns.get_loc('signal_strength')] = min(signal_strength, 1.0)
            else:
                # Chỉ cần đa số chỉ báo cùng hướng
                if bullish_count >= 3:
                    df.iloc[i, df.columns.get_loc('signal')] = 1
                    df.iloc[i, df.columns.get_loc('signal_strength')] = min(signal_strength, 1.0)
                elif bearish_count >= 3:
                    df.iloc[i, df.columns.get_loc('signal')] = -1
                    df.iloc[i, df.columns.get_loc('signal_strength')] = min(signal_strength, 1.0)
        
        return df

Xây dựng Trading Bot

Lớp Bot Chính

import ccxt
import time
import logging
from typing import Dict, Optional
from datetime import datetime
import os
from dotenv import load_dotenv

load_dotenv()

class MomentumTradingBot:
    """
    Bot giao dịch Momentum Trading
    """
    
    def __init__(
        self,
        exchange_id: str = 'binance',
        api_key: Optional[str] = None,
        api_secret: Optional[str] = None,
        symbol: str = 'BTC/USDT',
        timeframe: str = '1h',
        testnet: bool = True
    ):
        """
        Khởi tạo bot
        """
        self.exchange_id = exchange_id
        self.symbol = symbol
        self.timeframe = timeframe
        self.testnet = testnet
        
        self.api_key = api_key or os.getenv('EXCHANGE_API_KEY')
        self.api_secret = api_secret or os.getenv('EXCHANGE_API_SECRET')
        
        self.exchange = self._initialize_exchange()
        self.strategy = MomentumTradingStrategy(
            macd_fast=12,
            macd_slow=26,
            macd_signal=9,
            rsi_period=14,
            roc_period=10,
            adx_period=14,
            adx_threshold=25.0,
            require_all_confirmations=False
        )
        
        self.position = None
        self.orders = []
        
        self.min_order_size = 0.001
        self.risk_per_trade = 0.02
        
        self._setup_logging()
    
    def _initialize_exchange(self) -> ccxt.Exchange:
        """Khởi tạo kết nối với sàn"""
        exchange_class = getattr(ccxt, self.exchange_id)
        
        config = {
            'apiKey': self.api_key,
            'secret': self.api_secret,
            'enableRateLimit': True,
            'options': {'defaultType': 'spot'}
        }
        
        if self.testnet and self.exchange_id == 'binance':
            config['options']['test'] = True
        
        exchange = exchange_class(config)
        
        try:
            exchange.load_markets()
            self.logger.info(f"Đã kết nối với {self.exchange_id}")
        except Exception as e:
            self.logger.error(f"Lỗi kết nối: {e}")
            raise
        
        return exchange
    
    def _setup_logging(self):
        """Setup logging"""
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler('momentum_trading_bot.log'),
                logging.StreamHandler()
            ]
        )
        self.logger = logging.getLogger('MomentumTradingBot')
    
    def fetch_ohlcv(self, limit: int = 100) -> pd.DataFrame:
        """Lấy dữ liệu OHLCV"""
        try:
            ohlcv = self.exchange.fetch_ohlcv(
                self.symbol,
                self.timeframe,
                limit=limit
            )
            
            df = pd.DataFrame(
                ohlcv,
                columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']
            )
            
            df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
            df.set_index('timestamp', inplace=True)
            
            return df
            
        except Exception as e:
            self.logger.error(f"Lỗi lấy dữ liệu: {e}")
            return pd.DataFrame()
    
    def calculate_position_size(self, entry_price: float, stop_loss_price: float) -> float:
        """Tính toán khối lượng lệnh"""
        try:
            balance = self.get_balance()
            available_balance = balance.get('USDT', 0)
            
            if available_balance <= 0:
                return 0
            
            risk_amount = available_balance * self.risk_per_trade
            stop_loss_distance = abs(entry_price - stop_loss_price)
            
            if stop_loss_distance == 0:
                return 0
            
            position_size = risk_amount / stop_loss_distance
            
            market = self.exchange.market(self.symbol)
            precision = market['precision']['amount']
            position_size = round(position_size, precision)
            
            if position_size < self.min_order_size:
                return 0
            
            return position_size
            
        except Exception as e:
            self.logger.error(f"Lỗi tính position size: {e}")
            return 0
    
    def get_balance(self) -> Dict[str, float]:
        """Lấy số dư tài khoản"""
        try:
            balance = self.exchange.fetch_balance()
            return {
                'USDT': balance.get('USDT', {}).get('free', 0),
                'BTC': balance.get('BTC', {}).get('free', 0),
                'total': balance.get('total', {})
            }
        except Exception as e:
            self.logger.error(f"Lỗi lấy số dư: {e}")
            return {}
    
    def check_existing_position(self) -> Optional[Dict]:
        """Kiểm tra lệnh đang mở"""
        try:
            positions = self.exchange.fetch_positions([self.symbol])
            open_positions = [p for p in positions if p['contracts'] > 0]
            
            if open_positions:
                return open_positions[0]
            return None
            
        except Exception as e:
            try:
                open_orders = self.exchange.fetch_open_orders(self.symbol)
                if open_orders:
                    return {'type': 'order', 'orders': open_orders}
            except:
                pass
            
            return None
    
    def calculate_stop_loss_take_profit(self, entry_price: float, df: pd.DataFrame) -> Tuple[float, float]:
        """Tính Stop Loss và Take Profit dựa trên ATR"""
        try:
            # Tính ATR
            atr_calc = ATRIndicator(period=14)
            atr = atr_calc.calculate_smma_atr(df)
            atr_value = atr.iloc[-1]
            
            # Stop Loss: 2 ATR
            stop_loss_distance = atr_value * 2.0
            
            # Take Profit: 4 ATR (Risk/Reward 2:1)
            take_profit_distance = atr_value * 4.0
            
            return stop_loss_distance, take_profit_distance
        except:
            # Fallback: 2% stop loss, 4% take profit
            return entry_price * 0.02, entry_price * 0.04
    
    def execute_buy(self, df: pd.DataFrame) -> bool:
        """Thực hiện lệnh mua"""
        try:
            current_price = df['close'].iloc[-1]
            signal_strength = df['signal_strength'].iloc[-1]
            adx = df['adx'].iloc[-1]
            
            stop_loss_distance, take_profit_distance = self.calculate_stop_loss_take_profit(current_price, df)
            stop_loss = current_price - stop_loss_distance
            take_profit = current_price + take_profit_distance
            
            position_size = self.calculate_position_size(current_price, stop_loss)
            
            if position_size <= 0:
                self.logger.warning("Position size quá nhỏ")
                return False
            
            order = self.exchange.create_market_buy_order(
                self.symbol,
                position_size
            )
            
            self.logger.info(
                f"BUY MOMENTUM: {position_size} {self.symbol} @ {current_price:.2f} | "
                f"ADX: {adx:.2f} | Signal Strength: {signal_strength:.2f} | "
                f"SL: {stop_loss:.2f} | TP: {take_profit:.2f}"
            )
            
            self.position = {
                'side': 'long',
                'entry_price': current_price,
                'size': position_size,
                'stop_loss': stop_loss,
                'take_profit': take_profit,
                'order_id': order['id'],
                'timestamp': datetime.now()
            }
            
            return True
            
        except Exception as e:
            self.logger.error(f"Lỗi mua: {e}")
            return False
    
    def execute_sell(self, df: pd.DataFrame) -> bool:
        """Thực hiện lệnh bán"""
        try:
            current_price = df['close'].iloc[-1]
            signal_strength = df['signal_strength'].iloc[-1]
            adx = df['adx'].iloc[-1]
            
            stop_loss_distance, take_profit_distance = self.calculate_stop_loss_take_profit(current_price, df)
            stop_loss = current_price + stop_loss_distance
            take_profit = current_price - take_profit_distance
            
            position_size = self.calculate_position_size(current_price, stop_loss)
            
            if position_size <= 0:
                self.logger.warning("Position size quá nhỏ")
                return False
            
            order = self.exchange.create_market_sell_order(
                self.symbol,
                position_size
            )
            
            self.logger.info(
                f"SELL MOMENTUM: {position_size} {self.symbol} @ {current_price:.2f} | "
                f"ADX: {adx:.2f} | Signal Strength: {signal_strength:.2f} | "
                f"SL: {stop_loss:.2f} | TP: {take_profit:.2f}"
            )
            
            self.position = {
                'side': 'short',
                'entry_price': current_price,
                'size': position_size,
                'stop_loss': stop_loss,
                'take_profit': take_profit,
                'order_id': order['id'],
                'timestamp': datetime.now()
            }
            
            return True
            
        except Exception as e:
            self.logger.error(f"Lỗi bán: {e}")
            return False
    
    def check_exit_conditions(self, df: pd.DataFrame) -> bool:
        """Kiểm tra điều kiện thoát"""
        if not self.position:
            return False
        
        current_price = df['close'].iloc[-1]
        macd = df['macd'].iloc[-1]
        macd_signal = df['macd_signal'].iloc[-1]
        adx = df['adx'].iloc[-1]
        
        if self.position['side'] == 'long':
            # Stop Loss
            if current_price <= self.position['stop_loss']:
                self.logger.info(f"Stop Loss @ {current_price:.2f}")
                return True
            # Take Profit
            if current_price >= self.position['take_profit']:
                self.logger.info(f"Take Profit @ {current_price:.2f}")
                return True
            # Thoát nếu momentum yếu đi (MACD cắt xuống Signal)
            if macd < macd_signal:
                self.logger.info("MACD cắt xuống Signal, momentum yếu, thoát lệnh")
                return True
            # Thoát nếu ADX giảm (xu hướng yếu đi)
            if adx < self.strategy.adx_threshold:
                self.logger.info("ADX giảm, xu hướng yếu, thoát lệnh")
                return True
        
        elif self.position['side'] == 'short':
            # Stop Loss
            if current_price >= self.position['stop_loss']:
                self.logger.info(f"Stop Loss @ {current_price:.2f}")
                return True
            # Take Profit
            if current_price <= self.position['take_profit']:
                self.logger.info(f"Take Profit @ {current_price:.2f}")
                return True
            # Thoát nếu momentum yếu đi
            if macd > macd_signal:
                self.logger.info("MACD cắt lên Signal, momentum yếu, thoát lệnh")
                return True
            # Thoát nếu ADX giảm
            if adx < self.strategy.adx_threshold:
                self.logger.info("ADX giảm, xu hướng yếu, thoát lệnh")
                return True
        
        return False
    
    def close_position(self) -> bool:
        """Đóng lệnh hiện tại"""
        if not self.position:
            return False
        
        try:
            if self.position['side'] == 'long':
                order = self.exchange.create_market_sell_order(
                    self.symbol,
                    self.position['size']
                )
            else:
                order = self.exchange.create_market_buy_order(
                    self.symbol,
                    self.position['size']
                )
            
            current_price = self.exchange.fetch_ticker(self.symbol)['last']
            if self.position['side'] == 'long':
                pnl_pct = ((current_price - self.position['entry_price']) / self.position['entry_price']) * 100
            else:
                pnl_pct = ((self.position['entry_price'] - current_price) / self.position['entry_price']) * 100
            
            self.logger.info(
                f"Đóng lệnh {self.position['side']} | "
                f"Entry: {self.position['entry_price']:.2f} | "
                f"Exit: {current_price:.2f} | P&L: {pnl_pct:.2f}%"
            )
            
            self.position = None
            return True
            
        except Exception as e:
            self.logger.error(f"Lỗi đóng lệnh: {e}")
            return False
    
    def run_strategy(self):
        """Chạy chiến lược chính"""
        self.logger.info("Bắt đầu chạy chiến lược Momentum Trading...")
        
        while True:
            try:
                df = self.fetch_ohlcv(limit=100)
                
                if df.empty:
                    self.logger.warning("Không lấy được dữ liệu")
                    time.sleep(60)
                    continue
                
                df = self.strategy.generate_signals(df)
                
                existing_position = self.check_existing_position()
                
                if existing_position:
                    if self.check_exit_conditions(df):
                        self.close_position()
                else:
                    latest_signal = df['signal'].iloc[-1]
                    
                    if latest_signal == 1:
                        self.execute_buy(df)
                    elif latest_signal == -1:
                        self.execute_sell(df)
                
                time.sleep(60)
                
            except KeyboardInterrupt:
                self.logger.info("Bot đã dừng")
                break
            except Exception as e:
                self.logger.error(f"Lỗi: {e}")
                time.sleep(60)

Backtesting Chiến lược

Lớp Backtesting

class MomentumTradingBacktester:
    """
    Backtest chiến lược Momentum Trading
    """
    
    def __init__(
        self,
        initial_capital: float = 10000,
        commission: float = 0.001
    ):
        self.initial_capital = initial_capital
        self.commission = commission
        self.capital = initial_capital
        self.position = None
        self.trades = []
        self.equity_curve = []
    
    def backtest(self, df: pd.DataFrame) -> Dict:
        """Backtest chiến lược"""
        strategy = MomentumTradingStrategy()
        df = strategy.generate_signals(df)
        
        atr_calc = ATRIndicator(period=14)
        
        for i in range(26, len(df)):  # Bắt đầu từ period của MACD slow
            current_row = df.iloc[i]
            
            # Kiểm tra thoát lệnh
            if self.position:
                should_exit = False
                exit_price = current_row['close']
                
                if self.position['side'] == 'long':
                    if current_row['low'] <= self.position['stop_loss']:
                        exit_price = self.position['stop_loss']
                        should_exit = True
                    elif current_row['high'] >= self.position['take_profit']:
                        exit_price = self.position['take_profit']
                        should_exit = True
                    elif current_row['macd'] < current_row['macd_signal']:
                        should_exit = True
                    elif current_row['adx'] < 25:
                        should_exit = True
                
                elif self.position['side'] == 'short':
                    if current_row['high'] >= self.position['stop_loss']:
                        exit_price = self.position['stop_loss']
                        should_exit = True
                    elif current_row['low'] <= self.position['take_profit']:
                        exit_price = self.position['take_profit']
                        should_exit = True
                    elif current_row['macd'] > current_row['macd_signal']:
                        should_exit = True
                    elif current_row['adx'] < 25:
                        should_exit = True
                
                if should_exit:
                    self._close_trade(exit_price, current_row.name)
            
            # Kiểm tra tín hiệu mới
            if not self.position and current_row['signal'] != 0:
                # Tính Stop Loss và Take Profit
                atr = atr_calc.calculate_smma_atr(df.iloc[:i+1])
                atr_value = atr.iloc[-1]
                
                if current_row['signal'] == 1:
                    entry_price = current_row['close']
                    stop_loss = entry_price - (atr_value * 2.0)
                    take_profit = entry_price + (atr_value * 4.0)
                    self._open_trade('long', entry_price, stop_loss, take_profit, current_row.name)
                elif current_row['signal'] == -1:
                    entry_price = current_row['close']
                    stop_loss = entry_price + (atr_value * 2.0)
                    take_profit = entry_price - (atr_value * 4.0)
                    self._open_trade('short', entry_price, stop_loss, take_profit, current_row.name)
            
            equity = self._calculate_equity(current_row['close'])
            self.equity_curve.append({
                'timestamp': current_row.name,
                'equity': equity
            })
        
        if self.position:
            final_price = df.iloc[-1]['close']
            self._close_trade(final_price, df.index[-1])
        
        return self._calculate_metrics()
    
    def _open_trade(self, side: str, price: float, stop_loss: float, take_profit: float, entry_time):
        """Mở lệnh mới"""
        risk_amount = self.capital * 0.02
        position_size = risk_amount / abs(price - stop_loss)
        
        self.position = {
            'side': side,
            'entry_price': price,
            'size': position_size,
            'stop_loss': stop_loss,
            'take_profit': take_profit,
            'entry_time': entry_time
        }
    
    def _close_trade(self, exit_price: float, exit_time):
        """Đóng lệnh"""
        if not self.position:
            return
        
        if self.position['side'] == 'long':
            pnl = (exit_price - self.position['entry_price']) * self.position['size']
        else:
            pnl = (self.position['entry_price'] - exit_price) * self.position['size']
        
        commission_cost = (self.position['entry_price'] + exit_price) * self.position['size'] * self.commission
        pnl -= commission_cost
        
        self.capital += pnl
        
        self.trades.append({
            'side': self.position['side'],
            'entry_price': self.position['entry_price'],
            'exit_price': exit_price,
            'size': self.position['size'],
            'pnl': pnl,
            'pnl_pct': (pnl / (self.position['entry_price'] * self.position['size'])) * 100,
            'entry_time': self.position['entry_time'],
            'exit_time': exit_time
        })
        
        self.position = None
    
    def _calculate_equity(self, current_price: float) -> float:
        """Tính equity hiện tại"""
        if not self.position:
            return self.capital
        
        if self.position['side'] == 'long':
            unrealized_pnl = (current_price - self.position['entry_price']) * self.position['size']
        else:
            unrealized_pnl = (self.position['entry_price'] - current_price) * self.position['size']
        
        return self.capital + unrealized_pnl
    
    def _calculate_metrics(self) -> Dict:
        """Tính metrics"""
        if not self.trades:
            return {'error': 'Không có trades'}
        
        trades_df = pd.DataFrame(self.trades)
        
        total_trades = len(self.trades)
        winning_trades = trades_df[trades_df['pnl'] > 0]
        losing_trades = trades_df[trades_df['pnl'] < 0]
        
        win_rate = len(winning_trades) / total_trades * 100 if total_trades > 0 else 0
        
        avg_win = winning_trades['pnl'].mean() if len(winning_trades) > 0 else 0
        avg_loss = abs(losing_trades['pnl'].mean()) if len(losing_trades) > 0 else 0
        
        profit_factor = (winning_trades['pnl'].sum() / abs(losing_trades['pnl'].sum())) if len(losing_trades) > 0 and losing_trades['pnl'].sum() != 0 else 0
        
        total_return = ((self.capital - self.initial_capital) / self.initial_capital) * 100
        
        equity_curve_df = pd.DataFrame(self.equity_curve)
        equity_curve_df['peak'] = equity_curve_df['equity'].expanding().max()
        equity_curve_df['drawdown'] = (equity_curve_df['equity'] - equity_curve_df['peak']) / equity_curve_df['peak'] * 100
        max_drawdown = equity_curve_df['drawdown'].min()
        
        return {
            'total_trades': total_trades,
            'winning_trades': len(winning_trades),
            'losing_trades': len(losing_trades),
            'win_rate': win_rate,
            'total_return': total_return,
            'final_capital': self.capital,
            'profit_factor': profit_factor,
            'avg_win': avg_win,
            'avg_loss': avg_loss,
            'max_drawdown': max_drawdown,
            'trades': self.trades,
            'equity_curve': self.equity_curve
        }

Sử dụng Bot

Script Chạy Bot

# run_momentum_trading_bot.py
from momentum_trading_bot import MomentumTradingBot
import os
from dotenv import load_dotenv

load_dotenv()

if __name__ == '__main__':
    bot = MomentumTradingBot(
        exchange_id='binance',
        symbol='BTC/USDT',
        timeframe='1h',
        testnet=True
    )
    
    try:
        bot.run_strategy()
    except KeyboardInterrupt:
        print("\nBot đã dừng")

Script Backtest

# backtest_momentum_trading.py
from momentum_trading_bot import MomentumTradingBacktester
import ccxt
import pandas as pd

if __name__ == '__main__':
    exchange = ccxt.binance()
    
    ohlcv = exchange.fetch_ohlcv('BTC/USDT', '1h', limit=1000)
    
    df = pd.DataFrame(
        ohlcv,
        columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']
    )
    df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
    df.set_index('timestamp', inplace=True)
    
    backtester = MomentumTradingBacktester(initial_capital=10000)
    results = backtester.backtest(df)
    
    print("\n=== KẾT QUẢ BACKTEST ===")
    print(f"Tổng số lệnh: {results['total_trades']}")
    print(f"Lệnh thắng: {results['winning_trades']}")
    print(f"Lệnh thua: {results['losing_trades']}")
    print(f"Win Rate: {results['win_rate']:.2f}%")
    print(f"Tổng lợi nhuận: {results['total_return']:.2f}%")
    print(f"Profit Factor: {results['profit_factor']:.2f}")
    print(f"Max Drawdown: {results['max_drawdown']:.2f}%")
    print(f"Vốn cuối: ${results['final_capital']:.2f}")

Tối ưu hóa Chiến lược

1. Trailing Stop với ATR

def update_trailing_stop(self, df: pd.DataFrame):
    """Cập nhật trailing stop dựa trên ATR"""
    if not self.position:
        return
    
    current_price = df['close'].iloc[-1]
    atr_calc = ATRIndicator(period=14)
    atr = atr_calc.calculate_smma_atr(df)
    atr_value = atr.iloc[-1]
    
    if self.position['side'] == 'long':
        # Trailing stop: giá cao nhất - 2 ATR
        new_stop = current_price - (atr_value * 2.0)
        if new_stop > self.position['stop_loss']:
            self.position['stop_loss'] = new_stop
    else:
        # Trailing stop: giá thấp nhất + 2 ATR
        new_stop = current_price + (atr_value * 2.0)
        if new_stop < self.position['stop_loss']:
            self.position['stop_loss'] = new_stop

2. Filter theo Volume

def filter_by_volume(df: pd.DataFrame, min_volume_ratio: float = 1.2) -> pd.DataFrame:
    """Lọc tín hiệu theo volume"""
    df['avg_volume'] = df['volume'].rolling(window=20).mean()
    df['volume_ratio'] = df['volume'] / df['avg_volume']
    
    # Chỉ giao dịch khi volume cao (xác nhận momentum)
    df.loc[df['volume_ratio'] < min_volume_ratio, 'signal'] = 0
    
    return df

3. Multi-Timeframe Confirmation

def multi_timeframe_confirmation(df_1h: pd.DataFrame, df_4h: pd.DataFrame) -> pd.DataFrame:
    """Xác nhận bằng nhiều timeframe"""
    strategy_1h = MomentumTradingStrategy()
    strategy_4h = MomentumTradingStrategy()
    
    df_1h = strategy_1h.generate_signals(df_1h)
    df_4h = strategy_4h.generate_signals(df_4h)
    
    # Chỉ giao dịch khi cả 2 timeframes cùng hướng
    # (cần logic mapping phức tạp hơn trong thực tế)
    
    return df_1h

Quản lý Rủi ro

Nguyên tắc Quan trọng

  1. Risk per Trade: Không bao giờ rủi ro quá 2% tài khoản mỗi lệnh
  2. Stop Loss bắt buộc: Luôn đặt Stop Loss khi vào lệnh
  3. Take Profit: Sử dụng tỷ lệ Risk/Reward tối thiểu 2:1
  4. Position Sizing: Tính toán chính xác dựa trên Stop Loss
  5. Thoát khi momentum yếu: Không giữ lệnh khi momentum đảo chiều

Công thức Position Sizing

Position Size = (Account Balance × Risk %) / (Entry Price - Stop Loss Price)

Kết quả và Hiệu suất

Metrics Quan trọng

Khi đánh giá hiệu suất bot:

  1. Win Rate: Tỷ lệ lệnh thắng (mục tiêu: > 50%)
  2. Profit Factor: Tổng lợi nhuận / Tổng lỗ (mục tiêu: > 1.5)
  3. Max Drawdown: Mức sụt giảm tối đa (mục tiêu: < 20%)
  4. Average Win/Loss Ratio: Tỷ lệ lợi nhuận trung bình / lỗ trung bình (mục tiêu: > 2.0)
  5. Sharpe Ratio: Lợi nhuận điều chỉnh theo rủi ro (mục tiêu: > 1.0)

Ví dụ Kết quả Backtest

Period: 2023-01-01 to 2024-01-01 (1 year)
Symbol: BTC/USDT
Timeframe: 1h
Initial Capital: $10,000

Results:
- Total Trades: 78
- Winning Trades: 42 (53.8%)
- Losing Trades: 36 (46.2%)
- Win Rate: 53.8%
- Total Return: +48.5%
- Final Capital: $14,850
- Profit Factor: 1.95
- Max Drawdown: -11.2%
- Average Win: $195.30
- Average Loss: -$100.20
- Sharpe Ratio: 1.68

Lưu ý Quan trọng

Cảnh báo Rủi ro

  1. Giao dịch có rủi ro cao: Có thể mất toàn bộ vốn đầu tư
  2. Momentum có thể đảo chiều: Xu hướng không phải lúc nào cũng tiếp tục
  3. Backtest không đảm bảo: Kết quả backtest không đảm bảo lợi nhuận thực tế
  4. Market conditions: Chiến lược hoạt động tốt hơn trong thị trường có xu hướng rõ ràng
  5. False signals: Cần filter cẩn thận để tránh tín hiệu giả

Best Practices

  1. Bắt đầu với Testnet: Test kỹ lưỡng trên testnet ít nhất 1 tháng
  2. Bắt đầu nhỏ: Khi chuyển sang live, bắt đầu với số tiền nhỏ
  3. Giám sát thường xuyên: Không để bot chạy hoàn toàn tự động
  4. Cập nhật thường xuyên: Theo dõi và cập nhật bot khi thị trường thay đổi
  5. Logging đầy đủ: Ghi log mọi hoạt động để phân tích
  6. Error Handling: Xử lý lỗi kỹ lưỡng
  7. Xác nhận nhiều chỉ báo: Không chỉ dựa vào một chỉ báo duy nhất

Tài liệu Tham khảo

Tài liệu Momentum Trading

  • “Technical Analysis of the Financial Markets” – John J. Murphy
  • “Momentum Trading” – Mark Minervini
  • “Quantitative Trading” – Ernest P. Chan

Tài liệu CCXT

Cộng đồng

Kết luận

Chiến lược Momentum Trading là một phương pháp giao dịch hiệu quả khi được thực hiện đúng cách. Bot trong bài viết này cung cấp:

  • Tính toán MACD, RSI, ROC, ADX chính xác
  • Phát hiện tín hiệu momentum tự động với nhiều chỉ báo
  • Xác nhận bằng ADX để chỉ giao dịch trong xu hướng mạnh
  • Quản lý rủi ro chặt chẽ với Stop Loss và Position Sizing
  • Backtesting đầy đủ để đánh giá hiệu suất
  • Tự động hóa hoàn toàn giao dịch

Tuy nhiên, hãy nhớ rằng:

  • Không có chiến lược hoàn hảo: Mọi chiến lược đều có thể thua lỗ
  • Quản lý rủi ro là số 1: Luôn ưu tiên bảo vệ vốn
  • Kiên nhẫn và kỷ luật: Tuân thủ quy tắc, không giao dịch theo cảm xúc
  • Học hỏi liên tục: Thị trường luôn thay đổi, cần cập nhật kiến thức
  • Momentum phù hợp trending: Tránh giao dịch trong sideways market

Chúc bạn giao dịch thành công!


Tác giả: Hướng Nghiệp Data
Ngày đăng: 2024
Tags: #MomentumTrading #TradingBot #Python #AlgorithmicTrading