Bài viết gần đây
-
-
Phân Biệt MySQL Và PostgreSQL
Tháng 1 1, 2026 -
Gen Z Việt Nam trước làn sóng Web3
Tháng 12 29, 2025
| 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ả?
- 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
- Phù hợp thị trường trending: Hoạt động tốt khi có xu hướng rõ ràng
- Lợi nhuận cao: Có thể bắt được toàn bộ xu hướng
- Tín hiệu rõ ràng: Các chỉ báo momentum dễ nhận diện
- 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
- MACD (Moving Average Convergence Divergence): Đo lường sự thay đổi momentum
- RSI (Relative Strength Index): Xác định momentum và quá mua/quá bán
- Rate of Change (ROC): Tốc độ thay đổi giá
- Momentum Indicator: Chênh lệch giá giữa hai thời điểm
- Stochastic Oscillator: So sánh giá đóng cửa với phạm vi giá
- 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
.whltừ đây. - Đối với Linux/Mac:
sudo apt-get install ta-libhoặcbrew 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
- Xác định xu hướng: Sử dụng ADX để xác định xu hướng mạnh
- Xác định hướng: Sử dụng MACD, RSI để xác định hướng momentum
- 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
- Quản lý rủi ro: Stop Loss và Take Profit dựa trên ATR
- 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
- Risk per Trade: Không bao giờ rủi ro quá 2% tài khoản mỗi lệnh
- Stop Loss bắt buộc: Luôn đặt Stop Loss khi vào lệnh
- Take Profit: Sử dụng tỷ lệ Risk/Reward tối thiểu 2:1
- Position Sizing: Tính toán chính xác dựa trên Stop Loss
- 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:
- Win Rate: Tỷ lệ lệnh thắng (mục tiêu: > 50%)
- Profit Factor: Tổng lợi nhuận / Tổng lỗ (mục tiêu: > 1.5)
- Max Drawdown: Mức sụt giảm tối đa (mục tiêu: < 20%)
- Average Win/Loss Ratio: Tỷ lệ lợi nhuận trung bình / lỗ trung bình (mục tiêu: > 2.0)
- 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
- Giao dịch có rủi ro cao: Có thể mất toàn bộ vốn đầu tư
- Momentum có thể đảo chiều: Xu hướng không phải lúc nào cũng tiếp tục
- Backtest không đảm bảo: Kết quả backtest không đảm bảo lợi nhuận thực tế
- Market conditions: Chiến lược hoạt động tốt hơn trong thị trường có xu hướng rõ ràng
- False signals: Cần filter cẩn thận để tránh tín hiệu giả
Best Practices
- Bắt đầu với Testnet: Test kỹ lưỡng trên testnet ít nhất 1 tháng
- Bắt đầu nhỏ: Khi chuyển sang live, bắt đầu với số tiền nhỏ
- Giám sát thường xuyên: Không để bot chạy hoàn toàn tự động
- Cập nhật thường xuyên: Theo dõi và cập nhật bot khi thị trường thay đổi
- Logging đầy đủ: Ghi log mọi hoạt động để phân tích
- Error Handling: Xử lý lỗi kỹ lưỡng
- 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