nhật ký phản nghiệm · H&V

Cổng chặn lệnh của tôi chết 14 tiếng 5 phút và không một hệ thống nào báo — vì nó báo bằng dòng log

2026-09-27 · H&V Company

Mười bốn tiếng năm phút

Ngày 26/09/2026, cổng cấm vốn của tôi — thứ duy nhất đứng giữa một tín hiệu và một lệnh thật — không chạy được từ 00:32 đến 14:37. Mười bốn tiếng năm phút.

Trong 14 tiếng đó, mọi tín hiệu tới webhook đều được phép đi.

Không job nào báo. Không mail nào tới. Tôi biết chuyện này lúc 16:26 cùng ngày, khi chạy tay capital_ban_gate.py để kiểm một việc khác.

Điều làm tôi viết bài này không phải con số 14 tiếng. Là chuyện cổng có kêu — và tiếng kêu đó đi vào đúng nơi không ai nghe.

Hai tầng, hai hành vi ngược nhau

Hệ thống có hai tầng bảo vệ, và tôi từng nghĩ chúng cùng hướng. Chúng không.

tầngkích hoạt khihành vi
logic cổng capital_ban_gate.py:91cổng chạy được nhưng thiếu cấu hình✅ CHẶN (fail-closed)
lớp bọc webhook_server.py:459-461cổng không chạy nổi (import fail / crash / treo)🔴 LỆNH VẪN ĐI (fail-open)

Đây là lớp bọc, nguyên văn:

def _capital_ban_chan(nhan: str) -> bool:
    try:
        import capital_ban_gate as _cbg
        chan, _ly_do = _cbg.kiem_tra()
    except Exception as _e:                     # cổng hỏng KHÔNG được chặn lệnh,
        log.error(f"🔴 CỔNG CẤM VỐN LỖI ({_e}) — lệnh vẫn đi, cổng đang MÙ")
        return False                            # nhưng phải kêu to.
    ...

Đọc kỹ dòng comment tôi tự viết: "cổng hỏng KHÔNG được chặn lệnh, nhưng phải kêu to." Đó là một quyết định có ý thức, không phải bug. Lý do lúc đó: không muốn một lỗi import làm đóng băng cả hệ thống giao dịch.

Và nó có kêu to. log.error. Với emoji đỏ.

Không ai đọc log. Không có job nào grep file đó. Không có alert nào gắn vào nó. Nó "kêu to" vào một căn phòng trống suốt 14 tiếng.

Đó là toàn bộ lỗ hổng. Không phải logic sai — logic đúng như tôi thiết kế. Sai ở chỗ tôi coi ghi log là báo động.

Điều tôi đã kết luận sai về chính lỗi này

Phiên kiểm soát đầu tiên tôi viết hai kết luận, và cả hai đều sai. Ghi lại vì cái sai này dễ lặp:

tôi nóithật
"dọn cây thư mục B thì cổng mất nguồn cấu hình"❌ SAI. capital_ban_gate.py:47-49 → OFF_PATH = DIR/setup_off.json (cây A). BAN_PATHS[1] trỏ thư mục CHA của B, không phải B. setup_gate3.py:185 đọc HERE/gate_von.json cũng cây A. Không nguồn nào của cổng nằm trong cây B.
"cổng fail-open"❌ nửa sai. Logic cổng là fail-closed từ 26/09: capital_ban_gate.py:91 → if not giu: return True. Chỉ lớp bọc mới fail-open.

Tôi sai vì suy từ tên đường dẫn thay vì đọc dòng code. projects/trading/ và projects/trading/ES-NQ/ trông giống nhau trong đầu tôi; chúng là hai thư mục khác nhau trên đĩa.

Kiểm bằng lệnh thay vì bằng trực giác cho ra kết luận ngược: kiến trúc hai cây là đúng, không cần gộp — cây A chạy lệnh (435 file), cây B phân tích (53 file), 19 file trùng tên thì 16 giống hệt, chỉ 2 lệch thật.

Tôi để cái sai này trong bài vì phần lớn nội dung kỹ thuật tôi đọc chỉ đăng phiên bản sau khi đã đúng. Cái sai là phần có giá trị: nó cho thấy một chẩn đoán nghe hợp lý ("dọn thư mục làm mất cấu hình") sống được bao lâu trước khi một lệnh grep giết nó.

Vì sao fail-closed không phải câu trả lời ở đây

Phản xạ hiển nhiên: sửa return False thành return True. Cổng mù thì chặn hết.

Tôi không làm, và đây là lý do đo được:

Sáng 27/09, gate_von.json cho verdict KHONG_CO_CAU_HINH_SONG — 0/18 cặp (setup × sàn) qua đủ 5 cổng. Tức cổng đang CHẶN = True một cách hợp lệ. Trong trạng thái đó, đổi lớp bọc sang fail-closed không thêm một mảy bảo vệ nào, nhưng thêm một cách mới để cả hệ thống đứng: một lỗi import trong thư viện phụ giờ có thể đóng băng đường phát lệnh mà không ai biết vì sao.

Đổi fail-open thành fail-closed là đổi chế độ lỗi im lặng này lấy chế độ lỗi im lặng khác. Vấn đề chưa bao giờ là hướng của return. Vấn đề là 14 tiếng không ai biết.

Cái phải sửa là khâu biết.

Thứ tôi dựng thay vào: cổng tự báo khi nó chết

mach_cong.py, chạy mỗi 15 phút qua launchd (com.hv.trading.machcong):

cấp chứng chỉ cho mình. Nếu webhook import capital_ban_gate rồi gọi kiem_tra() thì mạch làm y hệt, cùng cwd, cùng biến môi trường.

mạch — mạch vẫn còn sống để báo. Đây là chỗ một healthcheck in-process sẽ chết cùng thứ nó đang canh.

exception nào để log.

chống spam; đúng loại tin này sẽ bị nó nuốt. Kênh báo động không được đi qua thứ có quyền im lặng.

nếu vẫn chết. Mạch bắn mỗi 15 phút sẽ bị lọc vào thư rác trong hai ngày, và lúc đó nó tệ hơn không có.

đang chạy.** Thứ đo không được phép thay đổi thứ bị đo.

Sổ nó ghi, đọc được bằng mắt:

{"song": true, "chan": true,
 "ly_do": "CẤM VỐN TOÀN BỘ: gate_von.json verdict=KHONG_CO_CAU_HINH_SONG — 0/18 cặp qua đủ cổng",
 "luc": "2026-09-27T06:09:50"}

Hai trường quan trọng là hai câu hỏi khác nhau, và trước 26/09 tôi gộp chúng làm một: song = cổng có chạy được không. chan = cổng có đang chặn không. Một cổng song=false báo chan=false không phải là "cho phép" — nó là "không biết". Suốt 14 tiếng ngày 26/09, hệ thống đọc "không biết" thành "cho phép".

Điều đáng lấy đi khỏi bài này

  1. log.error không phải báo động. Nó là báo động chỉ khi có một tiến trình khác

đọc nó và biết gọi ai. Nếu không, nó là nhật ký cho buổi hậu kiểm — tức là cho sau khi thiệt hại xảy ra.

  1. Kiểm cả hai tầng, không chỉ tầng logic. Cổng của bạn có thể fail-closed hoàn hảo

và vẫn vô dụng nếu lớp gọi nó fail-open. Hỏi: "nếu cổng không chạy nổi thì lệnh có đi không?" — không phải "nếu cổng thiếu cấu hình thì sao?".

  1. Thứ canh phải sống lâu hơn thứ bị canh. Tiến trình con, có timeout, kênh báo

riêng không qua bất cứ cổng im-lặng-mặc-định nào.

  1. Phân biệt "không" với "không biết". Số 0 do đọc-không-được và số 0 do

thật-sự-không-có phải là hai giá trị khác nhau trong sổ, nếu không bạn sẽ ra quyết định trên một con số dối.

  1. Đọc dòng code, đừng suy từ tên đường dẫn. Tôi mất một chẩn đoán sai vào bài học

này.

Tự kiểm chứng

# trạng thái cổng hiện tại (song / chan / lý do / thời điểm kiểm)
cat ~/Documents/company/projects/trading/mach_cong.json

# lớp bọc fail-open, nguyên văn
sed -n '455,465p' ~/Documents/trading/ES-NQ/webhook_server.py

# logic cổng fail-closed
sed -n '85,95p' ~/Documents/company/projects/trading/capital_ban_gate.py

# job canh có đang chạy không
launchctl list | grep machcong

Cảnh báo rủi ro

Bài này là phân tích giáo dục về hạ tầng giám sát rủi ro, không phải lời khuyên đầu tư. Hiệu suất quá khứ không bảo đảm kết quả tương lai. Giao dịch đòn bẩy có rủi ro mất toàn bộ vốn. Hệ thống mô tả trong bài chạy trên tài khoản demo (demo.ctrader.5885753); lỗ hổng fail-open mô tả ở đây áp dụng như nhau cho tài khoản thật, và đó là lý do tôi công bố nó. Tôi không nhận góp vốn, không bán tín hiệu.

Lấy sổ lệnh thật của tôi — và hai con số lỗ khác nhau trong cùng một sổ

Sổ giao dịch đầy đủ, không cắt lệnh xấu. Tài khoản mất −670,30 €, nhưng chỉ −343,30 € đến từ quyết định giao dịch — −327,00 € còn lại là 3 lệnh kiểm thử hạ tầng khớp thật trên sàn. Ai công bố một trong hai số mà không nói còn số kia thì đang kể một nửa.

Sổ sạch: 5 lệnh · WR 20 % · expR −0,090 · PF 0,298. Kèm bảng dựng lại đòn bẩy từng lệnh — 13 dòng vượt trần 10×, nhưng đúng 1 dòng là lệnh thật (02/09, US30, 22,72×, −296,40 € = 86 % toàn bộ lỗ thật). 12 dòng còn lại tới 188× là test. Phân biệt được hai loại đó là toàn bộ giá trị của file này.

Để email, tôi gửi kèm mỗi phép đo mới giết được một giả thuyết. Không tín hiệu, không kèo, không mời góp vốn.

Không lưu IP. Bỏ đăng ký bất cứ lúc nào. Không muốn đưa email? Sổ vẫn để công khai tại /tai/ — tôi không gate thứ mình rao là minh bạch.

← tất cả bài