Logo FinLensFinLens Docs

Changelog finlens Python: nhật ký thay đổi thư viện

Nhật ký thay đổi thư viện Python finlens: bản 1.4.0 thêm quỹ đầu tư, trái phiếu, thuyết minh BCTC và tỷ lệ chuyển đổi chứng quyền.

Nhật ký thay đổi của thư viện Python finlens. Bản mới nhất là 1.2.0 — kiểm bằng finlens.__version__.

Các mục 1.0.0a1 đến 1.0.0a7 bên dưới là bản thử nghiệm, chỉ phát hành trên TestPyPI và chưa bao giờ có trên PyPI. Nếu bạn cài bằng pip install finlens thì bản đầu tiên bạn nhận được là 1.1.1; các mục alpha giữ lại để tra cứu lịch sử chứ không phải để nâng cấp theo.

Dòng 1.x là một bản viết lại, không phải một bản nâng cấp tiệm tiến. Code viết cho 0.1.x sẽ không chạy nguyên trạng; bảng ngay dưới đây là danh sách đầy đủ những chỗ gãy.

Nâng cấp từ 0.1.x — mọi chỗ gãy

Năm nhóm. Gần như tất cả đều ném lỗi ngay nên bạn thấy liền — trừ một dòng duy nhất: đơn vị của net_active_value đổi mà không có exception nào. Đó là dòng đáng đọc kỹ nhất trong cả trang.

1. Đổi tên

Bản 0.1.xBản 1.x
client.eod.marketclient.eod.index
client.reportingclient.financials
Module finlens.typesfinlens.typing

Cả ba đều ném AttributeError hoặc ModuleNotFoundError, nên không có kịch bản nào chạy tiếp với dữ liệu sai.

2. Đổi chữ ký

Bản 0.1.xBản 1.x
Tham số symbol (số ít)symbols; ở cấp ngành là icb
ohlcv("HPG", start, end, "1W")Chỉ tham số đầu tiên là positional, phần còn lại keyword-only
Ngành nhận tên tiếng Việt "Ngân hàng"Ngành nhận mã ICB dạng chuỗi: "8600"
statement(statement="balance", period="year")statement(kind="balance_sheet", period="annual")

Luật keyword-only là hàng rào cho một lớp bug đã xảy ra thật: ở 0.1.x một chuỗi interval truyền theo vị trí có thể rơi vào một tham số boolean, không lỗi và dữ liệu vẫn về.

3. Đổi từ vựng interval

interval="1M" và interval="1m" đều bị từ chối bằng finlens.InvalidIntervalError với mã FL_VALIDATION_INTERVAL_AMBIGUOUS. Trong từ vựng cũ hai giá trị này chỉ khác nhau ở chữ hoa nhưng lệch nhau 43.200 lần.

  • Dữ liệu cuối ngày: 1d · 1w · 1mo · 3mo · 6mo · 1y
  • Dữ liệu trong phiên: 1min · 5min · 15min · 30min · 1h · 4h

4. Đổi hình dạng dữ liệu — nhóm im lặng nhất

Bản 0.1.xBản 1.x
Cột Date, Open, Close, Volumesnake_case viết thường: date, open, close, volume
Bảng dòng tiền không có cột groupluôn có group, kể cả khi chỉ hỏi một nhóm
Kết quả rỗng có thể thiếu cộtFrame luôn đủ cột đúng dtype, kể cả 0 dòng
net_active_value gọi là "tỷ đồng"VND thô — xem mục 1.0.0

df["Date"] ném KeyError chắc chắn chứ không thỉnh thoảng: vì frame rỗng cũng đủ cột đúng dtype, không có đường nào để một tên cột viết hoa lọt qua.

Đơn vị của net_active_value là chỗ duy nhất không ném lỗi khi bạn đọc sai. Đọc df.attrs["finlens"]["units"] thay vì giả định.

5. Đổi môi trường

Bản 0.1.xBản 1.x
Python ≥ 3.10Python ≥ 3.11
pandas ≥ 2.1.2pandas ≥ 3.0
requestshttpx
pydantic là phụ thuộc bắt buộcđã gỡ — còn đúng ba: pandas, httpx, packaging
Khoá API không tiền tốKhoá mới có tiền tố flk_

Trên Python 3.10 hoặc cũ hơn, pip install finlens không báo lỗi — nó lặng lẽ lùi về một bản 0.1.x. Chi tiết ở Cài đặt.


1.4.0 — 11/08/2026

Bốn nhóm dữ liệu mới, và bốn chỗ sửa mà bạn thấy được ngay.

Thêm — client.funds.*: quỹ đầu tư

188 quỹ Việt Nam. NAV theo ngày từ 1995, danh mục nắm giữ theo tháng từ 2014, và phép tra ngược: mã này đang được quỹ nào nắm.

client.funds.list(fund_type="Quỹ ETF")        # 32 quỹ ETF
client.funds.nav("E1VFVN30", start="2026-07-01")
client.funds.holders("HPG")                    # 80 quỹ đang nắm HPG
client.funds.managers("E1VFVN30")

Mã quỹ không phải mã chứng khoán

Nó giữ nguyên hoa thường và chứa được cả dấu cách lẫn dấu + — A+ Fund, ACBC-AGF, CSOP FTSE VN. Gõ đúng như cột fund_code của funds.list().

Và nó không duy nhất: VVDIF thuộc hai tổ chức. Lời gọi sẽ ném finlens.AmbiguousFundError, và ngoại lệ ấy mang theo organization_ids để bạn chọn đúng quỹ.

Tiền tệ là VND thô, khác nghìn VND của giá cổ phiếu. Bốn cột _ratio nằm thang 0–1, không phải phần trăm.

Thêm — client.bonds.*: trái phiếu doanh nghiệp

6.820 lô trái phiếu của 940 tổ chức phát hành: điều khoản từng lô, dư nợ đang lưu hành, và hồ sơ tổ chức đã tổng hợp sẵn.

client.bonds.list(sector="Bất động sản", outstanding=True)
client.bonds.issuers(has_stock=True)

Cột tiền đi theo CẶP `_mvnd` / `_usd`

Có 23 lô phát hành bằng USD bên cạnh 6.795 lô bằng VNĐ, và mệnh giá 100.000 tồn tại ở cả hai tiền tệ. Nên mỗi cột tiền có hai bản — mỗi dòng đúng một cái khác null — thay vì một cột trần mà mean() trộn hai đơn vị lệch nhau 26.000 lần.

Thư viện không quy đổi USD sang VND: không có bảng tỷ giá nào trong nguồn.

Thêm — client.financials.notes(): thuyết minh BCTC và phân nhóm nợ

Chi tiết mà bốn báo cáo chính không mang: dư nợ chia theo năm nhóm chất lượng nợ theo Thông tư 11, cho vay theo ngành, tiền gửi theo loại, thu dịch vụ từng khoản. 22 nhóm trên 30 tổ chức tín dụng, 2006 → nay.

df = client.financials.notes("VCB", section="loan_quality", start_year=2025)
df[["code", "parent_code", "line_item", "value"]]

client.financials.note_sections()   # 22 nhóm, kèm độ phủ đo lúc gọi

code là số thứ tự in trong chính bản BCTC (4.1, 4.2) — nó khớp đúng dòng trong bản PDF bạn mở cạnh. parent_code đi kèm mỗi dòng nên dựng lại cây bằng một groupby.

Ba điều dễ đọc sai

  1. quarter = 0 là CẢ NĂM, không phải "thiếu quý". Lọc df["quarter"] > 0 là cách nhanh nhất để mất sạch số liệu năm, im lặng.
  2. Dòng cả năm bằng đúng quý 4 với số dư cân đối kế toán. Cộng bốn quý rồi cộng thêm dòng cả năm là cộng quý 4 hai lần.
  3. 30 mã có dữ liệu, nhưng 28 mã mới là ngân hàng. EVF và TIN là công ty tài chính.

Chỉ có báo cáo hợp nhất — nguồn không có báo cáo riêng lẻ.

Trang đầy đủ: Thuyết minh BCTC.

Thêm — năm cột cho client.meta.warrants()

Từ 5 lên 10 cột. Thứ quan trọng nhất là tỷ lệ chuyển đổi — mảnh còn thiếu để tính giá trị nội tại và điểm hoà vốn của một chứng quyền.

w = client.meta.warrants(underlying="HPG")
w[["symbol", "conversion_ratio", "exercise_price", "last_trading_date"]]

Năm cột mới: conversion_ratio · issue_price · shares_issued · listing_date · last_trading_date.

Hai điều quyết định nếu bạn dựng chiến lược

conversion_ratio không phải số nguyên. 393 trên 750 chứng quyền có tỷ lệ thập phân — 1.7245, 3.5704 — vì nó được điều chỉnh sau sự kiện quyền của mã cơ sở. Ép int() là mất thông tin ở hơn nửa danh mục.

last_trading_date sớm hơn maturity_date 2–7 ngày, ở mọi dòng. Canh theo ngày đáo hạn nghĩa là giữ một thứ không bán được trong tối đa một tuần cuối. maturity_date là ngày thực hiện quyền, không phải ngày giao dịch cuối.

Mọi chứng quyền Việt Nam hôm nay là quyền mua, kiểu Âu (chỉ thực hiện tại ngày đáo hạn), thanh toán tiền, niêm yết HOSE.

Sửa — một bảng ĐẦY ĐỦ tự khai là đã bị cắt

funds.nav() với nhiều quỹ lấy trọn dữ liệu rồi vẫn đặt df.attrs["finlens"]["truncated"] = True và phát cảnh báo "Kết quả đã bị cắt". Đo trên 100 quỹ: 129.457 trên 129.457 dòng — đầy đủ — mà vẫn báo bị cắt.

Nếu bạn từng chia nhỏ khoảng thời gian vì tin cảnh báo đó, giờ không cần nữa.

Sửa — mọi ngoại lệ đều mang một liên kết chết

Mỗi ngoại lệ kết bằng một doc_url trỏ tới một trang chưa bao giờ tồn tại, và nó nằm ở đúng chỗ dễ thấy nhất: cuối traceback. Giờ nó trỏ về trang xử lý lỗi có thật.

Sửa — thông báo lỗi tiếng Việt không còn nuốt mất chính ngoại lệ

Trên console Windows dùng bảng mã cũ, một thông báo lỗi có dấu có thể làm mất chính nội dung ngoại lệ. Giờ thông báo luôn tới được, kể cả khi terminal không đọc được dấu.

Sửa — cảnh báo độ trễ dữ liệu trong phiên giờ thật sự tới tay bạn

df.attrs["finlens"] thiếu hai kênh mà máy chủ vẫn gửi: source_updated_at và warnings. Hệ quả cụ thể: docstring của intraday.stock.ohlcv() hứa meta.warnings mang CAGG_LAG — cảnh báo dữ liệu trong phiên đang trễ — nhưng bạn không có cách nào đọc nó. Giờ có.

Gỡ — cột company_type khỏi client.financials.periods()

⚠️ Đây là thay đổi phá tương thích, dù cột ấy chưa bao giờ có giá trị: nó rỗng ở mọi dòng qua bốn bản phát hành. df["company_type"] trên frame của periods() nay ném KeyError.

Cần ánh xạ mã → loại doanh nghiệp thì dùng client.meta.symbols(). statement() và indicators() vẫn có company_type thật.


1.3.0 — 10/08/2026

Thêm — client.financials.indicators(): chỉ tiêu tài chính tính sẵn

P/E, ROE, NIM, nợ xấu, CAR và hàng chục chỉ tiêu khác, đã tính sẵn theo năm, theo quý hoặc theo bốn quý gần nhất (TTM) — không phải tự dựng từ khoản mục báo cáo.

client.financials.indicators("VCB", codes="roe,nim,npl_ratio", period="annual")
client.financials.indicator_catalog(com_type="NH")   # nhãn, đơn vị, công thức

indicator_catalog() là bảng tra: mỗi mã chỉ tiêu đo cái gì, bằng đơn vị nào, theo công thức nào. Gọi một lần rồi giữ lại, merge theo cặp (company_type, code) để gắn nhãn tiếng Việt vào frame.

Hai quy ước phải đọc trước khi dùng

Đơn vị nằm ở cột unit của TỪNG DÒNG, không phải ở df.attrs. Một frame chỉ tiêu mang nhiều đơn vị cùng lúc — roe là tỷ lệ, total_assets là VND, inventory_days là ngày.

unit = "ratio" là phân số, và nó KHÔNG bị chặn trong 0–1. roe = 0.16721 nghĩa là 16,721%. Nhưng cir = 1.827 của NVB kỳ 2024Q1 cũng là số thật — một ngân hàng có chi phí hoạt động vượt thu nhập. Đừng coi mọi giá trị lớn hơn 1 là "chắc đang ở thang phần trăm": phép chia 100 ấy làm hỏng đúng những kỳ đáng chú ý nhất.

Lọc kỳ bằng cột period_type, đừng lọc bằng quarter một mình. quarter = 2 xuất hiện ở cả một quý rời rạc lẫn một cửa sổ TTM kết thúc ở quý đó, và hai đại lượng ấy chênh nhau khoảng bốn lần.

Thêm — client.watchlist: danh mục theo dõi, đọc và GHI

Đây là nhóm phương thức duy nhất trong thư viện có ghi dữ liệu, và nó dùng chung với web: cùng một bảng, cùng một id. Sửa ở Python thì thấy trên finlens.vn, và ngược lại.

dm = client.watchlist.create("Ngân hàng")
client.watchlist.add(dm["id"], ["VCB", "TCB", "ACB"])

df = client.watchlist.symbols(dm["id"])
df[["symbol", "close", "return_1m", "return_1m_rank"]]

Ba cách làm mất dữ liệu

create() gọi hai lần với cùng một tên tạo HAI danh mục — trùng tên được cho phép. Đó cũng là lý do thư viện không bao giờ tự gửi lại một create() đã timeout; hãy list() để kiểm rồi tự quyết.

replace() XOÁ những mã không nằm trong danh sách bạn gửi. Muốn thêm mà không xoá gì thì dùng add().

delete() và remove() thành công vô điều kiện, kể cả khi không có gì để xoá — nên bạn không phân biệt được "vừa xoá xong" với "chưa từng tồn tại".

Mọi cột *_rank xếp hạng trong chính danh mục đó, không phải trong thị trường hay trong ngành.

Trang đầy đủ: Danh mục theo dõi.


1.2.0 — 10/08/2026

Không phương thức nào đổi chữ ký, không cột nào đổi tên, không đơn vị nào đổi nghĩa. Nhưng bản này không phải chỉ thêm, và hai chỗ dưới đây phải đọc trước khi nâng cấp:

  • ta-lib thành phụ thuộc bắt buộc — pip install -U finlens sẽ kéo về một gói mới.
  • Sàn macOS nâng lên 13.0 (Intel) và 14.0 (Apple Silicon) — trên macOS cũ hơn, lệnh cài thất bại ở nơi trước đó nó thành công.

Thêm — 135 hàm TA-Lib: finlens.ta và df.finlens.*

df = client.eod.stock.ohlcv(["HPG", "VCB"], start="2024-01-01")

df = df.finlens.rsi(14).finlens.macd()
df.finlens.patterns("doji")

hpg = df[df["symbol"] == "HPG"]["close"]     # tầng mảng: một mã một lần
finlens.ta.RSI(hpg, timeperiod=14)

74 chỉ báo và 61 mẫu nến, ở hai tầng: df.finlens.* nhận DataFrame và trả về bản sao kèm cột mới, còn finlens.ta.* nhận mảng và trả về mảng. Tầng thứ nhất có 73 chỉ báo — MAVP chỉ tồn tại ở tầng mảng vì nó cần một mảng chu kỳ theo từng thanh chứ không phải một cột giá.

⚠️ Trộn mã là cách hỏng mà tầng accessor sinh ra để chặn. Gọi thẳng talib.RSI(df["close"]) trên một frame nhiều mã cho cửa sổ đầu của mã sau ăn giá cuối của mã trước. Đo trên ba mã × 1.107 phiên: sai 738 dòng — 66,7%, tức mọi dòng của hai mã đứng sau, chỗ lệch lớn nhất 56,27 điểm, và mọi giá trị sai đều nằm trong khoảng 0–100 hợp lệ. df.finlens.rsi(14) tự tách theo cột symbol.

Tên hàm, tên tham số và giá trị mặc định giữ nguyên của TA-Lib, nên một đoạn code TA-Lib có sẵn chạy được sau khi đổi mỗi dòng import. Khác đúng ba chỗ, cả ba để chặn một cách hỏng im lặng: tham số là keyword-only (RSI(close, 14) ném TypeError, vì MACD có ba số nguyên liền nhau và STOCH có năm); mọi lỗi đều là finlens.FinLensError; và timeperiod=14.5 bị từ chối thay vì được cắt phần thập phân trong im lặng.

Tên cột mang theo tham số: sma_20 và sma_50 là hai cột chứ không đè lên nhau, macd() cho macd_12_26_9 · macdsignal_12_26_9 · macdhist_12_26_9. Đơn vị của cột mới đi vào df.attrs["finlens"]["units"] cùng chỗ với đơn vị cột từ máy chủ, và mỗi lời gọi để lại một mục ở df.attrs["finlens"]["ta"].

⚠️ 24 trên 74 chỉ báo có trạng thái không ổn định — đổi start= của lời gọi dữ liệu là đổi con số bạn nhận về, chứ không chỉ ở vùng warm-up. Trên 369 phiên của HPG, RSI(c)[200:] lệch RSI(c[200:]) tới 5,93 điểm, và phải tới phần tử thứ 96 chênh lệch mới xuống dưới 0,01. SMA thì không.

⚠️ Cột signal của patterns() không chỉ có ±100. Ba mẫu ra thêm ±80 và hai mẫu ra thêm ±200, nên df[df.signal == 100] âm thầm đánh rơi chúng — trên ba mã × 1.107 phiên là 81 trên 1.265 dòng tăng giá. Lọc bằng df.signal > 0, hoặc bằng cột direction đã suy sẵn.

⚠️ Warm-up biểu diễn khác nhau ở hai họ hàm. Chỉ báo ra NaN; mẫu nến ra số 0, không phân biệt được với "đã quét và không thấy gì". Nhóm ngắn hơn warm-up ra toàn NaN / 0 mà TA-Lib không báo lỗi; ở tầng accessor nó thành một DataQualityWarning. Một ô thiếu ở giữa chuỗi lan tới hết chuỗi.

Chi tiết ở Chỉ báo kỹ thuật.

Thêm — finlens.IndicatorError và bốn mã lỗi

IndicatorError kế thừa FinLensError, dành cho những gì TA-Lib từ chối tính. FL_TA_PARAM và FL_TA_INPUT ánh xạ về ValidationError — tức cũng là ValueError; FL_TA và FL_TA_UNAVAILABLE về IndicatorError.

Tách PARAM khỏi INPUT vì hai bệnh khác nhau và cách sửa khác nhau: timeperiod=14.5 là bạn gõ sai tham số, còn thiếu cột high là frame này không phải thứ chỉ báo đó cần — thông báo của mã sau liệt kê các cột đang có.

Đổi — ta-lib là phụ thuộc bắt buộc

Phụ thuộc lúc chạy từ ba lên bốn: pandas, httpx, packaging, ta-lib.

Bắt buộc chứ không phải extra tuỳ chọn: một pip install finlens[ta] sẽ làm df.finlens.rsi(14) là một AttributeError tuỳ máy, và ranh giới "tính năng nào cần extra nào" là thứ bạn phải học thuộc mà không có gì nhắc.

Đổi — sàn macOS: 13.0 cho Intel, 14.0 cho Apple Silicon

⚠️ Đây là thay đổi duy nhất trong bản này có thể làm pip install thất bại ở nơi trước đó nó thành công. Trên macOS cũ hơn hai mốc trên, lệnh cài báo no matching distribution ngay lúc resolve.

Con số bám theo wheel của ta-lib. Giữ sàn thấp hơn nghĩa là bạn cài được finlens rồi mới hỏng lúc import, vì không có wheel ta-lib nào khớp máy — một dòng báo lỗi lúc cài là câu trả lời thật thà hơn.

Sàn Linux và Windows không đổi.

Sửa — thiếu khoá API thì thông báo nói đã tìm ở đâu

Thông báo cũ nêu hai cách trong khi client thử bốn tầng. Nay nó liệt kê đủ bốn, theo đúng thứ tự thật, kèm trạng thái từng chỗ:

Thiếu API key. Đã tìm theo thứ tự:
  1. tham số  finlens.client(api_key=...)  — không truyền
  2. env      FINLENS_API_KEY              — không đặt
  3. file     D:\proj\finlens.toml         — có file, nhưng không có `api_key`
  4. file     C:\...\finlens\config.toml   — KHÔNG ĐƯỢC ĐỌC — file ở trên đã che nó

Dòng thứ tư là dòng đáng giá nhất. File cấu hình đọc theo lối first-match-wins, không gộp: file đầu tiên tồn tại là file duy nhất được đọc. Một ./finlens.toml chỉ chứa timeout che hoàn toàn cấu hình cấp máy, và triệu chứng bạn gặp là "tôi đặt api_key trong config máy rồi mà vẫn báo thiếu".


1.1.1 — 06/08/2026

Bản này chỉ thêm. Không phương thức nào đổi chữ ký, không cột nào đổi tên, không đơn vị nào đổi nghĩa — code đang chạy trên 1.1.0 chạy nguyên trạng.

Năm nhóm dữ liệu mới. Bốn nhóm đầu nằm ở những chỗ dễ đọc nhầm thành một bảng đã có; nhóm thứ năm — vĩ mô — lại phá ba giả định nền của thư viện, nên nó đứng đầu danh sách dưới đây. Các cảnh báo đáng đọc trước khi gõ dòng đầu tiên.

Thêm — client.macro.*: dữ liệu vĩ mô

ds = client.macro.indicators(topic="cpi", freq="monthly")
df = client.macro.series(ds["code"].tolist()[:5])

Bốn phương thức: indicators() · series() · omo() · trade(). Nguồn là Tổng cục Thống kê và Ngân hàng Nhà nước; 3.348 chuỗi chỉ tiêu.

⚠️ Đây là namespace đầu tiên mà unit là một CỘT chứ không phải thuộc tính của cả bảng. Mọi namespace trước đó không bao giờ trộn đơn vị vì mỗi loại tài sản nằm ở một namespace riêng; vĩ mô không có ranh giới nào như thế, nên series(["gia_vang_giao_ngay_daily", "ty_gia_trung_tam_daily"]) trả về USD/Ounce nằm cạnh VND trong cùng cột value. Đọc unit theo từng dòng; df.attrs["finlens"]["units"] không trả lời được câu hỏi đó.

⚠️ Cột date là CUỐI KỲ QUAN SÁT, không phải một phiên giao dịch — loại cột thời gian thứ ba của thư viện. Nó đi kèm cột period mang nhãn kỳ ("7-2026", "Q1-2026") vì 2026-07-31 một mình không nói được đây là số của tháng 7.

⚠️ Mã chỉ tiêu phải TRA, không được dựng từ tên. Nó là một bản đồ tra cứu chứ không phải một hàm: nguồn đổi tên thì mã giữ nguyên, tên trùng nhau được khử bằng hậu tố thứ tự, và bảng ánh xạ thì sửa tay được. Chiều ngược lại thì an toàn: mã được cấp một lần rồi thôi, lưu lại thoải mái.

Chi tiết ở Dữ liệu vĩ mô.

Thêm — supply_demand(): khối lượng và số lệnh ĐẶT vào sổ

client.eod.stock.supply_demand("HPG", start="2026-08-01")

Sáu cột số: buy_order_volume · sell_order_volume · net_order_volume · buy_order_count · sell_order_count · net_order_count.

⚠️ Đây là lệnh ĐẶT, không phải lệnh đã khớp. Trung vị của tỷ lệ buy_order_volume / volume — cùng mã, cùng phiên, volume lấy từ ohlcv() — là 2,756 lần; 99,77% số dòng có khối lượng đặt lớn hơn hoặc bằng khối lượng khớp. Đó là lý do mọi tên cột mang chữ _order_: trừ một cột ở đây cho volume của ohlcv() là so hai đại lượng chênh nhau khoảng ba lần, và không có exception nào chặn.

Ba cột _count dùng Int64 viết hoa và mang được giá trị thiếu — máy chủ phát giá trị thiếu ở khoảng 11% số dòng. Kiểm bằng .isna(), đừng so với 0. Khi gộp kỳ, một phiên thiếu số lệnh làm cả ba cột _count của kỳ đó thiếu theo.

Chỉ có ở client.eod.stock. Chỉ số, ngành, chứng quyền và phái sinh không có dữ liệu sổ lệnh nào, nên client.eod.warrant.supply_demand là AttributeError chứ không phải một bảng rỗng.

Thêm — active_volume(): khối lượng khớp lệnh chủ động cuối ngày

client.eod.stock.active_volume("HPG")
client.eod.derivative.active_volume("VN30F1M")

Ba cột: active_buy_volume · active_sell_volume · net_active_volume.

⚠️ Không phải client.intraday.*.net_active_value(), và hai frame không cộng được với nhau. Cùng một phiên VN30F1M, ba cột tương ứng lệch nhau 57,5% · 520% · 30,1%. Chúng khác ở cả ba chiều: số rổ (hai ở đây, ba ở kia vì kia tách riêng phần khớp lệnh định kỳ), đơn vị (khối lượng ở đây và không có cột tiền nào; VND ở kia), và nguồn. Phép cộng hai frame không ném exception nào, chỉ cho ra một con số sai.

Phần khớp lệnh định kỳ không biến mất — nó bị trộn vào: active_buy_volume + active_sell_volume bằng đúng khối lượng khớp cả phiên.

Đơn vị khối lượng đổi theo namespace — cổ phiếu ở client.eod.stock, hợp đồng ở client.eod.derivative — trong khi bộ tên cột giống hệt nhau. Đọc df.attrs["finlens"]["units"].

Thêm — basis(): chênh lệch phái sinh và chỉ số cơ sở

client.eod.derivative.basis("VN30F1M")        # theo phiên
client.intraday.derivative.basis("VN30F1M")   # bước 1 phút

Bảy cột: symbol · index_symbol · date (hoặc time) · future_close · spot_close · basis · basis_pct.

basis     = future_close - spot_close     # điểm chỉ số
basis_pct = basis / spot_close * 100      # phần trăm, thang 0–100

⚠️ basis_pct là cột _pct đầu tiên của thư viện. Mẫu số là spot_close chứ không phải giá hợp đồng, và thang là 0–100 chứ không phải 0–1. Hai mẫu số chỉ lệch nhau khoảng 0,3% nên chọn nhầm gần như không nhìn ra được trên một biểu đồ — đó là lý do công thức được viết thẳng vào tài liệu thay vì để bạn suy.

Không phương thức nào trong cặp này có interval, kể cả bản theo phiên: gộp một chênh lệch qua nhiều bước không có nghĩa hiển nhiên nào — trung bình, giá trị cuối kỳ và biên độ là ba câu trả lời khác nhau. Dùng df.resample(...) nếu bạn cần, ở đó bạn tự chọn.

index_symbol là một cột thật, không phải một chi tiết trong df.attrs: attrs biến mất ngay khi bạn pd.concat hai frame.

Bản trong phiên bắt đầu từ 09:15, không phải 09:00 — trước 09:15 chỉ số chưa được tính theo từng phút, còn bar 09:00 là kết quả khớp lệnh định kỳ ATO, một cơ chế hình thành giá khác hẳn các bar liên tục phía sau.

Thêm — dòng tiền nhà đầu tư ở cấp hợp đồng phái sinh

client.eod.derivative.investor.flow("VN30F1M")
client.eod.derivative.investor.breakdown("VN30F1M")

Cùng bộ chín cột với client.eod.stock.investor.*, nên hai bảng ghép được với nhau. Khác đúng hai chỗ, và cả hai nằm trong kiểu của tham số:

  • Chỉ foreign và proprietary. Bốn nhóm chi tiết nhận 400 — nguồn của chúng khoá theo danh mục doanh nghiệp niêm yết, nơi không hợp đồng phái sinh nào có mặt. DerivativeInvestorGroup thu hẹp Literal để lỗi đó thành một gạch đỏ lúc gõ code, và breakdown() bỏ trống groups hỏi hai nhóm chứ không phải sáu như ở cấp mã.
  • Khối lượng tính bằng hợp đồng, không phải cổ phiếu.

⚠️ Đây là hợp đồng KHỚP ròng và giá trị DANH NGHĨA. net_volume là chênh lệch hợp đồng đã khớp trong kỳ, không phải vị thế mở — cộng dồn nó qua nhiều phiên không cho ra vị thế tích luỹ. Các cột *_value là giá trị danh nghĩa của số hợp đồng đó, đã quy đổi ra VND thô bằng hệ số hợp đồng ở máy chủ, không phải số tiền thực bỏ ra. Tự nhân net_volume với giá đóng cửa mà quên hệ số hợp đồng là lệch 100.000 lần, và phép nhân đó vẫn chạy.

Thêm — mã lỗi FL_DATA_NO_UNDERLYING

Ánh xạ về finlens.NoDataError. Nó đi trong phần lỗi theo từng mã, nên một hợp đồng không có chuỗi chỉ số cơ sở không làm hỏng cả lời gọi basis().

Mã riêng chứ không dùng lại FL_DATA_EMPTY vì hai bệnh khác nhau: FL_DATA_EMPTY là khoảng bạn hỏi không có dòng nào, còn mã này là hợp đồng này không có chỉ số cơ sở trong dữ liệu, tức không đổi start/end nào cứu được. Nó cũng cố ý không phải InvalidSymbolError — mã bạn gõ hoàn toàn đúng, và bảo người dùng đi kiểm tra một mã đã đúng là một chẩn đoán sai.

Thêm — hai kiểu công khai

finlens.DerivativeInvestorGroup và finlens.DerivativeInvestorGroupsArg.

Alias riêng chứ không dùng lại SectorInvestorGroup dù trùng tập giá trị: hai chỗ hẹp lại vì hai lý do khác nhau — ở cấp ngành là một nguồn thực chất chỉ có sàn HOSE, còn ở đây là một danh mục không chứa hợp đồng phái sinh — và hai lý do đó có thể đổi độc lập.


1.1.0 — 05/08/2026

Đã đổi — báo cáo tài chính dùng item_id, và cột field bị bỏ

client.financials.statement() và client.financials.line_items() nay trả item_id thay cho line_item_id, và không còn cột field. Cột parent_id mang item_id của khoản mục cha; level và order_index không đổi.

df = client.financials.statement("HPG", kind="balance_sheet")
df["item_id"]        # trước là df["line_item_id"] — tên cũ ném KeyError
df["field"]          # KeyError: cột này không còn

item_id là mã hiển thị do máy chủ cấp, ổn định theo thời gian, và chỉ có nghĩa trong phạm vi một (company_type, kind) — cùng một con số ở hai loại hình doanh nghiệp là hai khoản mục khác hẳn nhau. Nó không phải định danh của hệ thống nguồn, nên đừng dùng nó để tra ngược sang nơi khác.

Bản 1.0.0 và 1.0.1 gãy ở statement()

Máy chủ đã chuyển sang bộ cột mới. Nếu bạn đang chạy 1.0.0 hoặc 1.0.1, client.financials.statement() ném TypeError — không phải lỗi của finlens nên thông báo không nhắc gì tới phiên bản. Chữa bằng pip install -U finlens. Các nhóm dữ liệu khác (eod, intraday, meta) vẫn chạy bình thường ở bản cũ.

Đã đổi — cột date của một kỳ đã gộp nhóm là phiên cuối, không phải đầu kỳ

Với interval có gộp nhóm (1w · 1mo · 3mo · 6mo · 1y), cột date nay mang phiên giao dịch cuối cùng thực sự có dữ liệu trong kỳ. Trước bản này nó mang mốc đầu kỳ. interval="1d" không đổi — nó vốn đã là ngày phiên.

client.eod.stock.ohlcv("HPG", start="2023-01-01", end="2023-12-31", interval="1mo")
# trước: 2023-01-01, 2023-02-01, 2023-03-01, 2023-04-01, ...
# nay  : 2023-01-31, 2023-02-28, 2023-03-31, 2023-04-28, ...

Số dòng không đổi — bề rộng kỳ giữ nguyên, chỉ cái nhãn đổi.

Ba lý do, theo thứ tự quan trọng:

  • close của dòng đó là giá đóng cửa của phiên mang ngày đó. Trước đây ngày và giá kể hai chuyện khác nhau: dòng 1mo dán nhãn 2023-01-01 nhưng close lấy từ phiên cuối tháng.
  • Kỳ đang dở không mang mốc tương lai. Nếu dùng ngày cuối theo lịch thì 1y của năm nay sẽ là 2026-12-31, một mốc tương lai nằm giữa chuỗi dữ liệu, và df[df["date"] <= hôm_nay] sẽ âm thầm đánh rơi kỳ hiện tại.
  • Ngày trả về luôn là một phiên có thật, không phải một mốc tính ra. Lấy ngày cuối theo lịch thì 39% số kỳ 1mo sẽ mang một ngày thị trường đóng cửa — đo trên dữ liệu thật 2020–2026. Tháng 1/2025 nghỉ Tết là ví dụ rõ nhất: phiên cuối là 24/01 trong khi lịch nói 31/01.

Áp dụng cho client.eod.*.ohlcv(), client.eod.sector.ohlcv() và client.eod.*.investor.flow(). Với dòng tiền, mọi nhóm nhà đầu tư trong cùng một kỳ nhận cùng một date, kể cả khi các nguồn cập nhật lệch nhau một phiên.

Nếu bạn đang lưu kết quả đã resample: ngày cũ và ngày mới không khớp nhau, nên mọi phép join theo date với dữ liệu đã lưu sẽ trượt. Lấy lại dữ liệu thay vì ghép hai bên.


1.0.1 — 05/08/2026

Mã của thư viện không đổi một dòng nào so với 1.0.0. Nếu bạn đang dùng 1.0.0 thì không có lý do gì phải nâng cấp — bản này chỉ sửa trang giới thiệu trên PyPI.

Điều đó kiểm chứng được, không phải lời hứa: so từng byte hai wheel cp312-manylinux của 1.0.0 và 1.0.1 thì cả 15 module đã biên dịch đều giống hệt nhau; chỉ bốn file đổi, và cả bốn đều là siêu dữ liệu (METADATA, RECORD, _version.py, _build_info.py).

Đã sửa — bốn liên kết trên trang PyPI trả về 404

README.md là phần mô tả của gói, và PyPI hiển thị nó một mình, không có cây thư mục nào bên cạnh để một đường dẫn tương đối trỏ tới. Bốn liên kết vì vậy hỏng ngay trên trang của 1.0.0: huy hiệu giấy phép, hai liên kết CHANGELOG.md, và liên kết LICENSE. Nay chúng trỏ về chính trang bạn đang đọc và về toàn văn giấy phép MIT.

PyPI không cho tải lại một phiên bản đã phát hành. Trang của 1.0.0 vì thế giữ nguyên bốn liên kết hỏng vĩnh viễn — đó là toàn bộ lý do 1.0.1 tồn tại.


1.0.0 — 05/08/2026

Bản chính thức đầu tiên trên PyPI. Cài bằng pip install finlens.

pip install finlens

Gom toàn bộ bề mặt đã thử nghiệm ở các bản alpha, cộng một thay đổi về cache mô tả ngay dưới đây.

Đã sửa — dữ liệu cuối ngày của phiên vừa đóng không còn bị đóng băng

Bảng cuối ngày được cập nhật liên tục trong phiên, 08:45–15:00, và nguồn còn chạm vào dòng của ngày đó một lát sau khi đóng cửa: chốt giá ATC, tính lại hệ số điều chỉnh, bổ sung số khối ngoại.

Trước bản này, từ đúng 15:00 kết quả bị giữ trong bộ nhớ đệm bảy ngày — một bản chụp giữa chừng bị ghim suốt cả tuần, không ngoại lệ, không cảnh báo.

Bạn gọi lúcend bỏ trốngend là hôm nayend là năm cũ
08:45 – 14:5960 giây60 giây7 ngày
từ 15:0060 giây30 phút7 ngày

Điều kiện hẹp ở "khoảng chạm phiên vừa đóng", không phải "mọi khoảng đã chốt": một truy vấn năm 2024 đã bất biến thật, cho nó hết hạn sau 30 phút chỉ bắt bạn gọi lại cùng một thứ mà không đổi lấy gì.

Không cần làm gì để nhận thay đổi này — nó nằm trong logic chọn thời gian sống của bộ nhớ đệm. Muốn bỏ qua đệm ở một lời gọi bất kỳ thì thêm refresh=True.

Bề mặt đầy đủ của 1.0.0

client.eod.stock.ohlcv("HPG", start="2026-01-01", adjusted=False)
client.eod.sector.ohlcv("8355")
client.eod.stock.investor.breakdown("HPG")
client.intraday.stock.ticks("HPG", date="2026-08-03")
client.intraday.stock.net_active_value("HPG", interval="1h")
client.financials.statement("HPG", kind="balance_sheet", period="quarterly")
client.meta.symbols(exchange="HOSE", icb="8300")

Chi tiết từng nhóm nằm ở các mục alpha bên dưới, hoặc ở trang tham chiếu tương ứng trong mục lục bên trái.


1.0.0a7 — 04/08/2026

Bốn bề mặt mới.

Thêm — dữ liệu trong phiên

client.intraday.stock.ohlcv("HPG", interval="5min")
client.intraday.stock.ticks("HPG", date="2026-08-03")
client.intraday.stock.net_active_value("HPG")
  • Cột thời gian tên time và mang múi giờ Asia/Ho_Chi_Minh, khác cột date naive của EOD. Bỏ múi giờ thì pandas đoán UTC và 09:15 giờ Việt Nam bị đọc lệch bảy tiếng — ra ngoài cả phiên giao dịch.
  • Giá trong phiên là giá thô, không điều chỉnh quyền — ngược với client.eod.stock. Nối hai chuỗi mà không đọc df.attrs["finlens"]["price_basis"] sẽ cho một bậc thang giả ở mỗi lần chia tách quyền.
  • ticks() nhận một mã, một phiên mỗi lời gọi, và giới hạn đó nằm trong chữ ký — tham số đầu tên symbol số ít, không phải symbols.
  • client.intraday.index không có net_active_value(): dữ liệu tick của chỉ số không ghi chiều lệnh chủ động. Một sự vắng mặt trong dữ liệu là một sự vắng mặt trong API.
  • Cột side có ba giá trị chứ không phải hai: buy · sell · auction.

Chi tiết ở Dữ liệu trong phiên.

Đã sửa — net_active_value từng có ba đơn vị cho một tên cột

Đây là thay đổi phá vỡ tương thích nặng nhất của bản này, và nó nằm trong chính con số chứ không chỉ ở tên hàm:

Bản 0.1.xBản 1.x
Cổ phiếuchia cho 10⁶VND thô
Phái sinhkhông quy đổi hệ số hợp đồngVND thô, đã nhân hệ số hợp đồng
Tài liệugọi là "tỷ đồng"df.attrs["finlens"]["units"] khai rõ từng cột

Với cổ phiếu, con số mới lớn hơn con số cũ 10⁶ lần. Với phái sinh, con số cũ vốn không thuộc đơn vị nào nên không quy đổi được — đọc lại từ đầu.

Mã phái sinh chưa có hệ số hợp đồng xác nhận thì các cột *_value trả null kèm cảnh báo, chứ không phải một con số đoán; các cột *_volume vẫn trả bình thường.

Thêm — báo cáo tài chính

client.financials.statement("HPG,VCB,SSI", kind="income_statement",
                            period="quarterly", start_year=2023)
client.financials.line_items(com_type="NH", kind="balance_sheet")
client.financials.periods("HPG")

Frame ở dạng long và mỗi dòng mang company_type của chính nó, nên một lời gọi được phép trộn nhiều loại hình doanh nghiệp. Ràng buộc "mỗi lần một loại" của 0.1.x là do client tự áp.

Chi tiết ở Báo cáo tài chính.

Thêm — tra cứu danh mục

client.meta.symbols(exchange="HOSE", icb="8300")
client.meta.sectors(level=2)
client.meta.warrants(underlying="HPG")

Câu hỏi mở đầu một phiên làm việc không phải "giá HPG hôm qua" mà là "có những mã nào". Trước bản này thư viện không trả lời được.

  • Một tham số icb= nhận cả mã cấp 2 lẫn mã cấp 4 — bạn không phải biết trước mã mình cầm thuộc cấp nào.
  • Chứng chỉ quỹ nằm cùng bảng với cổ phiếu, phân biệt bằng cột kind.
  • exchange nhận "HOSE" · "HNX" · "UPCOM".

Chi tiết ở Tra cứu danh mục.

Thêm — chỉ số ngành ICB

client.eod.sector.ohlcv("8600") trả chuỗi giá của chỉ số ngành, đơn vị điểm chỉ số. Namespace client.eod.sector trước đó chỉ có investor.flow().

⚠️ Hai phương thức của namespace này khác thứ nguyên nhau: ohlcv() là điểm chỉ số, investor.flow() là VND thô.

Thêm — adjusted cho eod.*.ohlcv()

client.eod.stock.ohlcv("VCB", adjusted=False)     # giá khớp lệnh thật

Mặc định vẫn là True, nên không lời gọi nào đang chạy bị đổi kết quả. Cơ sở giá đọc ở df.attrs["finlens"]["price_basis"].

⚠️ client.intraday.*.ohlcv() không có tham số này — giá trong phiên vốn đã là giá thô. Bất đối xứng có chủ đích, không phải một chỗ quên.


1.0.0a6 — 04/08/2026

Đã sửa — lỗi chứng chỉ TLS không còn bị báo cáo thành lỗi máy chủ

Trên máy nằm sau proxy kiểm tra TLS, bản trước báo "backend đang không phản hồi" trong khi máy chủ hoàn toàn khoẻ — thông báo ấy gửi người đọc đi kiểm đúng chỗ không có vấn đề gì. Lỗi chứng chỉ còn bị thử lại ba lần, dù một chứng chỉ không tin được sẽ không trở nên đáng tin sau hai giây.

Nay lỗi chứng chỉ được nhận là vĩnh viễn: không thử lại, và có lớp riêng finlens.TlsVerificationError — kế thừa ConnectionFailedError nên code đang bắt lớp cha vẫn chạy y nguyên. Thông báo chỉ thẳng đường sửa: finlens.client(ca_bundle=...) hoặc biến môi trường FINLENS_CA_BUNDLE.

Đã sửa — finlens.build_info()["built_at"] trả cùng một mốc cho mọi bản

Trường này trả một dấu thời gian cố định cho mọi bản build. Với một gói mà bạn không đọc được mã nguồn, câu hỏi đầu tiên khi báo lỗi là "đang chạy binary nào, build lúc nào" — và trường đó đang trả lời sai cho tất cả mọi người. Nay nó là mốc thật của bản build.


1.0.0a5 — 04/08/2026

Thêm — investor.flow() và investor.breakdown()

client.eod.stock.investor.flow("HPG", group="foreign")
client.eod.stock.investor.breakdown(["HPG", "VCB"])
client.eod.sector.investor.flow("8600")
client.eod.index.investor.flow("VNINDEX")

flow() hỏi một nhóm, breakdown() hỏi nhiều nhóm — cùng một schema, nên hai bảng ghép được với nhau mà không phải đổi hình.

Đã sửa — sáu nhóm nhà đầu tư không cùng một định nghĩa

Đây là chỗ sửa lỗi ngữ nghĩa nặng nhất so với 0.1.x. Tài liệu cũ liệt kê sáu nhóm phẳng như sáu thứ ngang hàng. Thực tế:

  • 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 đủ — tổng net_value của chúng bằng 0.
  • foreign lấy cả giao dịch thoả thuận, nên nó không bằng foreign_individual + foreign_institutional.
  • proprietary (tự doanh) đến từ một vũ trụ dữ liệu khác và không phải nhóm con của local_institutional, dù trực giác nói ngược lại.

Cộng cả sáu nhóm cho ra một con số vô nghĩa, và 0.1.x không có gì ngăn điều đó. Cột group nay luôn có mặt, kể cả khi lời gọi chỉ hỏi một nhóm — đó là chỗ duy nhất mang được "con số này tính trên cơ sở nào" ở mức từng dòng.

Ở cấp ngành, kiểu SectorInvestorGroup chỉ nhận "foreign" và "proprietary", nên xin bốn nhóm chi tiết ở đó bị chặn ngay lúc gõ code.

Đã sửa — response sai hình dạng ném lỗi thay vì trả bảng rỗng

Tới bản trước, một response mà client không hiểu được có thể trở thành một bảng rỗng hoặc vài dòng bịa, im lặng cả hai trường hợp. Bảng rỗng là kịch bản xấu nhất của cả dự án: người dùng đọc "khối ngoại mua ròng 0 đồng" trong khi thật ra client không đọc được dữ liệu. Nay nó ném finlens.SchemaMismatchError với mã FL_DATA_SCHEMA.


1.0.0a4 — 03/08/2026

Thay đổi phá vỡ tương thích — Python và pandas

Yêu cầu pandas >= 3.0 và Python >= 3.11 (trước đó là pandas >= 2.1.2 và Python >= 3.10).

Hai dòng pandas khác nhau ở những chỗ im lặng: cùng một chuỗi ngày cho ra datetime64[us] trên pandas 3.0 nhưng datetime64[ns] trên 2.x — nên một frame rỗng và một frame có dữ liệu mang dtype khác nhau tuỳ phiên bản pandas của người dùng. Python 3.11 là hệ quả kéo theo: pandas 3.0 chỉ có wheel cho CPython 3.11 đến 3.14.

Wheel Linux đòi glibc ≥ 2.28 (Debian 10 / RHEL 8 / Ubuntu 18.10 trở lên) — chính ràng buộc mà pandas đặt ra, không phải một lựa chọn riêng.


1.0.0a1 — 03/08/2026

Bản alpha đầu tiên, chỉ phát hành trên TestPyPI. Có client.eod.stock, client.eod.index, client.eod.derivative, client.eod.warrant.

Toàn bộ nền tảng của dòng 1.x xuất hiện ở bản này:

  • finlens.client() và finlens.AsyncClient — bản async có cùng chữ ký với bản đồng bộ.
  • Tạo client không gọi mạng. Đặt finlens.client() ở cell đầu notebook luôn an toàn; muốn kiểm khoá ngay thì gọi client.connect().
  • Cấu hình bốn tầng: tham số truyền vào > biến môi trường FINLENS_* > finlens.toml > mặc định.
  • Cây ngoại lệ hoàn chỉnh gốc ở finlens.FinLensError, kèm mã lỗi FL_* ổn định và bảy lớp cảnh báo tắt được chọn lọc.
  • finlens.PartialFetchError mang theo dữ liệu đã lấy được ở .data — không bao giờ mất 49 mã thành công vì 1 mã hỏng.
  • finlens.ValidationError kế thừa đôi từ ValueError, nên code cũ viết except ValueError: vẫn chạy sau khi nâng cấp.
  • ca_bundle cho môi trường có proxy kiểm tra TLS. Cố ý không có tuỳ chọn tắt kiểm chứng chỉ.

Đã sửa so với 0.1.11

  • CompanyTypeMismatchError nằm ngoài cây FinLensError. Nay nó là finlens.CompanyTypeMismatchError(ValidationError), tức vừa trong cây vừa vẫn là ValueError. Hệ quả: except finlens.FinLensError giờ là đủ — nếu code của bạn còn một nhánh except RuntimeError viết theo hướng dẫn cũ, nhánh đó nay là code chết.
  • Cây ngoại lệ đảo chiều phụ thuộc, nguyên nhân khiến except FinLensError: ở 0.1.x gần như không bắt được gì.
  • So sánh phiên bản bằng chuỗi: "0.1.9" < "0.1.11" là False, nên cổng kiểm tra hỗ trợ đã chết đúng ở vùng phiên bản đang dùng.
  • finlens.__version__ báo sai. 0.1.x fallback về "0.0.0" ở mọi bản chưa cài đầy đủ.

Dòng 0.1.x — đã đóng băng

Bản phát hành cuối là 0.1.11 (09/01/2026). Dòng này không còn được cập nhật và không được tài liệu này mô tả — mọi trang tham chiếu ở đây nói về 1.x.

Nếu bạn đang đọc một tài liệu, một bài viết hay một câu trả lời của trợ lý AI mà nó dùng clients. (số nhiều), client.eod.market, client.reporting, cột Date viết hoa hay interval="1D", thì đó là nội dung của dòng 0.1.x — không lời gọi nào trong số đó chạy được trên bản 1.x.

Từng chỗ gãy nằm ở bảng Nâng cấp từ 0.1.x đầu trang.

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

Phiên bản mới nhất của thư viện Python finlens là bản nào?

Bản mới nhất là **1.2.0**, phát hành ngày 10/08/2026 trên PyPI. Cài bằng `pip install finlens` và kiểm bằng `finlens.__version__` — chuỗi trả về phải bắt đầu bằng `1.`. Nếu nó vẫn là `0.1.x` thì máy đang chạy Python 3.10 hoặc cũ hơn: bản 1.x khai `requires-python >= 3.11` nên pip lặng lẽ lùi về dòng cũ thay vì báo lỗi. Các bản `1.0.0a1` đến `1.0.0a7` chỉ tồn tại trên TestPyPI và chưa bao giờ có trên PyPI.

Nâng finlens từ 0.1.x lên 1.x thì đoạn code nào bị gãy?

Bốn nhóm. Một, đổi tên: `client.eod.market` thành `client.eod.index`, `client.reporting` thành `client.financials`, `finlens.types` thành `finlens.typing`. Hai, tên cột nay viết thường theo snake_case nên `df["Date"]` ném `KeyError`, phải đổi thành `df["date"]`. Ba, `interval="1M"` bị từ chối — dùng `"1mo"` cho dữ liệu cuối ngày và `"1min"` cho dữ liệu trong phiên. Bốn, chữ ký: tham số tên `symbols` (cấp ngành là `icb`) và chỉ tham số đầu tiên là positional, nên `ohlcv("HPG", "2026-01-01", "1w")` ném `TypeError`.

Vì sao client.eod.market báo AttributeError?

Vì ở bản 1.x của thư viện Python finlens, namespace chỉ số đã đổi tên thành `client.eod.index`. Gọi `client.eod.index.ohlcv("VNINDEX")` thay cho `client.eod.market.ohlcv("VNINDEX")`. Cùng đợt đổi tên đó, `client.reporting` thành `client.financials`, và bản 1.x thêm hai namespace giá cuối ngày chưa từng có: `client.eod.derivative` cho hợp đồng phái sinh và `client.eod.warrant` cho chứng quyền có bảo đảm. Namespace `client.eod.sector` nay có thêm `ohlcv()` trả chỉ số ngành ICB, tính bằng điểm chỉ số.

Vì sao df['Date'] báo KeyError khi đọc dữ liệu chứng khoán bằng Python?

Vì tên cột đã đổi sang snake_case viết thường, không có ngoại lệ nào. Trong thư viện Python finlens bản 1.x, bảy cột của `ohlcv()` là `symbol`, `date`, `open`, `high`, `low`, `close`, `volume`; cấp ngành dùng `icb`, `icb_name`, `icb_level` thay cho cột mã cũ. Đây là `KeyError` chắc chắn chứ không phải thỉnh thoảng, vì bản 1.x bảo đảm frame luôn đủ cột đúng dtype kể cả khi không có dòng nào — không có đường nào để một tên cột viết hoa lọt qua.

interval='1M' báo lỗi ở bản mới, phải thay bằng gì?

Thay bằng `"1mo"` nếu bạn muốn một tháng, hoặc `"1min"` nếu bạn muốn một phút. Trong từ vựng cũ `1M` là một tháng còn `1m` là một phút, chỉ khác nhau ở chữ hoa nhưng lệch nhau 43.200 lần, nên thư viện Python finlens từ chối thẳng cả hai bằng `finlens.InvalidIntervalError` với mã `FL_VALIDATION_INTERVAL_AMBIGUOUS` kèm gợi ý thay vì đoán ý. Dữ liệu cuối ngày nhận `1d`, `1w`, `1mo`, `3mo`, `6mo`, `1y`; dữ liệu trong phiên nhận `1min`, `5min`, `15min`, `30min`, `1h`, `4h`.

Bản 1.x của finlens thêm những nhóm dữ liệu nào so với 0.1.x?

Bốn bề mặt mới. Dữ liệu trong phiên: `client.intraday.stock` và `client.intraday.derivative` với `ohlcv()`, `ticks()` (từng lệnh đã khớp) và `net_active_value()`, còn `client.intraday.index` có `ohlcv()` và `ticks()`. Báo cáo tài chính: `client.financials.statement()`, `periods()`, `line_items()`. Tra cứu danh mục: `client.meta.symbols()`, `sectors()`, `warrants()`. Ở giá cuối ngày có thêm hai namespace `client.eod.derivative` và `client.eod.warrant`, còn `client.eod.sector` được bổ sung `ohlcv()` trả chỉ số ngành ICB. Kèm theo là tham số `adjusted`, `refresh`, `on_error` và siêu dữ liệu `df.attrs["finlens"]` mang đơn vị từng cột.

Bản 1.1.1 của thư viện Python finlens thêm những gì?

Bản 1.1.1 chỉ thêm, không phương thức nào đổi chữ ký và không cột nào đổi tên. Năm nhóm dữ liệu mới: `client.eod.stock.supply_demand()` trả khối lượng và số lệnh ĐẶT vào sổ — là lệnh đặt chứ không phải lệnh đã khớp, trung vị tỷ lệ trên khối lượng khớp là 2,756 lần; `client.eod.stock.active_volume()` cùng `client.eod.derivative.active_volume()` trả khối lượng khớp chủ động hai rổ và không có cột tiền, nên không cộng được với `client.intraday.*.net_active_value()` vốn có ba rổ tính bằng VND; `client.eod.derivative.basis()` cùng `client.intraday.derivative.basis()` trả chênh lệch hợp đồng tương lai với chỉ số cơ sở, cả hai đều không có tham số `interval`; và `client.eod.derivative.investor.flow()` cùng `breakdown()` trả dòng tiền cấp hợp đồng phái sinh với đúng hai nhóm `foreign` và `proprietary`. và `client.macro.*` với bốn phương thức `indicators()`, `series()`, `omo()`, `trade()` trả dữ liệu vĩ mô từ Tổng cục Thống kê và Ngân hàng Nhà nước — 3.348 chuỗi chỉ tiêu, nghiệp vụ thị trường mở và số liệu xuất nhập khẩu, và là namespace đầu tiên mà đơn vị nằm ở một cột chứ không phải thuộc tính của cả bảng. Kèm mã lỗi `FL_DATA_NO_UNDERLYING` và các kiểu `DerivativeInvestorGroup`, `DerivativeInvestorGroupsArg`, `MacroCodesArg`, `MacroFreqArg`, `MacroTopicArg`, `OmoKindArg`, `OmoFreqArg`, `TradeFlowArg`, `TradeBreakdownArg`, `TradeFreqArg`.

Bản 1.2.0 của thư viện Python finlens thêm những gì?

Bản 1.2.0 thêm 135 hàm TA-Lib — 74 chỉ báo và 61 mẫu nến — ở hai tầng: accessor `df.finlens.rsi(14)` trả về bản sao của `DataFrame` kèm cột mới và tự tách theo cột `symbol`, còn `finlens.ta.RSI(mang, timeperiod=14)` chạy trên mảng với tham số keyword-only. Kèm theo là lớp `finlens.IndicatorError` và bốn mã `FL_TA_PARAM`, `FL_TA_INPUT`, `FL_TA`, `FL_TA_UNAVAILABLE`. Hai chỗ phải đọc trước khi nâng cấp: `ta-lib` thành phụ thuộc bắt buộc, và sàn macOS nâng lên 13.0 trên Intel cùng 14.0 trên Apple Silicon.

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

Nội dung trang

Nâng cấp từ 0.1.x — mọi chỗ gãy1. Đổi tên2. Đổi chữ ký3. Đổi từ vựng interval4. Đổi hình dạng dữ liệu — nhóm im lặng nhất5. Đổi môi trường1.4.0 — 11/08/2026Thêm — client.funds.*: quỹ đầu tưThêm — client.bonds.*: trái phiếu doanh nghiệpThêm — client.financials.notes(): thuyết minh BCTC và phân nhóm nợThêm — năm cột cho client.meta.warrants()Sửa — một bảng ĐẦY ĐỦ tự khai là đã bị cắtSửa — mọi ngoại lệ đều mang một liên kết chếtSửa — thông báo lỗi tiếng Việt không còn nuốt mất chính ngoại lệSửa — cảnh báo độ trễ dữ liệu trong phiên giờ thật sự tới tay bạnGỡ — cột company_type khỏi client.financials.periods()1.3.0 — 10/08/2026Thêm — client.financials.indicators(): chỉ tiêu tài chính tính sẵnThêm — client.watchlist: danh mục theo dõi, đọc và GHI1.2.0 — 10/08/2026Thêm — 135 hàm TA-Lib: finlens.ta và df.finlens.*Thêm — finlens.IndicatorError và bốn mã lỗiĐổi — ta-lib là phụ thuộc bắt buộcĐổi — sàn macOS: 13.0 cho Intel, 14.0 cho Apple SiliconSửa — thiếu khoá API thì thông báo nói đã tìm ở đâu1.1.1 — 06/08/2026Thêm — client.macro.*: dữ liệu vĩ môThêm — supply_demand(): khối lượng và số lệnh ĐẶT vào sổThêm — active_volume(): khối lượng khớp lệnh chủ động cuối ngàyThêm — basis(): chênh lệch phái sinh và chỉ số cơ sởThêm — dòng tiền nhà đầu tư ở cấp hợp đồng phái sinhThêm — mã lỗi FL_DATA_NO_UNDERLYINGThêm — hai kiểu công khai1.1.0 — 05/08/2026Đã đổi — báo cáo tài chính dùng item_id, và cột field bị bỏĐã đổi — cột date của một kỳ đã gộp nhóm là phiên cuối, không phải đầu kỳ1.0.1 — 05/08/2026Đã sửa — bốn liên kết trên trang PyPI trả về 4041.0.0 — 05/08/2026Đã sửa — dữ liệu cuối ngày của phiên vừa đóng không còn bị đóng băngBề mặt đầy đủ của 1.0.01.0.0a7 — 04/08/2026Thêm — dữ liệu trong phiênĐã sửa — net_active_value từng có ba đơn vị cho một tên cộtThêm — báo cáo tài chínhThêm — tra cứu danh mụcThêm — chỉ số ngành ICBThêm — adjusted cho eod.*.ohlcv()1.0.0a6 — 04/08/2026Đã sửa — lỗi chứng chỉ TLS không còn bị báo cáo thành lỗi máy chủĐã sửa — finlens.build_info()["built_at"] trả cùng một mốc cho mọi bản1.0.0a5 — 04/08/2026Thêm — investor.flow() và investor.breakdown()Đã sửa — sáu nhóm nhà đầu tư không cùng một định nghĩaĐã sửa — response sai hình dạng ném lỗi thay vì trả bảng rỗng1.0.0a4 — 03/08/2026Thay đổi phá vỡ tương thích — Python và pandas1.0.0a1 — 03/08/2026Đã sửa so với 0.1.11Dòng 0.1.x — đã đóng băng