| Mô hình Máy Trạng Thái Hữu Hạn (FSM) trong Bot Trading

Được viết bởi Đặng Trí Thanh vào ngày 04/02/2026 lúc 08:50 | 322 lượt xem

Mô hình Máy Trạng Thái Hữu Hạn (Finite State Machine – FSM) là kiến trúc lõi của mọi bot giao dịch tự động chuyên nghiệp. Bài viết này — bản mở rộng đầy đủ — giải thích FSM từ khái niệm đến triển khai thực chiến trong Python và MT5, kèm code mẫu, case study và các lỗi cần tránh.

1. FSM là gì?

FSM là mô hình toán học mô tả một hệ thống chỉ ở một trạng thái tại một thời điểm, và chuyển trạng thái khi có sự kiệnđiều kiện thỏa. Ví dụ quen thuộc: máy bán nước tự động (chờ tiền → nhận tiền → trả hàng), đèn giao thông (xanh → vàng → đỏ).

Áp vào bot trading: FSM giúp trả lời “bot đang ở đâu trong vòng đời giao dịch?” — chờ tín hiệu, vào lệnh, giữ lệnh, thoát lệnh hay nghỉ ngắn.

1.1. Ba thành phần của FSM

  • States (trạng thái): tập hữu hạn các trạng thái bot có thể ở.
  • Events (sự kiện): tín hiệu, thay đổi giá, hết giờ, lỗi kết nối…
  • Transitions (chuyển trạng thái): quy tắc “từ A + sự kiện E → B”.

1.2. Vì sao bot cần FSM — không chỉ là “code gọn”

  • Chống nhồi lệnh: bot chỉ vào lệnh khi ở IDLE.
  • Giảm bug: thay vì hàng chục if lồng nhau, mỗi trạng thái một hành vi rõ ràng.
  • Dễ debug: log trạng thái để biết bot “đang nghĩ gì”.
  • An toàn: mất kết nối → giữ trạng thái, không hành động sai.
  • Mở rộng: thêm chiến lược = thêm trạng thái, không đụng code cũ.

2. Các trạng thái cơ bản của bot

Trạng thái Ý nghĩa Hành động Sự kiện ra
IDLE Chờ đợi Quét tín hiệu có tín hiệu → SIGNAL
SIGNAL Có tín hiệu Kiểm tra bộ lọc đủ điều kiện → ENTER; không → IDLE
ENTER Đặt lệnh Gửi lệnh thành công → IN_POSITION; lỗi → IDLE
IN_POSITION Giữ lệnh Theo dõi SL/TP/trailing chạm SL/TP → EXIT
EXIT Thoát lệnh Đóng lệnh, ghi log đóng xong → COOLDOWN
COOLDOWN Nghỉ ngắn Chờ hết giờ hết giờ → IDLE

3. Code FSM đầy đủ bằng Python

3.1. Bản hoàn chỉnh có bộ lọc và cooldown

import time, datetime

class BotFSM:
    def __init__(self, magic=6111, cooldown=120, max_spread=0.5):
        self.state = "IDLE"
        self.magic = magic
        self.cooldown = cooldown
        self.max_spread = max_spread
        self.cooldown_until = 0
        self.log = []

    def _log(self, msg):
        self.log.append(f"{datetime.datetime.now()} [{self.state}] {msg}")

    def on_tick(self, bid, ask, signal, adx, ma20):
        self._log(f"tick bid={bid} ask={ask} signal={signal}")
        spread = ask - bid
        if self.state == "IDLE":
            if signal == "BUY" and adx > 25 and spread < self.max_spread:
                self.state = "ENTER"
        elif self.state == "ENTER":
            self.place_order("BUY", ask)
            self.state = "IN_POSITION"
        elif self.state == "IN_POSITION":
            if bid <= self.stop_price:
                self.close_order()
                self.state = "EXIT"
        elif self.state == "EXIT":
            self.cooldown_until = time.time() + self.cooldown
            self.state = "COOLDOWN"
        elif self.state == "COOLDOWN":
            if time.time() >= self.cooldown_until:
                self.state = "IDLE"

    def place_order(self, side, price):
        self._log(f"PLACE {side} @ {price} magic={self.magic}")

    def close_order(self):
        self._log("CLOSE ALL")

bot = BotFSM()
# mô phỏng vài tick
for p in [67000, 67050, 66980]:
    bot.on_tick(p, p + 0.3, "BUY", 30, 67100)
print("
".join(bot.log))

3.2. Lưu trạng thái khi dùng MT5 (Global Variable)

MetaTrader 5 chạy bot theo tick; nếu terminal restart, biến trong bộ nhớ bị mất. Giải pháp: lưu trạng thái vào Global Variable (MQL5):

int state = (int)GlobalVariableGet("PyBot_State");
// sau mỗi lần đổi trạng thái:
GlobalVariableSet("PyBot_State", (double)state);
// khi OnTick bắt đầu, đọc lại:
state = (int)GlobalVariableGet("PyBot_State");

Với Python + API, bạn có thể lưu vào file hoặc cơ sở dữ liệu, hoặc dùng thư viện MetaTrader5 đọc Global Variable qua global_variable_get().

4. FSM cho bot hedging đa cụm (nâng cao)

Với chiến lược hedging rải nhiều cụm, mỗi cụm lệnh là một “bot con” với FSM riêng. Bot mẹ điều phối bằng global state:

  • ClusterState: mỗi cụm (theo magic/comment) có IDLE → ENTER → IN_POSITION → SURPLUS/HEDGE → EXIT.
  • Surplus: khi giá hồi về vùng lời, bot chốt từng phần lệnh trong cụm.
  • Hedge: khi giá đi ngược quá sâu, mở lệnh đối ứng để khóa rủi ro.
# ví dụ: cập nhật trạng thái từng cụm sau mỗi tick
for cluster in bot.clusters:
    if cluster.state == "IDLE" and signal_for(cluster):
        cluster.state = "ENTER"
    elif cluster.state == "IN_POSITION" and cluster.surplus_ready():
        cluster.take_profit_partial()
        cluster.state = "SURPLUS"

5. Case study: PyBot PyNhiQuaiBot thực chiến

PyBot dùng FSM để xử lý đồng thời hàng chục lệnh trên nhiều cụm: quét tín hiệu → lọc spread/ADX → vào lệnh → quản lý lưới → thoát theo step/X-level. Nhờ FSM, bot không nhồi lệnh dù tick về dồn dập, và khi VPS restart, bot đọc lại trạng thái từ Global Variable để tiếp tục đúng vòng đời — không sót lệnh, không đóng nhầm.

6. Các lỗi thường gặp và cách tránh

  • Quên cập nhật trạng thái: hành động nhưng không đổi state → lặp hành động. Luôn đổi state ngay sau hành động.
  • Thiếu COOLDOWN: bot vào lại ngay sau khi cắt lỗ → cháy tài khoản. Luôn có nghỉ ngắn.
  • Không lưu state khi restart: bot “mất trí nhớ”. Dùng Global Variable/DB.
  • So sánh sai kiểu: giá là float, đừng so với int 67000.
  • Không log: khó truy vết lỗi. Log mọi chuyển trạng thái.
  • Chuyển trạng thái bất hợp lệ: chỉ cho phép các đường chuyển trong bảng.

7. Kiểm thử FSM trước khi chạy real

  1. Vẽ bảng chuyển trạng thái, duyệt mọi cặp (state, event) hợp lệ.
  2. Mô phỏng: mất kết nối, lỗi lệnh, tin lớn, spread rộng.
  3. Chạy backtest trên dữ liệu lịch sử, quan sát log chuyển trạng thái.
  4. Paper trade 2–4 tuần trước khi chạy real.

8. Câu hỏi thường gặp (FAQ)

FSM có làm bot chậm không? Không — chỉ là so sánh trạng thái đơn giản.

Bot 1 lệnh đơn giản có cần FSM không? Chưa cần, nhưng khi có lưới/hedging/trailing thì nên.

FSM khác gì state machine của MT5? MT5 có sẵn cơ chế (OnTick + global vars); FSM là cách tổ chức logic, áp dụng được cả Python lẫn MQL5.

Làm sao xử lý lệnh tay lẫn lệnh bot? Dùng magic number để FSM chỉ quản lý lệnh đúng magic.

Học FSM ở đâu bài bản? Khóa Python Hedging / Bot Auto Trading tại Hướng Nghiệp Dữ Liệu dạy trọn kiến trúc này kèm thực hành.

8.1. Bảng chuyển trạng thái đầy đủ cho bot lưới + hedging

Dưới đây là bảng chuyển trạng thái mở rộng cho bot lưới kết hợp hedging — mỗi cụm lệnh là một FSM con:

Trạng thái cụm Sự kiện Điều kiện Trạng thái mới Hành động
IDLE tín hiệu vào spread < 0.3, ADX > 25 ENTER đặt lệnh mở cụm
ENTER khớp lệnh GRID_OPEN chờ giá hồi
GRID_OPEN giá đi ngược 1 step số bậc < max_grid GRID_ADD thêm lệnh cùng cụm
GRID_ADD giá hồi về profit GRID_OPEN theo dõi tiếp
GRID_OPEN giá hồi về vùng lời đạt surplus_pct SURPLUS chốt từng phần
SURPLUS chốt đủ EXIT đóng toàn cụm
GRID_OPEN đi ngược quá sâu loss > hedge_threshold HEDGE mở lệnh đối ứng
HEDGE giá hồi về vùng an toàn SURPLUS chốt dần

8.1.1. Vì sao phải có bảng này trước khi code

Bảng là “hợp đồng” giữa bạn và bot: mọi hành vi đều được định nghĩa trước, code chỉ là hiện thực hoá. Khi đổi chiến lược, bạn sửa bảng trước rồi sửa code theo — tránh vô vàn nhánh if mâu thuẫn nhau. Đây chính là điểm mà nhiều trader mới bỏ qua, dẫn tới bot hoạt động “theo kiểu ngẫu hứng”.

8.2. Template Expert Advisor (MQL5) dùng FSM

Áp dụng đúng mô hình trên trong MQL5 — bot đọc/ghi trạng thái qua Global Variable để sống sót qua restart:

enum EAState { S_IDLE, S_ENTER, S_GRID, S_SURPLUS, S_HEDGE, S_EXIT };

int g_state;

int OnInit() {
   g_state = (int)GlobalVariableGet("EA_State");
   return INIT_SUCCEEDED;
}

void OnTick() {
   switch (g_state) {
      case S_IDLE:
         if (SignalBuy() && SpreadOK()) {
            OpenFirst();
            g_state = S_ENTER;
            SaveState();
         }
         break;
      case S_GRID:
         if (PriceRetraced())        { g_state = S_SURPLUS; SaveState(); }
         else if (DeepLoss())        { OpenHedge(); g_state = S_HEDGE; SaveState(); }
         else if (GridStepHit())     { AddGridOrder(); }
         break;
      // ... các trạng thái khác
   }
}

void SaveState() {
   GlobalVariableSet("EA_State", (double)g_state);
}

Điểm mấu chốt: mọi chuyển trạng thái đều gọi SaveState(). Nếu VPS restart giữa chừng, OnInit đọc lại đúng trạng thái và tiếp tục vòng đời — không sót lệnh, không đóng nhầm.

8.3. Lưu trạng thái bằng JSON để dễ debug

Với Python, lưu toàn bộ trạng thái bot (kể cả lịch sử từng cụm) ra JSON giúp bạn xem lại “bot đã nghĩ gì” từng giây:

import json

def save_state(bot):
    payload = {
        "state": bot.state,
        "clusters": [c.to_dict() for c in bot.clusters],
        "updated": time.strftime("%Y-%m-%d %H:%M:%S"),
    }
    with open("bot_state.json", "w", encoding="utf-8") as f:
        json.dump(payload, f, ensure_ascii=False, indent=2)

def load_state(bot):
    try:
        with open("bot_state.json", "r", encoding="utf-8") as f:
            return json.load(f)
    except FileNotFoundError:
        return {}

8.4. So sánh FSM với các kiến trúc khác

Kiến trúc Độ phức tạp Dễ debug Khi nào dùng
If/else lồng nhau Thấp Thấp (rối) Bot demo, vài điều kiện
FSM Trung bình Cao (log state) Bot thực chiến 5–30 trạng thái
Behavior Tree Cao Rất cao Bot đa chiến lược, nhiều mục tiêu

Với đa số bot trading cá nhân, FSM là điểm cân bằng tốt nhất giữa đơn giản và đủ mạnh — không quá khó như Behavior Tree nhưng vượt trội so với if/else rối rắm.

Ví dụ hoàn chỉnh: FSM điều khiển bot lưới XAUUSD

Kết hợp tất cả các phần trên, dưới đây là một bot lưới XAUUSD có FSM đầy đủ — bạn có thể chạy thử trên tài khoản demo:

import time

class GridBot:
    def __init__(self, symbol="XAUUSD", lot=0.01, step=300, max_grid=4,
                 max_spread=0.30):
        self.symbol = symbol
        self.state = "IDLE"
        self.grid = []              # các lệnh trong cụm
        self.step = step
        self.max_grid = max_grid
        self.max_spread = max_spread
        self.cooldown_until = 0

    def on_tick(self, bid, ask):
        spread = ask - bid
        if self.state == "IDLE":
            if self.signal() and spread < self.max_spread:
                self.open_order("BUY", ask)
                self.state = "GRID_OPEN"
        elif self.state == "GRID_OPEN":
            if len(self.grid) < self.max_grid and bid < self.last_price() - self.step:
                self.open_order("BUY", ask)      # thêm bậc
            if self.surplus_ready():
                self.take_profit_partial()
                self.state = "SURPLUS"
        elif self.state == "SURPLUS":
            if not self.grid:
                self.state = "COOLDOWN"
                self.cooldown_until = time.time() + 300
        elif self.state == "COOLDOWN":
            if time.time() >= self.cooldown_until:
                self.state = "IDLE"

    def signal(self): return True
    def open_order(self, side, price): self.grid.append(price)
    def last_price(self): return self.grid[-1] if self.grid else 0
    def surplus_ready(self): return len(self.grid) > 0
    def take_profit_partial(self):
        # chốt bậc đầu khi giá hồi về vùng lời
        self.grid.pop(0)

Hãy chú ý: mỗi nhánh elif tương ứng đúng một dòng trong bảng chuyển trạng thái ở mục 8.1 — không có nhánh “lạc trôi” nào ngoài thiết kế.

FSM kết hợp quản lý vốn theo trạng thái

Rủi ro nên thay đổi theo trạng thái: khi IDLE thì rủi ro theo kế hoạch; khi SURPLUS (đang có lời) có thể tăng dần; khi HEDGE (phòng thủ) phải giảm lot hoặc khóa lệnh:

def lot_size(self):
    if self.state == "HEDGE":    return self.base_lot * 0.5
    if self.state == "SURPLUS":  return self.base_lot * 1.2   # dùng lời phóng to
    return self.base_lot

Quy tắc vàng: không bao giờ tăng lot khi đang lỗ sâu. FSM giúp bạn ép điều này bằng code thay vì dựa vào kỷ luật tự thân — vốn dễ sai khi cảm xúc lên xuống.

Kiến trúc đề xuất cho bot 24/7

  1. VPS Linux gần broker, chạy Python service (systemd) tự restart khi crash.
  2. FSM lõi + kết nối MT5/Binance qua API.
  3. Lưu state JSON/DB mỗi lần chuyển trạng thái.
  4. Log xoay vòng (RotatingFileHandler) + cảnh báo Telegram khi lỗi.
  5. Dashboard đơn giản đọc log để xem trạng thái hiện tại.

Với kiến trúc này, bot chạy nhiều tháng không cần can thiệp, và mọi sự cố đều có dấu vết để truy vết nhanh.

FSM và vòng đời đầy đủ của một lệnh giao dịch

Hãy đi theo vòng đời hoàn chỉnh của một lệnh từ lúc bot nhận tick đến khi chốt lời, để thấy FSM điều phối từng bước như thế nào trong thực tế. Giả sử bot chạy trên XAUUSD, tài khoản demo:

  1. 09:00:00 — IDLE: bot quét từng tick, tính MA20, ADX và spread. Chưa có tín hiệu nên trạng thái giữ nguyên IDLE, không làm gì khác ngoài cập nhật bộ đếm.
  2. 09:07:33 — IDLE → ENTER: giá đóng nến 5 phút trên MA20, ADX = 28, spread = 0.21 (nhỏ hơn 0.30). Đủ 3 điều kiện, bot chuyển ENTER và ghi log kèm lý do.
  3. 09:07:34 — ENTER: bot kiểm tra margin tự do đủ, magic đúng, không có tin lớn trong 15 phút tới. Gửi lệnh BUY 0.01 lot.
  4. 09:07:35 — ENTER → IN_POSITION: lệnh khớp tại 2350.5. Bot lưu giá vào lệnh, chuyển IN_POSITION, đặt SL 2343 và TP 2364 theo kế hoạch R:R 1:3.
  5. 09:07:35 → 11:30:00 — IN_POSITION: bot chỉ theo dõi giá, kiểm tra mỗi tick xem giá có chạm SL/TP không. Không vào lệnh mới trong lúc này (tránh nhồi lệnh).
  6. 11:30:00 — IN_POSITION → EXIT: giá chạm TP 2364. Bot đóng lệnh, ghi nhận lợi nhuận +13.5 USD (0.01 lot × 13.5).
  7. 11:30:01 — EXIT → COOLDOWN: bot đặt cooldown 120 giây để tránh vào lệnh vội vàng ngay sau khi thoát.
  8. 11:32:01 — COOLDOWN → IDLE: hết giờ nghỉ, bot quay lại quét tín hiệu từ đầu.

Chính nhờ FSM, mỗi bước đều có một trạng thái rõ ràng — không có nhánh code “tự do” nào có thể làm bot hành động sai lúc nửa đêm khi không ai canh.

Vì sao vòng đời này quan trọng với backtest

Khi bạn chạy backtest, hãy mô phỏng đúng vòng đời này: bot chỉ được phép vào lệnh khi ở IDLE, chỉ được phép đóng khi ở IN_POSITION. Nhiều bot lập trình kiểu if/else lộn xộn sẽ “đặt lệnh khi đang giữ lệnh” — backtest cho kết quả đẹp giả tạo, live thì vỡ mặt. FSM buộc bạn mô phỏng đúng trật tự, nên số liệu backtest đáng tin cậy hơn.

FSM với lệnh tay và lệnh bot — tránh xung đột

Một tình huống thực tế: bạn mở MetaTrader, tay đặt thêm một lệnh BUY trong lúc bot đang ở IN_POSITION. Nếu bot không biết lệnh đó tồn tại, nó có thể đóng nhầm hoặc để sót. Giải pháp chuẩn là tách theo magic number:

MY_MAGIC = 6111

def my_positions():
    return [p for p in positions_get() if p.magic == MY_MAGIC]

def on_tick():
    if state == "IN_POSITION":
        mine = my_positions()
        if not mine:                # lệnh bot đã bị đóng tay/khác
            state = "EXIT"
            log("Không thấy lệnh magic 6111 - đồng bộ lại")

Quy tắc: bot chỉ quản lý lệnh đúng magic của mình. Lệnh tay của bạn để nguyên — không đụng tới. Cách này tránh tai họa “bot đóng nhầm lệnh tay đang lời to”.

Đồng bộ FSM với trạng thái thật trên sàn (Reconciliation)

Sai lầm nguy hiểm nhất: FSM trong bộ nhớ tưởng là đang IN_POSITION nhưng thực tế lệnh đã bị sàn đóng (do SL phía server, do quản trị, do hết margin). Bot phải có bước đối soát định kỳ:

def reconcile():
    real = {p.ticket for p in positions_get()}
    expected = my_expected_tickets()
    lost = expected - real
    for t in lost:
        log(f"Lệnh {t} không còn trên sàn - chuyển trạng thái")
        handle_lost_order(t)

Gọi reconcile() mỗi 30 giây hoặc sau mỗi sự kiện quan trọng. Nếu FSM và thực tế lệch, ưu tiên theo thực tế rồi cập nhật lại FSM — không cố chấp giữ trạng thái ảo.

Kiểm thử FSM theo bảng (table-driven test)

Cách kiểm thử chuyên nghiệp nhất: liệt kê mọi (state, event) trong bảng chuyển trạng thái rồi tự động chạy từng trường hợp:

import itertools

CASES = [
    ("IDLE", "BUY_SIGNAL", True, True),
    ("IDLE", "BUY_SIGNAL", False, False),   # spread rộng -> giữ IDLE
    ("ENTER", "OK", True, True),
    ("ENTER", "FAIL", True, True),
    ("IN_POSITION", "SL", True, True),
    ("COOLDOWN", "TIMEOUT", False, True),
]

def run_tests(bot):
    failed = 0
    for state, event, cond, expect in CASES:
        bot.state = state
        bot.on_event(event, cond)
        ok = (bot.state_changed == expect)
        if not ok:
            failed += 1
            print(f"FAIL: {state}+{event} -> {bot.state}")
    return failed

Chạy test này mỗi khi sửa code FSM. Nếu quên một nhánh chuyển trạng thái, test báo lỗi ngay — thay vì phát hiện ra sau 3 tháng khi bot làm hỏng tài khoản.

Các câu hỏi nâng cao về FSM

FSM có giới hạn số trạng thái không? Không có giới hạn cứng, nhưng trên ~30 trạng thái thì nên tách thành nhiều FSM con (nested) hoặc chuyển sang Behavior Tree cho dễ quản lý.

Bot dùng FSM có chậm hơn if/else không? Không đáng kể — một phép so sánh trạng thái mất vài nano giây, trong khi tick đến mỗi 100ms. Vấn đề hiệu năng nằm ở kết nối API chứ không phải logic FSM.

Có nên dùng thư viện FSM có sẵn (vd transitions, python-statemachine) không? Nên. Thư viện giúp bạn khai báo bảng chuyển trạng thái gọn gàng, tự vẽ được sơ đồ và tránh lỗi thủ công. Nhưng hãy hiểu nguyên lý trước khi dùng thư viện — đừng phụ thuộc mù.

Làm sao biết bot đang ở trạng thái nào khi xem log? Luôn ghi trạng thái vào mỗi dòng log (vd [IN_POSITION] giá chạm TP). Lọc log theo trạng thái để dựng biểu đồ phân bố thời gian.

FSM xử lý tin tức lớn thế nào? Thêm trạng thái NEWS_BLOCK: trước tin lớn 15 phút chuyển sang đó, chặn vào lệnh mới nhưng vẫn quản lý lệnh đang mở; hết tin thì về lại trạng thái cũ.

Kết luận mở rộng

FSM không chỉ là “một mẹo code gọn” — nó là cách tư duy giúp bot của bạn có vòng đời rõ ràng, an toàn và dễ bảo trì. Bắt đầu từ 5–6 trạng thái cơ bản, thêm dần các trạng thái phòng thủ (NEWS_BLOCK, reconcile, cooldown) khi chiến lược phức tạp. Luôn: vẽ bảng chuyển trạng thái trước → code theo bảng → test theo bảng → log mọi chuyển động → đồng bộ với sàn. Làm đúng quy trình này, bạn có thể tin tưởng bot chạy 24/7 mà không cần canh từng phút.

9. Kết luận

FSM là nền tảng để bot hoạt động đúng, an toàn và dễ bảo trì. Bắt đầu từ 5–6 trạng thái, thêm dần khi chiến lược phức tạp. Luôn lưu trạng thái, log mọi chuyển động và kiểm thử kỹ mọi đường chuyển trước khi đưa vào tài khoản thật.

📌 Muốn làm chủ kiến trúc bot + hedging, tham gia khóa Python Hedging / Bot Auto Trading tại Hướng Nghiệp Dữ Liệu — hotline 0934.145.100 · Zalo 0934.145.100.

Đặng Trí Thanh

Đặng Trí Thanh

Giám đốc Công nghệ · DNT Digital · Giảng viên HNDL
1.319 Bài viết
15.4k Người theo dõi
120k+ Lượt đọc

Đặng Trí Thanh — Founder & CTO · Hướng Nghiệp Dữ Liệu - DNT Digital. Chuyên đào tạo và triển khai thực chiến Python, MT5 và hệ thống bot auto trading / IB cho học viên và doanh nghiệp.