Mẫu vẽ và onBar
- 🇻🇳 Tiếng Việt
- 🇬🇧 English
- 🇰🇷 한국어
- 🇨🇳 中文
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.
Indicators that use onBar: self-computed candles, labels, lines, boxes, tables, sequence counts. How to write them: Lesson 6, Lesson 7.
Heikin Ashi (separate pane)
The library template Heikin Ashi (ngăn riêng) (Heikin Ashi, separate pane): self-computed Heikin Ashi candles, where the state is the previous HA candle.
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" }),
}),
});
Highs / lows and labels
The library template Đỉnh / đáy và nhãn (Highs / lows and labels): Đỉnh (High) / Đáy (Low) labels at local highs and lows (highest / lowest within 2n + 1 bars, confirmed after n bars) and a count table in the corner.
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: () => ({}),
});
Automatic Fibonacci retracement
Levels at 0 – 23.6 – 38.2 – 50 – 61.8 – 78.6 – 100% between the high and low of the last n bars, redrawn automatically on each new bar. The lines extend 10 bars forward.
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: () => ({}),
});
Weekly pivot points on a daily chart
Classic P, R1, R2, S1, S2 levels computed from the previous week's high / low / close — no onBar needed, only 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" }),
};
},
});
On a minute / hourly chart, change "1W" to "1D" to get daily pivots. The library also has a pivots indicator (classic, Fibonacci, Camarilla…): ta.run("pivots", { method: "fibonacci" }).
9-bar sequence count (TD-style setup)
Counts consecutive bars whose close is higher (or lower) than the close 4 bars earlier; a number label on each bar, with the 9th bar highlighted — a sign the run has gone on for a while.
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) }),
});
Performance table
A table in the corner: price change over 1 week, 1 month, 3 months, 6 months, 1 year and year to date (on a daily chart).
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) inside onBar is the bar's time (not the machine clock), so the year always matches the bar being computed.
Automatic downtrend line and breakout
Connects the two most recent confirmed highs (the later high lower than the earlier one) into a downtrend line extended to the right; computes the line's value on each bar to alert when the close breaks above it.
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) }),
});
The line's value on the current bar = the later high + slope × the number of bars since the later high — plain JavaScript arithmetic inside onBar, then returned as the duong output so it can be compared with price using crossAbove and used in alerts.
onBar를 사용하는 지표: 직접 계산한 캔들, 라벨, 선, 박스, 표, 연속 카운트. 작성법: 6강, 7강.
하이킨 아시(별도 패널)
라이브러리의 Heikin Ashi (ngăn riêng)(하이킨 아시(별도 패널)) 템플릿: 직접 계산한 하이킨 아시 캔들이며, 상태는 이전 HA 캔들입니다.
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(고점 / 저점과 라벨) 템플릿: 국지적 고점·저점(2n + 1개 봉 중 가장 높음 / 낮음, n개 봉 후 확정)에 Đỉnh(고점) / Đáy(저점) 라벨을 달고, 모서리에 집계 표를 표시합니다.
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: () => ({}),
});
자동 피보나치 되돌림
최근 n개 봉의 고점과 저점 사이 0 – 23.6 – 38.2 – 50 – 61.8 – 78.6 – 100 % 수준이며, 새 봉이 생기면 자동으로 다시 그립니다. 선은 10개 봉만큼 앞으로 연장됩니다.
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: () => ({}),
});
일봉 차트의 주간 피벗 포인트
지난주의 고가 / 저가 / 종가로 계산한 전통적인 P, R1, R2, S1, S2 수준입니다. onBar 없이 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" }),
};
},
});
분봉 / 시간봉 차트에서는 "1W"를 "1D"로 바꾸면 일간 피벗이 됩니다. 라이브러리에는 pivots 지표(전통, 피보나치, 카마릴라…)도 있습니다: ta.run("pivots", { method: "fibonacci" }).
9개 봉 연속 카운트(TD 방식 셋업)
4개 봉 전 종가보다 높게(또는 낮게) 마감한 연속 봉 수를 셉니다. 봉마다 숫자 라벨을 달고 9번째 봉을 강조합니다 — 연속 흐름이 길어졌다는 신호입니다.
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) }),
});
수익률 표
모서리의 표: 1주, 1개월, 3개월, 6개월, 1년, 연초 대비 가격 변화(일봉 차트 기준).
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: () => ({}),
});
onBar 안의 new Date(ctx.time)은 봉의 시점(컴퓨터 시각이 아님)이므로, 연도는 항상 계산 중인 봉과 일치합니다.
자동 하락 추세선과 돌파
가장 최근에 확정된 두 고점(뒤 고점이 앞 고점보다 낮음)을 이어 하락 추세선을 만들고 오른쪽으로 연장합니다. 봉마다 추세선 값을 계산해 종가가 위로 넘으면 알립니다.
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) }),
});
현재 봉의 추세선 값 = 뒤 고점 가격 + 기울기 × 뒤 고점 이후의 봉 수 — onBar 안의 일반 JavaScript 계산입니다. 이를 출력 duong으로 반환해 crossAbove로 가격과 비교하고 알림에 사용합니다.
使用 onBar 的指标:自行计算的 K 线、标签、线条、方框、表格、序列计数。写法见:第 6 课、第 7 课。
Heikin Ashi(独立窗格)
库中的 Heikin Ashi (ngăn riêng)(Heikin Ashi(独立窗格))模板:自行计算 Heikin Ashi K 线,状态为前一根 HA K 线。
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(高点 / 低点与标签)模板:在局部高点、低点(2n + 1 根 K 线中的最高 / 最低,n 根 K 线后确认)处显示 Đỉnh(高点)/ Đáy(低点)标签,并在角落显示计数表。
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: () => ({}),
});
自动斐波那契回撤
在最近 n 根 K 线的高点与低点之间绘制 0 – 23.6 – 38.2 – 50 – 61.8 – 78.6 – 100 % 各水平,随新 K 线自动重绘。线条向前延伸 10 根 K 线。
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: () => ({}),
});
日线图上的周枢轴点(pivot)
根据