Dữ liệu quỹ đầu tư Việt Nam bằng Python
Dùng finlens lấy danh sách 188 quỹ đầu tư Việt Nam, giá trị tài sản ròng theo ngày, danh mục nắm giữ theo tháng và phép tra ngược mã cổ phiếu về pandas DataFrame.
Danh mục của nhà đầu tư tổ chức là thứ khó lấy nhất và cũng dễ dùng nhất khi đã
có. client.funds trả lời bốn câu: có những quỹ nào, quỹ này lãi lỗ ra sao, quỹ
này đang nắm gì, và — chiều ngược lại — mã này đang được quỹ nào nắm.
188 quỹ; 149 quỹ có chuỗi giá trị tài sản ròng, 141 quỹ có danh mục nắm giữ. NAV theo ngày, danh mục theo tháng.
| Gọi | Trả lời câu hỏi |
|---|---|
client.funds.list() | Thị trường có những quỹ nào, ai quản lý, quy mô bao nhiêu? |
client.funds.nav() | Giá trị tài sản ròng của quỹ này biến động thế nào? |
client.funds.holdings() | Quỹ này đang nắm những mã nào, tỷ trọng bao nhiêu? |
client.funds.holders() | Mã này đang được quỹ nào nắm? |
client.funds.managers() | Ai điều hành quỹ này? |
import finlens
client = finlens.client()
ds = client.funds.list(fund_type="Quỹ ETF")
nav = client.funds.nav("E1VFVN30", start="2026-01-01")
ai = client.funds.holders("HPG")Ba thứ sẽ làm bạn đọc sai số nếu bỏ qua
- Tổng
weight_ratiocủa một quỹ KHÔNG bằng 1. Bảng chỉ có phần danh mục được công bố. Đừng tính tiền mặt bằng1 − Σ. - Mã quỹ không phải mã chứng khoán. Nó giữ nguyên hoa thường và chứa được
dấu cách lẫn dấu
+. 53/188 mã có ký tự ngoài[A-Z0-9]. - Tiền là VND thô, không phải nghìn VND như cột giá cổ phiếu.
Tổng tỷ trọng không bằng 1, và đó không phải lỗi
Đây là chỗ dễ mắc nhất của cả trang, nên nó đứng trước phần tham chiếu.
Bảng holdings() liệt kê phần danh mục được công bố, không phải toàn bộ tài
sản. Đo trên mười hai quỹ:
for ma in ["ABBF", "ACBC-BGF", "AEIF", "ASBF", "ABEF"]:
h = client.funds.holdings(ma)
print(ma, len(h), round(h["weight_ratio"].sum(), 4))
# ABBF 14 0.8939
# ACBC-BGF 24 0.5691
# AEIF 23 0.4969
# ASBF 3 0.2757
# ABEF 28 0.9864Dải đo được là 0,2426 đến 0,9864, và không quỹ nào vượt 0,99. Quỹ công
bố ít dòng thì tổng càng thấp — ASBF có ba dòng và tổng 0,2757.
Nên 1 − Σ weight_ratio không phải tỷ lệ tiền mặt. Muốn biết quy mô thật,
đọc total_nav của nav().
list() — danh mục quỹ và hồ sơ
client.funds.list(
*,
fund_type=None,
structure=None,
manager=None,
refresh=False,
on_error="warn",
) -> DataFrame| Tham số | Kiểu | Mặc định | Nội dung |
|---|---|---|---|
fund_type | FundTypeArg | None | "Quỹ mở" (113) · "Quỹ ETF" (32) · "Quỹ thành viên" (31) · "Quỹ đóng" (12). |
structure | FundStructureArg | None | "Quỹ cổ phiếu" (126) · "Quỹ trái phiếu" (32) · "Quỹ cân bằng" (29) · "Quỹ BĐS" (1). |
manager | str | None | None | Tên công ty quản lý. Miền mở — lấy từ cột management_org. |
refresh | bool | False | Bỏ qua bản trong cache và lấy lại từ máy chủ. |
on_error | OnError | "warn" | warn · raise · ignore. |
Hai tham số đầu là từ vựng đóng và giữ nguyên tiếng Việt có dấu — chúng là nhãn phân loại của nguồn, không phải token ASCII.
Cột trả về
Frame có 28 cột. Những cột hay dùng nhất:
| Cột | dtype | Nội dung |
|---|---|---|
fund_code | string | Mã quỹ. Khoá bạn truyền lại cho bốn phương thức còn lại. |
organization_id | Int64 | Định danh tổ chức. Dùng khi mã quỹ nhập nhằng. |
name · short_name | string | Tên đầy đủ và tên rút gọn. |
fund_type · fund_structure | string | Loại quỹ và cấu trúc tài sản. |
management_org · monitoring_org | string | Công ty quản lý và ngân hàng giám sát. |
schedule | string | Nhịp công bố NAV: Ngày (127) · Tuần (19) · Quý (5) · Tháng (4). |
total_nav | Float64 | Tổng giá trị tài sản ròng, VND thô. |
nav_per_share_adjusted | Float64 | NAV trên một chứng chỉ quỹ, VND thô. |
foreign_ratio | Float64 | Tỷ lệ sở hữu nước ngoài, thang 0–1. |
nav_point_count · holding_period_count | Int64 | Số điểm dữ liệu sẵn có. Bằng 0 nghĩa là nguồn chưa công bố. |
min_invest | Float64 | Số tiền đầu tư tối thiểu, VND thô. |
Hai cột đếm cuối là cách rẻ nhất để biết trước một quỹ có dữ liệu hay không, thay vì gọi rồi nhận bảng rỗng.
nav() — giá trị tài sản ròng theo ngày
client.funds.nav(
funds,
*,
start=None,
end=None,
refresh=False,
on_error="warn",
) -> DataFrame| Tham số | Kiểu | Mặc định | Nội dung |
|---|---|---|---|
funds | FundsArg | — | Một mã, chuỗi nhiều mã ngăn bằng dấu phẩy, hoặc danh sách. Nhận cả organization_id. |
start · end | DateArg | None | "2026-01-01". None là toàn bộ lịch sử. |
refresh | bool | False | Bỏ qua bản trong cache và lấy lại từ máy chủ. |
on_error | OnError | "warn" | warn · raise · ignore. |
Đây là phương thức duy nhất của nhóm tự phân trang: bảng nguồn có hơn 136 nghìn dòng, vượt xa hạn mức một request, và thư viện đi hết chuỗi cursor giúp bạn. Nó cũng là phương thức duy nhất giữ cache 30 phút thay vì 24 giờ.
Cột trả về
| Cột | dtype | Có thể null | Nội dung |
|---|---|---|---|
fund_code · organization_id | string · Int64 | không | Định danh quỹ. |
date | datetime64[ns] | không | Ngày quan sát, không mang múi giờ. |
nav_per_share | Float64 | không | NAV trên một chứng chỉ quỹ, VND thô. |
nav_per_share_adjusted | Float64 | không | Bản đã điều chỉnh. |
nav_change_ratio | Float64 | không | Biến động so với kỳ trước, thang 0–1. |
total_nav | Float64 | không | Tổng NAV của quỹ, VND thô. |
share_outstanding | Float64 | không | Số chứng chỉ quỹ đang lưu hành. |
fund_flow | Float64 | không | Dòng tiền vào ra. |
foreign_volume · foreign_ratio | Float64 | không | Khối ngoại: khối lượng và tỷ lệ thang 0–1. |
close_price_adjusted | Float64 | có | Thị giá chứng chỉ quỹ. null với quỹ không niêm yết. |
discount | Float64 | có | Chênh lệch thị giá so với NAV, VND thô. |
discount_ratio | Float64 | có | Cùng đại lượng, thang 0–1. |
Ba cột cuối là `null`, không phải `0`
Chỉ một phần quỹ có thị giá — phần còn lại không niêm yết nên không có giá nào
để so với NAV. Nguồn ghi số 0 ở những dòng đó, nhưng một mức giá bằng 0 là bất
khả, nên thư viện đổi chúng thành null.
Nếu giữ nguyên số 0, df["discount_ratio"].mean() sẽ lặng lẽ cộng hàng chục
nghìn số 0 của những quỹ chưa từng giao dịch và kéo trung bình về gần 0.
holdings() — quỹ này đang nắm gì
client.funds.holdings(
fund,
*,
date=None,
refresh=False,
on_error="warn",
) -> DataFrame| Tham số | Kiểu | Mặc định | Nội dung |
|---|---|---|---|
fund | FundArg | — | Đúng một quỹ. Mã quỹ hoặc organization_id. |
date | DateArg | None | Kỳ quan sát. None là kỳ gần nhất. |
refresh | bool | False | Bỏ qua bản trong cache và lấy lại từ máy chủ. |
on_error | OnError | "warn" | warn · raise · ignore. |
Cột trả về
| Cột | dtype | Nội dung |
|---|---|---|
fund_code · organization_id | string · Int64 | Quỹ đang nắm. |
date | datetime64[ns] | Kỳ quan sát. Nhịp tháng. |
asset_code | string | Mã tài sản được nắm. |
asset_name | string | Tên tài sản. |
asset_type | string | "stock" hoặc "bond". |
weight_ratio | Float64 | Tỷ trọng trong danh mục, thang 0–1. |
quantity | Float64 | Số lượng nắm giữ. |
asset_type cho biết mã trong asset_code tra ở đâu: "stock" thì nó là mã
chứng khoán tra được bằng client.meta.symbols(), còn "bond" thì nó là mã
trái phiếu tra bằng client.bonds.list().
holders() — mã này đang được quỹ nào nắm
client.funds.holders(
symbols,
*,
date=None,
refresh=False,
on_error="warn",
) -> DataFrame| Tham số | Kiểu | Mặc định | Nội dung |
|---|---|---|---|
symbols | SymbolsArg | — | Một hoặc nhiều mã chứng khoán — không phải mã quỹ. |
date | DateArg | None | Kỳ quan sát. None là mọi kỳ sẵn có. |
refresh | bool | False | Bỏ qua bản trong cache và lấy lại từ máy chủ. |
on_error | OnError | "warn" | warn · raise · ignore. |
Đây là phép tra ngược của holdings(), và là thứ không tự tính được từ một
bảng phẳng: nó đòi quét danh mục của mọi quỹ rồi gom theo mã.
⚠️ Kết quả trải nhiều kỳ, không phải một ảnh chụp. holders("HPG") trả về
80 dòng nằm trên 5 kỳ quan sát khác nhau — lọc theo date nếu bạn chỉ cần kỳ
gần nhất:
k = client.funds.holders("HPG")
moi_nhat = k[k["date"] == k["date"].max()]Cột trả về
| Cột | dtype | Nội dung |
|---|---|---|
symbol | string | Mã chứng khoán bạn hỏi. |
fund_code · organization_id | string · Int64 | Quỹ đang nắm mã đó. |
fund_short_name | string | Tên rút gọn của quỹ. |
date | datetime64[ns] | Kỳ quan sát. |
weight_ratio | Float64 | Tỷ trọng của mã này trong danh mục của quỹ đó, thang 0–1. |
quantity | Float64 | Số lượng quỹ đang nắm. |
weight_ratio ở đây là tỷ trọng trong danh mục của quỹ, không phải tỷ lệ sở
hữu trên vốn của doanh nghiệp. Hai đại lượng đó khác mẫu số hoàn toàn.
managers() — người điều hành quỹ
client.funds.managers(
fund,
*,
refresh=False,
on_error="warn",
) -> DataFrame| Tham số | Kiểu | Mặc định | Nội dung |
|---|---|---|---|
fund | FundArg | — | Đúng một quỹ. Mã quỹ hoặc organization_id. |
refresh | bool | False | Bỏ qua bản trong cache và lấy lại từ máy chủ. |
on_error | OnError | "warn" | warn · raise · ignore. |
Cột trả về
| Cột | dtype | Nội dung |
|---|---|---|
fund_code · organization_id | string · Int64 | Quỹ. |
full_name | string | Họ tên người điều hành. |
position_name | string | Chức danh. |
Mã quỹ nhập nhằng
Một mã quỹ có thể thuộc hai tổ chức khác nhau. Khi đó thư viện không đoán:
import finlens
try:
client.funds.nav("VVDIF")
except finlens.AmbiguousFundError as e:
print(e.organization_ids)
df = client.funds.nav(e.organization_ids[0])Ngoại lệ mang theo fund_code và organization_ids để bạn chọn. Mọi frame của
nhóm này luôn trả về cả fund_code lẫn organization_id, nên bạn có sẵn
định danh không nhập nhằng ngay trong kết quả.
Độ phủ có lỗ, và API không lọc chúng đi
| Đo được | Con số |
|---|---|
| Quỹ có chuỗi NAV | 149 / 188 |
| Quỹ có danh mục nắm giữ | 141 / 188 |
Quỹ không có dữ liệu trả về bảng rỗng đủ cột đúng dtype, không phải lỗi:
df = client.funds.nav("A+ Fund")
len(df) # 0
len(df.columns) # 14
df["nav_per_share"].sum() # 0.0 — không KeyErrorKiểm trước bằng nav_point_count và holding_period_count của list() sẽ rẻ
hơn là gọi rồi nhận bảng rỗng.
Thư viện không tính sẵn chỉ số nào
Không có suất sinh lợi 1 tháng / 3 tháng / từ đầu năm, không xếp hạng, không
nhãn gom hay xả. finlens trả DataFrame; những thứ đó là hai dòng pandas:
nav = client.funds.nav("E1VFVN30", start="2026-01-01")
nav = nav.set_index("date")
loi_nhuan = nav["nav_per_share_adjusted"].pct_change(periods=20)Tính ở máy chủ buộc phải chốt quy ước qui năm và quy ước điều chỉnh vào hợp đồng — loại quyết định sai một lần rồi sai lặng lẽ mãi. Sản phẩm MCP có tính sẵn những chỉ số này, và đó là khác biệt có chủ đích giữa hai bề mặt.
Câu hỏi thường gặp
Làm sao biết một mã cổ phiếu đang được những quỹ đầu tư nào nắm giữ?
Gọi `client.funds.holders("HPG")` của thư viện Python finlens. Phương thức này làm phép tra ngược trên bảng danh mục nắm giữ: đầu vào là mã cổ phiếu, đầu ra là danh sách các quỹ đang nắm mã đó kèm tỷ trọng trong danh mục của từng quỹ và số lượng nắm giữ. Kết quả trải nhiều kỳ quan sát chứ không phải một ảnh chụp, nên hãy lọc theo cột `date` nếu bạn chỉ cần kỳ gần nhất. Đây là phép tra không tự tính được từ một bảng phẳng, vì nó đòi quét toàn bộ danh mục của mọi quỹ rồi gom theo mã.
Vì sao tổng tỷ trọng danh mục của một quỹ không bằng 100%?
Vì bảng danh mục nắm giữ chỉ liệt kê phần được công bố, không phải toàn bộ tài sản của quỹ. Đo trên mười hai quỹ bằng thư viện Python finlens, tổng cột `weight_ratio` chạy từ 0,24 đến 0,99 và không quỹ nào chạm 1,0. Hệ quả thực dụng: đừng tính tỷ lệ tiền mặt bằng công thức một trừ tổng tỷ trọng, con số đó sẽ sai và sai theo hướng phóng đại. Nếu bạn cần biết quy mô thật của quỹ, dùng cột `total_nav` của `client.funds.nav()` thay vì suy từ danh mục.
Mã quỹ khác mã cổ phiếu ở chỗ nào khi truyền vào hàm?
Mã quỹ không được viết hoa tự động và không phải lúc nào cũng là chuỗi chữ số liền nhau. Trong 188 quỹ mà thư viện Python finlens trả về, có 53 mã chứa ký tự ngoài bảng chữ hoa và chữ số — ví dụ `A+ Fund`, `ACBC-AGF`, `CSOP FTSE VN`. Vì thế hãy gõ đúng như cột `fund_code` của `client.funds.list()` trả về. Ngoài ra một vài mã trùng nhau giữa hai tổ chức khác nhau; khi đó thư viện ném `AmbiguousFundError` kèm danh sách `organization_ids` để bạn chọn đúng quỹ.
Giá trị tài sản ròng trả về đơn vị gì, có phải nghìn đồng như giá cổ phiếu không?
Không. Các cột tiền của nhóm quỹ trong thư viện Python finlens là **đồng Việt Nam thô**, khác với cột giá cổ phiếu vốn tính bằng nghìn đồng. Cụ thể `nav_per_share`, `nav_per_share_adjusted`, `total_nav`, `close_price_adjusted`, `discount` và `min_invest` đều là VND nguyên. Giá trị tài sản ròng trên một chứng chỉ quỹ trung bình vào khoảng vài trăm nghìn đồng, nên nếu bạn thấy con số cỡ trăm nghìn thì đó là đúng chứ không phải lỗi nhân sai thang đo.
Bốn cột có đuôi ratio nằm trong thang phần trăm hay thang 0–1?
Thang 0–1. Bốn cột `weight_ratio`, `nav_change_ratio`, `foreign_ratio` và `discount_ratio` của thư viện Python finlens đều là phân số, nên giá trị 0,0732 nghĩa là 7,32 phần trăm chứ không phải 0,07 phần trăm. Quy ước này áp cho toàn thư viện: hậu tố `_ratio` luôn là thang 0–1 còn hậu tố `_pct` mới là thang 0–100. Nguồn dữ liệu gốc đặt tên những cột này là `percent_*` trong khi giá trị thực là tỷ lệ, và tên ở đây được đổi để nói đúng thang đo.
Cập nhật lần cuối