Đi tới nội dung chính
Chuyển tới nội dung chính

Mẫu biến động và khối lượng

Dải Bollinger, Keltner, nén biến động, ATR, biến động lịch sử; khối lượng đột biến, OBV, MFI, dòng tiền Chaikin, VWAP.

Bollinger nén​

Mẫu Bollinger nén của thư viện: đánh dấu khi độ rộng dải hẹp nhất lookback nến — vùng nén hay đi trước biến động mạnh.

indicator({
name: "Bollinger nén",
description: "Dải Bollinger; đánh dấu khi độ rộng dải thấp nhất 120 nến.",
pane: "price",
params: {
n: param.int(20, { min: 5, max: 100, label: "Chu kỳ" }),
k: param.num(2, { min: 1, max: 4, step: 0.5, label: "Độ lệch chuẩn" }),
lookback: param.int(120, { min: 20, max: 500, label: "So với n nến" }),
},
plot: (d, p) => {
const b = ta.bb(d.close, p.n, p.k);
const squeeze = b.width.lte(b.width.lowest(p.lookback));
return {
Dải: band(b.upper, b.lower, { color: "accent", opacity: 0.12 }),
Giữa: line(b.mid, { color: "textMuted", dash: "dash" }),
Nén: shape(squeeze, { at: "below", shape: "circle", color: "ref" }),
};
},
alerts: (d, p) => {
const b = ta.bb(d.close, p.n, p.k);
return { "Bollinger nén chặt": b.width.lte(b.width.lowest(p.lookback)) };
},
});

Bóp Bollinger trong Keltner (squeeze)​

Khi dải Bollinger nằm trong kênh Keltner là biến động đang bị nén; thoát ra kèm động lượng (độ dốc) cho hướng bứt phá. Ngăn riêng: cột động lượng hai màu, chấm đỏ khi đang nén, xanh khi đã thoát.

indicator({
name: "Squeeze Bollinger / Keltner",
params: {
n: param.int(20, { min: 10, max: 50, label: "Chu kỳ" }),
bbK: param.num(2, { min: 1, max: 3, step: 0.5, label: "Hệ số Bollinger" }),
kcK: param.num(1.5, { min: 1, max: 3, step: 0.25, label: "Hệ số Keltner" }),
},
plot: (d, p) => {
const bb = ta.bb(d.close, p.n, p.bbK);
const kc = ta.keltner(d, p.n, p.kcK, p.n);
const nen = bb.upper.lt(kc.upper).and(bb.lower.gt(kc.lower));
const dongLuong = d.close.sub(d.high.highest(p.n).add(d.low.lowest(p.n)).div(2).add(ta.sma(d.close, p.n)).div(2));
const ml = ta.slope(dongLuong, p.n).mul(p.n);
return {
"Động lượng": hist(ml, { up: "up", down: "down", opacity: 0.7 }),
"Đang nén": shape(nen, { at: "value", value: 0, shape: "circle", size: "s", color: "down" }),
"Thoát nén": shape(nen.fillna(0).not().and(nen.shift(1)), { at: "value", value: 0, shape: "diamond", color: "up" }),
};
},
alerts: (d, p) => {
const bb = ta.bb(d.close, p.n, p.bbK);
const kc = ta.keltner(d, p.n, p.kcK, p.n);
const nen = bb.upper.lt(kc.upper).and(bb.lower.gt(kc.lower));
return { "Thoát nén": nen.fillna(0).not().and(nen.shift(1)) };
},
});

ATR theo % giá và vùng biến động​

ATR chia cho giá để so được giữa các mã; tô theo hạng của ATR % trong 250 nến (cao = biến động lớn bất thường).

indicator({
name: "ATR % và hạng biến động",
params: { n: param.int(14, { min: 5, max: 50, label: "Chu kỳ ATR" }) },
plot: (d, p) => {
const atrPct = ta.atr(d, p.n).div(d.close).mul(100);
const hang = atrPct.rolling(250).rank();
return {
"ATR %": area(atrPct, { color: "accent", opacity: 0.2, format: "number" }),
"Biến động cao (top 10 %)": bgcolor(hang.gt(0.9), { color: "down", opacity: 0.1 }),
"Biến động thấp (đáy 10 %)": bgcolor(hang.lt(0.1), { color: "up", opacity: 0.1 }),
};
},
});

Biến động lịch sử (năm hoá)​

Độ lệch chuẩn lợi nhuận log 20 phiên, nhân √250 — biến động theo năm (%).

indicator({
name: "Biến động lịch sử 20 phiên",
params: { n: param.int(20, { min: 5, max: 120, label: "Số phiên" }) },
plot: (d, p) => {
const hv = d.close.log().diff(1).rolling(p.n).std(true).mul(Math.sqrt(250)).mul(100);
return {
"HV %/năm": line(hv, { color: "accent" }),
"Trung vị 1 năm": line(hv.rolling(250).median(), { color: "textMuted", dash: "dash" }),
};
},
});

Khối lượng đột biến​

Mẫu Khối lượng đột biến của thư viện.

indicator({
name: "Khối lượng đột biến",
pane: "new",
params: {
n: param.int(20, { min: 5, max: 100, label: "Trung bình" }),
mult: param.num(2, { min: 1.2, max: 10, step: 0.1, label: "Gấp mấy lần" }),
},
plot: (d, p) => {
const avg = ta.sma(d.volume, p.n);
const spike = d.volume.gt(avg.mul(p.mult));
return {
"Khối lượng": hist(d.volume, { color: "textMuted", opacity: 0.6, format: "volume" }),
"Trung bình": line(avg, { color: "accent", format: "volume" }),
"Đột biến": shape(spike, { at: "above", shape: "diamond", color: "ref" }),
};
},
alerts: (d, p) => ({ "Khối lượng đột biến": d.volume.gt(ta.sma(d.volume, p.n).mul(p.mult)) }),
});

Khối lượng tô theo chiều giá​

indicator({
name: "Khối lượng hai màu",
plot: (d) => ({
"Khối lượng": columns(d.volume, { color: "down", colors: [{ when: d.close.gte(d.open), color: "up" }], opacity: 0.7, format: "volume" }),
"TB 20": line(ta.sma(d.volume, 20), { color: "ref", format: "volume" }),
"TB 50": line(ta.sma(d.volume, 50), { color: "ai", format: "volume", dash: "dash" }),
}),
});

OBV với đường tín hiệu​

indicator({
name: "OBV và tín hiệu",
params: { n: param.int(20, { min: 5, max: 100, label: "EMA của OBV" }) },
plot: (d, p) => {
const obv = ta.obv(d);
const tinHieu = ta.ema(obv, p.n);
return {
OBV: line(obv, { color: "accent", format: "compact" }),
"Tín hiệu": line(tinHieu, { color: "ref", format: "compact" }),
"OBV trên tín hiệu": fill("OBV", "Tín hiệu", { color: "up", below: "down", opacity: 0.12 }),
"OBV đỉnh mới, giá chưa": shape(obv.gte(obv.highest(60)).and(d.close.lt(d.close.highest(60))), { at: "top", shape: "circle", color: "up", size: "s" }),
};
},
});

"OBV đỉnh mới, giá chưa" — dòng tiền tích luỹ đi trước giá, một dấu hiệu tích luỹ hay được theo dõi.

MFI — RSI có khối lượng​

indicator({
name: "MFI",
scale: { range: [0, 100], levels: [20, 80] },
params: { n: param.int(14, { min: 5, max: 50, label: "Chu kỳ" }) },
plot: (d, p) => {
const m = ta.mfi(d, p.n);
return {
MFI: line(m, { color: "accent" }),
"Quá bán": bgcolor(m.lt(20), { color: "up", opacity: 0.12 }),
"Quá mua": bgcolor(m.gt(80), { color: "down", opacity: 0.12 }),
};
},
});

Dòng tiền Chaikin (CMF)​

indicator({
name: "Dòng tiền Chaikin",
params: { n: param.int(20, { min: 5, max: 60, label: "Chu kỳ" }) },
plot: (d, p) => {
const hl = d.high.sub(d.low);
const mfm = d.close.sub(d.low).sub(d.high.sub(d.close)).div(hl);
const cmf = mfm.mul(d.volume).rolling(p.n).sum().div(d.volume.rolling(p.n).sum());
return {
CMF: hist(cmf, { up: "up", down: "down" }),
"Thư viện (kiểm)": line(ta.run("cmf", { n: p.n }).v, { color: "textMuted", hidden: true }),
"0": hline(0),
};
},
});

Tự tính bằng phép chuỗi và đối chiếu với chỉ báo cmf của thư viện (đầu ra ẩn) — cách hay để học công thức.

VWAP phiên và độ lệch trên biểu đồ trong ngày​

// khung: 5 (script cho biểu đồ trong ngày)
indicator({
name: "VWAP phiên ± độ lệch",
pane: "price",
series: (d) => ({ dau: d.session.first, tp: d.hlc3 }),
state: () => ({ pv: 0, v: 0, pv2: 0 }),
onBar: (ctx, s) => {
if (ctx.v.dau === 1) {
s.pv = 0;
s.v = 0;
s.pv2 = 0;
}
s.pv += ctx.v.tp * ctx.volume;
s.v += ctx.volume;
s.pv2 += ctx.v.tp * ctx.v.tp * ctx.volume;
if (s.v === 0) return;
const vwap = s.pv / s.v;
const sd = Math.sqrt(Math.max(0, s.pv2 / s.v - vwap * vwap));
return { vwap, tren: vwap + 2 * sd, duoi: vwap - 2 * sd };
},
plot: (d, p, o) => ({
VWAP: line(o.vwap, { color: "ai", width: 2 }),
"Dải ±2σ": band(o.tren, o.duoi, { color: "ai", opacity: 0.06 }),
}),
});

Khối lượng tương đối (RVOL)​

Khối lượng nến so với trung bình 20 nến; trên biểu đồ ngày là "phiên hôm nay gấp mấy lần bình thường".

indicator({
name: "Khối lượng tương đối",
scale: { levels: [1, 2] },
plot: (d) => {
const rvol = d.volume.div(ta.sma(d.volume, 20).shift(1));
return {
RVOL: columns(rvol, { color: "textMuted", colors: [{ when: rvol.gte(2), color: "ref" }, { when: rvol.gte(1), color: "accent" }] }),
};
},
alerts: (d) => ({ "KL gấp đôi bình thường": d.volume.div(ta.sma(d.volume, 20).shift(1)).crossAbove(2) }),
});

ta.sma(d.volume, 20).shift(1) — trung bình 20 phiên trước (không tính phiên đang xét), để phiên đột biến không tự kéo trung bình lên.