Quản trị rủi ro tự động: SL, TP và position sizing
Trader thủ công thường chết vì cảm xúc. Trader dùng bot chết vì hai lỗi rất cụ thể: vào lệnh quá lớn so với tài khoản, và không có cơ chế dừng khi đang thua. Cả hai đều là lỗi lập trình, không phải lỗi thị trường.
Điểm hay của quản trị rủi ro tự động là: nó là phần dễ kiểm chứng nhất trong cả hệ thống. Bạn không cần dự đoán thị trường; bạn chỉ cần trả lời trước ba câu — mỗi lệnh rủi ro bao nhiêu, tổng rủi ro đang mở là bao nhiêu, khi nào bot tự dừng.
Bài viết này đi thẳng vào code: công thức position sizing có xử lý lot và giá trị lệnh tối thiểu, 5 kiểu stop loss và khi nào dùng, lộ trình break-even rồi trailing, một state machine quản lý vị thế, circuit breaker theo drawdown ngày, giới hạn rủi ro danh mục, và bộ chỉ số để biết hệ thống có thực sự có lợi thế.
Đọc trước: Pine Script cơ bản: tự tạo chỉ báo và alert để tín hiệu của bạn gửi kèmslvàatr.
Trả lời nhanh
Quản trị rủi ro tự động gồm bốn lớp, và lớp nào cũng phải ở trong code, không ở trong đầu bạn:
- Position sizing — khối lượng tính từ rủi ro cho phép, không từ "cảm giác vào nhiều hay ít":
khối lượng = (tài khoản × rủi ro %) / khoảng cách stop. - Stop loss — điểm thoát bắt buộc, xác định trước khi vào lệnh, dựa trên ATR hoặc cấu trúc giá, không dựa trên số tiền bạn sẵn sàng mất.
- Quản lý vị thế — dời stop về hoà vốn khi đạt 1R, bật trailing khi đạt 1,5R, và chỉ dời stop theo hướng giảm rủi ro, không bao giờ dời ngược.
- Circuit breaker — bot tự dừng khi lỗ trong ngày vượt ngưỡng, khi thua liên tiếp N lệnh, khi vượt số lệnh/ngày, hoặc khi mất kết nối API.
Ba con số nên chốt ngay từ đầu: rủi ro 0,5–1% tài khoản mỗi lệnh, tổng rủi ro mở tối đa 3–6%, và trần lỗ ngày 3%. Với tài khoản nhỏ, hãy giảm số lệnh chứ đừng tăng rủi ro mỗi lệnh.
Nguyên tắc xuyên suốt: bot không bao giờ được phép mở một lệnh mà không có stop, và không bao giờ được phép vào lệnh nếu tín hiệu không kèm khoảng cách stop.
Rủi ro phải do code quản lý, không do cảm xúc
Khi bạn tự giao dịch, bạn có một cơ chế "phanh" tự nhiên: cảm giác sợ. Khi bạn dùng bot, cơ chế đó biến mất hoàn toàn. Bot không sợ, không mệt, không do dự — và cũng không tự biết dừng. Nếu bạn không viết luật dừng, bot sẽ giao dịch đủ 24/7 cho tới khi tài khoản cạn.
Có một hiệu ứng tâm lý thú vị nhưng nguy hiểm: bot "nhìn" ổn định hơn bạn, nên bạn dễ tăng khối lượng hoặc tắt stop sau vài lệnh thắng. Đây chính là lúc rủi ro thật sự xuất hiện — không phải khi bot lỗ, mà khi bạn bắt đầu tin bot.
Cách chống lại là biến mọi quyết định rủi ro thành hằng số trong file cấu hình, không phải biến bạn gõ vào lệnh:
RISK = {
"risk_per_trade_pct": 0.75, # rui ro moi lenh
"max_portfolio_risk_pct": 4.0,# tong rui ro mo cung luc
"daily_loss_limit_pct": 3.0, # tran lo trong ngay
"max_trades_per_day": 6,
"max_consecutive_losses": 4,
"max_positions": 3, # so vi the mo cung luc
"min_rr": 2.0, # ty le lai/rui ro toi thieu de vao lenh
}Hai lợi ích rất thực tế của việc để cấu hình trong code:
- Bạn có thể review nó như review code. Sửa rủi ro thành một commit, có lịch sử, có lý do — thay vì "hôm đó tôi thấy tự tin".
- Bot có thể từ chối lệnh. Nếu tín hiệu không đủ điều kiện rủi ro (R:R dưới 2, số vị thế đã đủ, đã chạm trần lỗ ngày), bot không vào lệnh — và ghi log lý do. Việc "không vào lệnh" cũng là một hành động có giá trị.
Ba con số phải chốt trước khi viết dòng code đầu tiên
Rủi ro mỗi lệnh (risk per trade). Đây là số tiền tối đa bạn mất nếu stop bị chạm, tính theo % tài khoản. Với bot, dải hợp lý là 0,25% – 1%. Trên 2% mỗi lệnh, chỉ cần 5–6 lệnh thua liên tiếp là bạn mất 10–12% tài khoản — hoàn toàn bình thường với một chiến lược tốt.
Tổng rủi ro mở (portfolio risk). Nếu mỗi lệnh rủi ro 1% và bot mở 6 lệnh cùng lúc, bạn đang rủi ro 6% cho một cú đảo chiều thị trường — chưa kể các mã thường cùng giảm cùng lúc. Dải hợp lý: 3% – 6%, tuỳ mức tương quan giữa các mã bot giao dịch.
Trần lỗ ngày (daily loss limit). Khi chạm ngưỡng này, bot dừng mở lệnh mới hết ngày (vị thế đang mở vẫn được quản lý). Dải hợp lý: 2% – 4%. Đây là cơ chế chống "ngày tệ hại" mạnh nhất mà bạn có thể lập trình trong 20 dòng.
Một bảng tham chiếu theo kích thước tài khoản:
| Tài khoản | Rủi ro/lệnh (0,75%) | Tối đa mở cùng lúc (4%) | Trần lỗ ngày (3%) |
|---|---|---|---|
| 1.000 USDT | 7,5 USDT | 40 USDT | 30 USDT |
| 5.000 USDT | 37,5 USDT | 200 USDT | 150 USDT |
| 20.000 USDT | 150 USDT | 800 USDT | 600 USDT |
| 100.000 USDT | 750 USDT | 4.000 USDT | 3.000 USDT |
Với tài khoản nhỏ, sai lầm phổ biến là tăng rủi ro mỗi lệnh lên 3–5% để "có lãi đáng kể". Cách đúng là giữ rủi ro, giảm số lệnh và tăng chất lượng tín hiệu — hoặc chấp nhận rằng tài khoản nhỏ cần thời gian.
Position sizing: công thức cốt lõi và các biến thể
Công thức gốc, dùng cho mọi thị trường:
khối lượng = (tài khoản × rủi ro %) / khoảng cách stop
Ví dụ: tài khoản 10.000 USDT, rủi ro 0,75% = 75 USDT, khoảng cách stop 1.500 USDT (BTC) → khối lượng ≈ 0,05 BTC.
Bốn biến thể và khi nào dùng:
| Phương pháp | Công thức | Ưu điểm | Nhược điểm |
|---|---|---|---|
| Fixed fractional | equity × risk% / stop_dist | Rủi ro luôn cố định theo tiền | Khối lượng đổi liên tục, nhỏ khi stop xa |
| ATR-based | equity × risk% / (ATR × mult) | Thích ứng biến động từng thị trường | Phụ thuộc chất lượng ATR |
| % equity (allocation) | equity × alloc% / price | Đơn giản, dễ hiểu | Rủi ro thật khác nhau mỗi lệnh |
| Fixed value | 1.000 USDT / price | Dễ kiểm soát, tốt khi mới chạy | Không co giãn theo tài khoản |
Với bot, thứ tự khuyến nghị là: fixed fractional theo ATR ở tầng chính, cộng thêm giới hạn giá trị lệnh tối đa (ví dụ không quá 20% tài khoản cho một lệnh) và khối lượng tối thiểu theo quy định của sàn. Hai lớp phụ này quan trọng vì chúng chặn những trường hợp công thức chính sinh ra con số vô lý — điển hình là khi stop quá gần khiến khối lượng phình to.
Ví dụ điển hình: stop chỉ cách entry 0,05% (nến quá nhỏ ở khung M1). Công thức chính cho ra khối lượng khổng lồ, gấp 20 lần bình thường. Chỉ cần một cú trượt giá hoặc một cây nến bất thường là tài khoản bốc hơi. Vì vậy giới hạn khối lượng theo giá trị tuyệt đối là bắt buộc, không phải tuỳ chọn.
Code: hàm tính khối lượng an toàn
Hàm dưới đây xử lý đủ các trường hợp thực tế: làm tròn theo lot, giới hạn giá trị lệnh tối đa, kiểm tra giá trị lệnh tối thiểu, và trả về 0 để bỏ tín hiệu khi kết quả không hợp lệ.
import math
def position_size(
equity: float,
risk_pct: float,
entry: float,
stop: float,
lot: float = 0.0, # 0.001 crypto, 0.01 FX, 100 co phieu VN
min_qty: float = 0.0,
max_notional_pct: float = 20.0,
leverage_cap: float | None = None,
) -> float:
"""Tra ve khoi luong hop le, hoac 0 neu nen bo tin hieu."""
dist = abs(entry - stop)
if dist <= 0 or entry <= 0:
return 0.0
risk_amount = equity * risk_pct / 100.0
qty = risk_amount / dist
# 1) khong cho mot lenh vuot qua max_notional_pct tai khoan
max_qty_by_notional = (equity * max_notional_pct / 100.0) / entry
qty = min(qty, max_qty_by_notional)
# 2) gioi han theo don bay neu can
if leverage_cap:
qty = min(qty, equity * leverage_cap / entry)
# 3) lam tron xuong theo lot
if lot > 0:
qty = math.floor(qty / lot) * lot
else:
qty = round(qty, 8)
# 4) kiem tra lai rui ro that sau khi lam tron
real_risk_pct = qty * dist / equity * 100.0
if real_risk_pct > risk_pct * 1.5:
return 0.0 # lam tron lam rui ro phinh qua lon -> bo
if qty < min_qty or qty <= 0:
return 0.0
return qtyHai chi tiết trong hàm này đáng để ý:
Bước 4 — kiểm tra rủi ro thật sau khi làm tròn. Với chứng khoán Việt Nam (lot 100) hoặc crypto (lot nhỏ nhưng tài khoản nhỏ), làm tròn có thể khiến rủi ro thật lớn hơn kế hoạch 30–40%. Nếu vượt 1,5 lần kế hoạch, bỏ tín hiệu là quyết định đúng.
Trả về 0 thay vì "khối lượng nhỏ nhất". Rất nhiều bot tự động nâng lên khối lượng tối thiểu của sàn để "vào cho bằng được". Đó là cách biến một lệnh không nên tồn tại thành một lệnh có rủi ro không kiểm soát.
Hàm này nhận entry và stop — cả hai phải đến từ tín hiệu TradingView, không phải từ cảm hứng của bạn. Đây là lý do ở bài Pine Script, payload alert được thiết kế để luôn có sl hoặc atr.
Năm kiểu stop loss và khi nào dùng
Không có "stop tốt nhất". Mỗi kiểu stop phù hợp với một loại chiến lược:
| Kiểu stop | Cách tính | Phù hợp | Rủi ro |
|---|---|---|---|
| Cố định (theo giá) | entry ± n pip/point | Thị trường ổn định, khung lớn | Không thích ứng biến động |
| Theo ATR | entry ∓ ATR × mult | Hầu hết chiến lược, đa thị trường | ATR co lại khi thị trường nén |
| Theo cấu trúc | Đáy/đỉnh gần nhất ± đệm | Breakout, swing trading | Bị quét khi biến động mạnh |
| Trailing | Bám theo đỉnh/đáy đã đi qua | Xu hướng dài | Thoát sớm khi giá dao động |
| Theo thời gian | Đóng sau N nến/n phút | Chiến lược ngắn, scalp | Không liên quan tới giá |
Với bot, stop theo ATR là lựa chọn mặc định hợp lý nhất. Ba lý do:
- Tự co giãn theo thị trường — cùng một chiến lược chạy được trên vàng, BTC và EURUSD mà không phải đổi tham số.
- Rủi ro đo được — khoảng cách stop chính là mẫu số của công thức position sizing.
- Kiểm chứng được — bạn có thể backtest với
atr × multvà tìm được dải tham số ổn định.
Điều quan trọng hơn cả kiểu stop là kỷ luật cấu trúc: stop phải được tính từ giá vào lệnh, không phải từ số tiền bạn muốn mất. Nếu bạn tính "tôi sẵn sàng mất 50 USDT, vậy stop ở đâu?" thì câu trả lời phụ thuộc vào khối lượng — cách đó dẫn tới stop đặt ở những chỗ vô nghĩa về mặt kỹ thuật.
Với stop theo thời gian, hãy dùng như lớp bổ sung, không phải lớp chính. Ví dụ: "nếu sau 20 nến vị thế vẫn chưa đạt 0,5R thì đóng" — quy tắc này giải phóng vốn và tránh mắc kẹt trong lệnh đi ngang.
Một lưu ý quan trọng khi stop nằm ngoài giờ hoặc ngoài biên: bot phải biết giới hạn của sàn. Với chứng khoán Việt Nam, giá stop phải nằm trong biên độ sàn; nếu không, lệnh cắt lỗ sẽ bị từ chối. Luôn kẹp giá về biên hợp lệ và ghi log khi điều đó xảy ra.
Take profit, R:R và vì sao TP cứng thường thua trailing
Đây là phần bị hiểu sai nhiều nhất. Câu hỏi không phải "TP bao nhiêu?" mà là "thoát lệnh bằng cơ chế nào?"
Ba cơ chế thoát và đặc tính:
| Cơ chế | Ưu điểm | Nhược điểm | Hợp với |
|---|---|---|---|
| TP cứng (target cố định) | Kết quả ổn định, dễ backtest | Cắt bỏ các xu hướng dài | Mean reversion, khung nhỏ |
| Trailing stop | Bắt được xu hướng lớn | Win rate giảm, thoát sớm | Breakout, trend following |
| Thoát theo tín hiệu | Logic thống nhất với entry | Có thể nhả lại nhiều lợi nhuận | Hệ thống nhiều chỉ báo |
Với bot, dải R:R tối thiểu nên đặt ở 2:1. Dưới 2:1, bạn cần win rate trên 40–45% chỉ để hoà vốn sau phí — điều kiện không dễ đạt khi đã tự động hoá. Khi bot nhận tín hiệu, hãy kiểm tra R:R và từ chối lệnh nếu không đạt ngưỡng:
def passes_rr(entry: float, stop: float, tp: float, min_rr: float = 2.0) -> bool:
risk = abs(entry - stop)
reward = abs(tp - entry)
return risk > 0 and reward / risk >= min_rrVì sao trailing thường thắng TP cứng trong hệ thống trend following? Vì lợi nhuận của trend following đến từ một vài lệnh rất lớn, không phải từ nhiều lệnh nhỏ. TP cứng chặt mất đúng những lệnh đó, và bạn chỉ còn lại phần lớn lệnh nhỏ thắng với ít lệnh thua trung bình — cấu trúc có expectancy thấp.
Cách kết hợp tốt nhất cho bot: TP một phần + trailing phần còn lại. Ví dụ thoát 50% khối lượng khi đạt 1,5R, và trailing 50% còn lại theo ATR. Bạn vừa có dòng tiền về đều, vừa không bỏ lỡ xu hướng lớn. Đánh đổi duy nhất là code phức tạp hơn: bot phải quản lý nhiều phần của cùng một vị thế.
Break-even và trailing: lộ trình quản lý vị thế
Cách quản lý đơn giản và hiệu quả nhất là chia vòng đời vị thế thành bốn giai đoạn theo R (bội số của rủi ro ban đầu):
| Giai đoạn | Điều kiện | Hành động |
|---|---|---|
| OPEN | Vừa vào lệnh | Giữ stop ban đầu, không đụng |
| BREAKEVEN | Lãi ≥ 1R | Dời stop về entry (hoà vốn) |
| TRAILING | Lãi ≥ 1,5R | Bắt đầu bám theo đỉnh/đáy mới |
| CLOSED | Chạm stop/trailing/TP | Ghi log R-multiple, cập nhật thống kê |
Hai luật bất biến:
- Không dời stop theo hướng rủi ro nhiều hơn — với lệnh mua, stop chỉ được tăng, không bao giờ giảm. Với lệnh bán, chỉ được giảm.
- Không dời stop khi giá chưa đi đủ 1R. Dời stop quá sớm là cách tự biến lệnh thắng thành lệnh thua: giá chỉ cần "thở" một nhịp là bạn bị đá ra.
Công thức trailing theo ATR:
def trail_stop(side: str, peak: float, atr: float, mult: float,
current_stop: float, price: float) -> float:
if side == "buy":
candidate = peak - atr * mult
# chi duoc tang, va phai nho hon gia hien tai
if candidate > current_stop and candidate < price:
return candidate
else:
candidate = peak + atr * mult
if candidate < current_stop and candidate > price:
return candidate
return current_stopĐiều kiện candidate < price (hoặc > price với lệnh bán) là chốt an toàn bắt buộc: nếu trailing stop vượt qua giá hiện tại, bot sẽ gửi một lệnh đóng sai hướng, hoặc lệnh sẽ bị từ chối sau khi đã mất thời gian. Rất nhiều lỗi bot "đóng lệnh vô cớ" xuất phát từ chỗ thiếu đúng điều kiện này.
Với thị trường có T+ (chứng khoán Việt Nam), hãy thêm một luật thực tế: trailing chỉ bắt đầu sau khi cổ phiếu đã về tài khoản và có thể bán. Bán không được thì dời stop cũng vô nghĩa.
Code: state machine quản lý vị thế
Đây là phần "trái tim" của lớp rủi ro. Dùng dataclass giúp trạng thái rõ ràng và dễ log:
from dataclasses import dataclass, field
from enum import Enum
class State(str, Enum):
OPEN = "open"
BREAKEVEN = "breakeven"
TRAILING = "trailing"
CLOSED = "closed"
@dataclass
class Position:
symbol: str
side: str # "buy" | "sell"
qty: float
entry: float
stop: float
tp: float | None = None
atr: float | None = None
trail_mult: float = 2.0
be_at_r: float = 1.0
trail_at_r: float = 1.5
peak: float = field(init=False)
state: State = State.OPEN
def __post_init__(self):
self.peak = self.entry
@property
def risk_unit(self) -> float:
return abs(self.entry - self.stop)
def r_now(self, price: float) -> float:
if self.risk_unit == 0:
return 0.0
move = price - self.entry if self.side == "buy" else self.entry - price
return move / self.risk_unit
def update(self, price: float) -> str | None:
"""Cap nhat trang thai theo gia moi. Tra ve hanh dong neu co."""
r = self.r_now(price)
if self.state == State.OPEN and r >= self.be_at_r:
self.state = State.BREAKEVEN
self.stop = self.entry # doi stop ve hoa von
return "move_to_breakeven"
if self.state == State.BREAKEVEN and r >= self.trail_at_r:
self.state = State.TRAILING
return "start_trailing"
if self.state == State.TRAILING:
self.peak = max(self.peak, price) if self.side == "buy" else min(self.peak, price)
if self.atr:
self.stop = trail_stop(self.side, self.peak, self.atr,
self.trail_mult, self.stop, price)
# Kiem tra dieu kien thoat
if self.side == "buy":
if price <= self.stop:
self.state = State.CLOSED
return "close_stop"
if self.tp and price >= self.tp:
self.state = State.CLOSED
return "close_tp"
else:
if price >= self.stop:
self.state = State.CLOSED
return "close_stop"
if self.tp and price <= self.tp:
self.state = State.CLOSED
return "close_tp"
return NoneBốn việc cần làm với object này trong vòng lặp chính của bot:
- Gọi `update(price)` theo nhịp (1–5 giây cho crypto/FX, 5–15 giây cho chứng khoán) và chỉ thực hiện các hành động trả về.
- Ghi log mọi thay đổi stop — thời điểm, giá cũ, giá mới, lý do. Khi bot "đóng lệnh sớm" bất thường, log này là thứ duy nhất giúp bạn tìm ra nguyên nhân.
- Đồng bộ với sàn. Trạng thái trong RAM và trạng thái thật có thể lệch (mất mạng, bot restart, người thao tác tay). Định kỳ truy vấn vị thế thật và đối chiếu.
- Lưu trạng thái xuống đĩa (JSON/SQLite). Nếu VPS restart, bot phải đọc lại được stop hiện tại — nếu không, nó sẽ "quên" rằng stop đã được dời lên hoà vốn.
Khi sàn không hỗ trợ stop loss
Nhiều môi trường không cho bạn đặt stop thật: chứng khoán Việt Nam không có lệnh stop trên sàn, một số API crypto giới hạn loại lệnh, và MT5 với tài khoản netting cũng có những ràng buộc. Khi đó bot phải tự giám sát — gọi là stop watcher.
import time
def stop_watcher(client, positions: list[Position], interval: float = 5.0):
while True:
try:
for p in positions:
if p.state == State.CLOSED:
continue
price = client.last_price(p.symbol)
action = p.update(price)
if action in ("close_stop", "close_tp"):
client.market_close(p.symbol, p.qty)
log.info("Dong lenh %s | %s | gia %s | R=%.2f",
p.symbol, action, price, p.r_now(price))
elif action:
log.info("Cap nhat stop %s: %s -> %s (%s)",
p.symbol, p.stop, p.stop, action)
except ConnectionError as e:
log.error("Mat ket noi broker: %s", e)
notify_telegram("BOT MAT KET NOI BROKER - kiem tra ngay")
time.sleep(interval)Ba giới hạn của stop watcher mà bạn phải chấp nhận và bù đắp:
- Rủi ro gap. Nếu giá nhảy qua stop (tin tức, mở cửa phiên), bot chỉ đóng được ở giá hiện tại — có thể tệ hơn stop rất nhiều. Đây là lý do rủi ro mỗi lệnh phải nhỏ, và lý do nên tránh nắm vị thế qua các sự kiện lớn nếu chiến lược không dựa trên chúng.
- Rủi ro mất kết nối. Bot chết = không có stop. Cần cơ chế tự khởi động lại (systemd, Windows Service, Docker restart policy) và cảnh báo nếu quá N giây không nhận được dữ liệu giá.
- Rủi ro "chậm" do nhịp lặp.
interval = 5snghĩa là trong trường hợp xấu nhất bot phản ứng sau 5 giây. Với khung M1 và biến động mạnh, đó có thể là một khoảng cách đáng kể.
Khi sàn có hỗ trợ stop thật, hãy dùng stop thật làm lớp bảo hiểm và stop watcher làm lớp quản lý chính. Cách này cho bạn cả bảo vệ khi bot chết, lẫn khả năng dời stop linh hoạt.
Circuit breaker: khi nào bot phải tự dừng
Đây là lớp bảo vệ cuối, và cũng là lớp bị bỏ qua nhiều nhất. Bot của bạn không cần "thông minh" để biết mình đang thua; nó chỉ cần đếm.
@dataclass
class RiskGuard:
equity_open: float
daily_loss_limit_pct: float = 3.0
max_consecutive_losses: int = 4
max_trades_per_day: int = 6
max_positions: int = 3
consecutive_losses: int = 0
trades_today: int = 0
halted: bool = False
def allow_new_trade(self, equity_now: float, open_positions: int,
portfolio_risk_pct: float, max_portfolio_risk_pct: float) -> tuple[bool, str]:
if self.halted:
return False, "bot da dung trong ngay"
dd = (self.equity_open - equity_now) / self.equity_open * 100
if dd >= self.daily_loss_limit_pct:
self.halted = True
notify_telegram(f"DUNG BOT: lo ngay {dd:.2f}% >= {self.daily_loss_limit_pct}%")
return False, f"cham tran lo ngay ({dd:.2f}%)"
if self.consecutive_losses >= self.max_consecutive_losses:
self.halted = True
notify_telegram(f"DUNG BOT: thua lien tiep {self.consecutive_losses} lenh")
return False, "chuoi thua lien tiep"
if self.trades_today >= self.max_trades_per_day:
return False, "vuot so lenh/ngay"
if open_positions >= self.max_positions:
return False, "da du so vi the"
if portfolio_risk_pct >= max_portfolio_risk_pct:
return False, "tong rui ro danh muc da kin"
return True, "ok"Bốn lý do dừng này bao phủ gần hết các thảm hoạ thực tế:
- Chạm trần lỗ ngày — chặn chuỗi thua trong một phiên xấu.
- Thua liên tiếp N lệnh — dấu hiệu sớm rằng điều kiện thị trường đã khác so với lúc bạn tối ưu chiến lược.
- Vượt số lệnh/ngày — chặn bot rơi vào vòng "gỡ" bằng cách vào lệnh nhiều hơn. Không có cơ chế này, bot sẽ giao dịch liên tục trong lúc tệ nhất.
- Đủ số vị thế / kín rủi ro danh mục — chặn cùng lúc mở 8 lệnh tương quan cao.
Một chi tiết triển khai rất nên làm: dừng có thời hạn. Trần lỗ ngày reset đầu ngày giao dịch mới (theo múi giờ giao dịch bạn chọn, ghi rõ trong config). Nếu không có reset, chỉ cần đúng một ngày tệ là bot ngừng chạy mãi.
Và nhớ: khi bot dừng vì chạm trần rủi ro, đừng tắt tính năng đó ngay rồi chạy lại. Hãy ghi lại sự kiện, so tín hiệu trong ngày với điều kiện thị trường, rồi mới quyết định sửa gì.
Giới hạn rủi ro danh mục: tương quan và tập trung
Rủi ro lớn nhất của một bot đa mã không phải từng lệnh, mà là các lệnh giống nhau. Mở 5 vị thế mua trên 5 altcoin gần như là mở một vị thế duy nhất với khối lượng gấp 5 — vì chúng cùng tăng cùng giảm theo BTC.
Ba loại giới hạn nên có ở tầng danh mục:
- Số vị thế tối đa (
max_positions) — đơn giản và hiệu quả nhất. - Rủi ro tổng (
portfolio_risk_pct) — tổng khoảng cách stop × khối lượng của mọi vị thế, chia tài khoản. - Rủi ro theo nhóm — ví dụ tối đa 2 vị thế trong cùng một nhóm (cùng sàn, cùng ngành, cùng cặp tương quan cao).
def portfolio_risk_pct(positions: list[Position], equity: float) -> float:
total = sum(p.risk_unit * p.qty for p in positions if p.state != State.CLOSED)
return total / equity * 100 if equity else 0.0Với chứng khoán Việt Nam, "nhóm" tự nhiên là nhóm ngành (bank, chứng khoán, bất động sản, thép). Một bot mua 4 mã bank cùng lúc thực chất chỉ có một ý tưởng giao dịch — và cũng chỉ có một cách thua.
Một điểm nữa ít bot nào xử lý: rủi ro tập trung vào một sàn hoặc một broker API. Nếu toàn bộ vị thế nằm trên một sàn, một lần API lỗi hoặc bảo trì đúng lúc thị trường đảo chiều có thể khiến bạn không đóng được lệnh. Hãy xem đây là rủi ro vận hành, và giới hạn tổng vị thế trên mỗi sàn ở mức bạn chấp nhận được.
Đo lường: R-multiple và bộ chỉ số phải có
Bạn không thể cải thiện quản trị rủi ro nếu không đo nó. Và cách đo đúng là quy mọi kết quả về R — bội số của rủi ro ban đầu, thay vì đo bằng tiền.
Lợi ích của R: kết quả độc lập với kích thước tài khoản và với khối lượng. Một lệnh lãi 2R nghĩa là bạn ăn gấp đôi rủi ro, dù tài khoản 1.000 hay 100.000 USDT.
def log_trade(trade_id, symbol, side, qty, entry, stop, exit_price, fees):
risk = abs(entry - stop) * qty
gross = (exit_price - entry) * qty if side == "buy" else (entry - exit_price) * qty
net = gross - fees
return {
"trade_id": trade_id, "symbol": symbol, "side": side, "qty": qty,
"entry": entry, "stop": stop, "exit": exit_price,
"fees": round(fees, 4), "net": round(net, 4),
"r_multiple": round(net / risk, 3) if risk else None,
"outcome": "win" if net > 0 else ("loss" if net < 0 else "flat"),
}Bộ chỉ số cần tính định kỳ (hàng tuần, hàng tháng):
| Chỉ số | Công thức | Ý nghĩa |
|---|---|---|
| Expectancy (R) | win% × avg_win_R − loss% × avg_loss_R | Kỳ vọng mỗi lệnh, quan trọng nhất |
| Profit factor | tổng lãi / tổng lỗ | > 1,3 là dùng được, > 1,8 là tốt |
| Win rate | lệnh thắng / tổng lệnh | Chỉ có nghĩa khi biết avg R thắng/thua |
| Max drawdown | Mức giảm vốn lớn nhất | Xác định ngưỡng circuit breaker phù hợp |
| Chuỗi thua dài nhất | Số lệnh thua liên tiếp | Xác định max_consecutive_losses |
| Avg R thắng / thua | Trung bình R theo nhóm | Kiểm tra R:R thực tế so với thiết kế |
Hai cảnh báo thực tế:
- Win rate cao không có nghĩa là có lãi. Một hệ thống thắng 80% với avg win 0,4R và avg loss 2R có expectancy âm:
0,8 × 0,4 − 0,2 × 2 = −0,08R. Đây là kiểu hệ thống giết tài khoản chậm mà vẫn khiến bạn thấy "bot chạy tốt". - Expectancy dương nhưng drawdown quá lớn cũng vô dụng. Nếu expectancy 0,15R nhưng max drawdown 40%, bạn sẽ bỏ hệ thống trước khi nó kịp sinh lời. Giảm rủi ro mỗi lệnh là cách giảm drawdown trực tiếp, không cần thay đổi logic tín hiệu.
Điều đáng chú ý: giảm rủi ro mỗi lệnh không làm thay đổi expectancy theo R. Nó chỉ làm mọi thứ chậm lại — và an toàn hơn. Nếu bạn không chắc về hệ thống của mình, hãy giảm rủi ro trước khi nghĩ đến việc sửa tín hiệu.
Những sai lầm chết người khi tự động hoá SL/TP
- Vào lệnh không có stop. Chỉ cần một lần "tạm thời chưa đặt stop" là đủ để mất phần lớn tài khoản. Hãy coi tín hiệu thiếu
sllà tín hiệu không hợp lệ. - Dời stop ra xa hơn khi giá đi ngược. Đây là hành vi phá huỷ tài khoản nhanh nhất, và bot hoàn toàn có thể "được lập trình" để làm điều đó nếu bạn thêm logic kiểu "nếu giá đi ngược nhiều thì nới stop".
- Tính khối lượng từ số dư ký quỹ thay vì vốn. Với đòn bẩy, dùng "số dư khả dụng × 5%" có thể khiến rủi ro thật lớn gấp nhiều lần dự tính. Luôn tính rủi ro trên vốn thật (equity).
- Bỏ qua phí và funding. Với chiến lược vào–ra nhiều lần, phí + funding có thể ăn hết expectancy. Hãy đưa phí vào
log_tradengay từ đầu và tính R sau phí. - Không có circuit breaker. Một ngày thị trường bất thường có thể tạo 15 lệnh thua liên tiếp nếu bot không biết đếm.
- Trailing stop vượt qua giá hiện tại. Gây lệnh đóng sai hướng hoặc lệnh bị từ chối. Luôn kèm điều kiện
candidate < price. - Quên đồng bộ trạng thái sau khi restart. Bot khởi động lại mà không đọc state trên đĩa sẽ "quên" stop đã dời và quản lý vị thế sai.
- Tăng rủi ro sau chuỗi thắng. Cảm giác "bot đang chạy tốt" chính là lúc tăng rủi ro và nhận cú drawdown lớn nhất. Nếu muốn tăng vốn, hãy tăng vốn tài khoản, giữ nguyên rủi ro mỗi lệnh.
Checklist 12 điểm trước khi tăng vốn cho bot
- ✅ Mọi lệnh đều có stop tính từ giá vào lệnh (ATR hoặc cấu trúc).
- ✅ Khối lượng luôn qua hàm
position_size(), không bao giờ nhập tay. - ✅ Có giới hạn giá trị lệnh tối đa và khối lượng tối thiểu theo sàn.
- ✅ R:R tối thiểu được kiểm tra trước khi gửi lệnh; không đạt thì bỏ.
- ✅ Break-even ở 1R, trailing từ 1,5R; stop chỉ dời theo hướng giảm rủi ro.
- ✅ Có
max_positions,portfolio_risk_pctvà giới hạn theo nhóm. - ✅ Circuit breaker theo drawdown ngày, chuỗi thua, số lệnh/ngày; có reset đầu ngày.
- ✅ Bot có cơ chế tự khởi động lại và cảnh báo khi mất kết nối dữ liệu/API.
- ✅ Trạng thái vị thế được lưu xuống đĩa và đối soát với sàn định kỳ.
- ✅ Mọi lệnh đều được log với
r_multiplesau phí; có báo cáo hàng tuần. - ✅ Đã chạy tối thiểu 2–4 tuần với rủi ro tối thiểu trước khi tăng vốn.
- ✅ Có kịch bản xử lý sự cố: mất mạng, API lỗi, giá gap, broker bảo trì.
Nếu chưa đủ 12 điểm, hãy thêm vốn chậm hơn thay vì bỏ qua điểm nào. Mỗi điểm trong danh sách này tương ứng với một cách mất tiền đã được kiểm chứng bởi rất nhiều người trước bạn.
Tổng kết
Quản trị rủi ro tự động không phải một module "thêm vào" sau khi bot chạy được. Nó là cấu trúc quyết định bot có sống được hay không:
- Position sizing quyết định quy mô — và quy mô sai thì mọi thứ khác vô nghĩa.
- Stop loss quyết định số tiền tối đa mất cho một ý tưởng giao dịch sai.
- Break-even + trailing quyết định bạn giữ được bao nhiêu trong số lợi nhuận đã có.
- Circuit breaker + giới hạn danh mục quyết định bạn có còn vốn để chơi tiếp vào tháng sau.
Và đo lường theo R là thứ duy nhất cho bạn biết hệ thống có lợi thế thật hay chỉ đang may mắn trong một giai đoạn thị trường thuận lợi.
Câu hỏi thường gặp
Rủi ro bao nhiêu phần trăm tài khoản cho mỗi lệnh là hợp lý?
Với bot, dải an toàn là 0,25%–1% mỗi lệnh. Người mới nên bắt đầu ở 0,25–0,5%. Trên 2% mỗi lệnh, chỉ 5–6 lệnh thua liên tiếp — chuyện hoàn toàn bình thường — đã khiến bạn mất hơn 10% tài khoản, và việc phục hồi đòi hỏi lợi nhuận lớn hơn nhiều.
Nên đặt stop loss theo ATR hay theo số tiền cố định?
Nên theo ATR (hoặc theo cấu trúc giá), rồi để số tiền được tính từ khối lượng và khoảng cách stop. Nếu bạn chọn stop theo số tiền trước, stop sẽ nằm ở những mức vô nghĩa về mặt kỹ thuật, và bạn sẽ bị quét thường xuyên hơn hẳn.
Tại sao bot vào lệnh quá lớn dù tôi đã đặt rủi ro 1%?
Ba nguyên nhân phổ biến: (1) stop quá gần nên công thức cho ra khối lượng khổng lồ — thiếu giới hạn giá trị lệnh tối đa; (2) tính rủi ro trên số dư ký quỹ thay vì vốn thật; (3) làm tròn lô/lot làm rủi ro thật phình lên. Cách xử lý là thêm kiểm tra real_risk_pct sau khi làm tròn và bỏ tín hiệu nếu vượt 1,5 lần kế hoạch.
Có nên dời stop về hoà vốn ngay khi lệnh có lãi nhẹ không?
Không nên. Dời stop quá sớm khiến bot bị đá ra bởi dao động bình thường, biến lệnh thắng thành lệnh hoà hoặc thua. Mốc hợp lý là 1R — khi lệnh đã đi được đúng khoảng rủi ro ban đầu.
Trailing stop nên dùng ATR hay phần trăm?
ATR cho kết quả ổn định hơn vì tự co giãn theo biến động. Trailing theo phần trăm cố định (ví dụ 1%) sẽ quá chặt khi thị trường biến động mạnh và quá lỏng khi thị trường êm. Dải tham số thường dùng là ATR × 2 đến ATR × 3.
Khi sàn không hỗ trợ stop loss thì xử lý thế nào?
Bot phải tự giám sát giá theo nhịp (5–15 giây) và gửi lệnh đóng khi chạm ngưỡng — gọi là stop watcher. Bạn phải chấp nhận ba giới hạn: rủi ro gap giá qua stop, rủi ro mất kết nối, và độ trễ theo nhịp lặp. Vì vậy rủi ro mỗi lệnh cần nhỏ hơn và nhất thiết phải có cảnh báo khi mất kết nối.
Circuit breaker nên dừng bot khi nào?
Bốn ngưỡng thực dụng: lỗ trong ngày vượt 3% vốn, thua liên tiếp 4 lệnh, vượt 6 lệnh/ngày, hoặc đủ số vị thế tối đa. Khi chạm ngưỡng, bot dừng mở lệnh mới nhưng vẫn quản lý vị thế đang mở, và reset vào đầu ngày giao dịch tiếp theo.
Làm sao biết hệ thống quản trị rủi ro của tôi đang hoạt động tốt?
Đo theo R: expectancy phải dương (tốt nhất từ 0,15R trở lên), profit factor trên 1,3, và max drawdown nằm trong ngưỡng bạn chịu được. Nếu expectancy dương nhưng drawdown quá lớn, cách sửa nhanh nhất là giảm rủi ro mỗi lệnh — không cần thay đổi logic tín hiệu.
Muốn được hướng dẫn trực tiếp cách lập trình lớp quản trị rủi ro này cho bot chạy thật — từ crypto, Forex tới chứng khoán Việt Nam — Bootcamp TradingView Webhook Bot gồm 6 buổi online (20:00–22:00 Thứ 2, khai giảng 19/10/2026), học phí 990.000đ, tặng kèm 12 tháng FindMe VPS Guardian.
Đọc tiếp trong cụm bài:
- Pine Script cơ bản: tự tạo chỉ báo và alert
- Bot Python nhận webhook TradingView (Flask)
- Gửi lệnh TradingView lên Binance/Bybit bằng API
- Khóa Lập trình Bot Auto Trading không cần biết code
Weekly Digest — Nhận Bản Tin Hàng Tuần
Nhận các bài viết phân tích kỹ thuật chuyên sâu, thuật toán giao dịch tự động (Trading Bot) và các giải pháp công nghệ mới nhất từ Hướng Nghiệp Dữ Liệu.
