Logo FinLensFinLens Docs

Dữ liệu trái phiếu doanh nghiệp bằng Python

Dùng finlens lấy 6.820 lô trái phiếu doanh nghiệp Việt Nam và 940 tổ chức phát hành về pandas DataFrame, lọc theo ngành, tiền tệ, trạng thái dư nợ và khoảng đáo hạn.

Trái phiếu doanh nghiệp là phần thị trường vốn ít được số hoá nhất, và cũng là nơi rủi ro tín dụng nằm. client.bonds trả lời hai câu: có những lô trái phiếu nào, và ai đang nợ bao nhiêu.

6.820 lô trái phiếu của 940 tổ chức phát hành. Đây là ảnh chụp trạng thái hiện tại, không phải chuỗi thời gian dư nợ.

GọiTrả lời câu hỏi
client.bonds.list()Có những lô trái phiếu nào, lãi suất bao nhiêu, đáo hạn khi nào?
client.bonds.issuers()Ai phát hành, dư nợ còn lại bao nhiêu, xếp hạng tín nhiệm ra sao?
import finlens

client = finlens.client()

con_no = client.bonds.list(outstanding=True)
to_chuc = client.bonds.issuers(has_stock=True)

Cột tiền đi theo CẶP, và thư viện không quy đổi

Mọi khoản tiền có hai cột: X_mvnd (triệu VND) và X_usd (USD thô). Mỗi dòng chỉ một cột có giá trị — đúng ở cả 6.819 dòng có tiền tệ.

Không có cột tiền nào mang tên trần. df["outstanding_value"] sẽ ném KeyError, và đó là chủ đích: một cột tiền không nói đơn vị là một cái tên nói dối.

Vì sao không gộp hai loại tiền vào một cột

Toàn thị trường có 6.796 lô bằng VND và 23 lô bằng USD (một lô không xác định được tiền tệ). Tỷ lệ nhỏ, nên gộp lại nghe có vẻ vô hại — nhưng nó không.

Cùng một con số mệnh giá tồn tại ở cả hai loại tiền. Một cột currency đặt cạnh không ngăn được ai đó viết df["par_value"].mean() rồi cộng đô la với đồng lệch nhau hơn hai vạn lần.

Tách cột thì phép cộng nhầm ấy không viết ra được:

b = client.bonds.list()

b.groupby("currency")[["outstanding_value_mvnd", "outstanding_value_usd"]].sum()

Hệ quả phải sống chung: câu hỏi "tổng dư nợ trái phiếu doanh nghiệp là bao nhiêu" không có một con số duy nhất ở đây. Muốn có, bạn phải tự chọn một tỷ giá — và đó là lựa chọn của bạn, không phải mặc định của thư viện. Nguồn không có bảng tỷ giá nào để thư viện dùng.

Thang đo lệch một nghìn lần so với sản phẩm MCP

Hậu tố _mvndtriệu đồng. Sản phẩm MCP của FinLens phát cùng dữ liệu này ở thang tỷ đồng, nên mọi cột tiền giữa hai bề mặt lệch đúng 1.000 lần.

Thang triệu đồng được chọn vì tổng giá trị phát hành của thị trường đủ lớn để chạm ngưỡng mà số thực 64 bit biểu diễn số nguyên chính xác được, nếu để đơn vị đồng thô.

list() — danh mục lô trái phiếu

client.bonds.list(
    *,
    codes=None,
    issuer=None,
    symbol=None,
    sector=None,
    currency=None,
    status=None,
    outstanding=None,
    matured=None,
    maturity_from=None,
    maturity_to=None,
    refresh=False,
    on_error="warn",
) -> DataFrame
Tham sốKiểuMặc địnhNội dung
codesBondCodesArgNoneMột hoặc nhiều mã trái phiếu. None là toàn bộ 6.820 lô.
issuerBondIssuerArgNoneMã tổ chức phát hành.
symbolBondSymbolArgNoneMã cổ phiếu của tổ chức phát hành.
sectorBondSectorArgNoneNgành nghề, ví dụ "Bất động sản".
currencyBondCurrencyArgNone"VND" (6.796) hoặc "USD" (23).
statusBondStatusArgNone"not_outstanding" · "fully_outstanding" · "partly_outstanding".
outstandingBondFlagArgNoneTrue gộp cả hai trạng thái còn dư nợ.
maturedBondFlagArgNoneLọc theo mốc đáo hạn so với thời điểm chụp.
maturity_from · maturity_toDateArgNoneKhoảng đáo hạn.
refreshboolFalseBỏ qua bản trong cache và lấy lại từ máy chủ.
on_errorOnError"warn"warn · raise · ignore.

Bỏ trống codes thì cả 6.820 dòng về trong một lần gọi, nên "muốn nhiều hơn mười mã" luôn có đường đi không tốn hạn mức: lấy hết rồi lọc bằng pandas.

Cột trả về

Frame có 30 cột. Nhóm theo việc:

CộtdtypeNội dung
bond_codestringMã lô trái phiếu. Khoá của bảng.
symbolstringMã cổ phiếu của tổ chức phát hành. Thường null — xem mục cầu nối.
issuer · issuer_namestringMã và tên tổ chức phát hành.
trading_code · isinstringMã giao dịch và mã ISIN.
currencystringVND · USD. Một dòng là null.
business_sectorstringNgành nghề của tổ chức phát hành.
bond_statusstringnot_outstanding (4.773) · fully_outstanding (1.392) · partly_outstanding (654).
registration_statusstringTrạng thái đăng ký.
coupon_rate_pctFloat64Lãi suất danh nghĩa, thang 0–100.
interest_type · rate_type · interest_periodstringKiểu lãi, kiểu điều chỉnh, kỳ trả lãi.
principal_payment_method · interest_payment_methodstringPhương thức trả gốc và lãi.
issue_date · maturity_datedatetime64[ns]Ngày phát hành và ngày đáo hạn.
issued_volume · outstanding_volumeFloat64Khối lượng phát hành và còn lưu hành.
par_value_mvnd · par_value_usdFloat64Mệnh giá. Cặp.
issued_value_mvnd · issued_value_usdFloat64Giá trị phát hành. Cặp.
outstanding_value_mvnd · outstanding_value_usdFloat64Dư nợ còn lại. Cặp.
convertible · secured · green · with_warrantbooleanChuyển đổi · có bảo đảm · trái phiếu xanh · kèm chứng quyền.

⚠️ coupon_rate_pct mang hậu tố _pct nên nó là thang 0–100: giá trị 9,5 nghĩa là 9,5%/năm. Khác hẳn các cột _ratio ở những trang khác.

issuers() — tổ chức phát hành

client.bonds.issuers(
    *,
    codes=None,
    symbol=None,
    sector=None,
    has_bonds=None,
    has_stock=None,
    refresh=False,
    on_error="warn",
) -> DataFrame
Tham sốKiểuMặc địnhNội dung
codesBondCodesArgNoneMã tổ chức phát hành. None là cả 940.
symbolBondSymbolArgNoneLọc theo mã cổ phiếu.
sectorBondSectorArgNoneNgành nghề.
has_bondsBondFlagArgNoneChỉ tổ chức còn trái phiếu dư nợ.
has_stockBondFlagArgNoneChỉ tổ chức có mã cổ phiếu niêm yết.
refreshboolFalseBỏ qua bản trong cache và lấy lại từ máy chủ.
on_errorOnError"warn"warn · raise · ignore.

Cột trả về

CộtdtypeNội dung
issuer_codestringMã tổ chức. Khoá của bảng.
name · short_namestringTên đầy đủ và tên rút gọn.
symbolstringMã cổ phiếu. null với 812/940 tổ chức.
exchangestringSàn niêm yết của mã cổ phiếu.
tax_codestringMã số thuế.
legal_formstringLoại hình pháp lý, giữ nguyên tiếng Việt.
public_company_typestringPhân loại công ty đại chúng.
business_sectorstringNgành nghề.
credit_rating · rating_agency · rating_datestring · string · datetime64[ns]Xếp hạng tín nhiệm và tổ chức xếp hạng.
charter_capital_mvndFloat64Vốn điều lệ, triệu VND.
bond_count · outstanding_bond_countInt64Đếm lô trái phiếu, không phải đếm tiền.
outstanding_value_mvnd · outstanding_value_usdFloat64Tổng dư nợ. Cặp.
first_issue_date · nearest_maturity_datedatetime64[ns]Lần phát hành đầu và kỳ đáo hạn gần nhất.

⚠️ legal_form không phải company_type. Bốn token CT, NH, CK, BH của báo cáo tài chính là một từ vựng đóng khác hẳn; legal_form ở đây là chuỗi tiếng Việt do nguồn quyết định.

Cầu nối sang cổ phiếu chỉ phủ 128/940

Cột symbol là chỗ duy nhất nối dữ liệu trái phiếu với dữ liệu giá cổ phiếu, và nó thưa:

i = client.bonds.issuers()

i["symbol"].notna().sum()   # 128
len(i)                      # 940

Phần còn lại là doanh nghiệp chưa đại chúng hoặc chưa niêm yết. Nên khi ghép, dùng phép ghép trái và kiểm số dòng mất đi trước khi kết luận:

gia = client.eod.stock.ohlcv(
    i["symbol"].dropna().tolist()[:10], start="2026-01-01"
)

Nếu bạn ghép trong rồi thấy dữ liệu teo lại còn một phần bảy, đó là cầu nối thưa chứ không phải lỗi ghép.

Ảnh chụp, không phải chuỗi thời gian

Bảng này trả về trạng thái hiện tại của từng lô. Không có lịch sử dư nợ theo kỳ, và issue_date / maturity_datethuộc tính của lô trái phiếu chứ không phải nhịp quan sát.

Dựng lịch đáo hạn thì nhóm trên chính ảnh chụp đó:

b = client.bonds.list(outstanding=True)

theo_thang = (
    b.dropna(subset=["maturity_date"])
    .groupby(b["maturity_date"].dt.to_period("M"))["outstanding_value_mvnd"]
    .sum()
)

Vì đây là ảnh chụp, tham số matured so với thời điểm chụp chứ không phải thời điểm bạn gọi. Hai mốc gần nhau khi dữ liệu được thu thập đều đặn, nhưng chúng không đồng nhất.

Một dòng không có tiền tệ

Đúng một trong 6.820 dòng có currency bằng null, và cả sáu cột tiền của dòng đó cũng rỗng. Thư viện không lọc nó đi.

Đó là lựa chọn có chủ đích: một lô trái phiếu biến mất khỏi bảng dư nợ tệ hơn một ô trống có thể nhìn thấy. Nếu bạn cộng theo tiền tệ thì groupby tự bỏ qua nó; nếu bạn đếm số lô thì hãy biết nó có ở đó.

b = client.bonds.list()
b["currency"].isna().sum()   # 1

Câu hỏi thường gặp

Vì sao mỗi khoản tiền lại có tới hai cột thay vì một?

Vì trái phiếu trong nguồn được phát hành bằng hai loại tiền và thư viện Python finlens không quy đổi giữa chúng. Mỗi khoản tiền vì thế đi theo cặp: một cột đuôi `_mvnd` cho trái phiếu đồng Việt Nam và một cột đuôi `_usd` cho trái phiếu ngoại tệ, và trên mỗi dòng đúng một cột trong cặp có giá trị. Lý do không gộp thành một cột: cùng con số mệnh giá xuất hiện ở cả hai loại tiền, nên một cột tiền tệ đặt cạnh sẽ không ngăn được ai đó lấy trung bình cả cột và nhận một con số vô nghĩa. Đo trên dữ liệu thật, quy tắc này đúng ở cả 6.819 dòng có tiền tệ.

Đơn vị của các cột tiền trái phiếu là gì?

Cột đuôi `_mvnd` tính bằng **triệu đồng Việt Nam**, còn cột đuôi `_usd` tính bằng đô la Mỹ nguyên. Hậu tố mang cả tiền tệ lẫn thang đo nên bạn đọc được đơn vị từ chính tên cột mà không phải tra tài liệu. Thang triệu đồng được chọn vì tổng giá trị phát hành của toàn thị trường vượt quá khoảng số nguyên mà kiểu số thực biểu diễn chính xác được nếu để đơn vị đồng thô. Lưu ý sản phẩm MCP của FinLens dùng thang tỷ đồng cho cùng dữ liệu này, nên hai bề mặt lệch nhau đúng một nghìn lần.

Dữ liệu trái phiếu có phải chuỗi thời gian dư nợ theo ngày không?

Không. Đây là **ảnh chụp** tại thời điểm thu thập gần nhất, không phải chuỗi thời gian. Thư viện Python finlens trả về trạng thái hiện tại của từng lô trái phiếu — dư nợ còn lại, trạng thái đăng ký, ngày đáo hạn — chứ không trả lịch sử dư nợ qua các kỳ. Các cột ngày như `issue_date` và `maturity_date` là thuộc tính của lô trái phiếu, không phải nhịp quan sát. Nếu bạn cần dựng lịch đáo hạn theo tháng thì nhóm dữ liệu theo `maturity_date` trên chính ảnh chụp này.

Làm sao nối một lô trái phiếu với mã cổ phiếu của doanh nghiệp phát hành?

Dùng cột `symbol` có mặt trên cả hai bảng của thư viện Python finlens. Nhưng hãy biết trước giới hạn: chỉ 128 trên tổng số 940 tổ chức phát hành có mã cổ phiếu niêm yết, phần còn lại là doanh nghiệp chưa đại chúng hoặc chưa lên sàn. Vì vậy khi bạn ghép bảng trái phiếu với dữ liệu giá cổ phiếu, phần lớn tổ chức phát hành sẽ không khớp và điều đó là đúng chứ không phải lỗi ghép. Hãy dùng phép ghép trái và kiểm số dòng mất đi trước khi kết luận.

Trạng thái dư nợ của một lô trái phiếu có những giá trị nào?

Cột `bond_status` của thư viện Python finlens nhận ba giá trị: `not_outstanding` nghĩa là đã tất toán, `fully_outstanding` nghĩa là còn nguyên dư nợ, và `partly_outstanding` nghĩa là đã trả một phần. Đo trên dữ liệu thật, phân bố là 4.773 lô đã tất toán, 1.392 lô còn nguyên và 654 lô trả một phần. Muốn lọc nhanh phần còn dư nợ thì truyền tham số `outstanding` bằng đúng thay vì tự lọc theo chuỗi, vì tham số đó gộp cả hai trạng thái còn dư nợ.

Cập nhật lần cuối

Nội dung trang