Thư viện Python lấy dữ liệu chứng khoán Việt Nam
Thư viện Python finlens trả dữ liệu chứng khoán Việt Nam về pandas DataFrame: giá cuối ngày, dữ liệu trong phiên, dòng tiền nhà đầu tư và báo cáo tài chính.
finlens là thư viện Python lấy dữ liệu thị trường chứng khoán Việt Nam — giá
cuối ngày, giá trong phiên, từng lệnh khớp, dòng tiền theo nhóm nhà đầu tư, báo
cáo tài chính và danh mục mã. Mọi truy vấn trả về pandas.DataFrame trần: ghi
được to_parquet, nối được pd.concat, dùng được mọi tutorial pandas bạn từng
đọc.
pip install finlensBắt đầu trong ba dòng
import finlens
client = finlens.client() # đọc biến môi trường FINLENS_API_KEY
df = client.eod.stock.ohlcv("HPG,VCB,FPT", start="2026-01-01", end="2026-01-05") symbol date open high low close volume
0 FPT 2026-01-05 94.40 94.40 91.93 93.71 7129300.0
1 HPG 2026-01-05 23.57 23.70 22.94 23.17 59427911.0
2 VCB 2026-01-05 ... ... ... ... ...Giá cổ phiếu tính bằng nghìn đồng — 23.17 nghĩa là 23.170 VND. Tên cột
viết thường toàn bộ theo snake_case, kể cả date.
Chưa có khoá API thì bắt đầu ở Cài đặt và khoá API.
Cài đặt và khoá API
Yêu cầu môi trường, lấy khoá flk_..., truy vấn đầu tiên và cách kiểm tra
đã cài đúng bản.
Dữ liệu cuối ngày (EOD)
ohlcv() cho cổ phiếu, chỉ số, phái sinh, chứng quyền và chỉ số ngành ICB;
dòng tiền theo nhóm nhà đầu tư.
Dữ liệu trong phiên
Thanh giá theo phút, từng lệnh khớp với ticks(), và giá trị mua bán chủ
động.
Báo cáo tài chính
Cân đối kế toán, kết quả kinh doanh và lưu chuyển tiền tệ theo năm hoặc theo quý, ở dạng long.
Tra cứu danh mục
Có những mã nào, ngành ICB nào, chứng quyền nào — ba lời gọi mở đầu mọi phiên làm việc.
Kiểu dữ liệu
Giá trị hợp lệ của từng tham số, hình dạng df.attrs["finlens"], và các
TypedDict của whoami() / limits().
Xử lý lỗi
Cây ngoại lệ gốc FinLensError, mã FL_*, và các lớp cảnh báo tắt được
chọn lọc.
Lấy được những dữ liệu gì
Một đối tượng client duy nhất, chia thành các namespace theo loại dữ liệu và
loại tài sản.
| Đường dẫn | Dữ liệu | Ví dụ |
|---|---|---|
client.eod.stock | Giá cuối ngày của cổ phiếu, ETF và chứng chỉ quỹ | client.eod.stock.ohlcv("HPG") |
client.eod.index | Giá cuối ngày của chỉ số | client.eod.index.ohlcv("VNINDEX") |
client.eod.derivative | Giá cuối ngày của hợp đồng phái sinh | client.eod.derivative.ohlcv("VN30F1M") |
client.eod.warrant | Giá cuối ngày của chứng quyền có bảo đảm | client.eod.warrant.ohlcv("CHPG2628") |
client.eod.sector | Chỉ số ngành ICB | client.eod.sector.ohlcv("8355") |
client.eod.stock.investor | Dòng tiền theo nhóm nhà đầu tư, cấp mã | client.eod.stock.investor.flow("HPG") |
client.eod.sector.investor | Dòng tiền theo nhóm nhà đầu tư, cấp ngành | client.eod.sector.investor.flow("8355") |
client.eod.index.investor | Dòng tiền theo nhóm nhà đầu tư, cấp chỉ số | client.eod.index.investor.flow("VNINDEX") |
client.intraday.stock | Trong phiên: thanh giá, tick, mua bán chủ động | client.intraday.stock.ticks("HPG", date="2026-08-03") |
client.intraday.index | Trong phiên, chỉ số | client.intraday.index.ohlcv("VNINDEX", interval="5min") |
client.intraday.derivative | Trong phiên, phái sinh | client.intraday.derivative.net_active_value("VN30F1M") |
client.financials | Báo cáo tài chính | client.financials.statement("HPG") |
client.meta | Danh mục mã, cây ngành, chứng quyền | client.meta.symbols(exchange="HOSE") |
Ngoài dữ liệu, client còn trả lời về chính nó: client.whoami() cho gói dịch
vụ và tình trạng khoá, client.limits() cho hạn mức hiệu lực,
client.status() cho trạng thái service, client.cache.stats() cho số liệu
cache.
Giá cuối ngày
client.eod.stock.ohlcv("HPG", start="2020-01-01", interval="1w")
client.eod.stock.ohlcv("HPG", adjusted=False) # giá khớp lệnh thật của phiên
client.eod.index.ohlcv("VNINDEX", interval="1mo")
client.eod.sector.ohlcv("8355") # chỉ số ngành, mã ICB cấp 4Cổ phiếu có dữ liệu từ 2007, chỉ số ngành từ 2000. Giá cổ phiếu mặc
định đã điều chỉnh quyền; adjusted=False cho chuỗi giá đúng như phiên hôm
đó, dùng khi đối chiếu sao kê hoặc dựng lại một lệnh cũ. Cột volume không đổi
theo cờ này — nó luôn là số cổ phiếu thật.
Gộp nhóm chạy ở server: interval="1w" trả về thanh tuần đã gộp, không
phải thanh ngày để bạn tự gộp lại. Một kỳ đã gộp được dán nhãn bằng phiên giao
dịch cuối trong kỳ, nên interval="1mo" cho tháng 1/2025 ra 2025-01-24 —
phiên cuối trước kỳ nghỉ Tết — chứ không phải 2025-01-01 hay 2025-01-31.
Trong phiên và tick-by-tick
client.intraday.stock.ohlcv("HPG", interval="5min")
client.intraday.stock.ticks("HPG", date="2026-08-03") # từng lệnh khớp
client.intraday.stock.net_active_value("HPG", interval="1h") # mua/bán chủ độngTick có từ 2022. Cột thời gian ở đây tên time và mang múi giờ
+07:00, khác cột date naive của dữ liệu cuối ngày — một thanh trong phiên là
một thời điểm, không phải một ngày giao dịch.
ticks() lấy một mã, một phiên mỗi lời gọi, và giới hạn đó nằm trong chính
chữ ký hàm: tham số đầu là symbol số ít. Một phiên phái sinh sôi động là hàng
chục nghìn dòng.
Giá trong phiên là giá thô
Ngược với client.eod.stock, giá trong phiên chưa điều chỉnh quyền. Nối một
chuỗi trong phiên vào một chuỗi cuối ngày mà không đọc
df.attrs["finlens"]["price_basis"] sẽ cho một bậc thang giả ở đúng ngày chia
tách, và không có lỗi nào báo.
client.intraday.index không có net_active_value(), và đó không phải một
thiếu sót: bảng tick của chỉ số không ghi chiều lệnh chủ động, nên không có dữ
liệu nào để tính. Một sự vắng mặt trong dữ liệu là một sự vắng mặt trong API.
Dòng tiền theo nhóm nhà đầu tư
client.eod.stock.investor.flow("HPG", group="foreign")
client.eod.stock.investor.breakdown("HPG") # nhiều nhóm trong một frame
client.eod.sector.investor.flow("8355") # theo ngành ICBflow() hỏi một nhóm, breakdown() hỏi nhiều nhóm — cùng một dataset, cùng một
bộ cột. Cột group luôn có mặt, kể cả khi bạn chỉ hỏi một nhóm, vì đó là
chỗ duy nhất nói được "con số này tính trên cơ sở nào" ở mức từng dòng.
| Nhóm | Có từ | Phạm vi |
|---|---|---|
foreign | 2010 | cả ba sàn |
foreign_individual, foreign_institutional | 2024 | HOSE |
local_individual, local_institutional | 2024 | HOSE |
proprietary (tự doanh) | 2022 | cả ba sàn |
Sáu nhóm không cùng một vũ trụ dữ liệu
Bốn nhóm chi tiết (cá nhân/tổ chức × trong nước/nước ngoài) là một phân hoạch đầy đủ của khớp lệnh: tổng mua ròng của chúng bằng 0, vì mua ròng của nhóm này là bán ròng của nhóm kia.
foreign gồm cả giao dịch thoả thuận nên chồng lấn lên hai nhóm ngoại chi
tiết, còn proprietary (tự doanh) đến từ một nguồn khác hẳn và không phải
nhóm con của local_institutional. Cộng cả sáu nhóm lại cho ra một con số vô
nghĩa.
Ở cấp ngành chỉ có foreign và proprietary; bốn nhóm chi tiết không tồn
tại ở đó. Ở cấp chỉ số, hiện chỉ VNINDEX có dữ liệu.
Báo cáo tài chính
client.financials.statement("HPG", kind="balance_sheet", period="quarterly")
client.financials.periods("HPG") # kỳ nào có số liệu
client.financials.line_items(com_type="NH", kind="balance_sheet")Có từ 2004. Frame ở dạng long — một dòng là một khoản mục của một kỳ —
và mỗi dòng mang company_type của chính nó, nên statement(["HPG", "VCB"])
chạy được dù HPG là doanh nghiệp phi tài chính còn VCB là ngân hàng. Bốn loại
hình (CT phi tài chính, NH ngân hàng, CK chứng khoán, BH bảo hiểm) có
bốn cây khoản mục khác nhau.
Tra cứu danh mục
client.meta.symbols() # toàn bộ danh mục
client.meta.symbols(exchange="HOSE")
client.meta.symbols(icb="8300") # toàn ngành ngân hàng
client.meta.symbols(kind="fund") # chứng chỉ quỹ niêm yết
client.meta.sectors(level=2) # cây ngành ICB
client.meta.warrants(underlying="HPG")Đây thường là lời gọi đầu tiên của một phiên làm việc: trước khi hỏi giá của
một mã, người ta hỏi có những mã nào. Tham số icb= nhận cả mã cấp 2 lẫn mã
cấp 4, nên bạn không cần biết mã mình đang cầm thuộc cấp nào.
tickers = client.meta.symbols(exchange="HOSE")["symbol"].tolist()Chi tiết ba phương thức, bộ lọc và cột trả về ở Tra cứu danh mục.
Đơn vị — đọc trước khi tính toán
Đây là nguồn lỗi số một khi làm việc với dữ liệu chứng khoán Việt Nam, và nó sai âm thầm: không exception nào, chỉ là một con số sai.
| Loại dữ liệu | Cột giá | Khối lượng | Giá trị tiền |
|---|---|---|---|
| Cổ phiếu, ETF, chứng chỉ quỹ | nghìn VND (22.3 = 22.300 đ) | cổ phiếu | VND |
| Chỉ số | điểm chỉ số | cổ phiếu | — |
| Chỉ số ngành ICB | điểm chỉ số | cổ phiếu | — |
| Phái sinh | điểm chỉ số | hợp đồng | VND |
| Chứng quyền | VND thô | chứng quyền | VND |
| Dòng tiền nhà đầu tư | — | cổ phiếu | VND thô |
| Báo cáo tài chính | — | — | VND thô |
Đơn vị được khai báo chứ không phải đoán — mỗi frame mang theo đơn vị của chính nó:
df.attrs["finlens"]["units"] # {'close': 'kVND', 'volume': 'share', ...}
df.attrs["finlens"]["price_basis"] # 'adjusted' hoặc 'raw'
df.attrs["finlens"]["as_of"] # mốc nước của dữ liệu`df.attrs` không sống sót qua `merge` và `concat`
attrs mang tính thông tin, không phải cơ chế bảo vệ — pandas bỏ nó khi ghép
frame. Đọc đơn vị trước khi ghép.
Bảo vệ thật nằm ở chỗ tách namespace: client.eod.stock và client.eod.index
không bao giờ trả về cùng một bảng, nên một lời gọi không thể trộn nghìn VND với
điểm chỉ số.
Từ vựng interval
| Nhóm | Giá trị hợp lệ |
|---|---|
| Cuối ngày | 1d · 1w · 1mo · 3mo · 6mo · 1y |
| Trong phiên | 1min · 5min · 15min · 30min · 1h · 4h |
Không có `1m` và không có `1M`
Trong từ vựng cũ 1M là một tháng còn 1m là một phút — phân biệt duy
nhất bằng chữ hoa, và hai giá trị đó lệch nhau 43.200 lần. Ở đây tháng là 1mo,
phút là 1min, và "1M" bị từ chối kèm gợi ý thay vì âm thầm trả về một
biểu đồ sai.
Hai nhóm dán nhãn ngược nhau, và chỗ này dễ suy sai từ nhóm này sang nhóm kia:
| Nhóm | Cột thời gian | Mốc của một kỳ đã gộp |
|---|---|---|
| Cuối ngày | date, naive | Phiên giao dịch cuối trong kỳ |
| Trong phiên | time, mang +07:00 | Đầu bước thời gian |
Cuối ngày lấy phiên cuối vì close của dòng ấy là giá của đúng phiên đó — nhãn
theo lịch sẽ trỏ tới một ngày mà thị trường không mở, và với kỳ đang dở thì trỏ
cả tới một ngày ở tương lai. Chi tiết ở Giá cuối ngày.
Một mã lỗi không làm mất các mã còn lại
Mặc định on_error="warn": bạn nhận về những mã lấy được kèm một
PartialDataWarning, và chi tiết từng mã hỏng nằm ở
df.attrs["finlens"]["failed"]. Đặt on_error="raise" nếu muốn một mã hỏng làm
cả lời gọi thất bại — ngoại lệ PartialFetchError vẫn mang theo phần đã lấy
được ở .data.
Mọi phương thức cũng nhận refresh=True để bỏ qua cache và lấy lại từ server.
Kết quả rỗng vẫn đúng cột, đúng kiểu: ngày thị trường nghỉ, df["close"]
vẫn chạy và không KeyError. Bạn không cần bọc if not df.empty quanh mọi lời
gọi. Chi tiết ở Xử lý lỗi.
Yêu cầu môi trường
- Python 3.11+
- pandas 3.0+
- Chạy được trên Windows, Linux và macOS
- Có gợi ý kiểu theo chuẩn PEP 561, nên
autocomplete và
mypyhoạt động đầy đủ
Giấy phép MIT. Gói phát hành trên PyPI.
Bản bất đồng bộ
Mọi thứ có bản async với cùng chữ ký, dùng chung một lõi với bản đồng bộ — không có chuyện một bên được sửa bug còn bên kia thì không.
import asyncio
import finlens
async def main():
async with finlens.AsyncClient() as client:
df = await client.eod.stock.ohlcv("HPG,VCB")
asyncio.run(main())Không viết code thì dùng gì?
Nếu bạn muốn hỏi thẳng bằng tiếng Việt thay vì viết truy vấn, hãy dùng FinLens MCP — máy chủ Model Context Protocol nối Claude, ChatGPT và Gemini vào cùng nguồn dữ liệu chứng khoán Việt Nam, có thêm các nhóm dữ liệu SDK chưa phủ như báo cáo phân tích của công ty chứng khoán, quỹ đầu tư và trái phiếu doanh nghiệp. Muốn xem dạng giao diện đồ thị thì vào FinLens Web.
Hỗ trợ
Gặp lỗi hoặc cần tư vấn cách dùng, liên hệ trực tiếp:
Khi báo lỗi, kèm theo finlens.build_info() và request_id trong thông báo lỗi
— hai thứ đó cho biết chính xác bản build nào và request nào. Xem thêm
nhật ký thay đổi.
Câu hỏi thường gặp
Làm sao lấy dữ liệu giá cổ phiếu Việt Nam bằng Python?
Cài thư viện bằng `pip install finlens`, tạo client bằng `finlens.client()` (khoá đọc từ biến môi trường `FINLENS_API_KEY`), rồi gọi `client.eod.stock.ohlcv("HPG", start="2026-01-01", end="2026-01-31")`. Hàm trả về `pandas.DataFrame` với bảy cột viết thường `symbol`, `date`, `open`, `high`, `low`, `close`, `volume`, dùng được ngay trong notebook hay pipeline dữ liệu. Giá cổ phiếu tính bằng nghìn đồng: `23.17` nghĩa là 23.170 VND. Mặc định `interval="1d"` cho một dòng mỗi phiên; với `"1w"` hay `"1mo"` thì mỗi dòng là một kỳ đã gộp và cột `date` mang phiên giao dịch cuối trong kỳ.
Thư viện finlens cung cấp những loại dữ liệu chứng khoán nào?
Đối tượng client của finlens chia theo namespace. Giá cuối ngày nằm ở `client.eod.stock`, `client.eod.index`, `client.eod.derivative`, `client.eod.warrant` và `client.eod.sector` — mỗi loại tài sản một namespace vì mỗi loại một đơn vị. Dòng tiền theo nhóm nhà đầu tư ở `client.eod.stock.investor.flow()` và `breakdown()`. Dữ liệu trong phiên ở `client.intraday.stock` với `ohlcv()`, `ticks()` và `net_active_value()`. Báo cáo tài chính ở `client.financials.statement()`, còn danh mục mã, cây ngành ICB và chứng quyền ở `client.meta.symbols()`, `client.meta.sectors()`, `client.meta.warrants()`.
Tham số group của finlens nhận giá trị nào, và cộng được nhóm nào lại?
Ở cấp mã, `group` nhận sáu giá trị: `"foreign"`, `"foreign_individual"`, `"foreign_institutional"`, `"local_individual"`, `"local_institutional"` và `"proprietary"` (tự doanh). Nhưng chỉ bốn nhóm chi tiết — cá nhân và tổ chức, trong nước và nước ngoài — là một phân hoạch đầy đủ, tổng `net_value` của chúng bằng 0. `foreign` gồm cả giao dịch thoả thuận nên chồng lấn lên hai nhóm ngoại chi tiết, còn `proprietary` đến từ nguồn khác hẳn và không phải nhóm con của `local_institutional`; cộng cả sáu lại cho ra một con số vô nghĩa. Ở cấp ngành chỉ có `"foreign"` và `"proprietary"`.
Thư viện finlens yêu cầu môi trường gì và dùng giấy phép nào?
finlens yêu cầu Python từ 3.11 trở lên và pandas từ 3.0 trở lên, chạy được trên Windows, Linux và macOS. Lệnh `pip install finlens` chỉ kéo theo ba phụ thuộc: `pandas`, `httpx` và `packaging` — `pydantic` và `requests` không còn ở bản 1.x. Gói có gợi ý kiểu theo chuẩn PEP 561 nên autocomplete và `mypy` hoạt động đầy đủ. Thư viện phát hành trên PyPI dưới giấy phép MIT.
Đơn vị giá trong dữ liệu chứng khoán Việt Nam trả về là gì?
Với thư viện Python finlens, cổ phiếu và ETF trả giá bằng nghìn VND (`22.3` là 22.300 đồng), chỉ số và phái sinh trả bằng điểm chỉ số, còn chứng quyền trả bằng VND thô. Giá trị dòng tiền nhà đầu tư và số liệu báo cáo tài chính luôn là VND thô. Đừng đoán theo tên cột: mỗi DataFrame mang theo đơn vị của chính nó ở `df.attrs["finlens"]["units"]`, ví dụ `df.attrs["finlens"]["units"]["close"]` trả `'kVND'` với cổ phiếu và `'index_point'` với chỉ số. Lưu ý `df.attrs` không sống sót qua `pd.concat` hay `merge` của pandas.
Cập nhật lần cuối