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.1.1 thêm supply_demand, active_volume và basis; kèm mọi thay đổi phá vỡ tương thích khi nâng từ 0.1.x lên 1.x.

Nhật ký thay đổi của thư viện Python finlens. Bản mới nhất là 1.1.1 — 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"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.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.

Bốn nhóm dữ liệu mới, và cả bốn đều nằm ở những chỗ dễ đọc nhầm thành một bảng đã có. Ba cảnh báo dưới đây đáng đọc trước khi gõ dòng đầu tiên.

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_demandAttributeError 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ếuclient.eod.stock, hợp đồngclient.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ỉ foreignproprietary. 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_EMPTYkhoả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.DerivativeInvestorGroupfinlens.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()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; levelorder_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_idmã 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()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.01.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 timemang 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 sideba 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.TlsVerificationErrorkế 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()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""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.0Python >= 3.11 (trước đó là pandas >= 2.1.2Python >= 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()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ờ đủ — 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"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.1.1**, phát hành ngày 06/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. Bốn 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`. Kèm mã lỗi `FL_DATA_NO_UNDERLYING` và hai kiểu `DerivativeInvestorGroup`, `DerivativeInvestorGroupsArg`.

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.1.1 — 06/08/2026Thê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()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