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

Chuỗi (Series)

Chuỗi là một cột số theo nến (d.close, ta.rsi(d.close, 14)…). Mọi phép là phương thức viết sau dấu chấm và trả một chuỗi mới — nối tiếp được: d.high.sub(d.low).div(d.close).mul(100). Đối số x nhận chuỗi hoặc số.

Quy tắc chung:

  • NaN lan truyền: một vế NaN thì kết quả NaN. Chia cho 0 → NaN.
  • Điều kiện: 1 đúng, 0 sai, NaN chưa biết. Dấu / tô / cảnh báo / tín hiệu chỉ bật khi đúng.
  • So sánh làm tròn 12 chữ số có nghĩa trước khi so (tránh 0,1 + 0,2 ≠ 0,3).
  • Chỉ dùng quá khứ: không có phép nào đọc nến sau.
  • Toán tử JavaScript (+, >, &&) không dùng được giữa chuỗi — vì sao.

Số học​

Phương thứcKết quảVí dụ
a.add(x)a + xd.high.add(d.low)
a.sub(x)a − xd.close.sub(d.open) — thân nến
a.mul(x)a × xta.sma(d.volume, 20).mul(2)
a.div(x)a ÷ x (÷ 0 → NaN)d.close.div(ta.sma(d.close, 200))
a.pow(x)aˣd.ret.pow(2)
a.min(x)Nhỏ hơn của a và x, từng nếnd.open.min(d.close) — cạnh dưới thân nến
a.max(x)Lớn hơn của a và xd.open.max(d.close)
a.neg()−ad.ret.neg()
a.abs()|a|d.close.sub(d.open).abs()
a.log()ln a (a ≤ 0 → NaN)d.close.log().diff(1) — lợi nhuận log
a.exp()eᵃ
a.sqrt()√ad.ret.rolling(20).var().sqrt()
a.sign()−1 / 0 / 1 theo dấud.close.diff(1).sign()
a.round(chuSo = 0)Làm tròn tới chuSo chữ số thập phânta.rsi(d.close, 14).round(1)
a.clip(lo, hi)Kẹp a vào [lo, hi]d.ret.clip(-0.07, 0.07)

So sánh​

Kết quả là điều kiện (1 / 0; một vế NaN → NaN).

Phương thứcĐúng khiVí dụ
a.gt(x)a > xta.rsi(d.close, 14).gt(70)
a.gte(x)a ≥ xd.close.gte(d.close.highest(250)) — đỉnh mới 250 nến
a.lt(x)a < x
a.lte(x)a ≤ x
a.eq(x)a = xd.session.weekday.eq(5) — thứ Sáu
a.neq(x)a ≠ x
a.between(lo, hi)lo ≤ a ≤ hita.rsi(d.close, 14).between(40, 60)

Logic​

Phương thứcKết quảGhi chú
a.and(x)Đúng khi cả hai đúngMột vế sai → sai (kể cả vế kia NaN); không vế nào sai mà có NaN → NaN
a.or(x)Đúng khi một trong hai đúngMột vế đúng → đúng; không vế nào đúng mà có NaN → NaN
a.not()Đảo đúng / saiNaN → NaN. Muốn coi chưa biết là sai rồi đảo: a.fillna(0).not()
a.where(dk, khac = NaN)a khi dk đúng, khac khi sai hoặc chưa biếtst.line.where(st.dir.gt(0)) — chỉ giữ phần xu hướng tăng; x.where(dk, 0)
a.isNaN()1 khi a là NaN
a.notNaN()1 khi a có giá trị
a.fillna(so)Thay NaN bằng sod.foreignNet.fillna(0); so phải là số (chuỗi chữ báo fillna(value) cần một số)
a.ffill(gioiHan?)Lấp NaN bằng giá trị gần nhất trước đó (tối đa gioiHan nến)o.dinh.ffill()

"Nếu – thì – không thì" cho chuỗi: d.const(1).where(dk, -1) (1 khi đúng, −1 khi sai), a.where(dk, b) (a hoặc b).

Theo thời gian​

Phương thứcKết quảVí dụ
a.shift(n = 1)Giá trị n nến trước. n < 0 báo lỗi lookaheadd.close.shift(1) — đóng cửa hôm trước
a.diff(n = 1)a − a[n]d.close.diff(5)
a.pct(n = 1)a ÷ a[n] − 1d.close.pct(20) — % thay đổi 20 nến (dạng tỷ lệ)
a.cumsum()Cộng dồn từ nến đầu (bỏ qua NaN)d.volume.mul(d.close.diff(1).sign()).cumsum() — OBV tự viết
a.cumprod()Nhân dồnd.ret.add(1).cumprod() — tăng trưởng của 1 đồng
a.cummax(), a.cummin()Lớn / nhỏ nhất từ nến đầud.close.div(d.close.cummax()).sub(1) — sụt giảm từ đỉnh lịch sử
a.ewm(span)Trung bình hàm mũ, hệ số 2 ÷ (span + 1), khởi tạo bằng trung bình span giá trị đầuNhư ta.ema(a, span)
a.highest(n)Lớn nhất n nến gần nhất kể cả nến nàyd.high.highest(20).shift(1) — đỉnh 20 nến trước
a.lowest(n)Nhỏ nhất n nến
a.rolling(n)Cửa sổ trượt n nến — xem rollingd.close.rolling(20).std()
a.crossAbove(x)Cắt lên: nến trước a ≤ x, nến này a > xta.ema(d.close, 20).crossAbove(ta.ema(d.close, 50))
a.crossBelow(x)Cắt xuống: nến trước a ≥ x, nến này a < xd.close.crossBelow(70)
a.cross(x)Cắt lên hoặc xuống
dk.barsSince()Số nến kể từ lần dk đúng gần nhất (đúng ở nến này → 0; chưa từng → NaN)d.close.gte(d.close.highest(250)).barsSince()
dk.hold(n)Đúng ở nến dk đúng và thêm n nến sau đócat.hold(3) — "đã cắt trong 4 nến gần nhất"
a.valueWhen(dk, thu = 0)Giá trị của a ở lần thứ thu (0 = gần nhất) dk đúngd.low.valueWhen(cat) — đáy của nến tín hiệu gần nhất

crossAbove / crossBelow với một vế NaN cho 0 (không cắt), nên an toàn ở đoạn khởi động.

Cửa sổ trượt: rolling​

a.rolling(n) trả một cửa sổ n nến; gọi tiếp một phép để ra chuỗi. Cửa sổ nghiêm ngặt: chưa đủ n giá trị thì NaN.

PhépKết quả
.sum()Tổng
.mean()Trung bình (= ta.sma)
.std(mau = false)Độ lệch chuẩn tổng thể; std(true) độ lệch chuẩn mẫu
.var(mau = false)Phương sai
.min(), .max()Nhỏ / lớn nhất (= lowest / highest)
.count()Số giá trị có (không NaN) trong cửa sổ
.rank()Hạng 0…1 của giá trị cuối trong cửa sổ (1 = cao nhất cửa sổ)
.zscore()(a − trung bình) ÷ độ lệch chuẩn
.median()Trung vị
.quantile(q)Phân vị q (0 ≤ q ≤ 1)

Hàm tự viết​

Khi không có phép sẵn, viết hàm JavaScript trên số của từng nến. Hàm phải thuần: chỉ dùng đối số, không nhớ gì giữa các nến (cần nhớ → dùng onBar).

Phương thứcKết quảVí dụ
a.map((x, i) => số)Biến đổi từng giá trị (i: chỉ số nến)d.close.map((x) => Math.floor(x / 5) * 5) — làm tròn xuống bội 5
a.zip(b, (x, y) => số)Ghép hai chuỗi từng nếnd.close.zip(d.open, (c, o) => (c > o ? 1 : -1))
combine([a, b, c], (vs, i) => số)Ghép nhiều chuỗi — Tiện ích
a.named("tên")Đặt tên cho chuỗi (hiện trong giá trị tại con trỏ của trình soạn); trả chính chuỗi đóta.rsi(d.close, 14).named("RSI")

Script có hàm tự viết chạy chậm hơn một chút và chỉ chạy được nơi có bộ chạy JavaScript (biểu đồ, trình duyệt).

Phép chỉ có ở Quant AI Studio​

Các phép dưới cần nhiều mã hoặc đọc giá trị ngay lúc dựng; trên biểu đồ chúng báo lỗi:

PhépLỗi trên biểu đồ
rank(), zscore(), demean(), bucket(q), topN(n), bottomN(n), sumAcross(), meanAcross(), countAcross() — so giữa các mã trong một phiênuniverse-only: rank() là phép cắt ngang giữa các mã — chỉ dùng được ở dạng rổ (Studio)
resample("week" | "month" | …)universe-only — trên biểu đồ dùng d.tf("1W")
.data, .get(s, t), .row(s), .values(), .last(), .at(ngay), .mean(), .days, .symbols, .rows, .cols, .pick(), .sliceCols(), .range() — đọc giá trị ngayeager-in-stream: data: đọc giá trị lúc dựng chỉ có ở dạng rổ (Studio) — ở biểu đồ dùng onBar(ctx, state)
rowMap(fn)Dùng onBar

Chú ý a.rolling(n).rank() / .zscore() (theo thời gian, một mã) khác a.rank() / a.zscore() (cắt ngang giữa các mã).

Ví dụ tổng hợp​

Mọi phép chạy được trên biểu đồ trong một chỉ báo (để kiểm tên phép, xem giá trị ở cột Tại nến):

indicator({
name: "Mọi phép của chuỗi",
plot: (d) => {
const c = d.close;
const so = c.add(1).sub(1).mul(2).div(2).pow(1).min(c.add(1)).max(c.sub(1)).neg().neg().abs()
.log().exp().sqrt().pow(2).sign().mul(c).round(2).clip(c.mul(0.5), c.mul(2));
const sosanh = c.gt(c.shift(1)).or(c.gte(c.shift(1))).and(c.lt(c.add(1))).and(c.lte(c)).and(c.eq(c)).and(c.neq(0))
.and(ta.rsi(c, 14).between(0, 100));
const logic = sosanh.not().not().where(c.notNaN(), 0).fillna(0).ffill(5).add(c.isNaN());
const thoiGian = c.shift(2).diff(1).add(c.pct(1)).add(d.ret.cumsum()).add(d.ret.add(1).cumprod())
.add(c.cummax()).sub(c.cummin()).add(c.ewm(10)).sub(c.highest(10)).add(c.lowest(10));
const cuaSo = c.rolling(20);
const tk = cuaSo.sum().div(20).sub(cuaSo.mean()).add(cuaSo.std()).add(cuaSo.std(true)).add(cuaSo.var()).add(cuaSo.min())
.add(cuaSo.max()).add(cuaSo.count()).add(cuaSo.rank()).add(cuaSo.zscore()).add(cuaSo.median()).add(cuaSo.quantile(0.9));
const cat = c.crossAbove(ta.sma(c, 20)).or(c.crossBelow(ta.sma(c, 20))).or(c.cross(ta.sma(c, 50)));
const suKien = cat.barsSince().add(cat.hold(3)).add(d.low.valueWhen(cat)).add(d.low.valueWhen(cat, 1));
const tuViet = c.map((x) => Math.floor(x / 5) * 5).add(c.zip(d.open, (a, b) => (a > b ? 1 : -1))).named("Tự viết");
return {
"Số học": line(so, { hidden: true }),
"Logic": line(logic, { hidden: true }),
"Thời gian": line(thoiGian, { hidden: true }),
"Cửa sổ": line(tk, { hidden: true }),
"Sự kiện": line(suKien, { hidden: true }),
"Tự viết": line(tuViet, { color: "accent" }),
};
},
});

Các đầu ra đặt hidden: true vẫn có trong chú giải và cột Tại nến — bật lên để xem.