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.x | Bản 1.x |
|---|---|
client.eod.market | client.eod.index |
client.reporting | client.financials |
Module finlens.types | finlens.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.x | Bả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.x | Bản 1.x |
|---|---|
Cột Date, Open, Close, Volume | snake_case viết thường: date, open, close, volume |
Bảng dòng tiền không có cột group | luôn có group, kể cả khi chỉ hỏi một nhóm |
| Kết quả rỗng có thể thiếu cột | Frame 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.x | Bản 1.x |
|---|---|
| Python ≥ 3.10 | Python ≥ 3.11 |
| pandas ≥ 2.1.2 | pandas ≥ 3.0 |
requests | httpx |
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_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útBả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ỉ
foreignvà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.DerivativeInvestorGroupthu hẹpLiteralđể lỗi đó thành một gạch đỏ lúc gõ code, vàbreakdown()bỏ trốnggroupshỏ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ònitem_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:
closecủ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òng1modán nhãn2023-01-01nhưngcloselấ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ì
1ycủ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ỳ
1mosẽ 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 finlensGom 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úc | end bỏ trống | end là hôm nay | end là năm cũ |
|---|---|---|---|
| 08:45 – 14:59 | 60 giây | 60 giây | 7 ngày |
| từ 15:00 | 60 giây | 30 phút | 7 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
timevà mang múi giờAsia/Ho_Chi_Minh, khác cộtdatenaive 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 đọcdf.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ênsymbolsố ít, không phảisymbols.client.intraday.indexkhô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
sidecó 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.x | Bản 1.x | |
|---|---|---|
| Cổ phiếu | chia cho 10⁶ | VND thô |
| Phái sinh | không quy đổi hệ số hợp đồng | VND thô, đã nhân hệ số hợp đồng |
| Tài liệu | gọ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. exchangenhậ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ậtMặ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_valuecủa chúng bằng 0. foreignlấy cả giao dịch thoả thuận, nên nó không bằngforeign_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ủalocal_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ọiclient.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ỗiFL_*ổn định và bảy lớp cảnh báo tắt được chọn lọc. finlens.PartialFetchErrormang 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.ValidationErrorkế thừa đôi từValueError, nên code cũ viếtexcept ValueError:vẫn chạy sau khi nâng cấp.ca_bundlecho 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
CompanyTypeMismatchErrornằm ngoài câyFinLensError. Nay nó làfinlens.CompanyTypeMismatchError(ValidationError), tức vừa trong cây vừa vẫn làValueError. Hệ quả:except finlens.FinLensErrorgiờ là đủ — nếu code của bạn còn một nhánhexcept RuntimeErrorviế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.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