Rebalancing danh mục đầu tư là một trong những chiến lược quản lý vốn đã được chứng minh qua nhiều thập kỷ. Bot Python có thể tự động hóa toàn bộ quy trình này — từ tính toán tỷ trọng, thực hiện giao dịch đến báo cáo hiệu suất.
Tại Sao Cần Rebalancing Tự Động?
Giả sử danh mục mục tiêu của bạn: 60% BTC, 30% ETH, 10% USDT.
Sau 3 tháng BTC tăng mạnh, tỷ trọng tự nhiên thay đổi: 75% BTC, 20% ETH, 5% USDT. Danh mục bây giờ quá tập trung vào BTC — rủi ro tăng cao.
Rebalancing = bán bớt BTC, mua thêm ETH/USDT để về đúng 60/30/10. Bot làm điều này tự động, kỷ luật, không cảm xúc.
Cấu Trúc Bot Portfolio
from dataclasses import dataclass
from typing import Dict
import ccxt
import pandas as pd
from datetime import datetime
@dataclass
class PortfolioConfig:
target_weights: Dict[str, float] # {"BTC/USDT": 0.6, "ETH/USDT": 0.3}
rebalance_threshold: float = 0.05 # Rebalance khi lệch > 5%
min_trade_value: float = 10.0 # Không trade nếu < $10
class PortfolioBot:
def __init__(self, exchange: ccxt.Exchange, config: PortfolioConfig):
self.exchange = exchange
self.config = config
def get_portfolio_value(self) -> Dict[str, float]:
"""Lấy giá trị hiện tại của danh mục"""
balance = self.exchange.fetch_balance()
tickers = self.exchange.fetch_tickers(list(self.config.target_weights.keys()))
portfolio = {}
for symbol, weight in self.config.target_weights.items():
coin = symbol.split('/')[0]
qty = balance.get(coin, {}).get('total', 0)
price = tickers[symbol]['last']
portfolio[symbol] = qty * price
return portfolio
def calculate_rebalance_orders(self) -> list:
"""Tính toán các lệnh cần thiết để rebalance"""
portfolio = self.get_portfolio_value()
total_value = sum(portfolio.values())
orders = []
for symbol, target_weight in self.config.target_weights.items():
current_value = portfolio.get(symbol, 0)
current_weight = current_value / total_value
target_value = total_value * target_weight
diff = target_value - current_value
diff_weight = abs(current_weight - target_weight)
# Chỉ rebalance nếu lệch đủ nhiều
if (diff_weight > self.config.rebalance_threshold and
abs(diff) > self.config.min_trade_value):
orders.append({
'symbol': symbol,
'side': 'buy' if diff > 0 else 'sell',
'value': abs(diff),
'current_weight': f"{current_weight*100:.1f}%",
'target_weight': f"{target_weight*100:.1f}%"
})
return orders
def execute_rebalance(self):
"""Thực hiện rebalance"""
orders = self.calculate_rebalance_orders()
if not orders:
print(f"{datetime.now()}: Portfolio cân bằng, không cần rebalance")
return
for order in orders:
ticker = self.exchange.fetch_ticker(order['symbol'])
price = ticker['last']
quantity = order['value'] / price
print(f"Rebalancing: {order['side'].upper()} {order['symbol']} "
f"${order['value']:.2f} | "
f"{order['current_weight']} → {order['target_weight']}")
self.exchange.create_market_order(
order['symbol'],
order['side'],
round(quantity, 6)
)
Tính Hiệu Suất Danh Mục
def calculate_performance(portfolio_history: pd.DataFrame) -> dict:
"""
portfolio_history: DataFrame với index=date, column='value'
"""
daily_returns = portfolio_history['value'].pct_change().dropna()
total_return = (portfolio_history['value'].iloc[-1] /
portfolio_history['value'].iloc[0] - 1) * 100
# Sharpe Ratio (giả sử risk-free rate = 4%/năm)
rf_daily = 0.04 / 252
sharpe = (daily_returns.mean() - rf_daily) / daily_returns.std() * (252 ** 0.5)
# Max Drawdown
rolling_max = portfolio_history['value'].cummax()
drawdown = (portfolio_history['value'] - rolling_max) / rolling_max
max_drawdown = drawdown.min() * 100
return {
'total_return': f"{total_return:.2f}%",
'sharpe_ratio': f"{sharpe:.2f}",
'max_drawdown': f"{max_drawdown:.2f}%",
'volatility': f"{daily_returns.std() * (252**0.5) * 100:.2f}%"
}
Gửi Báo Cáo Telegram Hàng Ngày
import requests
def send_telegram_report(bot_token: str, chat_id: str, report: dict):
portfolio = report['portfolio']
perf = report['performance']
message = f"""
📊 *Báo Cáo Danh Mục Hôm Nay*
📅 {datetime.now().strftime('%d/%m/%Y %H:%M')}
💼 *Tổng giá trị:* ${report['total_value']:,.2f}
*Phân bổ hiện tại:*
"""
for symbol, data in portfolio.items():
message += f"• {symbol}: ${data['value']:,.2f} ({data['weight']:.1f}%)\n"
message += f"""
📈 *Hiệu suất:*
• Tổng lợi nhuận: {perf['total_return']}
• Sharpe Ratio: {perf['sharpe_ratio']}
• Max Drawdown: {perf['max_drawdown']}
"""
requests.post(
f"https://api.telegram.org/bot{bot_token}/sendMessage",
json={"chat_id": chat_id, "text": message, "parse_mode": "Markdown"}
)
Lên Lịch Chạy Tự Động
import schedule
bot = PortfolioBot(exchange, PortfolioConfig(
target_weights={"BTC/USDT": 0.6, "ETH/USDT": 0.3, "BNB/USDT": 0.1}
))
# Rebalance mỗi tuần vào thứ Hai lúc 8:00
schedule.every().monday.at("08:00").do(bot.execute_rebalance)
# Báo cáo hàng ngày lúc 20:00
schedule.every().day.at("20:00").do(lambda: send_telegram_report(...))
while True:
schedule.run_pending()
time.sleep(60)
Xây dựng hệ thống quản lý danh mục đầu tư tự động hoàn chỉnh tại Hướng Nghiệp Dữ Liệu.
Câu hỏi thường gặp
Nên rebalance danh mục bao lâu một lần?
Phổ biến là theo quý hoặc khi tỷ trọng lệch khỏi mục tiêu hơn 5 điểm phần trăm (rebalancing theo ngưỡng). Định kỳ cố định giúp đơn giản và dễ kỷ luật, còn theo ngưỡng giúp tiết kiệm phí giao dịch trong giai đoạn thị trường ổn định.
Rebalancing có làm giảm lợi nhuận không?
Về lợi nhuận tuyệt đối trong thị trường tăng mạnh, rebalancing có thể kém hơn để danh mục tự chạy vì bạn bán bớt tài sản đang tăng tốt. Đổi lại, nó giảm biến động danh mục và giúp bạn tránh tình trạng một tài sản chiếm tỷ trọng quá lớn gây rủi ro tập trung.
Bot rebalancing cần kết nối API đặt lệnh không?
Không bắt buộc. Nhiều người dùng bot ở chế độ báo cáo: bot tính toán danh mục và gửi thông báo qua Telegram, sau đó tự đặt lệnh thủ công. Cách này giảm rủi ro kỹ thuật và phù hợp khi API đặt lệnh của công ty chứng khoán còn hạn chế.
