Danh mục theo dõi bằng Python — đọc và ghi, chung với web
Dùng finlens tạo, sửa và đọc danh mục theo dõi trên finlens.vn từ Python: thêm bớt mã, lấy giá phiên gần nhất và xếp hạng suất sinh lợi trong danh mục.
Đây là nhóm phương thức duy nhất trong thư viện có GHI dữ liệu. Mọi thứ còn lại chỉ đọc, nên một lỗi ở đó cùng lắm cho ra số sai — một lỗi ở đây sửa danh mục thật của bạn.
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.
| Phương thức | Trả về |
|---|---|
list() | Mọi danh mục của bạn — list[WatchlistInfo] |
create(name) | Danh mục mới — WatchlistInfo |
rename(id, name=…) | WatchlistInfo đã đổi tên |
delete(id) | None |
symbols(id) | DataFrame 18 cột: giá phiên gần nhất + xếp hạng |
add(id, symbols) | list[str] — các mã đã ghi |
replace(id, symbols) | list[str] — ⚠️ xoá những mã không gửi |
remove(id, symbol) | None |
`list()` KHÔNG trả DataFrame
Bảy phương thức ở đây trả dict, list[str] hoặc None. Chỉ symbols() trả
DataFrame.
Đó là bất đối xứng có chủ đích: một danh mục theo dõi là một đối tượng có id
và tên, không phải một bảng số liệu. Nhưng nó khác phần còn lại của thư viện, nên
đừng gọi .head() trên kết quả của list().
Vòng làm việc thường gặp
import finlens
client = finlens.client()
dm = client.watchlist.create("Ngân hàng")
client.watchlist.add(dm["id"], ["VCB", "TCB", "ACB"])
df = client.watchlist.symbols(dm["id"])
print(df[["symbol", "close", "return_1m", "return_1m_rank"]])create() trả về một WatchlistInfo — một dict với bốn khoá:
| Khoá | Nội dung |
|---|---|
id | Định danh, dùng cho mọi thao tác sau đó. Dùng chung với web. |
name | Tên. ⚠️ Nhận None |
display_index | Thứ tự người dùng sắp. ⚠️ Nhận None, và trùng nhau được |
symbol_count | Số mã, có sẵn nên đếm không tốn thêm lời gọi |
Ba cách làm mất dữ liệu
Đọc trước khi gọi thao tác ghi đầu tiên
1. create() không idempotent. Trùng tên được cho phép, nên gọi hai lần với
cùng một tên tạo ra hai danh mục. Đó là lý do client không bao giờ tự gửi
lại một create() đã timeout — gửi lại có thể tạo bản thứ hai mà bạn không
biết. Khi timeout, gọi list() xem nó đã kịp tạo chưa rồi tự quyết.
2. replace() xoá. Nó làm danh mục chỉ còn đúng những mã bạn gửi. Muốn thêm
mà không xoá gì thì dùng add().
3. delete() và remove() thành công vô điều kiện, kể cả khi không có gì để
xoá. Chúng thử lại được mà không phải bắt ngoại lệ; cái giá là bạn không phân
biệt được "vừa xoá xong" với "chưa từng tồn tại".
Đừng sắp lại list()
ds = client.watchlist.list()
print([w["name"] for w in ds]) # đúng thứ tự web hiển thị`sorted()` trên `name` hoặc `display_index` sẽ ném `TypeError`
Cả hai trường đều nhận None, và đó là dữ liệu thật chứ không phải một khả
năng lý thuyết: web tạo được danh mục không đặt tên, và những dòng cũ chưa từng
được cấp số thứ tự.
sorted(ds, key=lambda w: w["name"]) # TypeError khi gặp None
sorted(ds, key=lambda w: w["display_index"]) # TypeError, và hai danh mục trùng
# số cũng là chuyện có thậtlist() đã trả về đúng thứ tự web hiển thị, nên không có gì để sắp. Cần một thứ
tự ổn định giữa hai lần gọi thì dùng id.
symbols() — 18 cột, và ba quy ước đơn vị
df = client.watchlist.symbols(42)
df.attrs["finlens"]["units"]["close"] # 'kVND'| Nhóm cột | Đơn vị |
|---|---|
close, change | nghìn VND đã điều chỉnh quyền — 23.4 là 23.400 đồng |
change_ratio, sáu cột return_* | tỷ lệ — 0.0234 là +2,34%, không phải 2,34 |
sáu cột return_*_rank | hạng, không có đơn vị |
symbol, sort_index, tradeable | — |
`*_rank` xếp trong CHÍNH danh mục này
Không phải trong thị trường, không phải trong ngành. Hạng 1 nghĩa là tốt nhất trong danh mục bạn hỏi.
Hai mã bằng nhau thì bằng hạng. Một mã có suất sinh lợi None thì hạng cũng
thiếu, chứ không phải bị đẩy xuống cuối.
`close` thiếu là bình thường
Mã mới niêm yết, mã đã huỷ niêm yết, hoặc một mã không tra được mà web đã ghi vào
từ trước — cả ba đều cho close rỗng. Dòng vẫn về đủ cột.
Lọc nó đi sẽ làm một mã biến mất khỏi danh mục của bạn trong kết quả mà không ai xoá nó.
df.attrs["finlens"]["as_of"] là ngày phiên của bảng giá, không phải lúc bạn
gọi — đó là cách phân biệt "thị trường chưa mở" với "dữ liệu cũ".
symbols() không có tham số refresh vì nó không đi qua cache: kết quả là
ảnh chụp của một thứ chính bạn vừa sửa bằng add() hay remove().
Mã lạ không làm hỏng cả lời gọi
ghi_duoc = client.watchlist.add(dm["id"], ["VCB", "KHONGCO"])
# ['VCB'] — và một PartialDataWarning nêu tên mã lỗiMã không tra được đi vào phần lỗi theo từng mã, HTTP vẫn 200, và những mã
còn lại đã được ghi bình thường. Giá trị trả về là danh sách mã thực sự
ghi, nên so nó với danh sách bạn gửi là cách rẻ nhất để biết cái gì đã lọt.
Xem thêm
Tra cứu danh mục
Lấy danh sách mã theo sàn hoặc ngành để nạp vào danh mục.
Dữ liệu cuối ngày (EOD)
Giá theo phiên cho từng mã, dải thời gian tuỳ ý.
Xử lý lỗi
PartialDataWarning, on_error và cây ngoại lệ FL_*.
Danh mục trên web
Cùng dữ liệu, giao diện web — và bộ lọc screener.
Câu hỏi thường gặp
Danh mục tạo bằng Python có hiện trên web finlens.vn không?
Có, và ngược lại. Danh mục theo dõi là bề mặt duy nhất mà thư viện Python finlens dùng chung dữ liệu với web: hai bên đọc ghi trên cùng một bảng, nên `client.watchlist.create("Ngân hàng")` tạo ra một danh mục bạn thấy ngay trên finlens.vn, và một mã bạn thêm trên web sẽ xuất hiện trong `client.watchlist.symbols()`. Cùng một `id` chỉ tới cùng một danh mục ở cả hai nơi.
Vì sao gọi create hai lần lại ra hai danh mục cùng tên?
Vì `create()` của thư viện Python finlens không idempotent — trùng tên được cho phép ở tầng dữ liệu, nên mỗi lời gọi tạo một danh mục mới. Đó cũng là lý do client không bao giờ tự gửi lại một `create()` đã timeout: gửi lại có thể tạo bản thứ hai mà bạn không biết. Khi một `create()` timeout, hãy gọi `list()` để xem nó đã kịp tạo chưa rồi tự quyết định.
replace khác add thế nào, có làm mất mã không?
Khác ở chỗ `replace()` XOÁ. `client.watchlist.replace(id, ["VCB"])` làm danh mục chỉ còn đúng `VCB` và bỏ mọi mã khác; `client.watchlist.add(id, ["VCB"])` thêm `VCB` vào và giữ nguyên phần còn lại. Nếu bạn chỉ muốn bổ sung, dùng `add()`. Trong thư viện Python finlens, `replace()` là thao tác duy nhất có thể làm bạn mất mã mà không có cảnh báo nào, vì việc xoá chính là ý nghĩa của nó.
Vì sao sắp danh mục theo tên lại ném TypeError?
Vì cả `name` lẫn `display_index` của `WatchlistInfo` đều nhận `None`, và đó là dữ liệu thật chứ không phải một khả năng lý thuyết — web tạo được danh mục không đặt tên, và những dòng cũ chưa từng được cấp số thứ tự. `sorted(ds, key=lambda w: w["name"])` sẽ ném `TypeError` ngay lần đầu gặp một dòng như vậy. Không cần sắp lại: `list()` của thư viện Python finlens đã trả về đúng thứ tự web hiển thị.
Các cột kết thúc bằng _rank được xếp hạng trong phạm vi nào?
Trong CHÍNH danh mục đó, không phải trong thị trường hay trong ngành. Sáu cột `return_1w_rank` tới `return_1y_rank` của thư viện Python finlens xếp các mã của riêng danh mục bạn hỏi, nên hạng 1 nghĩa là tốt nhất trong danh mục ấy. Hai mã bằng nhau thì bằng hạng, và một mã có suất sinh lợi thiếu thì hạng cũng thiếu chứ không phải bị đẩy xuống cuối.
Cập nhật lần cuối