Bài viết gần đây
-
Tính Chỉ Báo MACD Bằng Python – MACD Line, Signal, Histogram
Tháng 8 11, 2026 -
Tính Chỉ Báo RSI Bằng Python – Công Thức Wilder, Quá Mua, Quá Bán
Tháng 8 10, 2026
Trang chủ → Bài Viết → Tính Dải Bollinger Bands Bằng Python – Middle Band, Upper/Lower, %B, BandWidth
| Tính Dải Bollinger Bands Bằng Python – Middle Band, Upper/Lower, %B, BandWidth
Được viết bởi Đặng Trí Thanh vào ngày 12/08/2026 lúc 22:54 | 43 lượt xem
Mục lục
2. Công thức Bollinger — từng bước
3. Code Bollinger bằng Python thuần
4. Code Bollinger bằng pandas — vectorized
5. Dùng pandas_ta — 1 dòng là xong
7. %B và BandWidth — 2 thước đo nâng cao
8. Kết hợp Bollinger + RSI + MACD + MA
10. FAQ
11. Kết luận
1. Bollinger Bands là gì?
Bollinger Bands (Dải Bollinger) do John Bollinger phát triển đầu thập niên 1980, là một trong những chỉ báo biến động (volatility) phổ biến nhất thế giới. Thay vì đo xu hướng như MA hay đo sức mạnh như RSI, Bollinger đo độ rộng của biến động giá — giúp bạn biết lúc nào thị trường “yên ắng” (sideway hẹp) và lúc nào “sôi động” (trend mạnh).
Nếu MA trả lời “giá đang đi đâu?”, RSI trả lời “giá đang mạnh hay yếu?”, MACD trả lời “động lượng đổi chiều khi nào?”, thì Bollinger trả lời: “giá đang biến động nhiều hay ít — và nằm ở đâu trong vùng đó?”
Bollinger gồm 3 đường:
Middle Band = SMA(20) ← đường giữa (trung bình 20 phiên)
Upper Band = SMA(20) + 2 × StdDev(20) ← dải trên
Lower Band = SMA(20) - 2 × StdDev(20) ← dải dưới
Trực quan hóa:
↑ Giá
│ ╱╲
│ ╱ ╲ ╱╲
│ ──── ╱────╲──╱──╲────── Upper Band (+2σ)
│ ╱ ╲╱ ╲
│ ────────────────╱────── Middle Band (SMA20)
│ ╱
│ ──────────────╱──────── Lower Band (-2σ)
│
└─────────────────────────→ thời gian
Khi dải HẸP lại (Squeeze) → sắp có biến động LỚN
Khi dải MỞ RỘNG (Expansion) → đang có trend mạnh
Ý tưởng cốt lõi: khoảng 95% giá nằm trong dải ±2 độ lệch chuẩn (theo phân phối chuẩn). Giá chạm dải trên → có thể quá mua tạm thời; giá chạm dải dưới → có thể quá bán tạm thời. Quan trọng nhất: dải co hẹp là dấu hiệu báo trước một cú bùng nổ biến động.
2. Công thức Bollinger — từng bước
Để code chính xác, ta cần hiểu 4 thành phần toán học:
2.1. Middle Band (đường giữa)
Là trung bình động đơn giản 20 phiên — giống Ngày 48 đã học:
$$text{Middle}_t = frac{1}{20}sum_{i=0}^{19} text{Close}_{t-i}$$
2.2. Độ lệch chuẩn (Standard Deviation)
Đo mức độ “tản mác” của giá quanh trung bình:
$$sigma_t = sqrt{frac{1}{20}sum_{i=0}^{19}(text{Close}_{t-i} – text{Middle}_t)^2}$$
2.3. Upper & Lower Band
Cộng/trừ 2 lần độ lệch chuẩn:
$$text{Upper}_t = text{Middle}_t + 2sigma_t$$
$$text{Lower}_t = text{Middle}_t – 2sigma_t$$
2.4. %B và BandWidth (2 chỉ số phái sinh)
%B cho biết vị trí của giá trong dải (0 = chạm dưới, 1 = chạm trên):
$$%B_t = frac{text{Close}_t – text{Lower}_t}{text{Upper}_t – text{Lower}_t}$$
BandWidth đo độ rộng của dải (thấp = squeeze):
$$text{BandWidth}_t = frac{text{Upper}_t – text{Lower}_t}{text{Middle}_t}$$
Hai chỉ số này là “vũ khí bí mật” để phát hiện Squeeze và đo sức mạnh trend mà nhiều người mới bỏ qua.
3. Code Bollinger bằng Python thuần
Bắt đầu từ con số 0 — chỉ dùng Python chuẩn (không pandas, không numpy) để hiểu bản chất:
import csv
from math import sqrt
def read_closes(path):
"""Đọc cột Close từ file CSV (dạng date,open,high,low,close,volume)."""
closes = []
with open(path, encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
closes.append(float(row["close"]))
return closes
def sma(values, period):
"""Trung bình động đơn giản."""
if len(values) < period:
return None
return sum(values[-period:]) / period
def std(values, period):
"""Độ lệch chuẩn mẫu (sample) của period phần tử cuối."""
if len(values) < period:
return None
window = values[-period:]
mean = sum(window) / period
variance = sum((x - mean) ** 2 for x in window) / period
return sqrt(variance)
def bollinger(closes, period=20, num_std=2.0):
"""Trả về 4 list: middle, upper, lower, bandwidth."""
middle, upper, lower, bw = [], [], [], []
for i in range(len(closes)):
window = closes[: i + 1]
m = sma(window, period)
s = std(window, period)
if m is None or s is None:
middle.append(None); upper.append(None)
lower.append(None); bw.append(None)
else:
u = m + num_std * s
l = m - num_std * s
middle.append(m); upper.append(u); lower.append(l)
bw.append((u - l) / m)
return middle, upper, lower, bw
# Sử dụng
closes = read_closes("xauusd_h1.csv")
middle, upper, lower, bw = bollinger(closes)
for i in range(-5, 0): # 5 phiên cuối
print(f"Close={closes[i]:.2f} Middle={middle[i]:.2f} "
f"Upper={upper[i]:.2f} Lower={lower[i]:.2f} BW={bw[i]:.4f}")
Giải thích từng bước
1. sma cộng period giá cuối rồi chia — giống code Ngày 48.
2. std tính trung bình bình phương độ lệch rồi lấy căn — đây là “độ lệch chuẩn dân số” (chia cho N). Một số thư viện dùng “độ lệch chuẩn mẫu” (chia N-1) — khác biệt nhỏ khi N=20.
3. bollinger lặp qua từng mốc thời gian, dùng dữ liệu tính đến thời điểm đó (không nhìn tương lai — tránh look-ahead bias).
⚠️ Lưu ý quan trọng: Khi tự code, luôn dùng `closes[:i+1]` chứ không phải `closes` đầy đủ — nếu không bạn vô tình dùng dữ liệu tương lai, kết quả backtest sẽ “đẹp giả tạo”.
4. Code Bollinger bằng pandas — vectorized
Code thuần chạy chậm vì vòng lặp Python. Với 100.000 nến, hãy dùng pandas:
import pandas as pd
# Đọc dữ liệu
df = pd.read_csv("xauusd_h1.csv", parse_dates=["date"])
df = df.sort_values("date").reset_index(drop=True)
# Bollinger Bands vectorized — 3 dòng
df["middle"] = df["close"].rolling(20).mean()
df["std"] = df["close"].rolling(20).std(ddof=0) # ddof=0: chia N (dân số)
df["upper"] = df["middle"] + 2 * df["std"]
df["lower"] = df["middle"] - 2 * df["std"]
# %B và BandWidth
df["pct_b"] = (df["close"] - df["lower"]) / (df["upper"] - df["lower"])
df["bandwidth"] = (df["upper"] - df["lower"]) / df["middle"]
# Xem 5 dòng cuối
cols = ["date", "close", "middle", "upper", "lower", "pct_b", "bandwidth"]
print(df[cols].tail())
Lưu ý ddof
| Tham số | Công thức | Dùng khi |
| `ddof=0` | chia cho N | khớp định nghĩa gốc của Bollinger |
| `ddof=1` (mặc định) | chia cho N-1 | ước lượng mẫu thống kê |
John Bollinger dùng chia cho N (population std). Nếu bạn để mặc định ddof=1, dải sẽ hơi rộng hơn một chút — không sai, nhưng không khớp chart chuẩn.
Vẽ nhanh bằng matplotlib
import matplotlib.pyplot as plt
last = df.tail(200) # 200 nến gần nhất
plt.figure(figsize=(14, 7))
plt.plot(last["date"], last["close"], label="Close", color="black", lw=1)
plt.plot(last["date"], last["middle"], label="Middle (SMA20)", color="blue", lw=1)
plt.plot(last["date"], last["upper"], label="Upper (+2σ)", color="red", lw=1, ls="--")
plt.plot(last["date"], last["lower"], label="Lower (-2σ)", color="green", lw=1, ls="--")
plt.fill_between(last["date"], last["upper"], last["lower"], alpha=0.15, color="blue")
plt.legend()
plt.title("Bollinger Bands — XAU/USD H1")
plt.tight_layout()
plt.show()
5. Dùng pandas_ta — 1 dòng là xong
Nếu chỉ cần kết quả nhanh, pandas_ta làm hết trong 1 dòng:
import pandas as pd
import pandas_ta as ta
df = pd.read_csv("xauusd_h1.csv")
# 1 dòng tính Bollinger Bands (kèm bandwidth và %B)
df.ta.bbands(close=df["close"], length=20, std=2, append=True)
# Kết quả tự thêm các cột:
# BBU_20_2.0 (Upper)
# BBM_20_2.0 (Middle)
# BBL_20_2.0 (Lower)
# BBB_20_2.0 (Bandwidth)
# BBP_20_2.0 (%B)
print(df[["close", "BBU_20_2.0", "BBM_20_2.0", "BBL_20_2.0", "BBP_20_2.0"]].tail())
Đối chiếu code tay vs pandas_ta
| Tiêu chí | Code tay | pandas | pandas_ta |
| Số dòng | ~30 | ~8 | 1 |
| Tốc độ (100k nến) | chậm (vòng lặp) | nhanh | nhanh |
| Hiểu bản chất | ✅ sâu nhất | ✅ | ❌ |
| Kiểm soát ddof | ✅ | ✅ | cấu hình sẵn |
Khuyến nghị: viết code tay một lần để hiểu, rồi dùng pandas_ta cho công việc thực tế.
6. Tín hiệu: Squeeze & Bounce
Bollinger cho 2 nhóm tín hiệu chính:
6.1. Bollinger Squeeze (bóp dải) — báo hiệu bùng nổ
Khi BandWidth thu hẹp xuống mức thấp nhất trong N phiên, thị trường đang “tích nén” năng lượng — sắp có cú phá vỡ mạnh:
def detect_squeeze(bandwidth, lookback=20):
"""True khi bandwidth hiện tại là thấp nhất trong lookback phiên."""
signals = []
for i in range(len(bandwidth)):
if i < lookback:
signals.append(False)
continue
window = bandwidth[i - lookback : i + 1]
signals.append(bandwidth[i] <= min(window))
return signals
df["squeeze"] = detect_squeeze(df["bandwidth"].tolist())
Cách trade Squeeze: khi phát hiện squeeze, đặt lệnh chờ 2 chiều (buy stop trên đỉnh, sell stop dưới đáy vùng tích lũy) — giá phá hướng nào thì đi theo hướng đó.
6.2. Bollinger Bounce (nảy từ dải)
Trong thị trường sideway (không trend), giá thường nảy ngược khi chạm dải trên/dưới:
df["touch_upper"] = df["close"] >= df["upper"]
df["touch_lower"] = df["close"] <= df["lower"]
# Tín hiệu nảy: chạm dưới → mua kỳ vọng về giữa; chạm trên → bán
df["bounce_buy"] = df["touch_lower"] & df["close"].shift(1) > df["lower"].shift(1)
df["bounce_sell"] = df["touch_upper"] & df["close"].shift(1) < df["upper"].shift(1)
⚠️ Cảnh báo quan trọng: Bounce chỉ đúng khi KHÔNG có trend. Trong trend mạnh, giá “cưỡi” dải (walk the band) — chạm dải rồi đi tiếp, không nảy. Luôn kết hợp với chỉ báo trend (MA200, ADX) để phân loại chế độ thị trường trước.
6.3. Walk the Band (cưỡi dải) — tín hiệu trend mạnh
Khi %B > 1 liên tục nhiều phiên (giá bám trên dải trên), đó là trend tăng mạnh — đừng bán ngược:
df["walk_up"] = (df["pct_b"] > 1).rolling(5).sum() >= 3 # ≥3/5 phiên trên dải
df["walk_down"] = (df["pct_b"] < 0).rolling(5).sum() >= 3
7. %B và BandWidth — 2 thước đo nâng cao
7.1. %B — vị trí giá trong dải
| %B | Ý nghĩa |
| > 1 | Giá trên dải trên (mạnh/breakout) |
| 0.8 – 1.0 | Gần dải trên (áp lực) |
| 0.2 – 0.8 | Vùng trung lập |
| 0 – 0.2 | Gần dải dưới (hỗ trợ) |
| < 0 | Giá dưới dải dưới (yếu/breakdown) |
7.2. BandWidth — đo độ co giãn
# Phân vị bandwidth hiện tại so với 6 tháng
rolling_pct = df["bandwidth"].rolling(250).rank(pct=True)
df["bw_percentile"] = rolling_pct
# Squeeze mạnh: bandwidth dưới phân vị 10%
df["strong_squeeze"] = df["bw_percentile"] < 0.10
Bảng diễn giải BandWidth percentile:
| Phân vị | Trạng thái | Hành động |
| < 10% | Squeeze cực mạnh | Chuẩn bị breakout |
| 10–30% | Dải hẹp | Theo dõi sát |
| 30–70% | Bình thường | Trade theo chiến lược |
| 70–90% | Dải rộng | Trend đang mạnh |
| > 90% | Dải cực rộng | Cảnh báo đảo chiều |
8. Kết hợp Bollinger + RSI + MACD + MA
Bộ 4 chỉ báo (MA trend, RSI sức mạnh, MACD động lượng, Bollinger biến động) là “hệ điều hành” hoàn chỉnh:
8.1. Chiến lược ví dụ: Bollinger Bounce có lọc trend
# Điều kiện BUY (bounce từ dải dưới, chỉ khi trend tăng)
cond_trend_up = df["close"] > df["close"].rolling(200).mean() # trên MA200
cond_rsi_ok = (df["rsi14"] > 30) & (df["rsi14"] < 70) # không quá bán/mua
cond_bounce = df["bounce_buy"] # nảy từ lower
df["signal_buy"] = cond_trend_up & cond_rsi_ok & cond_bounce
# Điều kiện SELL (bounce từ dải trên, chỉ khi trend giảm)
cond_trend_down = df["close"] < df["close"].rolling(200).mean()
df["signal_sell"] = cond_trend_down & cond_rsi_ok & df["bounce_sell"]
8.2. Luồng quyết định
Dữ liệu giá
│
├─ MA200 → Trend UP / DOWN / SIDEWAY?
│
├─ Nếu SIDEWAY: dùng Bollinger Bounce (%B > 1 bán, < 0 mua)
│
├─ Nếu TREND: chỉ theo hướng trend
│ ├─ MACD xác nhận động lượng cùng hướng
│ └─ Bollinger walk-the-band xác nhận trend mạnh
│
└─ RSI: lọc vùng quá mua/quá bán tránh đuổi giá
8.3. Bảng tổng hợp 4 chỉ báo
| Chỉ báo | Câu hỏi trả lời | Ngày học |
| MA (SMA/EMA) | Xu hướng đi đâu? | Ngày 48 |
| RSI | Mạnh hay yếu? Quá mua/bán? | Ngày 57 |
| MACD | Động lượng đổi chiều? | Ngày 58 |
| Bollinger | Biến động nhiều hay ít? | Ngày 59 |
9. Script Ngày 59 hoàn chỉnh
Dưới đây là script hoàn chỉnh, tích hợp tất cả:
"""
Python 365 — Ngày 59: Bollinger Bands đầy đủ
Tính Middle/Upper/Lower + %B + BandWidth + Squeeze + Bounce
"""
import pandas as pd
import numpy as np
def compute_bollinger(df, period=20, num_std=2.0):
"""Tính toàn bộ Bollinger Bands + chỉ số phái sinh."""
df = df.copy()
df["middle"] = df["close"].rolling(period).mean()
df["std"] = df["close"].rolling(period).std(ddof=0)
df["upper"] = df["middle"] + num_std * df["std"]
df["lower"] = df["middle"] - num_std * df["std"]
# %B và BandWidth
df["pct_b"] = (df["close"] - df["lower"]) / (df["upper"] - df["lower"])
df["bandwidth"] = (df["upper"] - df["lower"]) / df["middle"]
# Squeeze: bandwidth thấp nhất trong 20 phiên
df["squeeze"] = df["bandwidth"].rolling(20).min() == df["bandwidth"]
# Bounce: chạm dải rồi nảy
df["touch_upper"] = df["close"] >= df["upper"]
df["touch_lower"] = df["close"] <= df["lower"]
df["bounce_sell"] = df["touch_upper"] & (df["close"].shift(1) < df["upper"].shift(1))
df["bounce_buy"] = df["touch_lower"] & (df["close"].shift(1) > df["lower"].shift(1))
# Walk the band: %B ngoài dải ≥3/5 phiên
df["walk_up"] = (df["pct_b"] > 1).rolling(5).sum() >= 3
df["walk_down"] = (df["pct_b"] < 0).rolling(5).sum() >= 3
return df
# ===== Chạy thử =====
if __name__ == "__main__":
# Tạo dữ liệu mẫu (nếu chưa có file CSV)
np.random.seed(42)
n = 500
dates = pd.date_range("2025-01-01", periods=n, freq="h")
# Giá mô phỏng: random walk + vài cú trend
ret = np.random.normal(0, 0.001, n)
ret[200:300] += 0.002 # trend tăng
close = 2000 * np.exp(np.cumsum(ret))
df = pd.DataFrame({"date": dates, "close": close})
df = compute_bollinger(df)
# Thống kê tín hiệu
print("=== Bollinger Bands Ngày 59 ===")
print(f"Số nến: {len(df)}")
print(f"Squeeze phát hiện: {df['squeeze'].sum()} lần")
print(f"Bounce buy: {df['bounce_buy'].sum()} | Bounce sell: {df['bounce_sell'].sum()}")
print(f"Walk up: {df['walk_up'].sum()} | Walk down: {df['walk_down'].sum()}")
print("n5 nến cuối:")
cols = ["date", "close", "upper", "lower", "pct_b", "bandwidth"]
print(df[cols].tail().to_string(index=False))
Kết quả mẫu (minh họa)
=== Bollinger Bands Ngày 59 ===
Số nến: 500
Squeeze phát hiện: 9 lần
Bounce buy: 23 | Bounce sell: 21
Walk up: 61 | Walk down: 14
5 nến cuối:
date close upper lower pct_b bandwidth
2025-01-21 21:00 2534.81 2548.12 2467.35 0.8351 0.0319
2025-01-21 22:00 2541.20 2551.03 2469.11 0.8799 0.0322
2025-01-21 23:00 2549.44 2553.98 2470.82 0.9455 0.0326
2025-01-22 00:00 2557.03 2557.15 2472.63 1.0000 0.0331
2025-01-22 01:00 2563.11 2560.11 2474.29 1.0345 0.0335
10. FAQ
Hỏi: Bollinger Bands có dự đoán được giá không?
→ Không. Bollinger đo biến động, không dự đoán hướng. Nó cho biết giá “đang ở đâu so với vùng biến động” và “biến động đang co hay giãn” — từ đó bạn tự ra quyết định.
Hỏi: Tại sao dùng SMA20 mà không phải EMA20?
→ John Bollinger chọn SMA20 vì đơn giản và ổn định. 20 phiên ≈ 1 tháng giao dịch. Bạn có thể thử EMA20 nhưng kết quả gần như tương đương — quan trọng là nhất quán.
Hỏi: Nên dùng 2 độ lệch chuẩn hay 1.5 hoặc 3?
→ 2σ là chuẩn (chứa ~95% giá). 1.5σ cho dải hẹp hơn (chạm thường xuyên hơn, nhiều tín hiệu nhưng nhiều nhiễu). 3σ cho dải rộng (ít tín hiệu, độ tin cậy cao). Bắt đầu với 2σ.
Hỏi: Squeeze phát hiện rồi nhưng giá không bùng nổ thì sao?
→ Squeeze báo hiệu “khả năng” bùng nổ, không đảm bảo. Luôn chờ xác nhận: nến phá vỡ vùng tích lũy với volume tăng rồi mới vào lệnh.
Hỏi: Bollinger có dùng được cho crypto không?
→ Có, Bollinger hoạt động tốt trên mọi thị trường có dữ liệu giá: forex, crypto, chứng khoán. Riêng crypto biến động mạnh hơn nên thời gian squeeze thường ngắn hơn.
Hỏi: Bounce và Walk-the-band mâu thuẫn nhau, nghe theo cái nào?
→ Phân biệt bằng TREND: sideway → dùng Bounce; trend mạnh → dùng Walk-the-band (đi theo trend). Đây chính là lý do cần kết hợp MA200 hoặc ADX để phân loại.
11. Kết luận
Bollinger Bands là mảnh ghép thứ 4 — hoàn thiện bộ chỉ báo nền tảng:
– MA (Ngày 48) → xu hướng
– RSI (Ngày 57) → sức mạnh
– MACD (Ngày 58) → động lượng
– Bollinger (Ngày 59) → biến động
Hôm nay bạn đã học được:
1. Công thức toán đầy đủ: SMA20, độ lệch chuẩn, ±2σ.
2. Code Python thuần (hiểu bản chất) và vectorized pandas (tốc độ).
3. Hai chỉ số phái sinh quan trọng: %B và BandWidth.
4. Ba tín hiệu thực chiến: Squeeze, Bounce, Walk-the-band.
5. Cách kết hợp bộ 4 chỉ báo thành hệ thống giao dịch hoàn chỉnh.
Bài tập về nhà Ngày 59:
1. Tải dữ liệu XAU/USD H1, tự code Bollinger Bands từ số 0.
2. Vẽ biểu đồ với 3 dải và tô màu vùng giữa.
3. Đếm số lần Squeeze xuất hiện trong 1 năm — kiểm tra sau mỗi squeeze giá bùng nổ bao nhiêu pip.
4. Kết hợp Bollinger Bounce + lọc MA200, backtest 2 năm và tính win rate.
Ngày mai — Ngày 60, chúng ta sẽ tổng kết bộ chỉ báo và bắt đầu học Backtest hệ thống giao dịch hoàn chỉnh (Entry + Exit + Risk Management) — bước tiến lớn nhất của chuỗi!
📺 Video: (cập nhật sau khi upload)
🌐 Website: https://www.huongnghiepdulieu.com/python365-ngay-59/
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.
Đặng Trí Thanh
Giám đốc Công nghệ · DNT Digital · Giảng viên HNDLĐặ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.