Giờ đầu tiên của phái sinh VN30F1M có báo trước cả phiên?

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

Giờ đầu tiên của phái sinh VN30F1M có báo trước cả phiên?

> Tóm tắt nhanh: Có, nhưng rất yếu: phần còn lại của phiên đi cùng chiều giờ đầu ở 52,7% số phiên, tương quan chỉ 0,12. Quy tắc "10:00 đi theo giờ đầu" sau phí vẫn dương nhưng mỏng, còn 30 phút đầu thì gần như không báo được gì cho đoạn cuối phiên.

"Giờ đầu định hướng cả phiên." Câu này anh em trên diễn đàn phái sinh nói nhiều đến mức thành chân lý. Sáng ra thị trường đỏ lòm thì cả buổi chiều cũng lo. Mở cửa xanh thì yên tâm gồng long (giữ lệnh đặt cược tăng) tới ATC.

Mình muốn kiểm tra câu đó bằng số, nên lôi toàn bộ nến 1 phút của VN30F1M từ 2017 đến 2026 ra đếm. Câu hỏi đầu tiên: nếu giờ đầu (9:00-10:00) tăng thì phần còn lại của phiên có hay tăng tiếp không.

Câu hỏi thứ hai đến từ bên Mỹ. Có một nghiên cứu khá nổi về "intraday momentum" (Gao, Han, Li, Zhou 2018) cho thấy 30 phút đầu báo trước được đoạn cuối phiên. Ở Việt Nam có như thế không?

Kết quả ngắn gọn: hiệu ứng có thật nhưng yếu hơn nhiều so với độ tự tin của câu cửa miệng. Phần 30 phút đầu thì gần như trượt.

> 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 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, mua bán trong ngày được. > - Điểm và tiền: giá tính bằng điểm, mỗi điểm biến động là 100.000 đồng lãi/lỗ cho 1 hợp đồng, giống "giá mỗi ký" ngoài chợ. > - Phiên ATC: 14:30-14:45, mọi lệnh gom lại khớp ở một giá duy nhất, chính là giá đóng cửa. Giống đấu giá kín. > - Hệ số tương quan: đo hai thứ có hay đi cùng nhau không, từ -1 đến 1. Chiều cao với cân nặng thì cao, chiều cao với điểm toán gần 0. > - Chia 5 nhóm (quintile): xếp các phiên theo tín hiệu từ thấp đến cao, chia 5 nhóm bằng nhau (Q1 thấp nhất, Q5 cao nhất) rồi so kết quả từng nhóm. > - t-stat: tín hiệu to hơn nhiễu bao nhiêu lần. Khoảng 2 trở lên thì khó đổ cho may, dưới 1 gần như chắc là nhiễu.

Vì sao giờ đầu có thể nói thật, và vì sao có thể nói dối

Người tin vào quán tính có hai lý lẽ khá chắc. Lý lẽ đầu là tin tức qua đêm: chứng Mỹ, giá dầu, tin vĩ mô đều dồn vào giờ đầu, và thị trường không tiêu hóa hết trong một buổi sáng. Lý lẽ sau mạnh hơn. Quỹ và tổ chức ít khi bắn hết lệnh một lần mà chia nhỏ ra giải ngân hoặc xả dần trong ngày. Nếu họ đã quyết định bán từ sáng thì áp lực bán kéo dài đến chiều, giống dòng xe giờ tan tầm: đầu đường đã kẹt thì cuối đường khó mà thông.

Bên nghi ngờ cũng có lý của họ. Giờ mở cửa là lúc nhà đầu tư cá nhân phản ứng mạnh nhất, cả sợ lẫn hưng phấn. Một cú đạp mạnh đầu phiên có thể chỉ là hoảng loạn, đến giữa buổi sáng người ta bình tĩnh lại và giá hồi về. Nếu kiểu này chiếm ưu thế thì giờ đầu báo ngược, tức là sau đó giá đảo chiều.

Hai lực này kéo hai hướng. Muốn biết lực nào thắng thì chỉ còn cách đếm.

Mình đo thế nào

Sau khi bỏ các phiên thiếu nến, dữ liệu còn 2.271 phiên. Mỗi phiên mình chia làm hai khúc. "Giờ đầu" tính từ giá mở cửa đến giá khớp cuối cùng trước 10:00. "Phần còn lại" tính từ mốc đó đến giá ATC lúc 14:45. Lợi suất của từng khúc tính theo phần trăm.

Với câu hỏi kiểu nghiên cứu Mỹ, mình làm tương tự: 30 phút đầu đặt cạnh đoạn từ 14:00 đến ATC. Mình không tối ưu gì, không thử mốc giờ nào khác.

Để quy ra tiền, mình thêm một quy tắc thô: đúng mốc 10:00, giờ đầu tăng thì long, giảm thì short (đặt cược giảm), giữ đến ATC. Chi phí giả định là 0,4 điểm mỗi lượt khứ hồi, tức 40.000 đồng/hợp đồng.

Số liệu nói gì

Mốc so sánh trước: đồng xu

Nếu giờ đầu chẳng báo gì thì phần còn lại đi cùng chiều khoảng một nửa số phiên, như tung đồng xu. Thực tế là 52,7%. Có lệch khỏi đồng xu, nhưng lệch rất ít.

Hệ số tương quan giữa hai khúc là 0,120. Thử thêm kiểu tương quan theo thứ hạng (ít bị vài phiên cực đoan chi phối) cũng ra 0,129, gần như y hệt. Theo thang ở hộp phía trên, đây là mức rất yếu.

![Giờ đầu và phần còn lại của phiên: mỗi chấm là một ngày](https://quantvn.com/blog-assets/gio-dau-tien-phai-sinh-vn30f1m/scatter.png)

Hình 1: Giờ đầu và phần còn lại của phiên: mỗi chấm là một ngày

Trục ngang là lợi suất giờ đầu, trục dọc là lợi suất phần còn lại, mỗi chấm là một phiên. Đám mây gần như tròn, chỉ hơi nghiêng lên. Đường cam có hệ số góc 0,282, thoai thoải chứ chẳng dốc gì. Đó là xu hướng trung bình của cả đám mây. Xét từng ngày thì các chấm nằm rải rác khắp bốn góc, ngày giờ đầu tăng mà chiều sập vẫn đầy ra đó.

Tách theo chiều thì rõ hơn. Ở 1.214 phiên giờ đầu tăng, phần còn lại trung bình +0,110%. Ở 1.027 phiên giờ đầu giảm, phần còn lại trung bình -0,159%, khoảng 194.500 đồng/hợp đồng. Phía giảm có quán tính rõ hơn phía tăng, khớp với cảm giác chung của dân trade: thị trường lên bằng cầu thang, xuống bằng thang máy.

Chia 5 nhóm: quán tính nằm ở hai đầu

Một con số tương quan có thể che mất cấu trúc bên trong, nên mình xếp các phiên theo độ mạnh của giờ đầu rồi chia nhóm. Nếu có quán tính thật thì cột phải thấp ở nhóm giờ đầu giảm mạnh nhất và cao ở nhóm tăng mạnh nhất.

![Trung bình đoạn sau, chia theo 5 nhóm của đoạn đầu](https://quantvn.com/blog-assets/gio-dau-tien-phai-sinh-vn30f1m/quintiles.png)

Hình 2: Trung bình đoạn sau, chia theo 5 nhóm của đoạn đầu

Hình đi đúng hướng quán tính, có điều không đều tay. Nhóm Q1 nổi bật nhất: phần còn lại trung bình -0,285% với t-stat -4,08, rất khó đổ cho may. Nhóm giữa Q3 nằm sát 0 như mong đợi.

Phía tăng thì lạ hơn một chút: Q4 (+0,145%) lại nhỉnh hơn Q5 (+0,123%), dù cả hai đều qua ngưỡng t-stat quen thuộc. Có thể những phiên giờ đầu tăng quá mạnh đôi khi bị chốt lời bớt, cũng có thể chỉ là nhiễu. Với chừng này mẫu mình không dám khẳng định bên nào.

Tín hiệu đáng tin nhất vì thế nằm ở những phiên giờ đầu bị đạp mạnh. Những ngày giờ đầu chỉ nhích nhẹ thì gần như không có gì để đọc.

Còn 30 phút đầu và đoạn cuối phiên?

Đây là phần mình kỳ vọng nhất, và cũng là phần thất vọng nhất. Tương quan giữa 30 phút đầu và đoạn cuối phiên chỉ có 0,039, sát 0.

Chia nhóm cũng vậy. Duy nhất nhóm Q1 có đoạn cuối trung bình -0,092% với t-stat -2,39, vừa qua ngưỡng. Các nhóm còn lại đều trong vùng nhiễu, nhóm tăng mạnh nhất chỉ +0,029% với t-stat 0,81.

Hiệu ứng intraday momentum kiểu Mỹ, ít nhất theo cách đo này, không chuyển sang được VN30F1M. Lý do có thể nằm ở cấu trúc riêng của thị trường mình, ví dụ đoạn cuối có phiên ATC gom lệnh, nhưng mình chưa kiểm tra nên không dám kết luận.

Quy ra tiền: quy tắc "10:00 đi theo giờ đầu"

Tương quan yếu như vậy, nhưng nhân lên hơn hai nghìn phiên thì sao? Quy tắc thô cho kết quả trung bình +1,502 điểm mỗi phiên trước chi phí, khoảng 150.200 đồng/hợp đồng, với t-stat 5,29. Về mặt thống kê, cái này rất khó do ăn may. Còn có kiếm tiền thật được không lại là chuyện khác.

Tỷ lệ phiên có lãi chỉ 52,0%. Mỗi ngày gần như là ván năm ăn năm thua, lợi thế chỉ hiện ra khi cộng dồn rất nhiều phiên. Ai kỳ vọng "đánh theo giờ đầu là ăn" sẽ gặp rất nhiều ngày ăn tát liên tiếp.

![Lãi/lỗ cộng dồn của quy tắc 'đi theo giờ đầu'](https://quantvn.com/blog-assets/gio-dau-tien-phai-sinh-vn30f1m/equity.png)

Hình 3: Lãi/lỗ cộng dồn của quy tắc 'đi theo giờ đầu'

Hai đường trên hình là lãi cộng dồn trước và sau chi phí. Cả giai đoạn, quy tắc gom được 3.412 điểm trước phí (341.160.000 đồng/hợp đồng). Trừ chi phí xong còn 2.515 điểm (251.520.000 đồng/hợp đồng), t-stat còn 3,90. Khoảng hở giữa hai đường chính là phần chi phí ăn mất, và nó chẳng hề nhỏ so với lãi gộp, giống mua món hàng lời mỏng mà phí ship không rẻ.

Đường đi cũng có đoạn rất khó chịu: mức sụt lớn nhất từ đỉnh là -314 điểm, tương đương 31.420.000 đồng/hợp đồng (trước chi phí). Với người đang ký quỹ vài hợp đồng, chừng đó đủ làm nghi ngờ cả nhân sinh.

Có phải chỉ ăn may một giai đoạn?

Mình cắt dữ liệu thành ba khúc thời gian, đều tính trước chi phí. Khúc 2017-2020 đẹp nhất, trung bình +1,730 điểm mỗi phiên.

Hai khúc sau thấp hơn một chút và sát nhau: 2021-2023 được +1,345 điểm/phiên, 2024-2026 được +1,394 điểm/phiên. Cả ba đều dương và cùng cỡ, hiệu ứng không nằm gọn trong một năm sốt nóng nào rồi biến mất. Đây là điểm làm mình tin nó hơn một chút.

Chỗ mình có thể sai

Lỗ hổng lớn nhất là giá vào lệnh lúc 10:00. Mình dùng giá khớp cuối cùng trước mốc đó, nhưng ngoài đời, đặt lệnh đúng lúc ấy bạn có thể khớp xấu hơn vài tick (bước giá nhỏ nhất). Tệ hơn, những phiên giờ đầu bị đạp mạnh chính là những phiên tín hiệu rõ nhất, và cũng là lúc trượt giá dễ cao nhất. Lợi thế mỗi phiên vốn đã mỏng, vài tick trượt giá là đủ gặm mất một phần đáng kể.

Mức chi phí mình dùng cũng chỉ là giả định. Phí thật khác nhau tùy công ty chứng khoán, trượt giá thì phụ thuộc thanh khoản từng thời điểm. Nếu chi phí thực của bạn cao hơn đáng kể, đường sau chi phí sẽ bẹp hơn nhiều so với hình.

Cuối cùng là chuyện chọn mốc giờ. Mình không tối ưu tham số, nhưng chọn 10:00 và 14:00 vẫn là lựa chọn của người làm nghiên cứu. Mốc tròn và phổ biến nên rủi ro "chọn đẹp" theo mình là thấp, nhưng không bằng 0.

Code để anh em tự chạy lại

Toàn bộ phép tính nằm gọn trong đoạn Python dưới đây.

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

# bars: nến 1 phút VN30F1M với các cột date (ngày), minute (phút tính từ 0:00, giờ VN),
#       open, high, low, close. Ví dụ:
# bars = pd.read_parquet("vn30f1m_1m.parquet")
# bars["ts"] = bars["ts"].dt.tz_convert("Asia/Ho_Chi_Minh")
# bars["date"] = bars["ts"].dt.tz_localize(None).dt.normalize()
# bars["minute"] = bars["ts"].dt.hour * 60 + bars["ts"].dt.minute

BLUE, ORANGE, AXIS = "#2a78d6", "#eb6834", "#c3c2b7"
COST_PTS = 0.4
def pct_axis(ax, axis="y", decimals=1):
    fmt = FuncFormatter(lambda v, _: f"{v*100:.{decimals}f}%")
    (ax.yaxis if axis == "y" else ax.xaxis).set_major_formatter(fmt)
def zero_line(ax, axis="y"):
    (ax.axhline if axis == "y" else ax.axvline)(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 intraday_segments(bars: pd.DataFrame) -> pd.DataFrame:
    """Chia mỗi phiên thành các đoạn: giờ đầu (9:00-10:00), phần còn lại (10:00-đóng cửa),
    30 phút đầu (9:00-9:30) và đoạn cuối (14:00-đóng cửa ATC 14:45)."""
    g = bars.groupby("date")
    seg = pd.DataFrame({
        "p_open": g["open"].first(),
        "p_0930": price_at(bars, "09:30"),
        "p_1000": price_at(bars, "10:00"),
        "p_1400": price_at(bars, "14:00"),
        "p_close": g["close"].last(),
        "n_bars": g.size(),
    })
    seg = seg[seg["n_bars"] >= 200]  # bỏ các phiên thiếu dữ liệu
    seg["first_hour"] = seg["p_1000"] / seg["p_open"] - 1
    seg["rest_of_day"] = seg["p_close"] / seg["p_1000"] - 1
    seg["first_30"] = seg["p_0930"] / seg["p_open"] - 1
    seg["last_leg"] = seg["p_close"] / seg["p_1400"] - 1
    seg["pts_rest"] = seg["p_close"] - seg["p_1000"]
    return seg


def quintile_means(seg: pd.DataFrame, signal: str, target: str) -> pd.DataFrame:
    """Chia `signal` thành 5 nhóm bằng nhau, tính thống kê của `target` trong từng nhóm."""
    q = pd.qcut(seg[signal], 5, labels=["Q1\n(giảm mạnh nhất)", "Q2", "Q3", "Q4", "Q5\n(tăng mạnh nhất)"])
    return pd.DataFrame({k: summarize(g[target]) for k, g in seg.groupby(q, observed=True)}).T


def follow_first_hour(seg: pd.DataFrame, cost_pts: float = COST_PTS) -> pd.DataFrame:
    """Quy tắc: 10:00 vào lệnh cùng chiều giờ đầu, giữ đến đóng cửa. Lãi/lỗ tính bằng điểm/hợp đồng."""
    side = np.sign(seg["first_hour"])
    gross = side * seg["pts_rest"]
    return pd.DataFrame({"gross": gross, "net": gross - cost_pts * (side != 0)})


def plot_scatter(seg: pd.DataFrame):
    fig, ax = plt.subplots(figsize=(7.6, 5.2))
    x, y = seg["first_hour"], seg["rest_of_day"]
    ax.scatter(x, y, s=9, color=BLUE, alpha=0.35, linewidths=0, zorder=2)
    slope, icpt = np.polyfit(x, y, 1)
    xs = np.linspace(x.quantile(0.005), x.quantile(0.995), 50)
    ax.plot(xs, icpt + slope * xs, color=ORANGE, lw=2, zorder=3, label=f"Đường hồi quy (hệ số góc {slope:.2f})")
    zero_line(ax, "y")
    zero_line(ax, "x")
    ax.set_xlim(x.quantile(0.005), x.quantile(0.995))
    ax.set_ylim(y.quantile(0.005), y.quantile(0.995))
    pct_axis(ax, "x", 1)
    pct_axis(ax, "y", 1)
    ax.grid(axis="x", color="#e1e0d9", linewidth=0.8)
    ax.set_xlabel("Lợi suất giờ đầu (9:00 → 10:00)")
    ax.set_ylabel("Lợi suất phần còn lại (10:00 → đóng cửa)")
    ax.set_title("Mỗi chấm là một phiên giao dịch")
    ax.legend(loc="upper left")
    return fig


seg = intraday_segments(bars)
print(seg[["first_hour", "rest_of_day"]].corr())
print(quintile_means(seg, "first_hour", "rest_of_day")[["n", "mean", "win_rate", "t_stat"]])
pnl = follow_first_hour(seg)
print(summarize(pnl["gross"]), pnl.sum())
plot_scatter(seg).savefig("scatter.png", dpi=150)

Luồng logic khá đơn giản. Hàm price_at lấy giá khớp cuối cùng trước một mốc giờ của mỗi ngày, ví dụ trước 10:00. Hàm intraday_segments dùng các giá đó để tính lợi suất giờ đầu, phần còn lại, 30 phút đầu và đoạn cuối phiên, đồng thời loại các phiên có quá ít nến. quintile_means chia các phiên thành nhóm theo tín hiệu rồi gọi summarize để tính trung bình, tỷ lệ dương và t-stat cho từng nhóm. Cuối cùng follow_first_hour lấy dấu của giờ đầu (dấu cộng là long, dấu trừ là short), nhân với số điểm đi được từ mốc vào lệnh đến đóng cửa, rồi trừ chi phí cho mỗi lượt có giao dịch.

Dùng được gì khi giao dịch

Mình coi kết quả này như gió thuận gió ngược, chưa đủ để thành hệ thống. Nếu bạn đang định long mà giờ đầu bị đạp mạnh, số liệu bảo rằng bạn đang đi ngược gió, nên cân nhắc lại điểm vào hoặc khối lượng. Phía tăng cũng có gió thuận, có điều yếu hơn. Còn tín hiệu 30 phút đầu dự báo cuối phiên thì mình không dựa vào.

Phái sinh có đòn bẩy và lỗ chạy nhanh, nên một lợi thế mỏng như thế chỉ có ý nghĩa khi bạn chịu được những chuỗi thua dài và chi phí thực của bạn đủ thấp.

Hỏi đáp

Tương quan yếu xìu vậy mà cũng viết cả bài, bác có làm quá không? Xét từng ngày thì gần như vô dụng, cái này mình đồng ý. Nhưng lặp lại qua hơn hai nghìn phiên, một lợi thế mỏng vẫn cộng dồn thành con số khó đổ cho may. Đáng nghiên cứu, nhưng không đáng để bạn tự tin all-in một phiên nào cả.

Lãi cộng dồn sau phí nhìn to thế, sao không ai giàu lên nhờ cái này? Đó là tổng của cả chục năm dữ liệu, chia ra từng phiên thì chẳng bao nhiêu, và trên đường đi có lúc sụt rất sâu. Thêm nữa, giá lúc 10:00 trong dữ liệu chưa chắc khớp được ngoài đời. Trượt giá thực tế cao hơn giả định một chút là lợi thế co lại rất nhanh.

Sao không thử mốc giờ khác, biết đâu đẹp hơn? Chắc chắn sẽ có mốc nào đó đẹp hơn, vì thử đủ nhiều thì kiểu gì cũng có cái đẹp do may. Mình cố tình giữ mốc tròn để khỏi tự lừa mình. Nếu muốn tự thử, mình gợi ý một biến thể có cơ sở hơn: chỉ vào lệnh khi giờ đầu rơi vào nhóm Q1, nhóm có tín hiệu rõ nhất trong bài, rồi xem sau chi phí còn lại bao nhiêu.

Kết

Giờ đầu có báo trước phần còn lại của phiên VN30F1M, nhưng rất khẽ: cùng chiều 52,7% số phiên, tương quan 0,12, rõ nhất ở những phiên bị đạp mạnh từ sáng. Câu "giờ đầu định hướng cả phiên" đúng về hướng nhưng nói quá về độ mạnh, còn chuyện 30 phút đầu báo trước đoạn cuối phiên như ở Mỹ thì dữ liệu Việt Nam chưa ủng hộ.

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ì.