🔥 🎁 QUÀ TẶNG khi đăng ký: Phần mềm DNT Digital SaaS FindMe VPS Guardian 12 tháng (~240 USD) và 24 tháng (~480 USD)
Quản trị rủi ro tự động: SL, TP và position sizing 2026
ĐTĐược viết bởi Đặng Trí Thanhvào ngày 08/10/2026 lúc 20:00|… lượt xem
TradingView08/10/2026 · 21 phút đọc

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èm sl và 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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ảnRủi ro/lệnh (0,75%)Tối đa mở cùng lúc (4%)Trần lỗ ngày (3%)
1.000 USDT7,5 USDT40 USDT30 USDT
5.000 USDT37,5 USDT200 USDT150 USDT
20.000 USDT150 USDT800 USDT600 USDT
100.000 USDT750 USDT4.000 USDT3.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ápCông thứcƯu điểmNhược điểm
Fixed fractionalequity × risk% / stop_distRủi ro luôn cố định theo tiềnKhối lượng đổi liên tục, nhỏ khi stop xa
ATR-basedequity × risk% / (ATR × mult)Thích ứng biến động từng thị trườngPhụ thuộc chất lượng ATR
% equity (allocation)equity × alloc% / priceĐơn giản, dễ hiểuRủi ro thật khác nhau mỗi lệnh
Fixed value1.000 USDT / priceDễ kiểm soát, tốt khi mới chạyKhô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 qty

Hai 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 stopCách tínhPhù hợpRủi ro
Cố định (theo giá)entry ± n pip/pointThị trường ổn định, khung lớnKhông thích ứng biến động
Theo ATRentry ∓ ATR × multHầu hết chiến lược, đa thị trườngATR co lại khi thị trường nén
Theo cấu trúcĐáy/đỉnh gần nhất ± đệmBreakout, swing tradingBị quét khi biến động mạnh
TrailingBám theo đỉnh/đáy đã đi quaXu hướng dàiThoát sớm khi giá dao động
Theo thời gianĐóng sau N nến/n phútChiến lược ngắn, scalpKhô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:

  1. 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ố.
  2. Rủi ro đo được — khoảng cách stop chính là mẫu số của công thức position sizing.
  3. Kiểm chứng được — bạn có thể backtest với atr × mult và 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ểmNhược điểmHợp với
TP cứng (target cố định)Kết quả ổn định, dễ backtestCắt bỏ các xu hướng dàiMean reversion, khung nhỏ
Trailing stopBắt được xu hướng lớnWin rate giảm, thoát sớmBreakout, trend following
Thoát theo tín hiệuLogic thống nhất với entryCó thể nhả lại nhiều lợi nhuậnHệ 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_rr

Vì 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ệnHành động
OPENVừa vào lệnhGiữ stop ban đầu, không đụng
BREAKEVENLãi ≥ 1RDời stop về entry (hoà vốn)
TRAILINGLãi ≥ 1,5RBắt đầu bám theo đỉnh/đáy mới
CLOSEDChạm stop/trailing/TPGhi log R-multiple, cập nhật thống kê

Hai luật bất biến:

  1. 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.
  2. 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 None

Bốn việc cần làm với object này trong vòng lặp chính của bot:

  1. 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ề.
  2. 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.
  3. Đồ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.
  4. 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 = 5s nghĩ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:

  1. Số vị thế tối đa (max_positions) — đơn giản và hiệu quả nhất.
  2. 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.
  3. 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.0

Vớ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_RKỳ vọng mỗi lệnh, quan trọng nhất
Profit factortổng lãi / tổng lỗ> 1,3 là dùng được, > 1,8 là tốt
Win ratelệnh thắng / tổng lệnhChỉ có nghĩa khi biết avg R thắng/thua
Max drawdownMức giảm vốn lớn nhấtXác định ngưỡng circuit breaker phù hợp
Chuỗi thua dài nhấtSố lệnh thua liên tiếpXác định max_consecutive_losses
Avg R thắng / thuaTrung bình R theo nhómKiể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

  1. 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 sl là tín hiệu không hợp lệ.
  2. 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".
  3. 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).
  4. 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_trade ngay từ đầu và tính R sau phí.
  5. 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.
  6. 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.
  7. 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.
  8. 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

  1. ✅ Mọi lệnh đều có stop tính từ giá vào lệnh (ATR hoặc cấu trúc).
  2. ✅ Khối lượng luôn qua hàm position_size(), không bao giờ nhập tay.
  3. ✅ Có giới hạn giá trị lệnh tối đa và khối lượng tối thiểu theo sàn.
  4. ✅ R:R tối thiểu được kiểm tra trước khi gửi lệnh; không đạt thì bỏ.
  5. ✅ Break-even ở 1R, trailing từ 1,5R; stop chỉ dời theo hướng giảm rủi ro.
  6. ✅ Có max_positions, portfolio_risk_pct và giới hạn theo nhóm.
  7. ✅ Circuit breaker theo drawdown ngày, chuỗi thua, số lệnh/ngày; có reset đầu ngày.
  8. ✅ 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.
  9. ✅ Trạng thái vị thế được lưu xuống đĩa và đối soát với sàn định kỳ.
  10. ✅ Mọi lệnh đều được log với r_multiple sau phí; có báo cáo hàng tuần.
  11. ✅ Đã 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.
  12. ✅ 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
Đọc xong rồi? Hãy thử ngay — miễn phí 7 ngày.
📬

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.