Ngày đáo hạn phái sinh VN30F1M: ATC có thật sự biến động mạnh?

· · Nghiên cứu thị trường

Ngày đáo hạn phái sinh VN30F1M: ATC có thật sự biến động mạnh?

> Tóm tắt nhanh: Lời đồn đúng một nửa. Trong 91 ngày đáo hạn, biến động ATC của chỉ số VN30 trung bình 0,333%, gấp 2,00 lần ngày thường. Hợp đồng VN30F1M thì gần như không khác ngày thường, còn basis co dần từ khoảng 6,32 điểm xuống 1,82 điểm khi tới ngày đáo hạn.

"Thứ Năm tuần ba thì đứng ngoài cho lành." Câu này anh em trên diễn đàn nhắc gần như tháng nào cũng có. Đi kèm là đủ thứ chuyện: cá mập đánh ATC, quỹ đạp giá thanh toán, nến cuối phiên dài như cây sào.

Mình tò mò nên lôi dữ liệu nến 1 phút từ đầu năm 2019 ra đếm thử. Kết quả có phần đúng, có phần trật. Cái "quẫy" nằm ở chỉ số cơ sở, còn hợp đồng phái sinh mà anh em đang cầm thì ngày đáo hạn lại hiền hơn ngày thường.

> Góc người mới (biết rồi thì lướt qua) > - Hợp đồng tương lai VN30F1M: cược lên/xuống của chỉ số VN30, không sở hữu cổ phiếu nào; F1M là hợp đồng đáo hạn tháng này, long hay short đều được. > - Điểm phái sinh: giá tính bằng điểm, mỗi điểm lên xuống là 100.000 đồng lãi/lỗ cho một hợp đồng, như "giá mỗi ký" ngoài chợ. > - Ngày đáo hạn: thứ Năm tuần thứ ba mỗi tháng, hợp đồng chốt sổ theo đúng giá đóng cửa chỉ số VN30 hôm đó. > - Phiên ATC: 14:30-14:45, mọi lệnh gom lại khớp ở một giá duy nhất, giống đấu giá kín cuối giờ mới công bố giá chốt. > - Basis: giá hợp đồng trừ giá chỉ số. Giống vé máy bay đặt sớm với giá tại quầy, càng gần ngày bay càng sát nhau. > - p-value: nếu thật ra chẳng có hiệu ứng gì, xác suất vẫn thấy chênh lệch cỡ này do may. Dưới 0,05 thì bắt đầu khó đổ cho may.

Vì sao lời đồn có thể đúng, và vì sao có thể chỉ là lời đồn

Lý do để tin khá rõ. Giá thanh toán cuối cùng của VN30F1M chính là giá đóng cửa chỉ số VN30, và giá đó được quyết trong đúng 15 phút ATC. Ai đang ôm vị thế phái sinh lớn sẽ có động cơ muốn con số cuối cùng nghiêng về phía mình. Thêm vào đó, các quỹ ETF và dân arbitrage (mua bên rẻ, bán bên đắt để ăn chênh lệch) thường phải đóng vị thế cổ phiếu cơ sở đúng ATC ngày đáo hạn để khớp với giá thanh toán. Nhiều lệnh lớn dồn vào một cửa hẹp thì giá dễ giật.

Ở chiều ngược lại, dòng tiền đóng vị thế không nhất thiết cùng một hướng: bên bán đẩy xuống thì có bên mua kéo lên, cuối cùng triệt tiêu nhau. Trí nhớ con người cũng hay giữ lại mấy phiên đáo hạn kịch tính và quên sạch những phiên phẳng lì. Một cây nến dài năm nào đó có thể được kể đi kể lại suốt mấy năm sau.

Còn basis thì lý thuyết nói thẳng: đến ngày đáo hạn hợp đồng phải bằng chỉ số, nên basis phải co về 0. Cái cần kiểm là nó co nhanh hay chậm, và trước đó lệch về phía nào.

Mình đo thế nào

Dữ liệu là nến 1 phút của VN30F1M và chỉ số VN30 từ 04/03/2019 đến 08/10/2026, gồm 91 ngày đáo hạn. Ngày đáo hạn tính theo quy tắc thứ Năm tuần thứ ba, gặp ngày nghỉ thì lùi về phiên trước. Mình đối chiếu với ngày đổi mã hợp đồng trong dữ liệu và khớp 110/110, nên ít nhất phần lịch không bị lệch.

Biến động ATC là giá đóng cửa so với giá khớp cuối cùng trước 14:30, tính cho cả chỉ số lẫn hợp đồng. Mình lấy giá trị tuyệt đối (bỏ dấu, chỉ quan tâm giật mạnh hay nhẹ), rồi so trung bình nhóm ngày đáo hạn với nhóm ngày thường bằng Welch t-test, một phép kiểm tra chuẩn khi hai nhóm có số mẫu và độ dao động khác nhau. Basis đo lúc 14:25, tức là trước khi vào ATC, xếp theo số phiên còn lại đến ngày đáo hạn.

Kết quả

Chỉ số thì có, hợp đồng thì không

![Ngày đáo hạn so với ngày thường](https://quantvn.com/blog-assets/ngay-dao-han-phai-sinh-vn30f1m/compare.png)

Hình 1: Ngày đáo hạn so với ngày thường

Hình này xếp từng cặp cột, một cột ngày thường đứng cạnh một cột ngày đáo hạn, cho ba thước đo. Vạch dọc trên đầu mỗi cột là thanh lỗi, tức vùng mà con số thật nhiều khả năng nằm trong đó. Hai thanh lỗi chồng lên nhau nhiều thì coi như chưa phân biệt được hai nhóm.

Ở hàng đầu tiên, hai cột tách nhau rõ nhất. Biến động ATC của chỉ số VN30 ngày thường trung bình 0,166%, ngày đáo hạn lên 0,333%, đúng gấp 2,00 lần, p-value dưới 0,001. Chênh lệch cỡ này khó đổ cho may rủi.

Quy ra tiền cho dễ hình dung: ở mức VN30 1.877 điểm, 0,333% tương đương khoảng 6,3 điểm, tức chừng 625.400 đồng mỗi hợp đồng chỉ trong một phiên ATC.

Sang chính hợp đồng VN30F1M thì lời đồn bắt đầu trật. Biến động ATC của nó ngày thường là 0,186%, ngày đáo hạn 0,227%, cao hơn 1,22 lần nhưng p-value tới 0,637. Kết quả này vẫn nằm trọn trong vùng nhiễu, thanh lỗi chồng lên nhau gần hết.

Biên độ cao-thấp cả ngày còn đi ngược lời đồn. Vào ngày đáo hạn, VN30F1M dao động trung bình 1,366%, thấp hơn ngày thường 1,596% (bằng 0,86 lần), với p-value 0,020. Theo dữ liệu này, cả ngày đáo hạn nhìn chung là ngày phái sinh chạy hẹp hơn, không phải rộng hơn.

Vì sao chỉ số giật mà hợp đồng không giật? Mình chưa kiểm chứng được, chỉ có giả thuyết: hợp đồng đã tự kéo về sát chỉ số từ trước ATC (phần basis bên dưới sẽ cho thấy điều đó), nên cú giật cuối phiên chủ yếu diễn ra ở rổ cổ phiếu cơ sở, nơi ETF và arbitrage phải khớp lệnh thật.

Từng ngày đáo hạn một

Trung bình dễ bị vài phiên cực đoan kéo lệch, nên mình vẽ từng ngày ra xem.

![Biến động ATC của VN30 ở từng ngày đáo hạn](https://quantvn.com/blog-assets/ngay-dao-han-phai-sinh-vn30f1m/atc_history.png)

Hình 2: Biến động ATC của VN30 ở từng ngày đáo hạn

Mỗi thanh là một ngày đáo hạn, xanh là ATC kéo chỉ số lên, đỏ là kéo xuống. Dải xám là vùng chứa 90% biến động ATC của ngày thường, từ -0,35% đến +0,34%. Có 36,3% số ngày đáo hạn thò ra ngoài dải đó, trong khi ngày thường theo đúng cách vẽ dải chỉ có số ít lọt ra.

Ba cây dài nhất là 21/05/2020, 21/11/2019 và 15/08/2019, cây lớn nhất kéo chỉ số lên +1,50%. Cả ba dồn vào giai đoạn đầu mẫu.

Về chiều thì gần như chia đôi: 48,4% số ngày đáo hạn ATC kéo chỉ số tăng, và biến động có dấu trung bình chỉ +0,003%. Ngày đáo hạn giật mạnh hơn thật, nhưng giật lên hay giật xuống thì chẳng khác gì tung đồng xu. Ai bảo "đáo hạn là bị đạp" thì dữ liệu này không ủng hộ.

Basis co lại đúng như sách

![Basis theo số phiên còn lại đến ngày đáo hạn](https://quantvn.com/blog-assets/ngay-dao-han-phai-sinh-vn30f1m/basis.png)

Hình 3: Basis theo số phiên còn lại đến ngày đáo hạn

Trục ngang là số phiên còn lại đến đáo hạn, trục dọc là basis tính bằng điểm. Đường trung bình |basis| đi xuống đều khi về gần ngày cuối. Còn 20 phiên thì hợp đồng lệch chỉ số trung bình 6,32 điểm, khoảng 631.900 đồng mỗi hợp đồng.

Đến chính ngày đáo hạn, độ lệch chỉ còn 1,82 điểm (khoảng 181.500 đồng). Nó không về hẳn 0 vì mình đo lúc 14:25, trước khi ATC chốt giá.

Đường trung vị (con số đứng giữa khi xếp tất cả từ thấp đến cao) thì kể thêm một chuyện. Còn 20 phiên, trung vị là -1,85 điểm, tức hợp đồng thường rẻ hơn chỉ số. Chỉ còn một phiên thì nó lật sang +0,67 điểm, và ngày đáo hạn là +0,28 điểm.

Tính chung cả mẫu, basis âm 57,3% thời gian. Thị trường phái sinh Việt Nam quen trạng thái chiết khấu, nhưng sát ngày chốt sổ thì cái chiết khấu ấy gần như biến mất.

Mình có thể sai ở đâu

Điểm yếu lớn nhất là mẫu nhỏ. 91 ngày đáo hạn đủ để thấy một chênh lệch gấp đôi như ở ATC chỉ số, nhưng chưa đủ để nói gì chắc về chiều biến động. Con số 48,4% kia hoàn toàn có thể xê dịch đáng kể nếu có thêm vài năm dữ liệu.

Kế đến là chuyện sự kiện cực đoan dồn cục. Ba ngày giật mạnh nhất đều nằm trong 2019-2020, giai đoạn thị trường còn mỏng và có đợt biến động lớn, nên mức trung bình 0,333% có thể đang được mấy phiên đó gánh một phần. Mình cũng chỉ dùng dữ liệu từ 2019 (khi có chỉ số 1 phút) và đã bỏ một đoạn dữ liệu phái sinh cũ bị lệch giá, nên những năm đầu thị trường phái sinh không có trong bài.

Cuối cùng, và đây là điểm mình muốn anh em nhớ nhất: biến động ATC lớn không đồng nghĩa với thao túng. Quỹ ETF tái cơ cấu, arbitrage đóng vị thế đều là hoạt động hợp pháp và có lý do để dồn đúng vào ATC ngày đáo hạn. Dữ liệu giá không phân biệt được ai đặt lệnh và vì sao.

Code cho ai muốn tự chạy

Đoạn dưới đây là phần lõi mình dùng để tính, anh em có dữ liệu nến 1 phút thì chạy lại được.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter

# fut_bars, idx_bars: nến 1 phút của VN30F1M và chỉ số VN30 với cột date, minute (phút từ 0:00 giờ VN), close
# expiries: DatetimeIndex các ngày đáo hạn (thứ Năm tuần thứ ba mỗi tháng)

POS, NEG, NEUTRAL, AXIS = "#2a78d6", "#e34948", "#b4b2a9", "#c3c2b7"
pct_axis = lambda ax, decimals=1: ax.yaxis.set_major_formatter(FuncFormatter(lambda v, _: f"{v*100:.{decimals}f}%"))
zero_line = lambda ax: ax.axhline(0, color=AXIS, linewidth=1)


def price_at(bars: pd.DataFrame, hhmm: str) -> pd.Series:
    """Giá khớp cuối cùng TRƯỚC mốc HH:MM của mỗi ngày (mỗi nến 1 phút được đánh dấu bằng giờ mở nến)."""
    h, m = map(int, hhmm.split(":"))
    cutoff = h * 60 + m
    return bars[bars["minute"] < cutoff].groupby("date")["close"].last()


def summarize(x) -> dict:
    """Số mẫu, trung bình, trung vị, tỷ lệ dương và t-stat của một chuỗi lợi suất."""
    x = pd.Series(x, dtype=float).dropna()
    n = len(x)
    std = x.std(ddof=1) if n > 1 else np.nan
    se = std / np.sqrt(n) if n > 1 else np.nan
    return {
        "n": n,
        "mean": x.mean(),
        "median": x.median(),
        "std": std,
        "win_rate": (x > 0).mean() if n else np.nan,
        "t_stat": x.mean() / se if n > 1 and se > 0 else np.nan,
        "ci95": 1.96 * se,
    }


def days_to_expiry(dates: pd.DatetimeIndex, expiries: pd.DatetimeIndex) -> pd.Series:
    """Số phiên giao dịch còn lại đến ngày đáo hạn gần nhất (0 = chính ngày đáo hạn)."""
    pos = pd.Series(np.arange(len(dates)), index=dates)
    exp_pos = pos[pos.index.isin(expiries)]
    nxt = np.searchsorted(exp_pos.values, pos.values)        # đáo hạn kế tiếp (hoặc hôm nay)
    valid = nxt < len(exp_pos)
    out = pd.Series(np.nan, index=dates)
    out[valid] = exp_pos.values[nxt[valid]] - pos.values[valid]
    return out


def atc_move(bars: pd.DataFrame) -> pd.Series:
    """Biến động trong phiên ATC: giá đóng cửa 14:45 so với giá khớp cuối trước 14:30."""
    close = bars.groupby("date")["close"].last()
    return close / price_at(bars, "14:30") - 1


def basis_at(fut_bars: pd.DataFrame, idx_bars: pd.DataFrame, hhmm: str = "14:25") -> pd.Series:
    """Basis = giá VN30F1M − chỉ số VN30 tại cùng thời điểm (điểm)."""
    return price_at(fut_bars, hhmm) - price_at(idx_bars, hhmm)


def plot_atc_history(moves: pd.Series, normal_band: tuple[float, float]):
    fig, ax = plt.subplots(figsize=(8.6, 4.4))
    ax.axhspan(*normal_band, color=NEUTRAL, alpha=0.25, linewidth=0, label="90% ngày thường nằm trong dải này")
    colors = [POS if v > 0 else NEG for v in moves]
    ax.vlines(moves.index, 0, moves.values, color=colors, lw=1.6)
    ax.scatter(moves.index, moves.values, color=colors, s=22, zorder=3)
    zero_line(ax)
    pct_axis(ax, decimals=1)
    ax.set_title("Biến động ATC của chỉ số VN30 ở từng ngày đáo hạn")
    ax.legend(loc="lower right")
    return fig


moves = atc_move(idx_bars)
is_exp = moves.index.isin(expiries)
print("ATC |TB| ngày đáo hạn:", moves[is_exp].abs().mean(), " ngày thường:", moves[~is_exp].abs().mean())
basis = basis_at(fut_bars, idx_bars)
dte = days_to_expiry(basis.index, expiries)
print(basis.groupby(dte).median().loc[:10])
band = tuple(moves[~is_exp].quantile([0.05, 0.95]))
plot_atc_history(moves[is_exp], band).savefig("atc.png", dpi=150)

Hàm price_at lấy giá khớp cuối cùng trước một mốc giờ trong mỗi ngày, ví dụ ngay trước khi ATC bắt đầu. atc_move dùng nó để so giá đóng cửa với giá ngay trước ATC, ra biến động ATC từng ngày, còn basis_at lấy giá hợp đồng trừ giá chỉ số tại cùng một mốc giờ. days_to_expiry đếm mỗi ngày còn bao nhiêu phiên nữa đến đáo hạn, để gom basis theo khoảng cách tới ngày chốt sổ. Cuối cùng code tách ngày đáo hạn ra khỏi ngày thường, lấy hai đầu của dải chứa 90% ngày thường làm dải xám, rồi vẽ hình.

Vậy dùng được gì khi giao dịch

Với người trade phái sinh, dữ liệu này không cho mình lý do để sợ ngày đáo hạn: hợp đồng F1M ngày đó chạy hẹp hơn ngày thường và ATC của nó không khác biệt đáng kể. Nó cũng không cho mình hướng để cược, vì chiều ATC chỉ số gần như năm ăn năm thua. Nếu phải tóm thành một lời, đáo hạn là "gió mạnh nhưng không rõ hướng" ở rổ cổ phiếu cơ sở.

Ai cầm cổ phiếu nhóm VN30 và hay đặt lệnh ATC thì nên biết giá khớp ngày đáo hạn có thể lệch xa hơn ngày thường, nên cân nhắc lệnh giới hạn thay vì lệnh ATC thuần. Nhớ thêm là cổ phiếu dính T+2, phái sinh có đòn bẩy và phí, một cú lệch vài điểm cũng ăn vào tài khoản thật.

Hỏi đáp nhanh

Chỉ số giật gấp đôi mà sao phái sinh không giật, vô lý vậy? Mình cũng thấy lạ. Giả thuyết của mình là hợp đồng đã bám sát chỉ số từ trước ATC nên phần giật cuối phiên chủ yếu nằm ở cổ phiếu cơ sở, nhưng đây mới là phỏng đoán chưa kiểm.

Thế có phải cá mập đánh ATC không? Dữ liệu giá không trả lời được câu này. Biến động lớn có thể đến hoàn toàn từ ETF và arbitrage đóng vị thế hợp pháp, và việc chiều tăng giảm gần như chia đôi cũng không giống một bên cố tình đè một phía.

Basis lật dương sát ngày đáo hạn thì có ăn được không? Chênh lệch trung vị lúc đó chỉ dưới 1 điểm, cộng phí giao dịch và trượt giá là gần như hết. Biến thể đáng thử hơn là đo lại basis sát giờ ATC hơn nữa để xem phần lệch còn lại trước phiên chốt giá nhỏ đến đâu.

Kết

Lời đồn "cẩn thận ngày đáo hạn" đúng với chỉ số VN30 trong phiên ATC: biến động gấp 2,00 lần ngày thường, nhưng chiều lên xuống thì không đoán được. Với chính hợp đồng VN30F1M, ngày đáo hạn không dữ hơn, thậm chí biên độ cả ngày còn hẹp hơn, và basis co từ khoảng 6,32 điểm xuống 1,82 điểm đúng như lý thuyết.

Bài viết chỉ nhằm mục đích nghiên cứu và chia sẻ kiến thức, không phải khuyến nghị đầu tư.

---

Muốn tự kiểm chứng một ý tưởng như thế này? Bạn có thể dựng chiến lược, backtest và chạy paper trading ngay trên nền tảng QuantVN. Xem thêm các nghiên cứu khác về phái sinh trong bài tổng hợp Chứng khoán phái sinh VN30F1M: dữ liệu nói gì.