Vnstock Logo

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

Thử nghiệm

Mục lục

Bản thử nghiệm, chưa phát hành chính thức

Trang này viết cho vnstock_quant v1.0.0rc1, bản thử nghiệm. Tên hàm, tham số và cột dữ liệu có thể còn đổi trước ngày phát hành; khi cài, ghi rõ số phiên bản như ở trang Cài đặt. Bản đang phát hành chính thức là v0.5.0, xem tài liệu bản đó.

Nguồn của bạn

Chọn nguồn bạn có khoá, mã trên trang sẽ điền theo. Chưa chọn thì mã giữ chỗ trống <nguồn>. Nguồn xếp theo bảng chữ cái, không phải khuyến nghị.

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 cổ phiếu Việt Nam, hồ sơ v5 của vnstock 6 (Market().equity(...).trades())time, price, volume, match_type, idmatch_type là Buy hoặc Sell; ATO, ATC không có bên
Khớp lệnh cổ phiếu Việt Nam, hồ sơ v6 (compat="v6")time, price, volume, side, session, ...side là buy hoặc sell; lệnh có session là ato hoặc atc luôn tính 0, kể cả khi nguồn gắn chiều cho cả lô
Hợp đồng tương lai (Market().futures("VN30F1M").trades())như trên, tuỳ hồ sơnhư 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 qua vnstock 6:

Python
import vnstock

vnstock.sources.readers("market.equity.trades")          # nguồn đọc được khớp lệnh cổ phiếu
ticks = vnstock.Market().equity("FPT").trades(source="<nguồn>", compat="v6", floating=None)
futures = vnstock.Market().futures("VN30F1M").trades(source="<nguồn>", compat="v6", floating=None)

Độ dài lịch sử khớp lệnh và số lệnh mỗi lần gọi tuỳ nguồn. Cần lưu nhiều phiên thì để vnstock_pipeline lưu vào kho rồi đọc lại.

Bảng không cho biết phiên ATO, ATC

Có nguồn không gửi cột session và không đánh dấu lô ATO, ATC, chỉ gắn chiều mua hoặc bán cho cả lô. Khi đó thư viện không tách được lệnh khớp trong phiên định kỳ, nên các lô này bị tính bên chủ động theo nhãn của nguồn; micro.classify và các hàm dùng nó báo cảnh báo. Với cổ phiếu HOSE, bỏ các lệnh khớp lúc 09:15 (ATO) và 14:45 (ATC) trước khi tính nếu điều đó quan trọng với bạn.


Bên chủ động

Python
sign = micro.classify(ticks)            # đọc match_type (hoặc side và session); 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