Logo FinLensFinLens Docs

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 finlens

Bắ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 đồng23.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.

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ẫnDữ liệuVí dụ
client.eod.stockGiá cuối ngày của cổ phiếu, ETF và chứng chỉ quỹclient.eod.stock.ohlcv("HPG")
client.eod.indexGiá cuối ngày của chỉ sốclient.eod.index.ohlcv("VNINDEX")
client.eod.derivativeGiá cuối ngày của hợp đồng phái sinhclient.eod.derivative.ohlcv("VN30F1M")
client.eod.warrantGiá cuối ngày của chứng quyền có bảo đảmclient.eod.warrant.ohlcv("CHPG2628")
client.eod.sectorChỉ số ngành ICBclient.eod.sector.ohlcv("8355")
client.eod.stock.investorDòng tiền theo nhóm nhà đầu tư, cấp mãclient.eod.stock.investor.flow("HPG")
client.eod.sector.investorDòng tiền theo nhóm nhà đầu tư, cấp ngànhclient.eod.sector.investor.flow("8355")
client.eod.index.investorDòng tiền theo nhóm nhà đầu tư, cấp chỉ sốclient.eod.index.investor.flow("VNINDEX")
client.intraday.stockTrong phiên: thanh giá, tick, mua bán chủ độngclient.intraday.stock.ticks("HPG", date="2026-08-03")
client.intraday.indexTrong phiên, chỉ sốclient.intraday.index.ohlcv("VNINDEX", interval="5min")
client.intraday.derivativeTrong phiên, phái sinhclient.intraday.derivative.net_active_value("VN30F1M")
client.financialsBáo cáo tài chínhclient.financials.statement("HPG")
client.metaDanh mục mã, cây ngành, chứng quyềnclient.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 4

Cổ 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ủ động

Tick có từ 2022. Cột thời gian ở đây tên timemang 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 ICB

flow() 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ómCó từPhạm vi
foreign2010cả ba sàn
foreign_individual, foreign_institutional2024HOSE
local_individual, local_institutional2024HOSE
proprietary (tự doanh)2022cả 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ó foreignproprietary; 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ệuCột giáKhối lượngGiá trị tiền
Cổ phiếu, ETF, chứng chỉ quỹnghìn VND (22.3 = 22.300 đ)cổ phiếuVND
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 đồngVND
Chứng quyềnVND thôchứng quyềnVND
Dòng tiền nhà đầu tưcổ phiếuVND thô
Báo cáo tài chínhVND 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.stockclient.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ómGiá trị hợp lệ
Cuối ngày1d · 1w · 1mo · 3mo · 6mo · 1y
Trong phiên1min · 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ómCột thời gianMốc của một kỳ đã gộp
Cuối ngàydate, naivePhiên giao dịch cuối trong kỳ
Trong phiêntime, 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à mypy hoạ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()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

Nội dung trang