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

Mẫu vẽ và onBar

Chỉ báo dùng onBar: nến tự tính, nhãn, đường, hộp, bảng, đếm chuỗi. Cách viết: Bài 6, Bài 7.

Heikin Ashi (ngăn riêng)​

Mẫu Heikin Ashi (ngăn riêng) của thư viện: nến Heikin Ashi tự tính, trạng thái là nến HA trước.

indicator({
name: "Heikin Ashi (ngăn riêng)",
pane: "new",
state: () => ({ o: NaN, c: NaN }),
onBar: (ctx, s) => {
const c = (ctx.open + ctx.high + ctx.low + ctx.close) / 4;
const o = Number.isNaN(s.o) ? (ctx.open + ctx.close) / 2 : (s.o + s.c) / 2;
s.o = o;
s.c = c;
return { o, h: Math.max(ctx.high, o, c), l: Math.min(ctx.low, o, c), c };
},
plot: (d, p, o) => ({
HA: candles({ open: o.o, high: o.h, low: o.l, close: o.c }, { up: "up", down: "down" }),
}),
});

Đỉnh / đáy và nhãn​

Mẫu Đỉnh / đáy và nhãn của thư viện: nhãn Đỉnh / Đáy tại đỉnh, đáy cục bộ (cao / thấp nhất trong 2n + 1 nến, xác nhận sau n nến) và bảng đếm ở góc.

indicator({
name: "Đỉnh / đáy và nhãn",
pane: "price",
params: { n: param.int(5, { min: 2, max: 30, label: "Nửa cửa sổ" }) },
limits: { labels: 200 },
state: () => ({ tops: 0, bottoms: 0 }),
onBar: (ctx, s, p) => {
const n = p.n;
if (ctx.i < 2 * n) return;
const mid = ctx.bar(n);
let top = true;
let bottom = true;
for (let k = 0; k <= 2 * n; k++) {
const b = ctx.bar(k);
if (b.high > mid.high) top = false;
if (b.low < mid.low) bottom = false;
}
if (top) {
s.tops += 1;
ctx.draw.label({ id: `top-${mid.time}`, at: { time: mid.time, price: mid.high }, text: "Đỉnh", style: "down", color: "down" });
}
if (bottom) {
s.bottoms += 1;
ctx.draw.label({ id: `bottom-${mid.time}`, at: { time: mid.time, price: mid.low }, text: "Đáy", style: "up", color: "up" });
}
ctx.table("Tóm tắt", { position: "top-right", header: true, rows: [["Loại", "Số"], ["Đỉnh", s.tops], ["Đáy", s.bottoms]] });
},
plot: () => ({}),
});

Fibonacci thoái lui tự động​

Các mức 0 – 23,6 – 38,2 – 50 – 61,8 – 78,6 – 100 % giữa đỉnh và đáy của n nến gần nhất, tự vẽ lại theo nến mới. Đường kéo dài 10 nến về phía trước.

indicator({
name: "Fibonacci tự động",
pane: "price",
params: { n: param.int(120, { min: 20, max: 500, label: "Số nến tìm đỉnh / đáy" }) },
limits: { lines: 10, labels: 10 },
series: (d, p) => ({
cao: d.high.highest(p.n),
thap: d.low.lowest(p.n),
tuCao: d.high.gte(d.high.highest(p.n)).barsSince(),
tuThap: d.low.lte(d.low.lowest(p.n)).barsSince(),
}),
onBar: (ctx) => {
const { cao, thap, tuCao, tuThap } = ctx.v;
if (!(cao > thap) || Number.isNaN(tuCao) || Number.isNaN(tuThap)) return;
// Đáy trước đỉnh: sóng tăng, thoái lui tính từ đỉnh xuống; ngược lại tính từ đáy lên
const songTang = tuThap > tuCao;
const batDau = Math.max(tuCao, tuThap);
const muc = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1];
muc.forEach((m, k) => {
const gia = songTang ? cao - (cao - thap) * m : thap + (cao - thap) * m;
const mau = m === 0.618 ? "ref" : m === 0 || m === 1 ? "textMuted" : "accent";
ctx.draw.line({ id: `fib-${k}`, a: { barsAgo: batDau, price: gia }, b: { barsAhead: 10, price: gia }, color: mau, dash: m === 0.5 ? "dash" : "solid" });
ctx.draw.label({ id: `fib-nhan-${k}`, at: { barsAhead: 10, price: gia }, text: `${(m * 100).toFixed(1)} % ${gia.toFixed(2)}`, style: "left", color: mau, size: "s" });
});
},
plot: () => ({}),
});

Điểm xoay (pivot) tuần trên biểu đồ ngày​

Mức P, R1, R2, S1, S2 kiểu cổ điển tính từ cao / thấp / đóng của tuần trước — không cần onBar, chỉ dùng d.tf.

indicator({
name: "Pivot tuần",
pane: "price",
plot: (d) => {
const w = d.tf("1W");
const p = w.high.add(w.low).add(w.close).div(3);
const bienDo = w.high.sub(w.low);
return {
P: line(p, { color: "text", step: true }),
R1: line(p.mul(2).sub(w.low), { color: "down", step: true, dash: "dash" }),
R2: line(p.add(bienDo), { color: "down", step: true, dash: "dot" }),
S1: line(p.mul(2).sub(w.high), { color: "up", step: true, dash: "dash" }),
S2: line(p.sub(bienDo), { color: "up", step: true, dash: "dot" }),
};
},
});

Trên biểu đồ phút / giờ, đổi "1W" thành "1D" để có pivot ngày. Thư viện cũng có chỉ báo pivots (cổ điển, Fibonacci, Camarilla…): ta.run("pivots", { method: "fibonacci" }).

Đếm chuỗi 9 nến (setup kiểu TD)​

Đếm số nến liên tiếp đóng cửa cao hơn (hoặc thấp hơn) đóng cửa 4 nến trước; nhãn số ở mỗi nến, tô nổi nến thứ 9 — dấu hiệu chuỗi đã kéo dài.

indicator({
name: "Đếm chuỗi 9 nến",
pane: "price",
limits: { labels: 500 },
state: () => ({ tang: 0, giam: 0 }),
onBar: (ctx, s) => {
if (ctx.i < 4) return;
const c4 = ctx.bar(4).close;
s.tang = ctx.close > c4 ? s.tang + 1 : 0;
s.giam = ctx.close < c4 ? s.giam + 1 : 0;
if (s.tang > 0 && s.tang <= 9) {
ctx.draw.label({ at: { barsAgo: 0, price: ctx.high }, text: `${s.tang}`, style: "none", color: "#00000000", textColor: s.tang === 9 ? "down" : "textMuted", size: s.tang === 9 ? "m" : "s" });
}
if (s.giam > 0 && s.giam <= 9) {
ctx.draw.label({ at: { barsAgo: 0, price: ctx.low }, text: `${s.giam}`, style: "none", color: "#00000000", textColor: s.giam === 9 ? "up" : "textMuted", size: s.giam === 9 ? "m" : "s" });
}
return { tang9: s.tang === 9, giam9: s.giam === 9 };
},
plot: (d, p, o) => ({
"Đủ 9 tăng": barcolor(o.tang9, { color: "down" }),
"Đủ 9 giảm": barcolor(o.giam9, { color: "up" }),
}),
alerts: (d, p, o) => ({ "Đủ 9 nến giảm": o.giam9.gt(0), "Đủ 9 nến tăng": o.tang9.gt(0) }),
});

Bảng hiệu suất​

Bảng ở góc: thay đổi giá 1 tuần, 1 tháng, 3 tháng, 6 tháng, 1 năm và từ đầu năm (trên biểu đồ ngày).

indicator({
name: "Bảng hiệu suất",
pane: "price",
state: () => ({ nam: 0, dauNam: NaN }),
onBar: (ctx, s) => {
const nam = new Date(ctx.time).getUTCFullYear();
if (nam !== s.nam) {
s.nam = nam;
s.dauNam = ctx.bar(1).close;
}
const doi = (k) => {
const truoc = ctx.bar(k).close;
return truoc > 0 ? ctx.close / truoc - 1 : NaN;
};
const o = (x) => ({ text: x, format: "percent", color: x > 0 ? "up" : x < 0 ? "down" : "textMuted" });
ctx.table("Hiệu suất", {
position: "top-left",
header: true,
rows: [
["Kỳ", "Thay đổi"],
["1 tuần", o(doi(5))],
["1 tháng", o(doi(21))],
["3 tháng", o(doi(63))],
["6 tháng", o(doi(126))],
["1 năm", o(doi(250))],
["Từ đầu năm", o(s.dauNam > 0 ? ctx.close / s.dauNam - 1 : NaN)],
],
});
},
plot: () => ({}),
});

new Date(ctx.time) trong onBar là thời điểm của nến (không phải giờ máy), nên năm luôn đúng với nến đang tính.

Đường xu hướng giảm tự động và bứt phá​

Nối hai đỉnh xác nhận gần nhất (đỉnh sau thấp hơn đỉnh trước) thành đường xu hướng giảm, kéo dài sang phải; tính giá trị của đường ở từng nến để báo khi giá đóng cửa vượt lên.

indicator({
name: "Đường xu hướng giảm và bứt phá",
pane: "price",
params: { n: param.int(5, { min: 2, max: 20, label: "Số nến mỗi bên đỉnh" }) },
state: () => ({ dinh: [] }),
onBar: (ctx, s, p) => {
if (ctx.i >= 2 * p.n) {
const giua = ctx.bar(p.n);
let laDinh = true;
for (let k = 0; k <= 2 * p.n; k++) if (k !== p.n && ctx.bar(k).high >= giua.high) laDinh = false;
if (laDinh) {
s.dinh.push({ i: ctx.i - p.n, gia: giua.high, time: giua.time });
if (s.dinh.length > 2) s.dinh.shift();
}
}
if (s.dinh.length < 2) return;
const [a, b] = s.dinh;
if (b.gia >= a.gia) return;
const doc = (b.gia - a.gia) / (b.i - a.i);
const giaDuong = b.gia + doc * (ctx.i - b.i);
ctx.draw.line({ id: "xu-huong", a: { time: a.time, price: a.gia }, b: { time: b.time, price: b.gia }, extend: "right", color: "down", width: 2 });
return { duong: giaDuong };
},
plot: (d, p, o) => ({
"Giá trị đường xu hướng": line(o.duong, { color: "down", hidden: true }),
"Bứt phá đường xu hướng": shape(d.close.crossAbove(o.duong), { at: "below", shape: "arrowUp", color: "up" }),
}),
alerts: (d, p, o) => ({ "Vượt đường xu hướng giảm": d.close.crossAbove(o.duong) }),
});

Giá trị đường ở nến hiện tại = giá đỉnh sau + độ dốc × số nến kể từ đỉnh sau — phép tính thường của JavaScript trong onBar, rồi trả ra thành đầu ra duong để so với giá bằng crossAbove và dùng trong cảnh báo.