Vnstock Logo

Vi cấu trúc thị trường

Mở rộng

Mục lục

Tính năng đang thử nghiệm

Tính năng trên trang này đang trong giai đoạn phát triển thử nghiệm và chưa ổn định. Bạn dùng được để tìm hiểu và chạy thử, nhưng tên hàm hay công cụ, tham số và cấu trúc kết quả có thể còn đổi giữa các bản phát hành, cách tính và cách xử lý cũng có thể còn được hiệu chỉnh.

Hãy đối chiếu kết quả với một cách tính độc lập trước khi đưa vào quy trình nghiên cứu, và ghim phiên bản thư viện trong dự án để mã đang chạy không tự đổi theo bản mới.

vnstock_quant.micro tính trên dữ liệu khớp lệnh từng lệnh (tick): gom thành nến theo thời gian, số lệnh, khối lượng hoặc giá trị; xác định bên chủ động; khối lượng mua trừ bán cộng dồn (CVD); VPIN; biến động thực hiện. Cần cấp quyền sử dụng từ Bronze, cùng nhóm quyền risk (Quyền dùng và hạn mức).

Hàm không phụ thuộc nguồn. Cùng một cách gọi cho:

Dữ liệuCột thường cóBên chủ động
Khớp lệnh trong phiên cổ phiếu Việt Nam (Market().equity(...).trades())time, price, volume, match_type, idmatch_type là Buy hoặc Sell; ATO, ATC không có bên
Hợp đồng tương lai (Market().futures("VN30F1M").trades())như trênnhư trên
Luồng giao dịch tài sản số lưu bằng vnstock_pipelinetime, price, quantity, buyer_is_makerbuyer_is_maker=True nghĩa là bên bán chủ động
Nguồn không có bêntime, price, volumesuy bằng tick rule

Dữ liệu mẫu

Ví dụ dùng một phiên tổng hợp có hình dạng của dữ liệu khớp lệnh HOSE (bước giá 50 đồng, phiên ATO, nghỉ trưa, ATC), chạy được không cần mạng.

Python
import numpy as np
import pandas as pd
from vnstock_quant import micro

rng = np.random.default_rng(7)
day = "2026-10-13"
tz = "Asia/Ho_Chi_Minh"
morning = pd.date_range(f"{day} 09:15:01", f"{day} 11:30", tz=tz, periods=12_000)
afternoon = pd.date_range(f"{day} 13:00", f"{day} 14:29:59", tz=tz, periods=8_000)
time = pd.DatetimeIndex([pd.Timestamp(f"{day} 09:15", tz=tz), *morning, *afternoon, pd.Timestamp(f"{day} 14:45", tz=tz)])
price = 25_000 + np.cumsum(rng.choice([-50, 0, 0, 50], size=len(time))).astype(float)
side = np.where(rng.random(len(time)) < 0.52, "Buy", "Sell").astype(object)
side[0], side[-1] = "ATO", "ATC"
volume = rng.integers(1, 200, size=len(time)) * 100.0
volume[0], volume[-1] = 250_000.0, 400_000.0
ticks = pd.DataFrame({"time": time, "price": price, "volume": volume, "match_type": side})
len(ticks)

Với dữ liệu thật:

Python
from vnstock import Market

ticks = Market().equity("FPT").trades(limit=1000)       # tối đa 1.000 lệnh mỗi lần gọi
futures = Market().futures("VN30F1M").trades(limit=1000)

Một phiên đầy đủ có vài chục nghìn lệnh. Lấy nhiều trang qua last_id, hoặc để vnstock_pipeline lưu dữ liệu khớp lệnh vào kho rồi đọc lại.


Bên chủ động

Python
sign = micro.classify(ticks)            # đọc match_type; ATO, ATC cho 0
sign.value_counts()
tick_rule = micro.classify(ticks, method="tick_rule")
(tick_rule == sign)[sign != 0].mean()   # tỷ lệ tick rule khớp với nguồn

Khi nguồn có cột bên chủ động thì nên dùng cột đó. Tick rule chỉ là ước lượng; trên dữ liệu ngẫu nhiên như ví dụ này, tỷ lệ khớp gần 50%, nghĩa là ví dụ không nói gì về độ chính xác của tick rule trên dữ liệu thật.


Nến theo thời gian, số lệnh, khối lượng, giá trị

Python
m1 = micro.bars(ticks, by="time", size="1min")      # tick → nến 1 phút
m5 = micro.bars(ticks, by="time", size="5min")
tb = micro.bars(ticks, by="tick", size=200)         # mỗi nến 200 lệnh
vb = micro.bars(ticks, by="volume", size=100_000)   # mỗi nến đúng 100.000 cổ phiếu
db = micro.bars(ticks, by="value", size=2e9)        # mỗi nến đúng 2 tỷ đồng
m1[["open", "high", "low", "close", "volume", "vwap", "buy_volume", "sell_volume"]].head()
  • vwap của từng nến là Σ(giá × khối lượng) / Σ khối lượng, giá bình quân thật của nến. Với nến khối lượng hay giá trị, đó là giá bình quân của đúng một lô.
  • Lệnh vắt qua ranh giới được tách để mỗi nến đủ đúng size (split=True). split=False để lệnh đó đóng nến nguyên vẹn, nến hơi vượt size.
  • Một lệnh lớn hơn max_split nến (mặc định 1.000) không bị tách mà thành một nến gắn cờ oversize. Ở ví dụ này, ATC 400.000 cổ phiếu với size=100 là một nến chứ không phải 4.000 nến.
  • Nến cuối chưa đủ size có complete=False.
  • Tổng khối lượng các nến bằng tổng khối lượng khớp: không mất, không đếm trùng.
Python
assert vb["volume"].sum() == ticks["volume"].sum()
vb[["volume", "complete", "oversize"]].tail(3)

Nến dùng thẳng cho chỉ báo kỹ thuật:

Python
from vnstock_quant.ta import Indicator

Indicator(m1).rsi(length=14).tail()

Khối lượng mua trừ bán (CVD)

Python
flow = micro.cvd(m1)            # cộng dồn, bắt đầu lại mỗi ngày
flow.tail()

CVD tăng trong khi giá đi ngang thường được đọc là lực mua chủ động đang bị hấp thụ. Hàm chỉ tính chuỗi số, không đưa tín hiệu.


VPIN

Python
v = micro.vpin(vb, window=50)
v.describe()

VPIN (Easley, López de Prado và O'Hara, 2012) là tỷ lệ mất cân bằng mua bán trên window nến khối lượng gần nhất, từ 0 đến 1. Bài gốc chọn kích thước nến khoảng 1/50 khối lượng trung bình ngày và window bằng 50. Chỉ số này còn tranh cãi: Andersen và Bondarenko (2014) cho rằng nó dự báo kém biến động ngắn hạn khi đã tính đến cường độ giao dịch. Dùng để nghiên cứu, không dùng làm tín hiệu.


Biến động thực hiện

Python
rv = micro.realized(m5)          # mỗi ngày: rv, bv, jump, jump_share, rvol, rvol_annual, n
rv[["rv", "bv", "jump_share", "rvol", "rvol_annual", "n"]]
  • rv là tổng bình phương log-return giữa các nến trong ngày (Andersen, Bollerslev, Diebold và Labys, 2003).
  • bv, biến thiên bipower, ít bị ảnh hưởng bởi một cú nhảy giá (Barndorff-Nielsen và Shephard, 2004); jump = max(rv − bv, 0) là phần nhảy.
  • Lợi suất không nối qua đêm. Giờ nghỉ trưa tính là một lợi suất.
  • Dùng nến khoảng 5 phút. Nến quá mịn dính nhiễu bước giá (giá nhảy qua lại giữa giá mua và giá bán), làm rv cao hơn thật. Với cổ phiếu Việt Nam, bước giá 10 đến 100 đồng là một phần đáng kể của biến động trong phút.
  • rvol_annual nhân với periods=252. Muốn theo lịch HOSE thì truyền periods=250.

Hiệu năng

Phép gom chạy trong lõi Rust; phần còn lại là chuẩn hoá cột bằng pandas. Một lần gọi bars gồm cả xác định bên chủ động từ match_type, đo trên máy tính xách tay chip Apple Silicon:

Số lệnhThời gian
20.000 (một phiên như ví dụ)khoảng 7 ms
500.000khoảng 85 ms

Đọc tiếp