# vnstock-js > vnstock-js là thư viện TypeScript lấy dữ liệu chứng khoán Việt Nam (giá, nến, bảng giá, công ty, tài chính, thị trường, khối ngoại, sàng lọc, chỉ báo, backtest, MCP). Tài liệu này dành cho bản 2.x. Quy tắc quan trọng: - Bản 2.x khác hẳn 1.x: `import { vn } from "vnstock-js"`, không có `init()`, không có default export, không có `stock.quote()`. - Tiền luôn tính bằng đồng: giá `127000` là 127.000 đồng. Không nhân hay chia 1000. - Chỉ số là điểm. `changePct` là phần trăm (`0.95` là 0,95%). Tỷ số tài chính là phân số (`roe: 0.15` là 15%). - Nến có `time` theo giờ Việt Nam, không có `date`. Range: `1d 5d 1mo 3mo 6mo ytd 1y 2y 5y max` (không có `6m`). - Tham số luôn là `symbol`, không phải `ticker`. - Chỉ báo import từ `vnstock-js/indicators`, nhận `Bar[]`, không gọi mạng. - Trình duyệt phải đi qua proxy: `nextHandler()` từ `vnstock-js/proxy` và `createClient({ proxy: "/api/vnstock" })`. - Lỗi có `code` và `hint`; hiển thị `hint` cho người dùng. --- # Giới Thiệu > Tổng quan về vnstock-js, thư viện dữ liệu chứng khoán Việt Nam cho JavaScript và TypeScript. **vnstock-js** là thư viện mã nguồn mở lấy dữ liệu chứng khoán Việt Nam cho JavaScript và TypeScript. Chạy trên Node, Bun, Deno, Cloudflare Workers, Vercel Edge, và trên trình duyệt qua proxy đóng gói sẵn. ```ts import { vn } from "vnstock-js"; const fpt = vn.stock("FPT"); await fpt.price(); // { price: 127000, changePct: 0.95, ... } await fpt.history("6mo"); // nến ngày, giá tính bằng đồng await vn.market(); // VN-Index, thanh khoản, độ rộng, khối ngoại ``` 👉 [vnstock-js trên npm](https://www.npmjs.com/package/vnstock-js) · [GitHub](https://github.com/ttqteo/vnstock-js) · [Trang demo vnstock-js-site](https://vnstock-js.vercel.app) ## Điểm chính của bản 2.0 - **Một API gọn**: `vn.stock('FPT')`, `vn.index('VNINDEX')`, `vn.market()`, `vn.screen()`... Không cần khóa API, không cần hàm khởi tạo. - **Tiền luôn tính bằng đồng**, chỉ số tính bằng điểm, tỷ số là phân số. Xem [Quy ước & đơn vị](/docs/getting-started/conventions). - **Chạy mọi nơi**: core dùng `fetch` có sẵn, không đụng ổ đĩa; trình duyệt đi qua proxy `vnstock-js/proxy`. - **Dành cho AI agent**: JSDoc kèm ví dụ cho mọi API, `AGENTS.md` trong package, MCP server 27 công cụ. ## Tính năng | Tính năng | Mô tả | |---|---| | Bảng giá, lịch sử giá | Giá hiện tại, nến từ 1 phút tới 1 tháng, gộp khung ngay trong thư viện | | Thông tin công ty, tài chính | Hồ sơ, cổ đông, ban lãnh đạo, sự kiện, cổ tức, báo cáo tài chính | | Thị trường | Tổng quan, độ rộng, thanh khoản, khối ngoại và lịch sử khối ngoại, thanh khoản trong phiên | | Sàng lọc | Lọc theo P/E, ROE, vốn hoá... bằng `where` và `sort` | | Chỉ báo & pattern | SMA, EMA, RSI, MACD, hỗ trợ/kháng cự, mẫu nến, Turtle, sức khoẻ thị trường | | Danh mục đầu tư | Định giá theo giá hiện tại, nhóm theo môi giới và ngành | | Theo dõi & cảnh báo | `watch()` qua WebSocket hoặc poll, `alert()` khi giá vượt đỉnh | | Backtest | Event study với chốt lời, cắt lỗ, số phiên giữ; tìm phiên tương tự | | Vàng, tỷ giá, tin tức | BTMC, GiaVang.net, Vietcombank, tin tài chính trong ngày | | MCP & CLI | Dùng trong Claude, Cursor, VS Code; tra cứu nhanh trên terminal | Đang dùng bản 1.x? Đọc [Nâng cấp từ v1](/docs/getting-started/migration). ## Nhà phát triển Được phát triển bởi [github.com/ttqteo](https://github.com/ttqteo). Lấy cảm hứng từ [thinh-vu/vnstock](https://github.com/thinh-vu/vnstock) cho Python. > **Lưu ý:** Dữ liệu chỉ phục vụ tham khảo, không phải khuyến nghị đầu tư. --- # Cài Đặt > Cài đặt vnstock-js cho Node, serverless, trình duyệt, CLI và MCP. ```bash npm install vnstock-js ``` Cần **Node 18 trở lên** (dùng `fetch` có sẵn). Không cần khóa API và không cần gọi hàm khởi tạo: danh mục mã và lịch nghỉ được tải ở lần dùng đầu. ## Các entry của package | Import | Dùng cho | |---|---| | `vnstock-js` | Client `vn`, `createClient()`, kiểu dữ liệu, lỗi | | `vnstock-js/indicators` | Chỉ báo và pattern, hàm thuần trên `Bar[]`, không gọi mạng | | `vnstock-js/proxy` | Proxy cho trình duyệt: `nextHandler()`, `workerHandler()`, `expressHandler()` | | `vnstock-js/node` | `fileStorage()` lưu cache ở `~/.vnstock-js` giữa các lần chạy | Package xuất cả CommonJS (`require`) và ESM (`import`). ## Cache trên ổ đĩa (tuỳ chọn, Node) Mặc định dữ liệu nền (danh mục mã, lịch nghỉ, tỷ số quý) chỉ cache trong bộ nhớ. Script chạy nhiều lần nên dùng `fileStorage()`: ```ts import { createClient } from "vnstock-js"; import { fileStorage } from "vnstock-js/node"; const vn = createClient({ storage: fileStorage(), preload: true }); ``` ## Trình duyệt VietCap chặn request từ trình duyệt, nên cần gắn proxy có sẵn vào server của bạn rồi tạo client với `proxy`. Xem [Trình duyệt & proxy](/docs/key-features/advanced/proxy). ## Theo dõi giá trực tiếp `watch()` ở chế độ stream cần WebSocket: Node 22 trở lên và trình duyệt có sẵn; Node 18 đến 21 cài thêm `ws`: ```bash npm install ws ``` Hoặc dùng `{ mode: 'poll' }` để hỏi bảng giá định kỳ, không cần WebSocket. ## CLI ```bash npx vnstock-js FPT # hoặc cài global npm install -g vnstock-js vnstock history FPT --range 3mo ``` Xem [CLI](/docs/getting-started/cli). ## MCP cho Claude, Cursor, VS Code ```bash claude mcp add --scope user vnstock npx -y vnstock-js mcp ``` Xem [MCP Server](/docs/key-features/advanced/mcp). --- # Hướng Dẫn Sử Dụng Nhanh > Những lời gọi hay dùng nhất của vnstock-js 2.x trong một trang. ## Cổ phiếu ```ts import { vn } from "vnstock-js"; const fpt = vn.stock("FPT"); // tạo đồng bộ, chưa gọi mạng const quote = await fpt.price(); // giá, trần, sàn, tham chiếu, khối lượng, giá trị const daily = await fpt.history("6mo"); // nến ngày, giá bằng đồng const weekly = await fpt.history({ from: "2025-01-01", interval: "1w" }); const profile = await fpt.company(); const holders = await fpt.shareholders(); const income = await fpt.financials("income", { period: "quarter" }); console.log(quote.price, quote.changePct, daily.at(-1)?.close, weekly.length, profile.industry, holders.length, income.rows.length); ``` Mã sai không làm hỏng lúc tạo handle; lời gọi đầu tiên mới báo lỗi kèm gợi ý, ví dụ `vn.stock("FTP")` nhận `Did you mean "FPT"?`. ## Chỉ số và thị trường ```ts import { vn } from "vnstock-js"; const vnindex = await vn.index("VNINDEX").history("1y"); // điểm const overview = await vn.market({ exchange: "HOSE" }); // chỉ số, thanh khoản, độ rộng, khối ngoại const vn30 = await vn.prices({ group: "VN30" }); const { gainers, losers } = await vn.topMovers({ limit: 5 }); console.log(vnindex.at(-1)?.close, overview.liquidity.value, vn30.length, gainers, losers); ``` ## Sàng lọc ```ts import { vn } from "vnstock-js"; const rows = await vn.screen({ group: "VN30", where: { pe: { lt: 15 }, roe: { gt: 0.15 } }, // ROE là phân số: 0.15 = 15% sort: "-roe", limit: 10, }); ``` ## Chỉ báo ```ts import { vn } from "vnstock-js"; import { sma, rsi, supportResistance } from "vnstock-js/indicators"; const bars = await vn.stock("FPT").history("1y"); const sma20 = sma(bars, { period: 20 }); const rsi14 = rsi(bars); const zones = supportResistance(bars); ``` ## Theo dõi giá ```ts import { vn } from "vnstock-js"; const stop = vn.watch(["FPT", "VCB"], (t) => console.log(t.symbol, t.price, t.changePct)); setTimeout(stop, 60000); // nhớ dừng, nếu không tiến trình chạy mãi ``` ## Tiếp theo - [Quy ước & đơn vị](/docs/getting-started/conventions): đọc trước khi tính toán với số liệu. - [Danh sách hàm](/docs/getting-started/api-references): toàn bộ API. - [Trình duyệt & proxy](/docs/key-features/advanced/proxy): dùng trong React, Next.js client component. --- # Quy Ước & Đơn Vị > Đơn vị tiền, chỉ số, phần trăm, thời gian và lỗi trong vnstock-js 2.x. Sai đơn vị là loại lỗi hay gặp nhất khi làm việc với dữ liệu chứng khoán. vnstock-js 2.x dùng một bộ quy ước duy nhất cho mọi hàm. ## Đơn vị | Dữ liệu | Đơn vị | Ví dụ | |---|---|---| | Giá cổ phiếu (giá khớp, trần, sàn, tham chiếu, nến cổ phiếu) | đồng | `127000` là 127.000 đồng | | Giá trị giao dịch, vốn hoá, khối ngoại, thanh khoản thị trường | đồng | `13354737798630` là khoảng 13,35 nghìn tỷ | | Báo cáo tài chính | đồng (`unit: 'VND'`) | | | Giá vàng | đồng/lượng | `142000000` | | Tỷ giá | đồng trên một đơn vị ngoại tệ | `25880` | | Chỉ số (VNINDEX, VN30...) | điểm | `1689.84` | | Phần trăm thay đổi giá (`changePct`, `pnlPct`, `ret` của backtest) | phần trăm | `0.95` là 0,95% | | Tỷ số tài chính (ROE, ROA, biên lợi nhuận), `winRate`, `weight` | phân số | `0.15` là 15% | > **Lưu ý:** Không nhân chia 1000 để "sửa" đơn vị. Muốn hiển thị giá kiểu bảng điện (`127.00`), chia cho 1000 ngay ở bước hiển thị. Riêng nến trong phiên (1m, 5m, 15m, 30m, 1h), `value` là giá trị giao dịch **luỹ kế** từ đầu phiên tới nến đó; nến ngày trở lên thì `value` là của riêng nến. ## Thời gian Mọi thời gian theo giờ Việt Nam (ICT, UTC+7), bất kể máy chạy ở múi giờ nào. - Nến ngày, tuần, tháng: `time: "2026-10-16"`. - Nến trong phiên: `time: "2026-10-16T09:15:00+07:00"`. - Nến có `time`, không có `date`. ## Khoảng thời gian và khung nến - Range: `1d 5d 1mo 3mo 6mo ytd 1y 2y 5y max`, hoặc `{ from, to }` dạng `YYYY-MM-DD`. - Interval: `1m 5m 15m 30m 1h 1d 1w 1mo`. Các khung 5m, 15m, 30m, 1w, 1mo được gộp từ nến gốc ngay trong thư viện. - `6m` không phải range hợp lệ (nhầm với 6 phút); dùng `6mo`. Truyền sai sẽ nhận lỗi kèm gợi ý. ## Tham số - Luôn là `symbol`, không phân biệt hoa thường: `vn.stock("fpt")` giống `vn.stock("FPT")`. - Sàn: `HOSE`, `HNX`, `UPCOM`, `ALL`; `HSX` được hiểu là `HOSE`. ## Lỗi Mọi lỗi kế thừa `VnstockError`, có `code` và thường có `hint` chỉ cách sửa: | Lỗi | `code` | Khi nào | |---|---|---| | `InvalidSymbolError` | `INVALID_SYMBOL` | Mã không có trong danh mục; `hint` gợi ý mã gần nhất | | `InvalidParameterError` | `INVALID_PARAMETER` | Tham số sai (range, interval, `tp`...) | | `NetworkError` | `NETWORK_ERROR` | Mất mạng, timeout | | `RateLimitError` | `RATE_LIMIT` | Nguồn từ chối vì gọi quá nhiều | | `ApiError` | `API_ERROR` | Nguồn trả lỗi HTTP | | `DataUnavailableError` | `DATA_UNAVAILABLE` | Không tải được dữ liệu nền và không có bản cache | | `ParseError` | `PARSE_ERROR` | Dữ liệu từ nguồn hoặc dữ liệu đã lưu không đọc được | ```ts import { vn, VnstockError } from "vnstock-js"; try { await vn.stock("FTP").price(); } catch (e) { if (e instanceof VnstockError) console.log(e.code, e.hint); // INVALID_SYMBOL, Did you mean "FPT"? } ``` Lỗi `JSON.stringify` được, tiện ghi log hoặc trả về qua API. --- # Dùng Với AI > Prompt mẫu, file quy tắc cho agent, llms.txt và MCP để AI viết code vnstock-js 2.x đúng ngay lần đầu. Các mô hình AI phần lớn đã học vnstock-js 1.x: gọi `init()`, tính giá theo nghìn đồng, dùng `stock.quote()`. Nếu không nói trước, chúng sẽ viết code kiểu cũ và code đó chạy sai với 2.x. Trang này gom những thứ cần đưa cho AI để tránh chuyện đó. ## Prompt mẫu Dán đoạn này vào đầu cuộc trò chuyện với ChatGPT, Claude, Gemini hay chat của Cursor, rồi viết yêu cầu của bạn ở dòng cuối. ```text Mình dùng thư viện vnstock-js bản 2.x (npm install vnstock-js). Bản 2.x khác hẳn 1.x, đừng dùng kiến thức cũ. Quy tắc: - import { vn } from "vnstock-js". Không có init(), không có default export, không có stock.quote(). - Tiền luôn tính bằng đồng: giá 127000 là 127.000 đồng. Không nhân hay chia 1000. - Chỉ số là điểm. changePct là phần trăm (0.95 là 0,95%). Tỷ số tài chính là phân số (roe 0.15 là 15%). - Nến có trường time theo giờ Việt Nam, không có date. Range: 1d 5d 1mo 3mo 6mo ytd 1y 2y 5y max (không có 6m). - Tham số luôn là symbol, không phải ticker. - Chỉ báo import từ "vnstock-js/indicators", nhận Bar[]. - Chạy trên trình duyệt phải qua proxy: nextHandler() từ "vnstock-js/proxy" và createClient({ proxy: "/api/vnstock" }). - Lỗi có code và hint, hiển thị hint cho người dùng. Tài liệu đầy đủ: https://vnstock-js-docs.vercel.app/llms-full.txt Nếu đọc được file trong project, đọc node_modules/vnstock-js/AGENTS.md trước khi viết code. Việc cần làm: ... ``` Nếu công cụ AI của bạn đọc được link, chỉ cần đưa link `llms-full.txt` là đủ, vì file đó đã có sẵn các quy tắc trên. ## File quy tắc cho agent trong project Với agent làm việc trực tiếp trong repo (Claude Code, Cursor, Codex, Copilot), thêm một đoạn vào file quy tắc của project để lần nào agent cũng đọc: | Công cụ | File | |---|---| | Claude Code | `CLAUDE.md` | | Codex, nhiều agent khác | `AGENTS.md` | | Cursor | `.cursor/rules/vnstock.mdc` | | GitHub Copilot | `.github/copilot-instructions.md` | ```md ## vnstock-js Project dùng vnstock-js 2.x. Trước khi viết code gọi vnstock-js, đọc node_modules/vnstock-js/AGENTS.md (quy tắc đơn vị và công thức mẫu). Tiền là đồng, changePct là phần trăm, tỷ số là phân số, không có init(). Kiểu dữ liệu và ví dụ của từng hàm nằm trong JSDoc ở node_modules/vnstock-js/dist/index.d.ts. ``` `AGENTS.md` đi kèm package nên luôn khớp với phiên bản bạn đã cài, kể cả khi trang tài liệu đã lên bản mới hơn. ## Đưa tài liệu cho AI - [`/llms.txt`](/llms.txt): mục lục tài liệu kèm quy tắc, theo chuẩn [llms.txt](https://llmstxt.org). - [`/llms-full.txt`](/llms-full.txt): toàn bộ tài liệu trong một file Markdown, hợp để dán vào chat hoặc đưa vào context. - Mỗi trang tài liệu có nút **Copy Markdown** dưới tiêu đề và bản `.md` riêng, ví dụ [`/md/getting-started/conventions.md`](/md/getting-started/conventions.md). Đưa đúng trang đang cần thì đỡ tốn context hơn đưa cả bộ. ## Để AI tự lấy dữ liệu qua MCP Nếu bạn muốn hỏi dữ liệu thay vì viết code, cho AI dùng thẳng vnstock-js qua MCP. Claude Desktop, Cursor, VS Code đều nhận cấu hình này: ```json { "mcpServers": { "vnstock": { "command": "npx", "args": ["-y", "vnstock-js", "mcp"] } } } ``` Sau đó hỏi bình thường, ví dụ "VN30 có mã nào P/E dưới 15 và ROE trên 15%?". AI sẽ gọi `screen_stocks` và trả lời bằng dữ liệu thật. Danh sách 27 công cụ xem ở trang [MCP](/docs/key-features/advanced/mcp). ## Nhận ra code AI viết theo 1.x Thấy một trong các dấu hiệu sau thì code đang theo bản cũ, nhắc AI đọc lại prompt mẫu: | Dấu hiệu | Cách đúng ở 2.x | |---|---| | `await init()`, `new Vnstock()`, `import vnstock from` | `import { vn } from "vnstock-js"` | | `stock.quote(...)`, `vnstock.stock.quote.history(...)` | `vn.stock("FPT").history("6mo")` | | `price * 1000`, `value / 1e6`, ghi chú "đơn vị nghìn đồng" | Bỏ phép đổi, số đã là đồng | | `bar.date` | `bar.time` | | `ticker: "FPT"`, range `"6m"` | `symbol`, `"6mo"` | | `filters`, `sortBy` khi sàng lọc | `where: { pe: { lt: 15 } }`, `sort: "-roe"` | Bảng đối chiếu đầy đủ ở trang [Nâng cấp từ v1](/docs/getting-started/migration). --- # Kiến Trúc > Cách vnstock-js 2.x tổ chức client, source, transport, storage và compute. Kiến trúc (theo chiều một lời gọi đi): - Ứng dụng của bạn (Node, Bun, Deno, Workers, trình duyệt) gọi Client: `vn` hoặc `createClient({ proxy, storage, ... })`. - Dữ liệu: Source VCI đổi dữ liệu VietCap sang `Bar`, `Quote` (đồng) → Transport (fetch, retry, rate limit, proxy) → `vnstock-js/proxy` (chỉ trên trình duyệt) → VietCap. - Thời gian thực: Realtime (`watch()`, một WebSocket chung hoặc poll) → SSI WebSocket. - Bộ nhớ đệm: Storage (`memoryStorage`, `fileStorage`) tải lười từ dữ liệu trên GitHub (mã, lịch nghỉ, tỷ số, khối ngoại). - Tính toán: compute và indicators là hàm thuần trên `Bar[]`, không gọi mạng. Mọi lời gọi đi theo một đường: **client → source → transport → nguồn dữ liệu**, còn phần tính toán là hàm thuần nằm riêng. ## Client `vn` là client mặc định; `createClient()` tạo client riêng với cấu hình của bạn. Handle như `vn.stock('FPT')` được tạo đồng bộ và không gọi mạng; lần dùng đầu mới kiểm tra mã có trong danh mục. ```ts import { createClient } from "vnstock-js"; const vn = createClient({ proxy: "/api/vnstock", // chỉ cần trên trình duyệt timeout: 10000, retries: 2, preload: true, // tải sẵn danh mục mã và lịch nghỉ }); ``` ## Source Source VCI gọi API của VietCap và đổi kết quả sang dạng chung của thư viện: `Bar` cho nến, `Quote` cho bảng giá, mọi số tiền về đồng, thời gian về giờ Việt Nam. ## Transport Transport bọc `fetch` có sẵn của môi trường: - Thử lại với backoff khi lỗi mạng, timeout hoặc lỗi 5xx. - Nhận diện bị giới hạn tần suất (HTTP 429) và trang chặn của Cloudflare, báo lỗi rõ ràng thay vì trả dữ liệu rác. - Khi client có `proxy`, viết lại URL sang proxy của bạn. Header phiên của VietCap chỉ được gắn phía server; trình duyệt luôn đi qua proxy. ## Storage Danh mục mã, lịch nghỉ và tỷ số quý được tải lười từ GitHub ở lần dùng đầu, rồi cache trong một `Storage`: ```ts interface Storage { get(key: string): Promise; set(key: string, value: string): Promise; remove(key: string): Promise; } ``` `memoryStorage()` là mặc định. `fileStorage()` của `vnstock-js/node` lưu ở `~/.vnstock-js` giữa các lần chạy. Trên trình duyệt có thể tự viết một `Storage` trên IndexedDB. Danh mục đầu tư (`vn.portfolio.save()`) và lịch sử khối ngoại cũng đi qua `Storage`. Core không dùng `fs`, nên chạy được trên serverless và edge mà không cần cấu hình gì. ## Compute và indicators Gộp nến, chỉ báo, pattern, định giá danh mục, backtest và tìm phiên tương tự là hàm thuần trên `Bar[]`, không gọi mạng. Chỉ báo được xuất riêng ở `vnstock-js/indicators` nên dùng được cả trong trình duyệt. ## Realtime `watch()` dùng chung một kết nối WebSocket tới SSI cho mọi watcher của một client, tự kết nối lại khi rớt. Ở chế độ `poll`, nó hỏi bảng giá định kỳ thay vì giữ WebSocket. ## Lịch sử khối ngoại VietCap chỉ cho xem khối ngoại của phiên hiện tại. Repo vnstock-js có một workflow chạy mỗi ngày sau 15:30, ghi lại khối ngoại của từng mã vào `data/foreign` và gộp theo tháng. `foreign('3mo')` đọc các file tháng này qua raw GitHub và cache trong `Storage`. --- # Danh Sách Hàm > Toàn bộ API công khai của vnstock-js 2.x. Mọi số tiền tính bằng **đồng**, chỉ số bằng **điểm**. Xem [Quy ước & đơn vị](/docs/getting-started/conventions). ## Import ```ts import { vn, createClient, memoryStorage } from "vnstock-js"; import { sma, rsi, macd /* ... */ } from "vnstock-js/indicators"; import { nextHandler, workerHandler, expressHandler } from "vnstock-js/proxy"; import { fileStorage } from "vnstock-js/node"; ``` ## Client | Hàm | Trả về | Mô tả | |---|---|---| | `createClient(options?)` | client | `{ proxy, timeout, retries, storage, fetch, preload }` | | `vn.stock(symbol)` | handle cổ phiếu | Tạo đồng bộ, không gọi mạng | | `vn.index(symbol)` | handle chỉ số | `VNINDEX`, `VN30`, `VN100`, `HNXIndex`, `HNX30`, `HNXUpcomIndex` | | `vn.prices(symbols \| { group } \| { exchange })` | `Quote[]` | Bảng giá nhiều mã trong một request | | `vn.watch(symbols, cb, options?)` | hàm dừng | Giá trực tiếp, `cb` nhận `Tick` | | `vn.market(options?)` | tổng quan | Chỉ số, thanh khoản, độ rộng, khối ngoại; `asOf` cho phiên đã qua | | `vn.market.breadth(options?)` | độ rộng | Số mã tăng, giảm, trần, sàn | | `vn.market.liquidity(options?)` | thanh khoản | Giá trị giao dịch so với phiên trước | | `vn.market.foreign(options?)` | khối ngoại | Phiên hiện tại; thêm `range` để lấy lịch sử | | `vn.market.intraday(options?)` | đường thanh khoản | Luỹ kế theo phút so với phiên trước | | `vn.market.context(options?)` | bối cảnh cho AI | Xu hướng, thanh khoản so với trung bình, độ rộng | | `vn.screen(options)` | `ScreenRow[]` | `{ group \| exchange, where, sort, limit }` | | `vn.portfolio(input)` | tổng hợp danh mục | `{ positions, cash }`; kèm `save()`, `load()` | | `vn.backtest(symbols, signal, options)` | kết quả backtest | Gộp lệnh và thống kê nhiều mã | | `vn.search(query, { limit }?)` | `SymbolInfo[]` | Tìm theo mã hoặc tên công ty | | `vn.symbols({ exchange, group, industry }?)` | `SymbolInfo[]` | Danh mục mã | | `vn.calendar.isTradingDay(date)` | `boolean` | Có phải ngày giao dịch | | `vn.calendar.next(date?)`, `previous(date?)` | `string` | Ngày giao dịch kế tiếp, trước đó | | `vn.calendar.holidays(year)` | `string[]` | Ngày nghỉ trong năm | | `vn.topMovers({ limit }?)` | `{ gainers, losers }` | Top tăng, giảm | | `vn.gold({ source }?)` | `{ source, unit, items }` | Giá vàng đồng/lượng | | `vn.fx({ date }?)` | `FxRate[]` | Tỷ giá Vietcombank | | `vn.news({ date, source, query }?)` | tin tức | Tin tài chính trong một ngày | ## Handle cổ phiếu: `vn.stock(symbol)` | Hàm | Trả về | Mô tả | |---|---|---| | `price()` | `Quote` | Giá, trần, sàn, tham chiếu, khối lượng, giá trị | | `history(range \| { from, to, interval })` | `Bar[]` | Nến từ 1 phút tới 1 tháng | | `company()` | hồ sơ | Ngành, số cổ phiếu, lịch sử | | `shareholders()`, `officers()` | danh sách | Cổ đông lớn, ban lãnh đạo | | `events()`, `dividends()`, `news()` | danh sách | Sự kiện, cổ tức, tin của công ty | | `financials(kind, { period })` | `{ unit, fields, rows }` | `'balance' \| 'income' \| 'cashflow' \| 'ratios'`, theo `year` hoặc `quarter` | | `foreign()` / `foreign(range)` | khối ngoại | Phiên hiện tại, hoặc lịch sử đã ghi | | `context({ lookback, asOf })` | `AIContext` | Xu hướng, chỉ báo, mốc giá cho AI | | `prompt({ lang })` | `string` | Bối cảnh dạng văn bản cho LLM | | `patterns({ recent })` | báo cáo pattern | Hỗ trợ/kháng cự, mẫu nến, Turtle, breakout | | `watch(cb, options?)` | hàm dừng | Giá trực tiếp của mã này | | `alert({ when, lookback }, cb)` | hàm dừng | Cảnh báo `breakout` hoặc theo hàm | | `backtest(signal, { tp, sl, hold, range })` | kết quả backtest | Event study trên nến ngày | | `similar({ top, range })` | phiên tương tự | Thử nghiệm | ## Handle chỉ số: `vn.index(symbol)` | Hàm | Trả về | Mô tả | |---|---|---| | `price()` | `Quote` | Mức điểm hiện tại so với phiên trước | | `history(range \| { from, to, interval })` | `Bar[]` | Nến tính bằng điểm | | `context(options?)`, `prompt(options?)` | bối cảnh | Như handle cổ phiếu | | `health(options?)` | sức khoẻ thị trường | Ngày phân phối, ngày bùng nổ xác nhận (phương pháp của William O'Neil) | ## Chỉ báo: `vnstock-js/indicators` `sma`, `ema`, `rsi`, `macd`, `bollinger`, `atr`, `superTrend`, `ichimoku`, `ichimokuFutureCloud`, `swingPivots`, `supportResistance`, `extremeZones`, `candlePatterns`, `turtleSignals`, `marketHealth`, `volumeZScore`, `breakouts`, `patternReport`. Mọi hàm nhận `Bar[]`, trả kết quả theo `time`. Xem [Chỉ báo & pattern](/docs/key-features/advanced/indicators). ## Proxy: `vnstock-js/proxy` `nextHandler()`, `workerHandler()`, `expressHandler()`. Xem [Trình duyệt & proxy](/docs/key-features/advanced/proxy). ## Lỗi `VnstockError`, `InvalidSymbolError`, `InvalidParameterError`, `NetworkError`, `RateLimitError`, `ApiError`, `ParseError`, `DataUnavailableError`. Xem [Quy ước & đơn vị](/docs/getting-started/conventions#lỗi). --- # Nâng Cấp Từ v1 > Bảng đối chiếu từng lời gọi vnstock-js 1.x sang 2.x và cách đổi đơn vị. Bản 2.0.0 làm lại API từ đầu. Không có lớp tương thích: code v1 phải sửa theo bảng dưới. Phần khó nhất là **đơn vị**: v2 trả tiền bằng đồng ở mọi nơi, nên mọi phép tính dựa trên số v1 (nghìn đồng, triệu đồng, tỷ đồng) phải đổi lại. Phần này không thể tự động hoá an toàn, hãy đọc kỹ mục "Đơn vị". ## Khởi tạo ```ts // v1 import vnstock from 'vnstock-js' await vnstock.init() // v2: không cần init(), dữ liệu nền tải ở lần dùng đầu import { vn, createClient } from 'vnstock-js' const client = createClient({ proxy: '/api/vnstock' }) // khi chạy trên trình duyệt ``` `init({ ratios: true })` không còn. Danh mục mã, lịch nghỉ và tỷ số quý được tải lười qua `Storage`. Trên Node, `fileStorage()` của `vnstock-js/node` cache chúng ở `~/.vnstock-js`. ## Bảng đối chiếu | v1 | v2 | |---|---| | `stock.quote({ ticker: 'FPT', start })` | `vn.stock('FPT').history({ from: start })` | | `stock.quote.history({ symbols: ['FPT'], start, timeFrame: '1H' })` | `vn.stock('FPT').history({ from: start, interval: '1h' })` | | `recentHistory('VCB', 30)` | `(await vn.stock('VCB').history('3mo')).slice(-30)` | | `stock.priceBoard({ ticker: 'FPT' })`, `stock.trading.priceBoard([...])` | `vn.stock('FPT').price()`, `vn.prices(['FPT', 'VCB'])` | | `quickQuote('VCB')` | `vn.stock('VCB').price()` | | `compareSymbols(['VCB', 'TCB'])` | `vn.prices(['VCB', 'TCB'])` | | `stock.topGainers()`, `stock.topLosers()`, `topMovers()` | `vn.topMovers({ limit: 10 })` trả `{ gainers, losers }` | | `stock.company({ ticker }).profile()` | `vn.stock('FPT').company()` | | `company.shareholders()`, `officers()`, `events()`, `dividends()`, `news()` | `vn.stock('FPT').shareholders()`, `.officers()`, `.events()`, `.dividends()`, `.news()` (dùng chung một request với `company()`) | | `stock.financials({ ticker, period })`, `financials.incomeStatement({ symbol, period })` | `vn.stock('FPT').financials('income', { period: 'quarter' })`; loại `'balance'`, `'income'`, `'cashflow'`, `'ratios'` | | `stock.screening({ filters, sortBy, order })`, `stock.screening.screen(...)` | `vn.screen({ group: 'VN30', where: { pe: { lt: 15 } }, sort: '-roe', limit: 10 })` | | `stock.listing.symbolsByGroup('VN30')` | `vn.symbols({ group: 'VN30' })` | | `stock.listing.industriesIcb()` | `vn.symbols({ industry: '...' })`, ngành có trong `industry`, `industryEn` của từng mã | | `stock.aiContext('VCB', { asOf })` | `vn.stock('VCB').context({ asOf })` | | `stock.toAIPrompt('VCB', { lang })` | `vn.stock('VCB').prompt({ lang })` | | `market.overview({ exchange })` | `vn.market({ exchange })` | | `market.breadth({ exchange })` | `vn.market.breadth({ exchange })` | | `market.foreignFlow({ exchange, top })` | `vn.market.foreign({ exchange, top })`; lịch sử: `vn.market.foreign({ range: '1mo' })` | | `stock.foreignFlow('VCB')` | `vn.stock('VCB').foreign()`; lịch sử: `vn.stock('VCB').foreign('3mo')` | | `market.aiContext()` | `vn.market.context()` | | `commodity.gold.priceSJC()`, `commodity.goldPrice({ source })` | `vn.gold({ source: 'auto' })` | | `commodity.exchange()` | `vn.fx({ date })` | | `news.byDate()`, `news.bySource('Vietstock')`, `news.search('VCB')` | `vn.news({ date })`, `vn.news({ source: 'Vietstock' })`, `vn.news({ query: 'VCB' })` | | `realtime.connect(...)`, `realtime.subscribe(socket, { symbols })` | `const stop = vn.watch(['FPT', 'VNM'], (t) => ...)`; `vn.stock('FPT').watch(cb)` | | `import { sma, rsi, ... } from 'vnstock-js'` | `import { sma, rsi, ... } from 'vnstock-js/indicators'` | | `watchlist.create/add/list` | Không còn trong SDK. Dùng CLI `vnstock watchlist` hoặc tool MCP `watchlist` (cùng file `~/.vnstock-js/watchlist.json`), hoặc tự lưu qua `Storage` | Tham số luôn là `symbol`, không còn `ticker`. Mã viết thường cũng được, `HSX` hiểu là `HOSE`. Danh mục mã (`vn.symbols()`, `vn.search()`) cũng trả sàn `HOSE`, không còn `HSX` như dữ liệu VCI. ## Khoảng thời gian và khung nến - Range: `1d 5d 1mo 3mo 6mo ytd 1y 2y 5y max`. Range kiểu cũ như `6m` báo lỗi kèm gợi ý dùng `6mo`. - Interval: `1m 5m 15m 30m 1h 1d 1w 1mo`. `timeFrame: '1H'` thành `interval: '1h'`. - Nến dùng `time` thay cho `date`: `'2026-10-16'` cho nến ngày, `'2026-10-16T09:15:00+07:00'` cho nến trong phiên. - `from`/`to` thay cho `start`/`end`. ## Đơn vị | Dữ liệu | v1 | v2 | Đổi số v1 sang v2 | |---|---|---|---| | Giá cổ phiếu (nến, bảng giá, trần, sàn, tham chiếu) | nghìn đồng (`125.8`) | đồng (`125800`) | nhân 1.000 | | `QuoteHistory.value` (giá trị giao dịch của nến) | triệu đồng | đồng | nhân 1.000.000 | | Thanh khoản thị trường, dòng tiền khối ngoại toàn sàn | tỷ đồng | đồng | nhân 1.000.000.000 | | Giá trị giao dịch trên bảng giá, vốn hoá | không thống nhất | đồng | đối chiếu từng trường | | Chỉ số (VNINDEX, VN30) | điểm | điểm | giữ nguyên | | Thay đổi giá theo phần trăm (`changePct`) | phần trăm | phần trăm (`0.96` là 0,96%) | giữ nguyên | | Biến động trong `context().performance` (`change1d`, `change7d`, `change30d`, `change90d`) | phân số (`0.05`) | phần trăm (`5`) | nhân 100 | | Tỷ số (ROE, biên lợi nhuận) | không thống nhất | phân số (`0.15` là 15%) | đối chiếu từng trường | | Giá vàng | theo nguồn | đồng/lượng | đối chiếu nguồn | Hai lưu ý: - **Giá vốn trong danh mục.** `vn.portfolio()` nhận `cost` bằng đồng (`120000`). Nếu bạn đang lưu giá vốn theo nghìn đồng (`120`), phải nhân 1.000 trước khi truyền vào. - **Nến trong phiên.** `value` của nến 1m, 5m, 15m, 30m, 1h là giá trị giao dịch luỹ kế từ đầu phiên tới nến đó, không phải của riêng nến đó. Nến ngày trở lên vẫn là giá trị của riêng nến. ## Lỗi Các class lỗi giữ nguyên tên (`InvalidSymbolError`, `InvalidParameterError`, `NetworkError`, `RateLimitError`, `ApiError`, `ParseError`, `DataUnavailableError`) và có thêm `hint` chỉ cách sửa, ví dụ `Did you mean "FPT"?`. `NotInitializedError` bị gỡ vì không còn `init()`. ## Môi trường - Node 18 trở lên. Không còn axios: mọi request đi qua `fetch` có sẵn. - Trình duyệt cần proxy vì VCI chặn CORS: xem `vnstock-js/proxy` (`nextHandler`, `workerHandler`, `expressHandler`). - `vn.watch()` ở chế độ stream cần WebSocket: Node 22 trở lên có sẵn, Node 18 đến 21 cài thêm `ws`, hoặc dùng `{ mode: 'poll' }`. ## Tính năng mới không có ở v1 `vn.portfolio()`, lịch sử khối ngoại, `watch()`, `alert()`, `vn.market.intraday()`, `backtest()`, `similar()`, `patterns()`, `vn.index('VNINDEX').health()`. Xem các trang trong mục Tính năng. --- # CLI > Tra cứu giá, lịch sử, thị trường, sàng lọc, backtest và danh mục ngay trên terminal. vnstock-js kèm lệnh `vnstock` để dùng thẳng trong terminal, không cần viết code. ## Cài đặt ```bash npx vnstock-js FPT # chạy một lần npm install -g vnstock-js # cài global, sau đó gọi `vnstock` ``` Gõ mã trần là xem giá: `vnstock FPT` giống `vnstock quote FPT`. ## Giá và lịch sử ```bash vnstock quote FPT vnstock history FPT --range 3mo vnstock history FPT --from 2026-01-01 --to 2026-06-30 --limit 50 vnstock compare FPT VCB HPG ``` ## Tìm mã ```bash vnstock search "ngân hàng" --limit 5 vnstock symbols --exchange HOSE ``` ## Thị trường ```bash vnstock market # chỉ số, thanh khoản, độ rộng, khối ngoại vnstock market --exchange HNX --index HNXIndex vnstock foreign --top 5 # khối ngoại toàn sàn vnstock foreign FPT # khối ngoại một mã vnstock gold # giá vàng, đồng/lượng ``` ## Phân tích ```bash vnstock patterns FPT # hỗ trợ/kháng cự, mẫu nến, Turtle, breakout vnstock screen --group VN30 --where "pe<15,roe>0.15" --sort -roe --limit 10 vnstock backtest FPT VCB --preset breakout --tp 7 --sl 4 --hold 10 --range 2y vnstock similar FPT --top 5 # thử nghiệm ``` `--where` nhận các so sánh cách nhau bằng dấu phẩy: `<`, `<=`, `>`, `>=`, `=`. ROE, ROA là phân số (`roe>0.15` là trên 15%). Preset backtest: `breakout`, `oversold_bounce`, `ema_cross`; `--tp`, `--sl` tính bằng phần trăm. ## Danh mục và watchlist Lưu ở `~/.vnstock-js`. ```bash vnstock portfolio add FPT 100 120000 --broker DNSE # giá vốn bằng đồng vnstock portfolio cash 5000000 --broker DNSE vnstock portfolio # định giá theo giá hiện tại vnstock portfolio remove FPT vnstock watchlist create bank vnstock watchlist add bank VCB TCB MBB vnstock watchlist quote bank vnstock watchlist # liệt kê các danh sách ``` Gõ giá vốn theo nghìn đồng (`120` thay vì `120000`) sẽ bị từ chối kèm gợi ý. ## Định dạng đầu ra | Cờ | Tác dụng | |---|---| | `--json` | In dữ liệu dạng JSON để dùng trong script | | `--csv` | CSV, với `quote`, `history`, `search`, `symbols`, `market`, `foreign` | | `--quiet` | Ẩn thông báo phụ (cập nhật phiên bản...) | | `--no-color` | Tắt màu | Phần hiển thị đổi đơn vị cho dễ đọc (`125.8k`, `392.5 tỷ`); `--json` giữ nguyên số liệu bằng đồng. ## MCP ```bash vnstock mcp # chạy MCP server qua stdio ``` Xem [MCP Server](/docs/key-features/advanced/mcp). --- # Lịch Sử Phiên Bản > Các thay đổi qua từng phiên bản vnstock-js ## 2.0.0 1.x có hai bộ API song song làm cùng một việc, mỗi chỗ trả tiền một đơn vị (nghìn đồng, triệu đồng, tỷ đồng), và phải gọi `init()` trước khi dùng. Bản 2.0 viết lại từ đầu: một API, mọi số tiền là đồng, không cần khởi tạo, chạy được trên Node, serverless và trình duyệt. ```ts // 1.x import vnstock from 'vnstock-js' await vnstock.init() const history = await vnstock.stock.quote({ ticker: 'FPT', start: '2026-01-01' }) // [{ date: '2026-01-02', close: 125.8, ... }] giá theo nghìn đồng // 2.0 import { vn } from 'vnstock-js' const history = await vn.stock('FPT').history({ from: '2026-01-01' }) // [{ time: '2026-01-02', close: 125800, ... }] giá theo đồng ``` Không có lớp tương thích với 1.x. Trước khi nâng cấp, đọc [Nâng cấp từ v1](/docs/getting-started/migration): có bảng đối chiếu từng lời gọi và mục Đơn vị. Đơn vị là chỗ dễ sai nhất: đổi lời gọi mà giữ phép nhân chia cũ thì code vẫn chạy, nhưng số lệch 1.000 lần. ### Thay đổi không tương thích **Đơn vị.** Mọi số tiền là đồng: | Dữ liệu | 1.x | 2.0 | |---|---|---| | Giá cổ phiếu (nến, bảng giá, trần, sàn, tham chiếu) | nghìn đồng | đồng | | Giá trị giao dịch của nến | triệu đồng | đồng | | Thanh khoản thị trường, khối ngoại toàn sàn | tỷ đồng | đồng | | Vốn hoá, báo cáo tài chính, giá vàng | tuỳ nguồn | đồng (vàng: đồng/lượng) | | `context().performance` | phân số (`0.05`) | phần trăm (`5`) | Không đổi: chỉ số vẫn là điểm, `changePct` vẫn là phần trăm. Tỷ số tài chính là phân số (`roe: 0.15` là 15%). **API.** - Một điểm vào: `import { vn, createClient } from 'vnstock-js'`. `vn.stock('FPT')` và `vn.index('VNINDEX')` trả về handle, các hàm cấp thị trường nằm ngay trên client (`vn.market()`, `vn.screen()`, `vn.prices()`, `vn.gold()`...). - Gỡ toàn bộ API 1.x: default export, class `Vnstock`, `stock`, `market`, `commodity`, `news`, `realtime`, `watchlist` và các hàm easy mode `quickQuote`, `recentHistory`, `compareSymbols`, `topMovers`. - Không còn `init()` và `NotInitializedError`. Danh mục mã, lịch nghỉ, tỷ số quý tự tải ở lần dùng đầu. - Chỉ báo chuyển sang `vnstock-js/indicators`. - Watchlist rời khỏi SDK, còn ở CLI `vnstock watchlist` và tool MCP `watchlist` (đọc file `~/.vnstock-js/watchlist.json` cũ). **Tên trường và tham số.** - Tham số luôn là `symbol`, không còn `ticker`. - Nến có `time` thay cho `date`, theo giờ Việt Nam: `2026-10-16` cho nến ngày, `2026-10-16T09:15:00+07:00` cho nến trong phiên. Nến trong phiên có `value` là giá trị luỹ kế từ đầu phiên. - Bảng giá trả `Quote`: `ref`, `ceiling`, `floor`, `volume`, `value`, `bids`, `asks`... thay cho `referencePrice`, `ceilingPrice`, `totalVolume`, `totalValue`, `bidPrices`... - Khoảng thời gian `1d 5d 1mo 3mo 6mo ytd 1y 2y 5y max` hoặc `{ from, to }`, khung nến `1m 5m 15m 30m 1h 1d 1w 1mo`. Viết kiểu cũ như `6m` thì báo lỗi kèm gợi ý. - Sàng lọc dùng `where: { pe: { lt: 15 } }` và `sort: '-roe'` thay cho `filters`, `sortBy`, `order`. - `vn.symbols()` và `vn.search()` trả sàn `HOSE` thay cho `HSX`. **Môi trường.** Cần Node 18 trở lên. Đã gỡ axios và date-fns, mọi request đi qua `fetch` có sẵn. `watch()` ở chế độ stream cần WebSocket: có sẵn từ Node 22, Node 18 đến 21 cài thêm `ws`. ### Mới **Chạy ở mọi nơi.** - `createClient({ proxy, timeout, retries, storage, fetch, preload })`. Handle tạo đồng bộ, không gọi mạng cho tới lần dùng đầu. Gõ sai mã thì lời gọi đầu tiên báo lỗi kèm `Did you mean "FPT"?`. - Core không đụng ổ đĩa nên chạy trên Vercel, Cloudflare Workers, Deno, Bun. Cache qua `Storage`: mặc định trong bộ nhớ, `fileStorage()` ở `vnstock-js/node` lưu vào `~/.vnstock-js`. - Proxy cho trình duyệt ở `vnstock-js/proxy` (`nextHandler()`, `workerHandler()`, `expressHandler()`), vì VietCap chặn CORS. Proxy chỉ chuyển tiếp tới hai host VietCap theo danh sách đường dẫn cố định, không phải open proxy. - Build cả CommonJS và ESM. Entry gốc bundle được cho trình duyệt mà không cần `fs`, `path`, `events` hay `ws`. - Lỗi có `hint` chỉ cách sửa, và `JSON.stringify` được. **Dữ liệu.** - Lịch sử khối ngoại: `vn.stock('FPT').foreign('3mo')`, `vn.market.foreign({ range: '1mo' })`. VietCap chỉ cho xem phiên hiện tại, nên repo tự ghi lại mỗi ngày. Dữ liệu có từ năm 2026. - Thanh khoản trong phiên: `vn.market.intraday()` trả thanh khoản luỹ kế theo phút, so với phiên trước tại cùng giờ. - Báo cáo tài chính trả `{ unit: 'VND', fields, rows }` theo năm hoặc quý, kèm lịch sử tỷ số. - Gộp nến 5m, 15m, 30m, 1w, 1mo phía client. **Danh mục, theo dõi giá, cảnh báo.** - `vn.portfolio({ positions, cash })` định giá danh mục theo giá hiện tại, nhóm theo môi giới và theo ngành. `cost` là giá vốn mỗi cổ phiếu, tính bằng đồng. - `vn.watch(['FPT', 'VCB'], cb)` dùng chung một WebSocket cho mọi watcher; `mode: 'poll'` cho serverless và trình duyệt. - `alert({ when: 'breakout' })` báo một lần mỗi phiên khi giá vượt đỉnh N phiên. `when` nhận cả hàm `(tick) => boolean`. **Phân tích.** - Chỉ báo mới: vùng hỗ trợ/kháng cự, vùng quá mua/quá bán, bảy mẫu nến đảo chiều, tín hiệu Turtle, breakout có xác nhận khối lượng, sức khoẻ thị trường theo William O'Neil. Gọi nhanh qua `vn.stock('FPT').patterns()` và `vn.index('VNINDEX').health()`. - Backtest kiểu event study: `vn.stock('FPT').backtest('breakout', { tp: 7, sl: 4, hold: 10 })`. Vào lệnh ở giá mở cửa phiên sau tín hiệu; phiên chạm cả chốt lời lẫn cắt lỗ thì tính cắt lỗ trước. `tp`, `sl` tính bằng phần trăm. Chưa tính phí, trượt giá, trần sàn. - Phiên tương tự (thử nghiệm): `similar()` tìm các phiên trong 5 năm có trạng thái kỹ thuật giống phiên gần nhất, và giá đã đi bao nhiêu sau 5, 20, 60 phiên. **Cho AI agent.** - `AGENTS.md` đi kèm package: quy tắc đơn vị và công thức mẫu cho coding agent. Mọi API công khai có JSDoc kèm ví dụ, đọc được ngay từ `.d.ts`. Mọi khối code trong README và AGENTS.md được type-check trong CI. - MCP có 27 tool, thêm `find_patterns`, `get_foreign_history`, `portfolio_summary`, `backtest_signal`, `find_similar_setups`. Tên 22 tool cũ giữ nguyên. - CLI thêm `patterns`, `similar`, `backtest`, `gold`, `compare`, `watchlist`, `screen`, `portfolio`. Thêm `--json` để lấy dữ liệu thô. ### Sửa Các lỗi này có ở 1.x: - Lịch sử giá bỏ qua ngày bắt đầu: `stock.quote({ start })` luôn trả 365 phiên tính lùi từ hôm nay, và khoảng dài hơn khoảng 1,5 năm bị cắt mất phần đầu. - Ngày (`end`, `asOf`, ngày sự kiện, tin tức) lệch một ngày tuỳ múi giờ của máy chạy. Nay luôn đọc theo giờ Việt Nam. - `changePct` luôn `null` ở `quickQuote`, `compareSymbols` và tool MCP `get_quote`. - Tỷ lệ sở hữu của cổ đông và ban lãnh đạo nhỏ hơn thật 100 lần (6,8% hiện thành 0,068%). Nay là phân số đúng: `0.068` là 6,8%. - Giá bạc BTMC gấp 10 lần, và `gold()` trả hàng trăm dòng lặp. Nay mỗi sản phẩm còn một dòng mới nhất. Đồng xu vàng bị bỏ khỏi kết quả, vì nguồn báo giá cả đồng xu, không quy được ra đồng/lượng. - Độ mạnh xu hướng phụ thuộc mức giá: cùng một xu hướng, mã giá 5 nghìn và mã giá 125 nghìn cho độ mạnh khác nhau. - CLI crash khi thoát trên Windows (`UV_HANDLE_CLOSING`). ## 1.5.1 Bản vá cho môi trường serverless và cách báo lỗi mạng. ### Sửa - **`import "vnstock-js"` không còn làm sập app trên Vercel, AWS Lambda hay Cloudflare.** Trước đây chỉ cần nạp package là ném `ENOENT: mkdir '/home/.../.vnstock-js'` và mọi route trả 500, kể cả app không hề dùng watchlist. Nguyên nhân: instance `watchlist` được tạo ở cấp module, mà storage lại tạo thư mục trong home ngay lúc khởi tạo, trong khi serverless không có home ghi được. Storage nay không đụng ổ đĩa lúc khởi tạo. Thư mục chỉ tạo ở lần ghi đầu tiên, và nếu home không ghi được thì tự chuyển sang thư mục tạm thay vì ném lỗi. - **Request đứt giữa chừng không còn bị báo là `HTTP 200: OK`.** Phản hồi lớn có thể đứt khi đang tải thân dữ liệu, sau khi header 200 đã về, khiến thư viện đọc nhầm thành lỗi HTTP thành công. Ngoài thông báo vô nghĩa, request kiểu này còn không được thử lại lần nào. Nay nhận diện đúng là lỗi truyền tải, ném `NetworkError` và thử lại theo backoff. Không breaking. Chạy trên máy cá nhân không thấy khác biệt. ## 1.5.0 Nhóm API mức thị trường: độ rộng, thanh khoản, khối ngoại, bối cảnh cho AI. ### Thêm - **`market.breadth({ exchange })`**: số mã tăng, giảm, đứng giá, trần, sàn. Sàn nhận `HOSE` (mặc định), `HNX`, `UPCOM`, `ALL`. - **`market.liquidity({ index, asOf })`**: giá trị giao dịch toàn sàn, kèm phiên trước và % thay đổi. - **`market.foreignFlow({ exchange, top })`** và **`stock.foreignFlow(symbol)`**: mua, bán, ròng của khối ngoại, kèm top mua bán ròng. - **`market.overview({ exchange })`**: gộp chỉ số, thanh khoản, độ rộng, khối ngoại trong một lời gọi. - **`market.aiContext({ exchange, asOf })`**: regime thị trường, thanh khoản so với trung bình 20 phiên, độ rộng, khối ngoại. - **`asOf`** cho `stock.aiContext()` và `stock.toAIPrompt()`: tính chỉ báo tại cuối một phiên quá khứ. - **`init({ ratios: true })`**: tải bộ chỉ số theo quý dựng sẵn từ GitHub, cache 24h. Sàng lọc theo ROE, ROA, biên lợi nhuận không gọi mạng lần nào. - **MCP lên 22 tool** (trước 13): thêm nhóm thị trường và lấp các module chưa lộ ra MCP là tin tức, báo cáo tài chính, sàng lọc, hàng hoá, watchlist. - **CLI**: `vnstock market` và `vnstock foreign [symbol]`. Đơn vị tiền nhóm thị trường là **tỷ VND**, output kèm trường `unit`. ### Breaking - **Giá chỉ số không còn chia 1000.** `VNINDEX` trước trả `1.66901`, nay trả `1669.01` điểm. Nếu code đang nhân 1000 để bù thì bỏ đi. - **`goldPriceGiaVangNet()` trả dữ liệu đã chuẩn hoá.** Field đổi tên: `type_code` thành `code`, `type` thành `name`, `buy` thành `buyPrice`, `sell` thành `sellPrice`. - **`screening.screen()` bắt buộc có phạm vi**: phải truyền `group` hoặc `exchange`, không thì ném `InvalidParameterError`. ### Sửa - **`screening` chuyển sang REST.** Endpoint GraphQL cũ trả body rỗng nên `screen()` trả mảng rỗng, người dùng đọc nhầm thành "không mã nào khớp". Bản mới lọc theo bảng giá trước rồi mới gọi chỉ số cho các mã sống sót. - **Lỗi mạng nay serialize được.** Trước đây `cause` giữ nguyên đối tượng axios chứa socket trỏ vòng, nên `JSON.stringify(err)` ném lỗi và log qua Sentry hay pino bị hỏng. - **`asOf` lệch một ngày**, do biên `end` của endpoint là loại trừ. - **CLI nuốt lệnh mới**: `vnstock market` từng bị hiểu nhầm là mã chứng khoán. ## 1.4.3 ### Thêm - **`QuoteHistory.value`**: giá trị giao dịch từng bar, đơn vị **triệu VND**. - **`QuoteHistory.symbol`**: mở khoá batch nhiều mã cho `quote.history({ symbols: [...] })`. Một request cho cả danh sách thay vì N request, giảm nguy cơ 429. Không breaking, cả hai đều là trường optional. ## 1.4.2 ### Thêm - **MCP tool `get_dividends`** và **`get_corporate_events`**: lịch cổ tức và sự kiện doanh nghiệp (ĐHCĐ, phát hành, niêm yết, giao dịch nội bộ). ### Sửa - **Root export thiếu `macd`, `bollinger`, `atr`**: ba indicator này có từ 1.4.0 nhưng quên re-export, buộc phải import qua subpath. Nay import thẳng từ `vnstock-js`. ## 1.4.1 ### Thêm - **`superTrend(candles, opts?)`**: chỉ báo theo xu hướng, mặc định `period=10, multiplier=3`. - **`ichimoku(candles, opts?)`** và **`ichimokuFutureCloud()`**: mây Ichimoku, bản thứ hai cho projection 26 phiên tới. - **`aiContext`** nay kèm `superTrend` và `ichimoku`, và phân loại xu hướng có tính tới hai chỉ báo này. - **`VciAdapter({ cache })`**: cache TTL cho các endpoint ít đổi. Mặc định bật, tắt bằng `{ cache: false }`. ### Sửa - **Nhận diện Cloudflare Error 1015**: ném `RateLimitError` và retry với backoff 30s, 60s, 120s thay vì báo lỗi chung. - **`goldPriceSJC()` deprecated**: endpoint trả 403 từ IP ngoài Việt Nam. Dùng `goldPriceBTMC()` hoặc `goldPriceGiaVangNet()`. ## 1.4.0 Chuyển định vị từ SDK dữ liệu sang bộ công cụ nghiên cứu cho AI. ### Thêm - **MCP server** (`vnstock mcp`): dùng được với Claude Desktop, Cursor, VS Code. 11 tool song ngữ. - **Indicators v2**: `macd`, `bollinger`, `atr`. - **`stock.aiContext(symbol)`**: JSON có cấu trúc gồm xu hướng, snapshot chỉ báo, kháng cự hỗ trợ, z-score khối lượng, hiệu suất 1d/7d/30d/90d. - **`stock.toAIPrompt(symbol, { lang })`**: bản văn xuôi cho LLM không dùng MCP. - **Easy mode**: `quickQuote`, `recentHistory`, `compareSymbols`, `topMovers`. - **Watchlist** `vnstock.watchlist`: CRUD, lưu tại `~/.vnstock-js/watchlist.json`. Không breaking. Code 1.3.x chạy y nguyên. ## 1.3.3 ### Thêm - **Module `news`**: tin tức tài chính Việt Nam từ [news-crawler](https://github.com/ttqteo/news-crawler). `news.byDate(date?)`, `news.bySource(source, date?)`, `news.search(keyword, date?)`. ### Sửa - **Listing, Company, Financial hoạt động trở lại.** Ba module này chuyển từ GraphQL (offline từ khoảng 04/2026) sang REST mới của Vietcap. API công khai không đổi. ### Còn tồn tại - `listing.allSymbols()` vẫn bỏ qua vì endpoint trả 403. - `screening` chưa chuyển REST, để dành bản sau. ## 1.3.2 ### Thêm - **CLI tự phát hiện bản mới**: kiểm tra ngầm registry npm, in banner ở cuối output. Cache 24h, timeout 2s, tự tắt khi chạy trong CI hoặc khi đặt `VNSTOCK_NO_UPDATE_CHECK=1`. ### Sửa - **`history --range 1d`** tính đúng `Change %` cho phiên cũ nhất, nhờ fetch thêm buffer 10 phiên để có mốc so sánh. ## 1.3.1 Bản vá cho CLI sau khi release 1.3.0. ### Sửa - **`history --range 7d`** giờ trả đúng ~7 phiên (trước đây trả ~365 phiên do VCI API bỏ qua `start` khi `countBack` mặc định lớn). Handler tự tính `countBack` theo khoảng ngày và filter lại kết quả. - **Hiển thị giá:** làm tròn tối đa 2 chữ số thập phân, cắt số 0 cuối. Không còn `56.291760000000004k`. - **`symbols --exchange HOSE`** giờ hoạt động (trước đây trả rỗng vì data lưu `HSX`). `Directory.getByExchange` tự map `HOSE` → `HSX`. - **`symbols` mặc định trả đầy đủ** (trước đây tự cắt ở 50). `--limit N` chỉ áp dụng khi user chỉ định. - **`vnstock -v`** cho `--version` (trước đây dùng `-V` hoa mặc định của commander). Version đọc từ `package.json` thay vì hardcode. `--verbose` ở sub-command bỏ alias `-v` để tránh xung đột. ### Thêm - **Header cho `history` và `symbols`:** ví dụ `VCB 2026-04-07 → 2026-04-14 (5 phiên)` và `HOSE (702 mã)`. - **`docs/local-test.md`**: hướng dẫn `npm link`, watch mode, `npm pack`, debug CLI trước khi release. - **`npm run dev`**: `tsc --watch` để auto rebuild khi sửa code. ## 1.3.0 ### Breaking - **`await init()` bắt buộc** trước khi dùng symbol lookup hoặc calendar API. Symbol và holiday data không còn bundle trong package, fetch runtime từ raw GitHub để luôn mới. ```typescript import { init } from "vnstock-js"; await init(); // gọi 1 lần lúc khởi động app ``` `init()` nhận options: `symbolsUrl`, `holidaysUrl`, `ttl`, `force`, `cacheDir`, `noCache`, `timeout`. ### Tính năng mới - **Remote data module**: symbols/holidays fetch từ `raw.githubusercontent.com/ttqteo/vnstock-js/master/data/*.json` - **Disk cache** tại `~/.vnstock-js/cache/`, TTL mặc định 24h - **Offline fallback**: fetch fail thì dùng cache cũ - **`NotInitializedError`** và **`DataUnavailableError`** error types - **CLI tool**: cài `npm i -g vnstock-js` hoặc `npx vnstock-js `: - `vnstock quote `: snapshot 1 mã - `vnstock history [--from 7d|1w|1m|1y] [--range 7d] [--limit N]`: OHLCV - `vnstock search `: tìm mã theo tên/ticker - `vnstock symbols [--exchange HOSE|HNX|UPCOM]`: liệt kê mã - Flags chung: `--json`, `--csv`, `--no-color`, `-v, --verbose`. Non-TTY auto plain text. ### Realtime hardening - **Sửa:** `unsubscribe()` giờ gửi unsub message cho server. Message routing phân biệt quote/JSON/plain text. Loại bỏ WebSocket `ping()` vô nghĩa (browser không còn reset oan ~40s). - **Dead-man's switch** thay heartbeat: reconnect nếu không nhận message nào trong `deadManTimeout` (mặc định 60s). - **Breaking (minor):** bỏ `RealtimeClientOptions.heartbeatInterval` và `heartbeatTimeout`, thay bằng `deadManTimeout?: number`. ### Thay đổi - `data/*.json` không còn bundle trong npm package. ## 1.2.0 (03-04-2026) ### Tính năng mới - **Symbol Directory** -- danh sách ~3300 mã offline với search theo tên/mã/ngành, relevance ranking, VN30 boost - **Market Calendar** -- `isTradeDay`, `nextTradeDay`, `prevTradeDay`, `holidays`, giờ giao dịch sàn HOSE - **Rate limit auto-wait** -- gặp HTTP 429 tự động chờ theo `Retry-After` header rồi retry ### API mới - `stock.search(query, { limit })` -- tìm mã cổ phiếu offline - `listing.search()`, `listing.getBySymbol()`, `listing.getByExchange()`, `listing.getByIndustry()`, `listing.allLocal()` - `market.calendar.isTradeDay(date)`, `nextTradeDay(date)`, `prevTradeDay(date)`, `holidays(year)`, `session()` ### Nội bộ - `fetchWithRetry` hỗ trợ `rateLimitWait` option (mặc định 5s) - Thêm `SymbolInfo`, `TradingSession` types - Thêm script `npm run update-symbols` - Bundle `data/symbols.json` và `data/holidays.json` trong npm package ## 1.1.0 (03-04-2026) ### Tính năng mới - **Error taxonomy** -- 6 custom error classes: `VnstockError`, `NetworkError`, `RateLimitError`, `ApiError`, `InvalidSymbolError`, `InvalidParameterError`, `ParseError` - **Realtime v2** -- `RealtimeClient` dùng EventEmitter pattern, auto-reconnect với exponential backoff, heartbeat, subscribe queue - **Adapter pattern** -- `StockDataAdapter` interface + `VciAdapter`, chuẩn bị cho multi-source sau này - **FinancialStatement typing** -- thêm 7 optional typed fields (revenue, grossProfit, netIncome, totalAssets, totalEquity, totalDebt, operatingCashFlow) ### Thay đổi breaking - Realtime API cũ (`VnstockRealtime.connect/subscribe/parseData`) thay bằng `realtime.create()` + `RealtimeClient` event emitter - `realtime` giờ là top-level export, không còn trên `Vnstock` class hay `stock` object ### Nội bộ - Pipeline `fetchWithRetry` wrap axios errors thành custom error classes - Tất cả validation dùng `InvalidParameterError` thay vì `throw new Error` - Core modules (quote, trading, listing, financial, company) gọi qua adapter thay vì trực tiếp URL ## 1.0.1 (02-04-2026) - Xóa dependency `xlsx` (2 CVE: Prototype Pollution + ReDoS) - Chuyển sang VCB JSON API thay vì parse Excel - Sửa RSI type signature (`period` giờ optional) - Thêm request timeout 15 giây - Bổ sung fields thiếu trong `ScreenResult` - Bỏ duplicate `QuoteHistory` type - Sửa mapping index WebSocket SSI realtime data ## 1.0.0 (02-04-2026) Breaking changes so với v0.5.x. Refactor toàn bộ kiến trúc. ### Kiến trúc mới - **Pipeline architecture** -- Request Pipeline (fetch + retry) -> Transform Pipeline (parse, clean, rename, normalize, shape) - Tất cả output chuẩn hóa: Array of Objects, camelCase, giá chia 1000, ISO dates - Retry tự động 2 lần với exponential backoff cho lỗi 5xx/timeout ### Tính năng mới - **Sàng lọc cổ phiếu** (`stock.screening`) -- lọc theo PE, ROE, vốn hóa - **Chỉ báo kỹ thuật** -- SMA, EMA, RSI (pure functions) - **Company mở rộng** -- affiliates, analysisReports, insiderDeals - TypeScript interfaces đầy đủ cho tất cả output ### Thay đổi breaking - Output format thay đổi hoàn toàn (giá chia 1000, field names đổi) - `stock.price()` -> `stock.quote()` - `stock.company()` giờ là factory method trả về Company instance - `VnstockTypes` trỏ sang normalized types - Realtime `parseData()` trả về `RealtimeQuote` với field names mới ## 0.5.1 - Sửa lỗi nhỏ ## 0.5.0 - Thêm realtime WebSocket từ SSI - Đổi `stock.price` -> `stock.quote` - Thêm `trading.topGainers`, `trading.topLosers` ## 0.4.x - Thêm giá vàng SJC - Sửa lỗi Company - Tái cấu trúc codebase ## 0.3.x - Bỏ hỗ trợ TCBS - Thêm giá vàng GiaVang.net ## 0.2.0 - Hỗ trợ VCI: listing, báo cáo tài chính - Thêm tỷ giá VCB ## 0.1.0 - Phiên bản đầu tiên - Giá giao dịch, lịch sử giá từ VCI và TCBS - Giá vàng Việt Nam --- # Câu Hỏi Thường Gặp > Những lưu ý quan trọng khi sử dụng vnstock-js 2.x. ## vnstock-js có chạy được trên trình duyệt không? **Có, qua proxy.** VietCap chặn request từ trình duyệt (CORS), nên gọi thẳng sẽ lỗi: ``` Access to fetch at 'https://trading.vietcap.com.vn/...' has been blocked by CORS policy ``` Gắn proxy có sẵn của thư viện vào server của bạn rồi tạo client với `proxy`: ```ts // app/api/vnstock/[...path]/route.ts import { nextHandler } from "vnstock-js/proxy"; export const { GET, POST } = nextHandler(); ``` ```ts // client component import { createClient } from "vnstock-js"; const vn = createClient({ proxy: "/api/vnstock" }); const quote = await vn.stock("FPT").price(); ``` Server Component, API Route, Express, script Node thì gọi thẳng, không cần proxy. Chỉ báo ở `vnstock-js/indicators` là hàm thuần, chạy được ở mọi nơi. Xem [Trình duyệt & proxy](/docs/key-features/advanced/proxy). --- ## Sao giá là 127000 chứ không phải 127? Từ 2.0, mọi số tiền tính bằng **đồng**: `127000` là 127.000 đồng. Bản 1.x trả giá theo nghìn đồng (`127`) và thanh khoản theo tỷ đồng, dễ nhầm khi cộng trừ. Muốn hiển thị kiểu bảng điện, chia cho 1000 ở bước hiển thị. Chỉ số vẫn là điểm. Xem [Quy ước & đơn vị](/docs/getting-started/conventions). --- ## Có cần gọi `init()` không? Không. Danh mục mã, lịch nghỉ và tỷ số quý được tải ở lần dùng đầu. Muốn tải trước, dùng `createClient({ preload: true })`. Muốn giữ cache giữa các lần chạy script, dùng `fileStorage()` của `vnstock-js/node`. --- ## `watch()` chỉ có dữ liệu trong giờ giao dịch? Chế độ stream nhận giá khớp qua WebSocket nên chỉ có tick mới khi thị trường mở (9:00 tới 11:30, 13:00 tới 14:45, thứ Hai tới thứ Sáu, trừ ngày lễ). Ngoài giờ, chế độ `poll` vẫn trả một tick với giá đóng cửa. Nhớ gọi hàm dừng mà `watch()` trả về, nếu không tiến trình sẽ chạy mãi. --- ## Sàng lọc chạy chậm? Điều kiện trên bảng giá và tỷ số quý (ROE, ROA, biên lợi nhuận) chạy trước, không tốn request. P/E, P/B và vốn hoá phải hỏi theo từng mã, nên chỉ được hỏi cho các mã còn lại sau bước đầu. Lọc theo nhóm (`group: 'VN30'`) nhanh hơn lọc cả sàn. Kết quả nên cache ở tầng ứng dụng. --- ## Lịch sử khối ngoại trả mảng rỗng? VietCap chỉ cho xem khối ngoại của phiên hiện tại. Lịch sử do repo vnstock-js tự ghi mỗi ngày từ năm 2026, nên phiên trước ngày bắt đầu ghi không có trong kết quả. Gọi `foreign()` không tham số để lấy phiên hiện tại. --- ## Bị lỗi `RATE_LIMIT`? Nguồn từ chối vì gọi quá nhiều trong thời gian ngắn. Thư viện đã tự thử lại với backoff; nếu vẫn lỗi, giảm số request song song, gộp nhiều mã vào một `vn.prices([...])`, và cache kết quả. --- ## Tại sao không dùng TCBS, SSI REST API? Hai nguồn này cần xác thực. vnstock-js chỉ dùng nguồn công khai, không cần token: - **VietCap** cho cổ phiếu, chỉ số, công ty, tài chính - **SSI WebSocket** cho giá trực tiếp - **BTMC, GiaVang.net** cho giá vàng - **Vietcombank** cho tỷ giá --- ## Có hỗ trợ phái sinh không? Chưa. Hiện hỗ trợ cổ phiếu, chỉ số, vàng và tỷ giá. --- ## Làm sao đóng góp? Xem [CONTRIBUTING.md](https://github.com/ttqteo/vnstock-js/blob/master/CONTRIBUTING.md) trên GitHub. --- # Cơ Bản > Các lời gọi cơ bản của vnstock-js 2.x với đầu ra mẫu. ```ts import { vn } from "vnstock-js"; ``` `vn` là client mặc định, dùng được ngay. Mọi số tiền tính bằng đồng. ## Giá hiện tại ```ts const quote = await vn.stock("FPT").price(); ``` ```json { "symbol": "FPT", "name": "Công ty Cổ phần FPT", "exchange": "HOSE", "price": 57500, "change": 400, "changePct": 0.701, "ref": 57100, "ceiling": 61000, "floor": 53200, "high": 57800, "low": 56800, "volume": 3259200, "value": 186521720000, "matchVolume": 133100, "foreignBuyVolume": 512856, "foreignSellVolume": 807002, "bids": [{ "price": 57500, "volume": 1000 }], "asks": [{ "price": 57600, "volume": 40600 }] } ``` Xem đủ các trường ở [Quote](/docs/data/PriceBoardItem). ## Lịch sử giá ```ts const bars = await vn.stock("FPT").history("6mo"); ``` ```json [{ "time": "2026-10-20", "open": 57000, "high": 57800, "low": 56800, "close": 57600, "volume": 3259200, "value": 186521720000 }] ``` Range: `1d 5d 1mo 3mo 6mo ytd 1y 2y 5y max`, hoặc `{ from, to, interval }`. Xem [Lịch sử giá](/docs/key-features/advanced/quote). ## Chỉ số ```ts const vnindex = await vn.index("VNINDEX").history("1mo"); // điểm, ví dụ close: 1689.12 ``` ## Nhiều mã một lúc ```ts const quotes = await vn.prices(["FPT", "VCB", "HPG"]); const vn30 = await vn.prices({ group: "VN30" }); ``` ## Tổng quan thị trường ```ts const m = await vn.market({ exchange: "HOSE" }); console.log(m.index.close, m.liquidity.value, m.breadth.advancing, m.foreign.netValue); ``` ## Top tăng, giảm ```ts const { gainers, losers } = await vn.topMovers({ limit: 10 }); ``` ## Thông tin công ty ```ts const fpt = vn.stock("FPT"); const profile = await fpt.company(); // ngành, số cổ phiếu const holders = await fpt.shareholders(); const events = await fpt.events(); // cổ tức, phát hành, đại hội ``` ## Vàng, tỷ giá, tin tức ```ts const gold = await vn.gold(); // đồng/lượng const usd = (await vn.fx()).find((r) => r.currencyCode === "USD"); const news = await vn.news({ query: "lãi suất" }); ``` ## Xử lý lỗi ```ts try { await vn.stock("FTP").price(); } catch (e) { console.log(e.code, e.hint); // INVALID_SYMBOL, Did you mean "FPT"? } ``` --- # Nâng Cao > Các nhóm tính năng của vnstock-js 2.x. Mọi tính năng đều đi qua cùng một client: ```ts import { vn, createClient } from "vnstock-js"; ``` - [Bảng giá](/docs/key-features/advanced/trading): giá hiện tại một hay nhiều mã, top tăng giảm - [Lịch sử giá](/docs/key-features/advanced/quote): nến từ 1 phút tới 1 tháng, gộp khung - [Danh mục mã](/docs/key-features/advanced/listing) và [Tìm kiếm](/docs/key-features/advanced/search) - [Tài chính](/docs/key-features/advanced/financials): báo cáo tài chính, tỷ số, thông tin công ty - [Thị trường](/docs/key-features/advanced/market): tổng quan, độ rộng, thanh khoản, khối ngoại, dữ liệu trong phiên - [Sàng lọc](/docs/key-features/advanced/screening): `where` và `sort` - [Chỉ báo & pattern](/docs/key-features/advanced/indicators): `vnstock-js/indicators` - [AI Context](/docs/key-features/advanced/ai-context): bối cảnh kỹ thuật cho LLM - [Danh mục đầu tư](/docs/key-features/advanced/portfolio): định giá theo giá hiện tại - [Theo dõi & cảnh báo](/docs/key-features/advanced/realtime): `watch()`, `alert()` - [Backtest & phiên tương tự](/docs/key-features/advanced/backtest) - [Trình duyệt & proxy](/docs/key-features/advanced/proxy) - [Lịch giao dịch](/docs/key-features/advanced/calendar) - [Vàng, tỷ giá, tin tức](/docs/key-features/advanced/commodity) - [MCP Server](/docs/key-features/advanced/mcp) và [Watchlist](/docs/key-features/advanced/watchlist) --- # Bảng Giá - Prices > Giá hiện tại của một hay nhiều mã, top tăng giảm. ## Một mã ```ts import { vn } from "vnstock-js"; const q = await vn.stock("FPT").price(); console.log(q.price, q.ref, q.ceiling, q.floor, q.changePct); ``` Trả về một [Quote](/docs/data/PriceBoardItem): giá khớp, tham chiếu, trần, sàn, cao, thấp, giá trung bình, khối lượng và giá trị luỹ kế, khối lượng khớp gần nhất, khối ngoại mua bán, ba bước giá mua bán. Mọi giá tính bằng đồng. Trước lệnh khớp đầu tiên của phiên, `price` là 0 và `change`, `changePct` là 0. ## Nhiều mã trong một request ```ts const list = await vn.prices(["FPT", "VCB", "HPG"]); const vn30 = await vn.prices({ group: "VN30" }); // VN30, VN100, HNX30... const hose = await vn.prices({ exchange: "HOSE" }); // cả sàn ``` Gộp nhiều mã vào một lời gọi `vn.prices` nhanh hơn và ít bị giới hạn tần suất hơn gọi `price()` từng mã. ## Chỉ số ```ts const vni = await vn.index("VNINDEX").price(); // mức điểm hiện tại so với phiên trước ``` ## Top tăng, giảm ```ts const { gainers, losers } = await vn.topMovers({ limit: 10 }); console.log(gainers.map((m) => [m.symbol, m.exchange, m.price1DayAgo])); ``` Xem [TopMover](/docs/data/TopStock). ## Hiển thị kiểu bảng điện Bảng điện in giá theo nghìn đồng (`57.50`). Chia cho 1000 ở bước hiển thị, đừng đổi dữ liệu gốc: ```ts const board = (dong: number) => (dong / 1000).toFixed(2); console.log(board(q.price)); // "57.50" ``` --- # Lịch Sử Giá - History > Nến từ 1 phút tới 1 tháng, range và khoảng ngày, gộp khung trong thư viện. ```ts import { vn } from "vnstock-js"; const daily = await vn.stock("FPT").history("6mo"); const range = await vn.stock("FPT").history({ from: "2025-01-01", to: "2025-12-31" }); const weekly = await vn.stock("FPT").history({ from: "2025-01-01", interval: "1w" }); const intraday = await vn.stock("FPT").history({ from: "2026-10-20", interval: "15m" }); const index = await vn.index("VNINDEX").history("1y"); // điểm ``` Trả về mảng [Bar](/docs/data/ChartData), cũ trước mới sau. ## Range | Range | Ý nghĩa | |---|---| | `1d`, `5d` | 1 hoặc 5 phiên gần nhất | | `1mo`, `3mo`, `6mo` | 1, 3, 6 tháng | | `ytd` | Từ đầu năm | | `1y`, `2y`, `5y` | 1, 2, 5 năm | | `max` | Toàn bộ lịch sử | Hoặc `{ from, to }` dạng `YYYY-MM-DD`; bỏ `to` là tới hôm nay. `6m` không hợp lệ (dễ nhầm với 6 phút), dùng `6mo`. ## Khung nến | Interval | Nguồn | |---|---| | `1m`, `1h`, `1d` | Nến gốc từ VCI | | `5m`, `15m`, `30m` | Gộp từ nến 1m | | `1w`, `1mo` | Gộp từ nến ngày | Khung 30m bám đồng hồ: ô 09:00 chứa nến 09:15 tới 09:29. Dữ liệu 1 phút chỉ có trong độ sâu VCI lưu (khoảng từ đầu 2025); range dài hơn sẽ trả phần đang có thay vì báo lỗi. ## Giá trị giao dịch của nến - Nến ngày, tuần, tháng: `value` là giá trị giao dịch của riêng nến đó, bằng đồng. - Nến trong phiên (1m tới 1h): `value` là giá trị **luỹ kế** từ đầu phiên tới nến đó. ## Thời gian `time` theo giờ Việt Nam: `"2026-10-20"` cho nến ngày, `"2026-10-20T09:15:00+07:00"` cho nến trong phiên. ## Dùng với chỉ báo ```ts import { sma, rsi } from "vnstock-js/indicators"; const bars = await vn.stock("FPT").history("1y"); const sma20 = sma(bars, { period: 20 }); const rsi14 = rsi(bars); ``` --- # Danh Mục Mã - Symbols > Danh sách mã theo sàn, nhóm, ngành. ```ts import { vn } from "vnstock-js"; const all = await vn.symbols(); const hose = await vn.symbols({ exchange: "HOSE" }); const vn30 = await vn.symbols({ group: "VN30" }); const banks = await vn.symbols({ industry: "Ngân hàng" }); ``` Mỗi phần tử: ```json { "symbol": "FPT", "companyName": "Công ty Cổ phần FPT", "companyNameEn": "FPT Corporation", "exchange": "HOSE", "industry": "Phần mềm & Dịch vụ Máy tính", "industryEn": "Software & Computer Services", "sector": "Công nghệ Thông tin", "sectorEn": "Technology", "icbCode": "9537", "vn30": true } ``` Danh mục mã được tải từ GitHub ở lần dùng đầu và cache trong `Storage`, nên `symbols()` không gọi VCI. Danh mục này cũng là thứ thư viện dùng để kiểm tra mã và gợi ý khi gõ sai. Muốn bảng giá của cả nhóm thì dùng thẳng `vn.prices({ group: "VN30" })`. --- # Tìm Kiếm - Search > Tìm mã theo ký hiệu hoặc tên công ty. ```ts import { vn } from "vnstock-js"; const banks = await vn.search("ngân hàng", { limit: 5 }); const fpt = await vn.search("FPT"); ``` Tìm theo mã, tên công ty (tiếng Việt có dấu hoặc tiếng Anh) và tên ngành, không phân biệt hoa thường. Thứ tự: mã khớp chính xác, mã bắt đầu bằng từ khoá, tên bắt đầu bằng từ khoá, rồi các kết quả chứa từ khoá. Mặc định trả 10 kết quả. Kết quả là danh sách mã như [Danh mục mã](/docs/key-features/advanced/listing). `search` đọc danh mục đã cache nên không gọi mạng sau lần tải đầu. --- # Tài Chính - Financials > Báo cáo tài chính, tỷ số, hồ sơ công ty, cổ đông, sự kiện. ## Báo cáo tài chính ```ts import { vn } from "vnstock-js"; const income = await vn.stock("FPT").financials("income", { period: "quarter" }); const balance = await vn.stock("FPT").financials("balance", { period: "year" }); const cashflow = await vn.stock("FPT").financials("cashflow"); // theo quý const ratios = await vn.stock("FPT").financials("ratios", { period: "quarter" }); ``` `kind`: `'balance' | 'income' | 'cashflow' | 'ratios'`. `period`: `'quarter'` (mặc định) hoặc `'year'`. Kết quả: ```ts { symbol: "FPT", kind: "income", period: "quarter", unit: "VND", fields: [{ field: "isa3", titleVi: "Doanh thu thuần", titleEn: "Net sales", level: 1, parent: null }, ...], rows: [{ period: "2026-Q2", values: { isa3: 13788503461199, isa22: 2567587014434, ... } }, ...], } ``` - `rows` cũ trước mới sau; `period` là `"2026"` theo năm hoặc `"2026-Q2"` theo quý. - Số liệu bằng đồng. Với `ratios`, các tỷ số như `roe`, `roa`, biên lợi nhuận là phân số, `pe`, `pb` là bội số. - Mã trường (`isa3`...) khác nhau giữa doanh nghiệp thường, ngân hàng và công ty chứng khoán. Dùng `fields` để tra theo tên: ```ts const net = income.fields.find((f) => f.titleEn === "Net sales")?.field; const latest = income.rows[income.rows.length - 1]; console.log(net && latest.values[net]); // doanh thu thuần quý gần nhất, đồng ``` ## Hồ sơ công ty ```ts const fpt = vn.stock("FPT"); const profile = await fpt.company(); // ngành, số cổ phiếu lưu hành, giới thiệu const holders = await fpt.shareholders(); // cổ đông lớn const officers = await fpt.officers(); // ban lãnh đạo const events = await fpt.events(); // sự kiện: cổ tức, phát hành, đại hội... const dividends = await fpt.dividends(); // chỉ sự kiện cổ tức const news = await fpt.news(); // tin của công ty ``` Các phần này dùng chung một request tới VCI. Ngày tháng theo giờ Việt Nam. Sự kiện có `exRightDate` (ngày giao dịch không hưởng quyền), `recordDate` (ngày đăng ký cuối cùng), `ratio` (tỷ lệ, phân số). Xem [CompanyProfile](/docs/data/CompanyProfile). --- # Thị Trường - Market > Tổng quan thị trường, độ rộng, thanh khoản, khối ngoại và lịch sử khối ngoại, thanh khoản trong phiên. Mọi số tiền tính bằng đồng, chỉ số tính bằng điểm. Sàn: `HOSE` (mặc định), `HNX`, `UPCOM`, `ALL`. ## Tổng quan ```ts import { vn } from "vnstock-js"; const m = await vn.market({ exchange: "HOSE" }); ``` ```json { "date": "2026-10-20", "index": { "symbol": "VNINDEX", "close": 1689.12, "change": -0.72, "changePercent": -0.043, "volume": 514718024 }, "liquidity": { "index": "VNINDEX", "value": 11650231190150, "valuePrevious": 13354737798630, "changePercent": -12.763, "unit": "VND" }, "breadth": { "exchange": "HOSE", "advancing": 184, "declining": 144, "unchanged": 55, "ceiling": 10, "floor": 5, "total": 383 }, "foreign": { "exchange": "HOSE", "buyValue": 1682715999463, "sellValue": 1857911081930, "netValue": -175195082467, "topNetBuy": [], "topNetSell": [], "unit": "VND" } } ``` Truyền `asOf` để lấy bối cảnh của một phiên đã qua (xu hướng, thanh khoản so với trung bình 20 phiên); độ rộng và khối ngoại khi đó là `null` vì nguồn chỉ có cho phiên hiện tại. ```ts const past = await vn.market({ asOf: "2026-10-10" }); console.log(past.regime, past.notes); ``` ## Từng phần ```ts const breadth = await vn.market.breadth({ exchange: "HNX" }); const liquidity = await vn.market.liquidity({ index: "VN30" }); const foreign = await vn.market.foreign({ exchange: "HOSE", top: 5 }); // phiên hiện tại, kèm top mua bán ròng const context = await vn.market.context({ index: "VNINDEX" }); // cho LLM ``` ## Lịch sử khối ngoại VCI chỉ cho xem khối ngoại của phiên hiện tại. Repo vnstock-js tự ghi mỗi ngày sau 15:30 vào `data/foreign` và gộp theo tháng; truyền `range` để đọc lại: ```ts const fpt = await vn.stock("FPT").foreign("3mo"); // mỗi phiên một dòng const hose = await vn.market.foreign({ range: "1mo" }); const all = await vn.market.foreign({ range: { from: "2026-10-01" }, exchange: "ALL" }); const net = fpt.reduce((sum, d) => sum + d.netValue, 0); // ròng cộng dồn, đồng ``` > **Lưu ý:** Dữ liệu chỉ có từ ngày bắt đầu ghi (năm 2026). Phiên trước đó không có trong kết quả, không phải số 0. Phiên đã ghi mà mã không có giao dịch khối ngoại thì trả số 0. ## Thanh khoản trong phiên ```ts const d = await vn.market.intraday({ exchange: "HOSE" }); console.log(d.liquidity.value, d.liquidity.changePercent, d.breadth.advancing, d.points.length); ``` `points` là thanh khoản luỹ kế theo từng phút của phiên hiện tại (phiên gần nhất khi đã đóng cửa), mỗi điểm kèm `valuePrevious` là luỹ kế của phiên trước tại cùng giờ. Giờ nghỉ trưa không có điểm nào; phiên ATC là một điểm lúc 14:45. Độ rộng là của bảng giá lúc gọi, không theo thời gian. ## Sức khoẻ thị trường ```ts const health = await vn.index("VNINDEX").health(); console.log(health.status, health.distributionCount); ``` Chấm sức khoẻ thị trường theo phương pháp của William O'Neil: đếm ngày phân phối trong 25 phiên và tìm ngày bùng nổ xác nhận sau một đáy. --- # Sàng Lọc - Screening > Lọc cổ phiếu theo bảng giá và chỉ số tài chính với where và sort. ```ts import { vn } from "vnstock-js"; const rows = await vn.screen({ group: "VN30", // hoặc exchange: "HOSE" where: { pe: { lt: 15 }, roe: { gt: 0.15 } }, sort: "-roe", // "-" là giảm dần limit: 10, }); console.log(rows.map((r) => [r.symbol, r.pe, r.roe, r.marketCap])); ``` Cần chỉ định `group` (VN30, VN100, HNX30...) hoặc `exchange` (HOSE, HNX, UPCOM). ## Điều kiện Mỗi trường nhận một hay nhiều toán tử: `lt`, `lte`, `gt`, `gte`, `eq`. ```ts where: { price: { gte: 10000, lte: 50000 }, // đồng roe: { gt: 0.15 }, // phân số: 15% pe: { lt: 12 }, } ``` | Nhóm trường | Ví dụ | Chi phí | |---|---|---| | Bảng giá | `price`, `changePercent`, `volume`, `value` | Không thêm request | | Tỷ số quý dựng sẵn | `roe`, `roa`, `roic`, `grossMargin`, `ebitMargin`, `currentRatio`, `quickRatio`, `debtToEquity`, `dividendYield` | Không thêm request | | Theo giá | `pe`, `pb`, `ps`, `marketCap` | Một request mỗi mã còn lại | Điều kiện rẻ chạy trước, nên các trường theo giá chỉ được hỏi cho những mã đã qua bước đầu. Lọc theo nhóm nhanh hơn lọc cả sàn. Mỗi dòng kết quả là một [ScreenRow](/docs/data/ScreenResult): giá, vốn hoá bằng đồng, tỷ số là phân số. ## Từ bản 1.x `filters`, `sortBy`, `order` đã bỏ; truyền vào sẽ nhận lỗi kèm gợi ý. `{ field: "pe", operator: "<", value: 15 }` thành `where: { pe: { lt: 15 } }`, `sortBy: "roe", order: "desc"` thành `sort: "-roe"`. --- # Chỉ Báo & Pattern > Chỉ báo kỹ thuật, vùng hỗ trợ/kháng cự, mẫu nến, Turtle, breakout, sức khoẻ thị trường. ```ts import { vn } from "vnstock-js"; import { sma, rsi, macd, supportResistance, candlePatterns } from "vnstock-js/indicators"; const bars = await vn.stock("FPT").history("1y"); const sma20 = sma(bars, { period: 20 }); ``` `vnstock-js/indicators` là hàm thuần: nhận `Bar[]` (cũ trước mới sau), không gọi mạng, chạy được cả trong trình duyệt. Kết quả có cùng độ dài và thứ tự với `bars`, mỗi phần tử có `time`; giá trị là `null` cho tới khi đủ dữ liệu khởi động. Đơn vị theo đơn vị của nến: đồng với cổ phiếu, điểm với chỉ số. ## Chỉ báo xu hướng và động lượng | Hàm | Kết quả mỗi phần tử | Mặc định | |---|---|---| | `sma(bars, { period, field? })` | `{ time, sma }` | `field: "close"` | | `ema(bars, { period, field? })` | `{ time, ema }` | khởi tạo bằng SMA | | `rsi(bars, { period? })` | `{ time, rsi }` (0 tới 100) | Wilder, 14 | | `macd(bars, { fast?, slow?, signal? })` | `{ time, macd, signal, histogram }` | 12, 26, 9 | | `bollinger(bars, { period?, stddev? })` | `{ time, upper, middle, lower, percentB }` | 20, 2 | | `atr(bars, period?)` | `{ time, atr }` | Wilder, 14 | | `superTrend(bars, { period?, multiplier? })` | `{ time, superTrend, direction, upperBand, lowerBand }` | 10, 3 | | `ichimoku(bars, options?)` | `{ time, tenkanSen, kijunSen, senkouSpanA, senkouSpanB, chikouSpan, cloudTop, cloudBottom }` | 9, 26, 52 | | `volumeZScore(bars, { period? })` | `{ time, z }` | 20 phiên trước, không tính phiên hiện tại | ```ts const last = macd(bars).at(-1); // { macd, signal, histogram } const pctB = bollinger(bars).at(-1)?.percentB; const trend = superTrend(bars).at(-1)?.direction; // "bullish" | "bearish" ``` ## Vùng giá và pattern | Hàm | Kết quả | |---|---| | `swingPivots(bars, window)` | Đỉnh, đáy xoay `{ index, time, price, kind: "high" \| "low" }` | | `supportResistance(bars, { window?, maxZones?, tolerance?, minTouches? })` | Vùng `{ low, high, kind: "support" \| "resistance", touches, lastTouch }`, xếp theo số lần chạm | | `extremeZones(bars, { rsiPeriod?, lower?, upper? })` | `{ time, zone: "bottom" \| "top" \| null }`: RSI dưới 30 kèm chạm Bollinger dưới, và ngược lại | | `candlePatterns(bars, { trendLookback? })` | `{ time, pattern, bias }`: engulfing, hammer, shooting star, doji, morning/evening star | | `breakouts(bars, { lookback?, volumeZ? })` | `{ time, direction, level, volumeZ }`: đóng cửa vượt đỉnh/đáy `lookback` phiên kèm khối lượng đột biến | | `turtleSignals(bars, options?)` | `{ time, action: "buy" \| "sell", price, stop, reason }`: phá đỉnh 20 phiên mua, thủng đáy 10 phiên thoát, dừng lỗ 2 x ATR, lọc EMA200; chỉ mua | ```ts const zones = supportResistance(bars); const recentCandles = candlePatterns(bars).filter((p) => p.bias === "bullish").slice(-3); const spikes = breakouts(bars, { lookback: 20, volumeZ: 2 }); ``` `patternReport(bars, { recent })` gộp tất cả trong một lần; trên client là `vn.stock('FPT').patterns()`: ```ts const report = await vn.stock("FPT").patterns({ recent: 10 }); console.log(report.zones, report.extreme, report.candles, report.turtle, report.breakouts); ``` ## Sức khoẻ thị trường ```ts const health = await vn.index("VNINDEX").health(); // hoặc: marketHealth(await vn.index("VNINDEX").history("1y")) console.log(health.status, health.distributionCount, health.events.at(-1)); ``` `status` là `"uptrend"`, `"pressure"` hoặc `"correction"`. Theo phương pháp của William O'Neil: đếm ngày phân phối (giảm điểm trên khối lượng lớn hơn phiên trước) trong 25 phiên gần nhất và tìm ngày bùng nổ xác nhận (follow-through day) sau một đáy. > **Lưu ý:** Các pattern là quy tắc kỹ thuật tự động, có tỷ lệ báo nhầm. Dùng để tham khảo, không phải khuyến nghị đầu tư. --- # AI Context > Bối cảnh kỹ thuật tính sẵn cho LLM, dạng JSON hoặc văn bản. Thay vì đưa nến thô cho LLM, `context()` tính sẵn xu hướng, chỉ báo, mốc giá, khối lượng và hiệu suất; `prompt()` trả cùng nội dung ở dạng văn bản để dán thẳng vào prompt. ```ts import { vn } from "vnstock-js"; const ctx = await vn.stock("FPT").context(); const text = await vn.stock("FPT").prompt({ lang: "vi" }); // hoặc "en" const past = await vn.stock("FPT").context({ asOf: "2026-10-10" }); const vni = await vn.index("VNINDEX").context(); ``` Tuỳ chọn: `lookback` (số phiên dùng để tính, mặc định 200), `asOf` (`YYYY-MM-DD`, chỉ dùng nến tới hết phiên đó, để backtest không nhìn trước). ## Cấu trúc ```ts { symbol: "FPT", asOf: "2026-10-20", trend: { direction: "bearish", strength: 1, rationale: "EMA20 < EMA50 < EMA200, ..." }, indicators: { rsi14: 33.46, macd: { line, signal, histogram, crossover: "bullish" | "bearish" | "none" }, sma: { 20, 50, 200 }, ema: { 20, 50, 200 }, bollinger: { upper, middle, lower, percentB }, atr14, superTrend: { value, direction }, ichimoku: { tenkanSen, kijunSen, cloudTop, cloudBottom, priceVsCloud, tkCross }, }, levels: { support: number[], resistance: number[] }, volume: { today, avg20, signal: "above_average" | "below_average" | "normal", zscore }, performance: { change1d, change7d, change30d, change90d }, } ``` - Mọi mức giá (SMA, EMA, Bollinger, ATR, `levels`) tính bằng đồng với cổ phiếu, điểm với chỉ số. - `trend.strength` từ 0 tới 1, tính theo độ dốc EMA20 tính bằng phần trăm nên không phụ thuộc mức giá. - `performance.change*` là phần trăm so với giá đóng cửa 1, 7, 30, 90 phiên trước, cùng quy ước với `changePct` (`-5` là giảm 5%). `prompt()` in giá theo dạng dễ đọc (`57.5k`), phù hợp để LLM đọc. ## Bối cảnh thị trường ```ts const market = await vn.market.context({ index: "VNINDEX" }); console.log(market.regime, market.rationale, market.liquidity.signal, market.notes); ``` `regime` là một trong `trending_up`, `trending_down`, `sideways`. Với `asOf`, độ rộng và khối ngoại là `null` và `notes` giải thích lý do. ## Dùng với MCP Tool `get_ai_context` và `to_ai_prompt` của [MCP Server](/docs/key-features/advanced/mcp) gọi đúng các hàm này, nên Claude hay Cursor nhận cùng dữ liệu. --- # Danh Mục Đầu Tư > Định giá danh mục theo giá hiện tại, nhóm theo môi giới và ngành, lưu và đọc lại. ```ts import { vn } from "vnstock-js"; const p = await vn.portfolio({ positions: [ { symbol: "FPT", quantity: 100, cost: 120000, broker: "DNSE" }, { symbol: "VCB", quantity: 200, cost: 58000, broker: "TCBS" }, { symbol: "HPG", quantity: 500, cost: 28000 }, ], cash: { DNSE: 5000000, TCBS: 2000000 }, }); console.log(p.totals.nav, p.totals.pnlPct); ``` > **Lưu ý:** `cost` là **giá vốn mỗi cổ phiếu bằng đồng**: `120000`, không phải `120`. Nếu đang lưu giá vốn theo nghìn đồng, nhân 1000 trước khi truyền vào. ## Đầu vào | Trường | Ý nghĩa | |---|---| | `positions[].symbol` | Mã cổ phiếu | | `positions[].quantity` | Số cổ phiếu, lớn hơn 0 | | `positions[].cost` | Giá vốn mỗi cổ phiếu, đồng | | `positions[].broker` | Tuỳ chọn; vị thế không ghi được gom vào `"default"` | | `positions[].note`, `tags` | Tuỳ chọn, giữ nguyên trong kết quả | | `cash` | Tiền mặt bằng đồng: một số (gom vào `"default"`) hoặc `{ broker: số }` | Mã sai bị báo `INVALID_SYMBOL` kèm gợi ý trước khi hỏi giá. ## Kết quả ```ts { date: "2026-10-20", positions: [ { symbol: "FPT", quantity: 100, cost: 120000, broker: "DNSE", price: 57500, value: 5750000, pnl: -6250000, pnlPct: -52.083, weight: 0.27, industry: "Công nghệ Thông tin" }, ], byBroker: { DNSE: { value, cost, pnl, pnlPct, cash, nav }, TCBS: { ... }, default: { ... } }, byIndustry: { "Ngân hàng": { value, weight }, ... }, totals: { value, cost, pnl, pnlPct, cash, nav }, unit: "VND", } ``` - `price` là giá khớp, hoặc giá tham chiếu trước lệnh khớp đầu tiên của phiên. - Tiền bằng đồng, `pnlPct` là phần trăm, `weight` là phân số của NAV. ## Lưu và đọc lại ```ts await vn.portfolio.save({ positions: [{ symbol: "FPT", quantity: 100, cost: 120000 }], cash: 5000000 }); const saved = await vn.portfolio.load(); // { positions: [] } khi chưa lưu gì const summary = await vn.portfolio(saved); ``` `save()` và `load()` dùng `storage` của client. Với `fileStorage()` của `vnstock-js/node`, danh mục nằm ở `~/.vnstock-js/portfolio.json`, cùng file mà CLI `vnstock portfolio` và tool MCP `portfolio_summary` dùng. Trên trình duyệt, tự viết một `Storage` trên IndexedDB hoặc localStorage. ```ts import { createClient } from "vnstock-js"; import { fileStorage } from "vnstock-js/node"; const vn = createClient({ storage: fileStorage() }); ``` --- # Theo Dõi & Cảnh Báo > Giá trực tiếp với watch() và cảnh báo với alert(). ## watch() ```ts import { vn } from "vnstock-js"; const stop = vn.watch(["FPT", "VCB"], (t) => { console.log(t.symbol, t.price, t.changePct, t.time); }); const stopHpg = vn.stock("HPG").watch((t) => console.log(t.price)); // Dừng khi không cần nữa, nếu không tiến trình sẽ chạy mãi setTimeout(() => { stop(); stopHpg(); }, 60000); ``` Callback nhận một [Tick](/docs/data/RealtimeQuote) mỗi khi có lệnh khớp mới: giá bằng đồng, `changePct` là phần trăm, khối lượng và giá trị luỹ kế của phiên, ba bước giá mua bán. - Mọi watcher của một client dùng **chung một kết nối WebSocket**, mở khi có watcher đầu tiên và đóng khi watcher cuối cùng dừng. Tự kết nối lại khi rớt. - Mã được kiểm tra trước khi theo dõi; mã sai được báo qua `onError` (mặc định in ra `console.warn`) và không theo dõi gì. - Chế độ stream chỉ có dữ liệu khi thị trường mở. Node 22 trở lên và trình duyệt có WebSocket sẵn; Node 18 tới 21 cần `npm install ws`. ### Chế độ poll ```ts const stop = vn.watch(["FPT"], (t) => console.log(t.price), { mode: "poll", interval: 5000, // ms, tối thiểu 1000, mặc định 5000 onError: (e) => console.error(e.message), }); ``` `poll` hỏi bảng giá định kỳ và chỉ gọi callback khi giá hoặc khối lượng đổi. Dùng trên serverless và trên trình duyệt đi qua proxy, nơi không giữ được WebSocket lâu. Tick từ poll có `side: null` và `source: "poll"`. ## alert() ```ts const stopBreakout = vn.stock("FPT").alert({ when: "breakout" }, (e) => { console.log(e.time, e.price, e.level); }); const stopPrice = vn.stock("HPG").alert({ when: (t) => t.price >= 30000, mode: "poll" }, (e) => { console.log("HPG chạm 30.000 đồng", e.price); }); ``` - **`when: "breakout"`**: báo **một lần mỗi phiên** khi giá khớp vượt đỉnh cao nhất của `lookback` phiên trước (mặc định 20, từ 2 tới 200, không tính phiên hôm nay). Không lọc khối lượng, vì khối lượng giữa phiên không so được với cả ngày. Cảnh báo chạy qua đêm tự tính lại mức đỉnh khi sang phiên mới. - **`when: (tick) => boolean`**: chạy mỗi tick, báo khi hàm đổi từ `false` sang `true`. Hàm ném lỗi thì lỗi đi vào `onError`. - Callback nhận `{ symbol, rule, time, price, level, tick }`; `level` là mức đỉnh với `breakout`, `null` với hàm tự viết. - Nhận đủ tuỳ chọn của `watch()`: `mode`, `interval`, `onError`. - Không có gì được lưu lại: cảnh báo sống cùng tiến trình. Muốn chạy liên tục thì giữ tiến trình (server, worker) của bạn chạy. Mức breakout dùng chung với preset `breakout` của [Backtest](/docs/key-features/advanced/backtest): cảnh báo báo khi giá khớp vượt đỉnh trong phiên, còn backtest xét giá đóng cửa. ## Trong React ```tsx "use client"; import { useEffect, useState } from "react"; import { createClient, type Tick } from "vnstock-js"; const vn = createClient({ proxy: "/api/vnstock" }); export function LivePrice({ symbol }: { symbol: string }) { const [tick, setTick] = useState(null); useEffect(() => vn.stock(symbol).watch(setTick), [symbol]); return {tick ? (tick.price / 1000).toFixed(2) : "–"}; } ``` `watch()` trả hàm dừng, nên trả thẳng nó từ `useEffect` là đủ để dọn dẹp khi component unmount. --- # Backtest & Phiên Tương Tự > Đo kết quả của một tín hiệu bằng event study, và tìm các phiên trong quá khứ có trạng thái giống hiện tại. ## backtest() ```ts import { vn } from "vnstock-js"; const r = await vn.stock("FPT").backtest("breakout", { tp: 7, sl: 4, hold: 10 }); console.log(r.stats.all.trades, r.stats.all.winRate, r.stats.all.avgRet); const many = await vn.backtest(["FPT", "VCB", "HPG"], "oversold_bounce", { tp: 5, sl: 3, hold: 10, range: "5y" }); ``` Cách tính (event study trên nến ngày): 1. Tín hiệu được xét ở cuối mỗi phiên, chỉ với các nến **tới phiên đó** nên không nhìn trước được. 2. Vào lệnh ở **giá mở cửa phiên sau** tín hiệu. 3. Thoát khi chạm chốt lời `tp` hoặc cắt lỗ `sl`; phiên chạm cả hai thì **tính cắt lỗ trước**. Mở cửa vượt qua mức (gap) thì thoát ở giá mở cửa. 4. Hết `hold` phiên mà chưa chạm thì thoát ở giá đóng cửa phiên cuối (`timeout`). 5. Mỗi mã chỉ một lệnh mở tại một thời điểm; `overlap: true` để đếm mọi tín hiệu. Không tính phí, trượt giá, trần sàn. Tín hiệu quá gần cuối chuỗi (chưa đủ `hold` phiên phía sau) bị bỏ qua. ### Tuỳ chọn | Tuỳ chọn | Ý nghĩa | |---|---| | `tp` | Chốt lời, **phần trăm** trên giá vào: `7` là +7% | | `sl` | Cắt lỗ, phần trăm dưới giá vào, từ 0 tới 100 | | `hold` | Số phiên giữ tối đa, tính cả phiên vào lệnh (1 tới 250) | | `range` | Khoảng dữ liệu, mặc định `2y` | | `overlap` | Đếm cả tín hiệu khi lệnh trước còn mở | ### Tín hiệu Preset theo tên: | Preset | Quy tắc | |---|---| | `breakout` | Đóng cửa vượt đỉnh cao nhất của 20 phiên trước | | `oversold_bounce` | RSI 14 cắt lên 30 | | `ema_cross` | EMA20 cắt lên EMA50 | Hoặc hàm của riêng bạn: nhận các nến tới phiên đang xét, trả `true` (có tín hiệu), một chuỗi (có tín hiệu, chuỗi là nhãn nhóm), hoặc `false`/`null`. ```ts const r = await vn.backtest(["FPT", "VCB", "HPG"], (bars) => { const last = bars[bars.length - 1]; const prev = bars[bars.length - 2]; if (!prev) return false; return last.close > prev.high ? (last.volume > prev.volume * 2 ? "vol-x2" : "vol-normal") : false; }, { tp: 5, sl: 3, hold: 5, range: "5y" }); console.log(r.stats.byTag["vol-x2"], r.stats.byTag["vol-normal"]); ``` ### Kết quả ```ts { trades: [{ symbol, signalTime, tag, entryTime, entry, exitTime, exit, outcome: "tp" | "sl" | "timeout", ret, held }], stats: { all: { trades, wins, winRate, avgRet, medianRet, best, worst, tp, sl, timeout, avgHeld }, byTag: { [tag]: { ... } }, }, } ``` `entry`, `exit` bằng đồng; `ret`, `avgRet`, `medianRet` là phần trăm; `winRate` là phân số (`0.55` là 55%), `null` khi không có lệnh. > **Lưu ý:** Kết quả quá khứ không bảo đảm tương lai. Event study đo từng tín hiệu độc lập, không mô phỏng vốn, phí hay cách bạn thực sự vào lệnh. ## similar() (thử nghiệm) ```ts const s = await vn.stock("FPT").similar({ top: 10 }); console.log(s.asOf, s.matches.map((m) => [m.time, m.similarity, m.ret20]), s.summary.ret20); ``` Tìm các phiên trong 5 năm (đổi bằng `range`) có trạng thái kỹ thuật giống phiên gần nhất, theo năm chỉ số: RSI 14, MACD histogram chia giá, z-score khối lượng, độ dốc EMA20 qua 5 phiên, khoảng cách tới đỉnh 250 phiên. Mỗi chỉ số được chuẩn hoá trên lịch sử của chính mã đó rồi so bằng cosine; 20 phiên gần nhất bị loại vì chúng giống chỉ vì nằm sát nhau. Mỗi phiên khớp kèm `ret5`, `ret20`, `ret60`: giá thay đổi bao nhiêu **phần trăm** sau 5, 20, 60 phiên (`null` khi chưa đủ phiên). `summary` gom số phiên, trung bình và tỷ lệ tăng cho từng mốc. Cần ít nhất 250 phiên dữ liệu. > **Lưu ý:** Trạng thái giống nhau không có nghĩa là kết quả giống nhau. ## Qua CLI và MCP ```bash vnstock backtest FPT VCB --preset breakout --tp 7 --sl 4 --hold 10 vnstock similar FPT --top 5 ``` Tool MCP `backtest_signal` (nhận tên preset) và `find_similar_setups` gọi đúng các hàm này. --- # Trình Duyệt & Proxy > Dùng vnstock-js trong client component qua proxy có sẵn. VietCap chặn request từ trình duyệt (CORS) và còn từ chối Origin lạ ở phía server. Thư viện kèm sẵn một proxy nhỏ để đặt trên server của bạn; client trên trình duyệt gọi qua đó. ## Next.js (App Router) ```ts // app/api/vnstock/[...path]/route.ts import { nextHandler } from "vnstock-js/proxy"; export const { GET, POST } = nextHandler(); ``` ```tsx // components/price.tsx "use client"; import { useEffect, useState } from "react"; import { createClient, type Quote } from "vnstock-js"; const vn = createClient({ proxy: "/api/vnstock" }); export function Price({ symbol }: { symbol: string }) { const [q, setQ] = useState(null); useEffect(() => { vn.stock(symbol).price().then(setQ); }, [symbol]); return {q ? q.price.toLocaleString() : "…"}; } ``` ## Cloudflare Workers, Deno, Bun ```ts import { workerHandler } from "vnstock-js/proxy"; export default { fetch: workerHandler() }; ``` ## Express ```ts import express from "express"; import { expressHandler } from "vnstock-js/proxy"; const app = express(); app.use(expressHandler()); ``` ## Tuỳ chọn Cả ba hàm nhận cùng tuỳ chọn: | Tuỳ chọn | Mặc định | Ý nghĩa | |---|---|---| | `basePath` | `/api/vnstock` | Đường dẫn gắn proxy; client đặt `proxy` trùng giá trị này | | `cacheSeconds` | `30` | Số giây CDN được cache phản hồi GET thành công; `0` để tắt | ## An toàn - Proxy **không phải open proxy**: chỉ chuyển tiếp tới hai host VietCap mà thư viện dùng, theo danh sách đường dẫn cố định. Đường dẫn lạ (kể cả kiểu `%2e%2e`) bị chặn với 403. - Header phiên của VietCap chỉ được gắn ở server. - Nguồn không phản hồi thì trả 502. ## Không cần proxy khi - Gọi từ Server Component, API Route, Server Action, script Node: dùng thẳng `vn`. - Dữ liệu nền (danh mục mã, lịch nghỉ, lịch sử khối ngoại) tải từ raw GitHub, vốn cho phép CORS. - Chỉ báo ở `vnstock-js/indicators`: hàm thuần, không gọi mạng. - `watch()` ở chế độ stream kết nối WebSocket tới SSI. Nếu WebSocket bị chặn trên môi trường của bạn, dùng `{ mode: 'poll' }`. --- # Lịch Giao Dịch - Calendar > Ngày giao dịch, ngày nghỉ lễ, phiên kế tiếp và phiên trước. ```ts import { vn } from "vnstock-js"; await vn.calendar.isTradingDay("2026-10-14"); // true await vn.calendar.isTradingDay("2026-09-02"); // false, Quốc khánh await vn.calendar.next("2026-09-01"); // "2026-09-03" await vn.calendar.previous(); // phiên trước hôm nay await vn.calendar.holidays(2026); // ["2026-01-01", ...] ``` | Hàm | Trả về | |---|---| | `isTradingDay(date)` | `true` nếu là ngày thường không trùng ngày nghỉ | | `next(date?)` | Ngày giao dịch đầu tiên sau `date` (mặc định hôm nay) | | `previous(date?)` | Ngày giao dịch gần nhất trước `date` | | `holidays(year)` | Ngày nghỉ của năm, `YYYY-MM-DD` | Ngày dạng `YYYY-MM-DD`, tính theo giờ Việt Nam. Lịch nghỉ tải từ GitHub ở lần dùng đầu và cache trong `Storage`. Ngày sai định dạng ném `InvalidParameterError`. Giờ giao dịch HOSE: khớp lệnh liên tục 9:15 tới 11:30 và 13:00 tới 14:30, ATC 14:30 tới 14:45, thứ Hai tới thứ Sáu. --- # Vàng, Tỷ Giá, Tin Tức > Giá vàng trong nước, tỷ giá Vietcombank, tin tài chính trong ngày. ## Giá vàng ```ts import { vn } from "vnstock-js"; const gold = await vn.gold(); // BTMC, lỗi thì chuyển GiaVang.net const sjc = gold.items.find((g) => /SJC/.test(g.name)); console.log(gold.source, sjc?.buy, sjc?.sell); // đồng/lượng await vn.gold({ source: "giavangnet" }); // chọn nguồn: "auto" | "btmc" | "giavangnet" ``` ```json { "source": "btmc", "unit": "VND/luong", "items": [ { "name": "VÀNG MIẾNG SJC (Vàng SJC)", "code": "", "karat": "24k", "buy": 138600000, "sell": 142600000, "buyChange": null, "sellChange": null, "updatedAt": "2026-10-20T12:19:00+07:00" } ] } ``` - Mọi giá là **đồng/lượng** (1 lượng = 37,5 g). Vàng BTMC báo theo chỉ và được nhân 10; bạc báo theo sản phẩm và được chia theo trọng lượng ghi trong tên. - Mỗi sản phẩm chỉ còn một dòng mới nhất. Đồng xu vàng (báo giá cả đồng xu) bị bỏ vì không quy được ra đồng/lượng. - `buy`, `sell` là `null` khi nguồn không niêm yết giá đó. ## Tỷ giá ```ts const rates = await vn.fx(); // hôm nay const usd = rates.find((r) => r.currencyCode === "USD"); // { currencyCode: "USD", currencyName: "US DOLLAR", buyCash: 25650, buyTransfer: 25680, sell: 26060 } await vn.fx({ date: "2026-10-15" }); ``` Tỷ giá Vietcombank, đồng trên một đơn vị ngoại tệ; `null` khi ngân hàng không niêm yết. ## Tin tức ```ts const today = await vn.news(); const banks = await vn.news({ query: "ngân hàng" }); const one = await vn.news({ date: "2026-10-14", source: "Vietstock" }); ``` Tin tài chính tổng hợp mỗi ngày từ [news-crawler](https://github.com/ttqteo/news-crawler). `query` lọc theo tiêu đề và tóm tắt, `source` lọc theo nguồn, không phân biệt hoa thường. Mỗi tin có `id`, `source`, `title`, `summary`, `link`, `image`, `publishedAt`. Tin riêng của một công ty: `vn.stock("FPT").news()`. --- # MCP Server > 27 công cụ dữ liệu chứng khoán Việt Nam cho Claude, Cursor, VS Code qua Model Context Protocol. MCP (Model Context Protocol) cho phép trợ lý AI gọi công cụ thật. vnstock-js kèm sẵn một MCP server chạy local qua stdio: không cần khóa API, không cần deploy server. ## Cài đặt ### Claude Code ```bash claude mcp add --scope user vnstock npx -y vnstock-js mcp claude mcp list # kiểm tra "vnstock: ... ✓ Connected" ``` ### Claude Desktop Thêm vào `claude_desktop_config.json` rồi khởi động lại Claude Desktop: ```json { "mcpServers": { "vnstock": { "command": "npx", "args": ["-y", "vnstock-js", "mcp"] } } } ``` ### Cursor, VS Code Thêm cùng cấu hình `{ "command": "npx", "args": ["-y", "vnstock-js", "mcp"] }` vào phần MCP server trong cài đặt của editor. Cần Node 18 trở lên. Để chạy một bản cài sẵn thay vì tải qua `npx`, dùng `{ "command": "node", "args": ["/đường/dẫn/node_modules/vnstock-js/dist/cli.js", "mcp"] }`. ## Đơn vị Phần dữ liệu (JSON) của mọi tool tính tiền bằng **đồng**: giá `57500` là 57.500 đồng. Phần tóm tắt in dạng dễ đọc như `57.5k`, `19.2 nghìn tỷ`. Nếu prompt hay file của bạn ghi giá theo nghìn đồng, nhắc AI chia giá của tool cho 1000 trước khi so sánh. ## Danh sách công cụ | Tool | Tác dụng | Tham số chính | |---|---|---| | `get_quote` | Giá hiện tại của một mã | `symbol` | | `get_history` | Lịch sử giá OHLCV | `symbol`, `from`, `to`, `limit` | | `search_symbols` | Tìm mã theo tên hoặc ký hiệu | `query`, `limit` | | `list_symbols` | Liệt kê mã theo sàn | `exchange`, `limit` | | `top_movers` | Mã tăng, giảm mạnh hôm nay | `limit` | | `is_trade_day` | Có phải ngày giao dịch | `date` | | `get_trading_calendar` | Ngày nghỉ lễ trong năm | `year` | | `get_company_info` | Tên, sàn, ngành của công ty | `symbol` | | `get_dividends` | Lịch sử và lịch chia cổ tức | `symbol` | | `get_corporate_events` | Sự kiện doanh nghiệp | `symbol` | | `get_ai_context` | Bối cảnh kỹ thuật dạng JSON | `symbol`, `lookback`, `as_of` | | `to_ai_prompt` | Bối cảnh kỹ thuật dạng văn bản | `symbol`, `lang`, `as_of` | | `compare_symbols` | So sánh 2 tới 10 mã | `symbols` | | `get_market_breadth` | Độ rộng một sàn | `exchange` | | `get_foreign_flow` | Khối ngoại phiên hiện tại | `symbol`, `exchange`, `top` | | `get_foreign_history` | Lịch sử khối ngoại theo phiên | `symbol`, `exchange`, `range` | | `get_market_context` | Bối cảnh thị trường cho AI | `exchange`, `index`, `as_of` | | `get_news` | Tin tài chính theo ngày | `date`, `source`, `keyword`, `limit` | | `get_financials` | Báo cáo tài chính | `symbol`, `report`, `period` | | `screen_stocks` | Sàng lọc theo chỉ số tài chính | `group`, `exchange`, `where`, `sort`, `limit` | | `find_patterns` | Hỗ trợ/kháng cự, mẫu nến, Turtle, breakout | `symbol`, `recent` | | `portfolio_summary` | Định giá danh mục; không truyền `positions` thì đọc danh mục đã lưu | `positions`, `cash`, `save` | | `backtest_signal` | Backtest preset `breakout`, `oversold_bounce`, `ema_cross` | `symbols`, `preset`, `tp`, `sl`, `hold`, `range` | | `find_similar_setups` | Phiên quá khứ có trạng thái giống (thử nghiệm) | `symbol`, `top` | | `get_gold_price` | Giá vàng trong nước, đồng/lượng | `source` | | `get_exchange_rate` | Tỷ giá Vietcombank | `currency`, `date` | | `watchlist` | Quản lý danh sách theo dõi | `action`, `name`, `symbols` | Tên 22 tool cũ được giữ nguyên từ bản 1.x. `screen_stocks` vẫn hiểu `filters`, `sort_by`, `order` kiểu cũ. ## Ví dụ hỏi - "Giá FPT hôm nay thế nào, so với 3 tháng trước?" - "Khối ngoại mua bán ròng VCB trong tháng này ra sao?" - "Lọc VN30 các mã có P/E dưới 15 và ROE trên 15%." - "Backtest tín hiệu breakout trên FPT, VCB, HPG với chốt lời 7%, cắt lỗ 4%, giữ 10 phiên." - "Danh mục của tôi đang lãi lỗ bao nhiêu?" (dùng danh mục lưu ở `~/.vnstock-js/portfolio.json`) ## Dữ liệu lưu local `watchlist` và `portfolio_summary` đọc, ghi file trong `~/.vnstock-js`, dùng chung với CLI `vnstock watchlist`, `vnstock portfolio`. > **Lưu ý:** Dữ liệu chỉ phục vụ tham khảo. AI có thể diễn giải sai; kiểm tra lại số liệu quan trọng trước khi ra quyết định. --- # Watchlist > Danh sách mã theo dõi lưu ở ~/.vnstock-js, dùng qua CLI và MCP. Từ 2.0, watchlist không còn nằm trong SDK. Nó được dùng qua CLI `vnstock watchlist` và tool MCP `watchlist`, lưu ở `~/.vnstock-js/watchlist.json` (cùng file với bản 1.x, nên danh sách cũ vẫn còn). ## CLI ```bash vnstock watchlist create bank vnstock watchlist add bank VCB TCB MBB ACB vnstock watchlist list bank vnstock watchlist quote bank # bảng giá mọi mã trong danh sách vnstock watchlist remove bank ACB vnstock watchlist delete bank vnstock watchlist # liệt kê các danh sách ``` Thêm `--json` để lấy dữ liệu cho script. ## MCP Tool `watchlist` nhận `action` (`list_all`, `list`, `create`, `delete`, `add`, `remove`, `quote`), `name` và `symbols`. Ví dụ hỏi: "Thêm VCB, TCB vào danh sách bank rồi cho tôi giá". ## Trong code Trong ứng dụng của bạn, một watchlist chỉ là danh sách mã. Lưu nó ở đâu tuỳ bạn (database, localStorage, cookie) rồi lấy giá bằng một request: ```ts import { vn } from "vnstock-js"; const symbols = ["VCB", "TCB", "MBB"]; const quotes = await vn.prices(symbols); const stop = vn.watch(symbols, (t) => console.log(t.symbol, t.price)); ``` --- # Bar > Một cây nến OHLCV. ```ts interface Bar { time: string; // "2026-10-20" hoặc "2026-10-20T09:15:00+07:00" open: number; high: number; low: number; close: number; volume: number; // số cổ phiếu value: number | null; // giá trị giao dịch, đồng } ``` Trả về bởi `vn.stock(symbol).history()` và `vn.index(symbol).history()`, mảng cũ trước mới sau. | Trường | Ý nghĩa | |---|---| | `time` | Giờ Việt Nam. Nến ngày, tuần, tháng: `YYYY-MM-DD` (tuần, tháng đóng dấu bằng phiên đầu). Nến trong phiên: ISO có `+07:00` | | `open`, `high`, `low`, `close` | Giá bằng **đồng** với cổ phiếu, **điểm** với chỉ số | | `volume` | Khối lượng của riêng nến | | `value` | Nến ngày trở lên: giá trị giao dịch của riêng nến. Nến trong phiên: giá trị **luỹ kế** từ đầu phiên. `null` khi nguồn không có | ## Ví dụ ```json { "time": "2026-10-20", "open": 57000, "high": 57800, "low": 56800, "close": 57600, "volume": 3259200, "value": 186521720000 } ``` ## Hàm liên quan - [Lịch sử giá](/docs/key-features/advanced/quote) - [Chỉ báo & pattern](/docs/key-features/advanced/indicators): mọi chỉ báo nhận `Bar[]`. --- # Quote > Một dòng bảng giá. ```ts interface Quote { symbol: string; name: string; nameEn: string; exchange: string; // "HOSE" | "HNX" | "UPCOM" price: number; // giá khớp gần nhất change: number; // so với tham chiếu changePct: number; // phần trăm ref: number; // tham chiếu ceiling: number; // trần floor: number; // sàn high: number; low: number; avg: number; // giá khớp trung bình volume: number; // khối lượng luỹ kế của phiên value: number; // giá trị luỹ kế của phiên, đồng matchVolume: number; // khối lượng của lệnh khớp gần nhất foreignBuyVolume: number; foreignSellVolume: number; bids: { price: number; volume: number }[]; // 3 bước giá mua, tốt nhất trước asks: { price: number; volume: number }[]; // 3 bước giá bán } ``` Trả về bởi `vn.stock(symbol).price()`, `vn.prices()`. Mọi giá bằng **đồng**. - Trước lệnh khớp đầu tiên của phiên, `price`, `change`, `changePct`, `matchVolume` là 0. - `changePct` là phần trăm: `0.701` là 0,701%. - Với `vn.index(symbol).price()`, giá là **điểm** và các trường không áp dụng (trần, sàn, bước giá) là 0. ## Ví dụ ```json { "symbol": "FPT", "name": "Công ty Cổ phần FPT", "nameEn": "FPT Corporation", "exchange": "HOSE", "price": 57500, "change": 400, "changePct": 0.701, "ref": 57100, "ceiling": 61000, "floor": 53200, "high": 57800, "low": 56800, "avg": 57229.3, "volume": 3259200, "value": 186521720000, "matchVolume": 133100, "foreignBuyVolume": 512856, "foreignSellVolume": 807002, "bids": [{ "price": 57500, "volume": 1000 }], "asks": [{ "price": 57600, "volume": 40600 }] } ``` ## Từ bản 1.x `PriceBoardItem` đổi thành `Quote`: `referencePrice` thành `ref`, `ceilingPrice`/`floorPrice` thành `ceiling`/`floor`, `highestPrice`/`lowestPrice` thành `high`/`low`, `averagePrice` thành `avg`, `totalVolume`/`totalValue` thành `volume`/`value`, `bidPrices`/`askPrices` thành `bids`/`asks`, `companyName` thành `name`. Giá từ nghìn đồng thành đồng, `totalValue` từ triệu đồng thành đồng. --- # Tick > Một lần khớp lệnh trực tiếp từ watch(). ```ts interface Tick { symbol: string; price: number; // đồng change: number; // so với tham chiếu, đồng changePct: number; // phần trăm volume: number; // khối lượng luỹ kế của phiên value: number; // giá trị luỹ kế của phiên, đồng bids: { price: number; volume: number }[]; asks: { price: number; volume: number }[]; side: "buy" | "sell" | null; // chiều của lệnh khớp; null ở chế độ poll time: string; // lúc nhận tick, giờ Việt Nam source: "stream" | "poll"; } ``` Nhận trong callback của `vn.watch()` và `vn.stock(symbol).watch()`. ## Ví dụ ```json { "symbol": "FPT", "price": 57600, "change": 500, "changePct": 0.876, "volume": 3304500, "value": 189120000000, "bids": [{ "price": 57500, "volume": 12000 }], "asks": [{ "price": 57600, "volume": 3500 }], "side": "buy", "time": "2026-10-20T10:41:05+07:00", "source": "stream" } ``` ## Từ bản 1.x `RealtimeQuote` và `realtime.create()` đã bỏ. `matched.price` thành `price`, `matched.changePercent` (phân số) thành `changePct` (phần trăm), `totalVolume`/`totalValue` thành `volume`/`value`, `bidPrices`/`askPrices` thành `bids`/`asks`. Xem [Theo dõi & cảnh báo](/docs/key-features/advanced/realtime). --- # TopMover > Mã tăng, giảm mạnh trong phiên. ```ts interface TopMover { symbol: string; exchange: string; price1DayAgo: number | null; // đồng price5DaysAgo: number | null; // đồng price20DaysAgo: number | null; // đồng marketCap: number | null; liquidity: number | null; } ``` Trả về bởi `vn.topMovers({ limit })` dưới dạng `{ gainers: TopMover[]; losers: TopMover[] }`, mặc định 10 mã mỗi bên. ## Ví dụ ```json { "gainers": [{ "symbol": "BHH", "exchange": "UPCOM", "price1DayAgo": 17400, "price5DaysAgo": 18800, "price20DaysAgo": 15300 }], "losers": [{ "symbol": "LAF", "exchange": "HOSE", "price1DayAgo": 18000, "price5DaysAgo": 17800, "price20DaysAgo": 19850 }] } ``` `marketCap` (vốn hoá) và `liquidity` (giá trị giao dịch bình quân theo cách VietCap tính) đều bằng đồng như mọi số tiền khác. Muốn tự xếp hạng theo phần trăm thay đổi trong phiên, lấy bảng giá cả sàn rồi sắp xếp: ```ts import { vn } from "vnstock-js"; const board = await vn.prices({ exchange: "HOSE" }); const top = board.filter((q) => q.volume > 0).sort((a, b) => b.changePct - a.changePct).slice(0, 10); ``` --- # CompanyProfile > Hồ sơ công ty, cổ đông, ban lãnh đạo, sự kiện. ## CompanyProfile Trả về bởi `vn.stock(symbol).company()`. ```ts interface CompanyProfile { symbol: string; id: string; issuedShares: number; // số cổ phiếu lưu hành history: string; // lịch sử hình thành historyEn: string; profile: string; // giới thiệu profileEn: string; industry: string; industryEn: string; sector: string; sectorEn: string; subIndustry: string; subIndustryEn: string; } ``` Trường nguồn không có thì là chuỗi rỗng. ## Shareholder `shareholders()`: cổ đông lớn. ```ts interface Shareholder { id: string; symbol: string; name: string; nameEn: string; quantity: number; // số cổ phiếu nắm giữ percentage: number; // tỷ lệ sở hữu, phân số: 0.068 là 6,8% updatedAt: string; // ngày nguồn cập nhật } ``` `percentage` theo số liệu nguồn công bố tại `updatedAt`, nên có thể lệch nhẹ so với `quantity / issuedShares` khi công ty vừa phát hành thêm. ## Officer `officers()`: ban lãnh đạo, gồm `name`, `position`, `positionEn`, `positionShort`, `quantity` (số cổ phiếu nắm giữ), `ownership` (phân số), `updatedAt`. ## CorporateEvent `events()` (mọi sự kiện) và `dividends()` (chỉ cổ tức). ```ts interface CorporateEvent { id: string; symbol: string; title: string; // "Phát hành cổ phiếu - Cổ phiếu thưởng tỉ lệ 10.0%" titleEn: string; eventType: string; // mã loại sự kiện eventTypeName: string; // "Phát hành cổ phiếu" eventTypeNameEn: string; ratio: number | null; // tỷ lệ, phân số: 0.1 là 10% value: number | null; // giá trị tiền mặt nếu có, đồng publishedAt: string; // ngày công bố issuedAt: string; // ngày thực hiện recordDate: string; // ngày đăng ký cuối cùng exRightDate: string; // ngày giao dịch không hưởng quyền sourceUrl: string; } ``` Ngày dạng `YYYY-MM-DD` theo giờ Việt Nam. Xem thêm mục Academy về [ngày giao dịch không hưởng quyền](/docs/academy/ex-date). ## StockNews `news()`: tin của riêng công ty, gồm `title`, `summary`, `sourceUrl`, `imageUrl`, `publishedAt`. --- # ScreenRow > Một dòng kết quả sàng lọc. Trả về bởi `vn.screen()`. ```ts interface ScreenRow { symbol: string; companyName: string; companyNameEn: string; industry: string; industryEn: string; exchange: string; price: number; // đồng priceChange: number; // đồng changePercent: number; // phần trăm volume: number; value: number; // giá trị giao dịch của phiên, đồng marketCap: number; // vốn hoá, đồng pe: number | null; pb: number | null; ps: number | null; roe: number | null; // phân số: 0.15 là 15% roa: number | null; // các trường đã dùng trong where/sort cũng có mặt, ví dụ roic, grossMargin, dividendYield } ``` ## Ví dụ ```ts import { vn } from "vnstock-js"; const rows = await vn.screen({ group: "VN30", where: { pe: { lt: 15 }, roe: { gt: 0.15 } }, sort: "-roe", limit: 5 }); for (const r of rows) { console.log(r.symbol, r.pe?.toFixed(1), ((r.roe ?? 0) * 100).toFixed(1) + "%", (r.marketCap / 1e9).toFixed(0) + " tỷ"); } ``` Xem [Sàng lọc](/docs/key-features/advanced/screening). --- # GoldQuote & FxRate > Giá vàng và tỷ giá. ## GoldQuote `vn.gold()` trả `{ source: "btmc" | "giavangnet", unit: "VND/luong", items: GoldQuote[] }`. ```ts interface GoldQuote { name: string; // "VÀNG MIẾNG SJC (Vàng SJC)" code: string; // mã sản phẩm (chỉ GiaVang.net) karat: string; // "24k"; trống với bạc buy: number | null; // đồng/lượng sell: number | null; // đồng/lượng buyChange: number | null; // thay đổi so với lần trước, đồng sellChange: number | null; updatedAt: string; // giờ Việt Nam } ``` ```json { "name": "VÀNG MIẾNG SJC (Vàng SJC)", "code": "", "karat": "24k", "buy": 138600000, "sell": 142600000, "buyChange": null, "sellChange": null, "updatedAt": "2026-10-20T12:19:00+07:00" } ``` 1 lượng = 10 chỉ = 37,5 g. Mỗi sản phẩm chỉ có một dòng, giá mới nhất. ## FxRate `vn.fx({ date })` trả `FxRate[]` của Vietcombank. ```ts interface FxRate { currencyCode: string; // "USD" currencyName: string; // "US DOLLAR" buyCash: number | null; // đồng trên một đơn vị ngoại tệ buyTransfer: number | null; sell: number | null; } ``` ```json { "currencyCode": "USD", "currencyName": "US DOLLAR", "buyCash": 25650, "buyTransfer": 25680, "sell": 26060 } ``` Xem [Vàng, tỷ giá, tin tức](/docs/key-features/advanced/commodity).