import pandas as pdimport numpy as npimport matplotlib.pyplot as pltimport matplotlib.ticker as mtickerimport matplotlib.dates as mdatesimport statsmodels.api as smimport statsmodels.formula.api as smffrom scipy import statsimport warningswarnings.filterwarnings("ignore")plt.rcParams.update({"figure.dpi": 150,"axes.spines.top": False,"axes.spines.right": False,"font.size": 11,})
Một câu hỏi nền tảng trong nghiên cứu thị trường vốn là liệu thông tin kế toán có thực sự quan trọng đối với nhà đầu tư hay không. Nếu báo cáo tài chính chỉ đóng gói lại những gì thị trường đã biết từ các nguồn khác, chẳng hạn báo chí, báo cáo của nhà phân tích, định hướng của ban lãnh đạo, dữ liệu vĩ mô, thì toàn bộ bộ máy lập báo cáo tài chính phần lớn là thừa đối với quá trình khám phá giá. Ngược lại, nếu các thông báo lợi nhuận kích hoạt những thay đổi đo lường được trong hành vi giao dịch, chúng ta có bằng chứng rằng hệ thống kế toán tạo ra thông tin mà thị trường trước đó chưa có.
Beaver (1968) đã trả lời câu hỏi này bằng cách xem xét liệu khối lượng giao dịch và độ biến động lợi nhuận có tăng lên quanh ngày các doanh nghiệp công bố lợi nhuận năm hay không. Logic rất trực tiếp: nếu một thông báo lợi nhuận chứa thông tin mới, nó sẽ làm thay đổi niềm tin của nhà đầu tư. Niềm tin thay đổi khiến một số nhà đầu tư giao dịch, làm tăng khối lượng. Và trong chừng mực thông báo giải tỏa được sự bất định, giá cổ phiếu sẽ điều chỉnh, tạo ra độ biến động lợi nhuận bất thường. Cả hai kênh (tức là khối lượng và độ biến động) đều cung cấp những góc nhìn riêng biệt về hàm lượng thông tin của lợi nhuận.
Thiết kế thực nghiệm so sánh hoạt động giao dịch của doanh nghiệp trong một cửa sổ thông báo hẹp (những ngày ngay quanh thời điểm công bố lợi nhuận) với hoạt động giao dịch của chính doanh nghiệp đó trong một cửa sổ không thông báo (những ngày tương đương, cách xa sự kiện). Nếu thông báo mang thông tin, cửa sổ thông báo sẽ có khối lượng và độ biến động cao hơn so với giai đoạn không thông báo.
25.0.1 Hai tín hiệu khác biệt: khối lượng và độ biến động
Mặc dù khối lượng và độ biến động lợi nhuận đều được dùng để đo phản ứng của nhà đầu tư, hai đại lượng này phản ánh những hiện tượng kinh tế khác nhau. Sự phân biệt này, được hình thức hóa trong các nghiên cứu lý thuyết về sau, rất quan trọng cho việc diễn giải kết quả:
Độ biến động lợi nhuận phản ánh mức điều chỉnh trung bình trong niềm tin của nhà đầu tư. Khi toàn thị trường bị bất ngờ bởi một con số lợi nhuận, giá biến động mạnh. Độ lớn của phần dư lợi nhuận bình phương (hoặc tuyệt đối) đo lường mức độ thông báo đã dịch chuyển nhận định đồng thuận. Đây là thước đo hàm lượng thông tin tổng hợp của thông báo (Kim và Verrecchia 1991).
Khối lượng giao dịch phản ánh sự bất đồng giữa các nhà đầu tư về hàm ý của thông báo. Ngay cả một thông báo làm giá thay đổi rất ít cũng có thể tạo ra giao dịch sôi động nếu các nhà đầu tư diễn giải cùng một tín hiệu theo những cách khác nhau. Chẳng hạn, một số người cho rằng kết quả lợi nhuận vượt kỳ vọng là bền vững trong khi những người khác cho rằng nó chỉ mang tính tạm thời (Kandel và Pearson 1995). Do đó, khối lượng phản ánh hàm lượng thông tin khác biệt của thông báo.
Karpoff (1987) khảo sát mối quan hệ lý thuyết và thực nghiệm giữa hai biến này. Hiểu biết then chốt là khối lượng không đơn thuần là phiên bản nhiễu hơn của độ biến động; nó cung cấp thông tin độc lập về cách thị trường xử lý dữ liệu kế toán.
25.0.2 Vì sao là Việt Nam?
Thị trường chứng khoán Việt Nam là bối cảnh đặc biệt để nghiên cứu phản ứng với thông báo, vì một số lý do sau:
Môi trường thông tin. Các doanh nghiệp niêm yết Việt Nam ít bị soi xét trước thông báo hơn so với các doanh nghiệp Mỹ. Mức độ bao phủ của nhà phân tích còn thưa (hầu hết doanh nghiệp ngoài rổ VN30 không có phân tích của các tổ chức). Định hướng lợi nhuận của ban lãnh đạo hiếm khi được đưa ra. Điều này tạo ra một “khoảng trống thông tin” dày hơn để thông báo lợi nhuận lấp đầy, có khả năng khuếch đại cả phản ứng về khối lượng lẫn độ biến động.
Sự áp đảo của nhà đầu tư cá nhân. Hơn 80% khối lượng giao dịch hằng ngày trên HOSE là của nhà đầu tư cá nhân. Nhà đầu tư cá nhân có thể xử lý thông tin lợi nhuận khác với các tổ chức, với nhiều thiên lệch hành vi hơn, ít khả năng phân tích cơ bản hơn và khung thời gian giao dịch khác. Điều này ảnh hưởng đến cả thời điểm lẫn độ lớn của phản ứng thị trường.
Biên độ giá. HOSE áp dụng biên độ giá hằng ngày \(\pm\) 7% và HNX là \(\pm\) 10%. Khi một thông báo lợi nhuận đủ bất ngờ, cổ phiếu có thể chạm biên độ giá, khiến thị trường không thể phản ánh đầy đủ thông tin trong một phiên giao dịch. Điều này tạo ra một mô hình đặc trưng về điều chỉnh kéo dài nhiều ngày mà các nghiên cứu gốc trên thị trường Mỹ không có.
Thời điểm công bố thông tin. Quy định của Việt Nam yêu cầu báo cáo tài chính năm đã kiểm toán phải được công bố trong vòng 90 ngày kể từ ngày kết thúc năm tài chính. Dữ liệu tài chính quý (chưa kiểm toán) phải được công bố trong vòng 20 ngày. Sự chồng lấn giữa thông báo quý và thông báo năm, cùng độ trễ thay đổi giữa ngày kết thúc năm tài chính và ngày thông báo, tạo ra tính không đồng nhất về hàm lượng thông tin của các thông báo lợi nhuận năm.
Cấu trúc thị trường. Việt Nam vận hành khớp lệnh liên tục trong giờ giao dịch (9:00–11:30 và 13:00–14:30 trên HOSE; 9:00–11:30 và 13:00–15:00 trên HNX), với các phiên khớp lệnh định kỳ xác định giá mở cửa và đóng cửa. Vi cấu trúc này ảnh hưởng đến các mô hình khám phá giá trong ngày quanh thời điểm thông báo.
25.1 Khung lý thuyết
25.1.1 Giả thuyết hàm lượng thông tin
Gọi \(P_{i,t}\) là giá của cổ phiếu \(i\) vào ngày \(t\). Theo giả thuyết thị trường hiệu quả, giá phản ánh toàn bộ thông tin công khai sẵn có:
trong đó \(D_{i,t+s}\) là cổ tức tương lai, \(r\) là tỷ suất chiết khấu, và \(\Omega_t\) là tập thông tin tại thời điểm \(t\).
Khi doanh nghiệp \(i\) công bố lợi nhuận \(X_i\) tại thời điểm \(\tau\), tập thông tin được mở rộng: \(\Omega_{\tau^+} = \Omega_{\tau^-} \cup \{X_i\}\). Nếu thông báo chứa thông tin mới, nghĩa là \(X_i \notin \Omega_{\tau^-}\), thì nói chung:
\[
P_{i,\tau^+} \neq P_{i,\tau^-}
\tag{25.2}\]
Độ lớn của thay đổi giá phản ánh thành phần bất ngờ của thông báo:
25.1.2 Khối lượng như tín hiệu của niềm tin không đồng nhất
Kim và Verrecchia (1991) xây dựng một mô hình trong đó khối lượng giao dịch được quyết định bởi độ chính xác của thông tin riêng của nhà đầu tư so với độ chính xác của tín hiệu công khai. Định nghĩa \(V_{i,\tau}\) là khối lượng giao dịch của cổ phiếu \(i\) tại thời điểm \(\tau\). Trong khung phân tích của họ:
trong đó \(\Omega^{(j)}_{\tau^-}\) là tập thông tin riêng của nhà đầu tư \(j\) và \(\varepsilon_i\) là nhiễu. Khối lượng tăng lên khi các nhà đầu tư bất đồng về ý nghĩa của thông báo, một điều kiện mà Kandel và Pearson (1995) gọi là diễn giải khác biệt.
Sự phân biệt này có ý nghĩa với Việt Nam. Trong một thị trường bị chi phối bởi các nhà đầu tư cá nhân với thông tin và năng lực phân tích không đồng nhất, chúng ta có thể kỳ vọng phản ứng về khối lượng lớn không tương xứng so với phản ứng về giá, vì mức độ bất đồng cao ngay cả khi mức điều chỉnh giá tổng hợp là khiêm tốn.
25.1.3 Đo lường hàm lượng thông tin
Chúng ta sử dụng hai thước đo bổ sung cho nhau:
Độ biến động lợi nhuận bất thường. Với mỗi cặp doanh nghiệp–ngày, tính lợi nhuận điều chỉnh theo thị trường bình phương:
\[
U^2_{i,t} = (R_{i,t} - R_{m,t})^2
\tag{25.5}\]
trong đó \(R_{i,t}\) là lợi nhuận của cổ phiếu \(i\) và \(R_{m,t}\) là lợi nhuận thị trường. Theo giả thuyết không về việc không có hàm lượng thông tin, \(E[U^2_{i,t}]\) phải như nhau trong cửa sổ thông báo và cửa sổ không thông báo. Beaver (1968) chuẩn hóa thước đo này bằng cách chia cho \(U^2\) trung bình của doanh nghiệp trong các tuần không có thông báo.
Khối lượng tương đối. Với mỗi cặp doanh nghiệp–ngày, tính:
trong đó \(\overline{Vol}_i\) là khối lượng giao dịch trung bình hằng ngày của doanh nghiệp trong cửa sổ sự kiện. Giá trị \(RV > 1\) cho thấy giao dịch trên mức trung bình. Beaver (1968) dùng một cách tiếp cận tương tự, tính “tỷ lệ khối lượng” so với mức trung bình của các giai đoạn không công bố báo cáo.
25.2 Dữ liệu và xây dựng biến
25.2.1 Yêu cầu về dữ liệu
Phân tích cần ba thành phần dữ liệu:
Bảng 25.1: Nguồn dữ liệu cho phân tích hàm lượng thông tin
Dữ liệu
Nguồn
Các trường chính
Ngày thông báo lợi nhuận
Báo cáo tài chính/hồ sơ công bố
Mã cổ phiếu, ngày kết thúc năm tài chính, ngày thông báo
Lợi nhuận cổ phiếu hằng ngày
Dữ liệu sàn giao dịch (HOSE/HNX)
Mã cổ phiếu, ngày, giá đóng cửa, lợi nhuận
Khối lượng giao dịch hằng ngày
Dữ liệu sàn giao dịch (HOSE/HNX)
Mã cổ phiếu, ngày, khối lượng (cổ phiếu hoặc VND)
25.2.2 Lịch ngày giao dịch
Một thành phần hạ tầng quan trọng là lịch ngày giao dịch, ánh xạ các ngày dương lịch sang chỉ số ngày giao dịch tuần tự. Điều này cho phép chúng ta đếm đúng “20 ngày giao dịch trước thông báo”, bỏ qua cuối tuần và ngày lễ.
Construct a trading day calendar for HOSE/HNX
def build_trading_calendar( start_date: str="2008-01-01", end_date: str="2024-12-31",) -> pd.DataFrame:""" Build a trading day calendar for Vietnamese stock exchanges. Vietnamese exchanges trade Monday–Friday, with closures for: - Tết Nguyên Đán (Lunar New Year, ~5 days) - Ngày Giỗ Tổ Hùng Vương (Hung Kings' Commemoration) - Ngày Giải phóng miền Nam (Reunification Day, Apr 30) - Ngày Quốc tế Lao động (Labour Day, May 1) - Quốc khánh (National Day, Sep 2) - Tết Dương lịch (New Year, Jan 1) For simulation, we approximate by removing weekends and ~10 holidays/year. """ all_dates = pd.date_range(start_date, end_date, freq="B") # business days# Remove approximate Vietnamese public holidays# (In practice, use the exact holiday calendar from SSC/HOSE) rng = np.random.default_rng(42) n_holidays_per_year =10 years = all_dates.year.unique() holidays = []for yr in years: yr_dates = all_dates[all_dates.year == yr]iflen(yr_dates) > n_holidays_per_year: idx = rng.choice(len(yr_dates), n_holidays_per_year, replace=False) holidays.extend(yr_dates[idx]) trading_dates = all_dates.difference(pd.DatetimeIndex(holidays)) cal = pd.DataFrame({"date": trading_dates,"td": np.arange(1, len(trading_dates) +1), })return caltrading_cal = build_trading_calendar()print(f"Trading calendar: {len(trading_cal):,} trading days, "f"{trading_cal['date'].min().date()} to {trading_cal['date'].max().date()}")
Trading calendar: 4,266 trading days, 2008-01-01 to 2024-12-31
25.2.3 Căn chỉnh ngày thông báo
Các thông báo lợi nhuận có thể xảy ra vào những ngày không giao dịch (cuối tuần, ngày lễ). Khi đó, chúng ta gán thông báo cho ngày giao dịch kế tiếp gần nhất, vì đó là cơ hội đầu tiên để thị trường phản ứng. Chúng ta cũng cần xử lý trường hợp thông báo diễn ra sau khi thị trường đóng cửa. Lý tưởng nhất, những thông báo này nên được gán cho ngày giao dịch tiếp theo. Nếu không có dữ liệu thời điểm trong ngày (phổ biến trong các công bố thông tin của Việt Nam), chúng ta dùng ngày thông báo như được báo cáo.
Map announcement dates to trading day indices
def build_announcement_mapper(trading_cal: pd.DataFrame) -> pd.DataFrame:""" Create a mapping from every calendar date to the next available trading day index. This handles announcements on weekends/holidays. """ min_date = trading_cal["date"].min() max_date = trading_cal["date"].max() all_dates = pd.date_range(min_date, max_date, freq="D") mapper = pd.DataFrame({"annc_date": all_dates}) mapper = mapper.merge( trading_cal.rename(columns={"date": "annc_date"}), on="annc_date", how="left", )# Forward-fill: assign non-trading dates to next trading day mapper["td"] = mapper["td"].bfill() mapper["td"] = mapper["td"].astype("Int64")return mapperannc_mapper = build_announcement_mapper(trading_cal)
25.2.4 Tạo dữ liệu mô phỏng cho thị trường Việt Nam
Chúng ta tạo một bộ dữ liệu bảng (panel) tổng hợp các doanh nghiệp niêm yết Việt Nam với các đặc tính thực tế để minh họa phương pháp. Mô phỏng này cài sẵn một hiệu ứng thông báo lợi nhuận, tức khối lượng và độ biến động tăng cao vào ngày 0 và +1, mà sau đó chúng ta tìm cách phát hiện bằng khung phân tích của Beaver (1968).
Generate synthetic Vietnamese earnings announcement data
def generate_beaver_data( n_firms: int=300, years: list[int] =None, seed: int=2024, days_window: int=20,) ->tuple[pd.DataFrame, pd.DataFrame]:""" Generate: 1. earn_annc: DataFrame of earnings announcement dates 2. daily_data: DataFrame of daily returns and volume The DGP embeds: - An announcement-day return volatility spike (2-3x normal) - An announcement-day volume spike (1.5-2.5x normal) - Price limit truncation at ±7% (HOSE) - Cross-sectional variation by firm size and exchange """if years isNone: years =list(range(2012, 2024)) rng = np.random.default_rng(seed) tc = build_trading_calendar() am = build_announcement_mapper(tc) exchanges = ["HOSE", "HNX"] price_limits = {"HOSE": 0.07, "HNX": 0.10}# ── Generate announcement dates ── annc_records = []for i inrange(n_firms): ticker =f"VN{i:04d}" exchange = rng.choice(exchanges, p=[0.6, 0.4])# Firm size (log market cap in VND billions) log_mcap = rng.normal(6.5, 1.5)for yr in years:# Fiscal year ends Dec 31; announcement 60-90 days later fy_end = pd.Timestamp(f"{yr}-12-31") annc_lag = rng.integers(45, 90) annc_date = fy_end + pd.Timedelta(days=int(annc_lag)) annc_records.append({"ticker": ticker,"fiscal_year": yr,"datadate": fy_end,"annc_date": annc_date,"exchange": exchange,"log_mcap": log_mcap, }) earn_annc = pd.DataFrame(annc_records)# Map to trading day index earn_annc["annc_date_dt"] = pd.to_datetime(earn_annc["annc_date"]) earn_annc = earn_annc.merge( am[["annc_date", "td"]].rename(columns={"td": "event_td"}), left_on="annc_date_dt", right_on="annc_date", how="left", suffixes=("", "_map"), ) earn_annc = earn_annc.dropna(subset=["event_td"]) earn_annc["event_td"] = earn_annc["event_td"].astype(int) earn_annc["start_td"] = earn_annc["event_td"] - days_window earn_annc["end_td"] = earn_annc["event_td"] + days_window# Map start/end td back to dates td_to_date = tc.set_index("td")["date"] earn_annc["start_date"] = earn_annc["start_td"].map(td_to_date) earn_annc["end_date"] = earn_annc["end_td"].map(td_to_date) earn_annc = earn_annc.dropna(subset=["start_date", "end_date"])# ── Generate daily return and volume data ── daily_records = []for _, row in earn_annc.iterrows(): ticker = row["ticker"] exchange = row["exchange"] plimit = price_limits[exchange] log_mcap = row["log_mcap"] event_td = row["event_td"]# Base volatility inversely related to size base_vol =0.015+0.01* np.exp(-0.3* log_mcap)# Base volume (shares/day) base_volume = np.exp(log_mcap + rng.normal(0, 0.5)) *1000# Window trading days window_tds = tc[ (tc["td"] >= row["start_td"]) & (tc["td"] <= row["end_td"]) ].copy()for _, td_row in window_tds.iterrows(): date = td_row["date"] td = td_row["td"] relative_td = td - event_td# Market return (common factor) mkt_ret = rng.normal(0.0003, 0.012)# Firm-specific return# Announcement effect: elevated volatility on days 0, +1if relative_td in [0, 1]: vol_mult = rng.uniform(2.0, 3.5)elifabs(relative_td) <=2: vol_mult = rng.uniform(1.2, 1.8)else: vol_mult =1.0 firm_shock = rng.normal(0, base_vol * vol_mult) raw_ret = mkt_ret + firm_shock# Apply price limits ret = np.clip(raw_ret, -plimit, plimit) ret_mkt = ret - mkt_ret# Volume: spike on announcement daysif relative_td in [0, 1]: vol_spike = rng.uniform(1.5, 2.8)elifabs(relative_td) <=3: vol_spike = rng.uniform(1.0, 1.5)else: vol_spike = rng.uniform(0.7, 1.3) volume = base_volume * vol_spike * np.exp(rng.normal(0, 0.3)) daily_records.append({"ticker": ticker,"fiscal_year": row["fiscal_year"],"date": date,"td": td,"relative_td": relative_td,"ret": ret,"mkt_ret": mkt_ret,"ret_mkt": ret_mkt,"vol": max(volume, 100),"exchange": exchange,"log_mcap": log_mcap, }) daily_data = pd.DataFrame(daily_records)return earn_annc, daily_dataearn_annc, daily_data = generate_beaver_data(n_firms=300, seed=2024)print(f"Announcements: {len(earn_annc):,}")print(f"Daily observations: {len(daily_data):,}")
Announcements: 3,600
Daily observations: 147,600
25.3 Minh họa với một sự kiện đơn lẻ
Trước khi mở rộng ra toàn bộ mẫu, chúng ta lần theo phương pháp trên một thông báo lợi nhuận duy nhất để xây dựng trực giác và kiểm tra từng bước.
25.3.1 Chọn sự kiện
Select a single earnings announcement for demonstration
Chúng ta trích xuất dữ liệu của 41 ngày giao dịch (từ 20 ngày trước đến 20 ngày sau thông báo) và tính các thước đo chính.
Extract and process data for the single event
single_daily = daily_data[ (daily_data["ticker"] == single_ticker) & (daily_data["fiscal_year"] == single_fy)].copy()# Compute measuressingle_daily["sq_ret_mkt"] = single_daily["ret_mkt"] **2single_daily["abs_ret_mkt"] = single_daily["ret_mkt"].abs()# Relative volume: normalize by own averageavg_vol = single_daily["vol"].mean()single_daily["rel_vol"] = single_daily["vol"] / avg_volprint(f"Trading days in window: {len(single_daily)}")print(f"Average daily volume: {avg_vol:,.0f} shares")
Trading days in window: 41
Average daily volume: 3,780,757 shares
25.3.3 Trực quan hóa sự kiện đơn lẻ
Hình 25.1 và Hình 25.2 thể hiện mô hình khối lượng và độ biến động của thông báo lợi nhuận đơn lẻ này.
Hiện mã
fig, ax = plt.subplots()colors = ["firebrick"ifabs(t) <=1else"steelblue"for t in single_daily["relative_td"]]ax.bar(single_daily["relative_td"], single_daily["vol"], color=colors, edgecolor="white", width=0.8)ax.axhline(avg_vol, color="grey", linestyle="--", linewidth=1, label="Window average")ax.axvline(0, color="black", linewidth=0.5, alpha=0.3)ax.set_xlabel("Trading Days Relative to Announcement")ax.set_ylabel("Volume (shares)")ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x, _: f"{x/1e6:.1f}M"))ax.legend(fontsize=9)plt.tight_layout()plt.show()
Hình 25.1: Khối lượng giao dịch hằng ngày quanh một thông báo lợi nhuận đơn lẻ. Ngày 0 là ngày giao dịch đầu tiên vào hoặc sau ngày thông báo. Đường đứt nét đánh dấu mức trung bình của cửa sổ sự kiện.
Hiện mã
fig, ax = plt.subplots()ax.bar(single_daily["relative_td"], single_daily["sq_ret_mkt"], color=["firebrick"ifabs(t) <=1else"steelblue"for t in single_daily["relative_td"]], edgecolor="white", width=0.8)ax.axhline(single_daily["sq_ret_mkt"].mean(), color="grey", linestyle="--", linewidth=1, label="Window average")ax.axvline(0, color="black", linewidth=0.5, alpha=0.3)ax.set_xlabel("Trading Days Relative to Announcement")ax.set_ylabel("Squared Market-Adjusted Return")ax.legend(fontsize=9)plt.tight_layout()plt.show()
Hình 25.2: Lợi nhuận điều chỉnh theo thị trường bình phương quanh một thông báo lợi nhuận đơn lẻ. Các giá trị cao vào ngày 0 và +1 cho thấy thông báo đã đẩy giá cổ phiếu đi xa hơn mức mà biến động chung của thị trường dự báo.
Đọc các biểu đồ
Một sự kiện đơn lẻ vốn có nhiều nhiễu, các tin tức đặc thù khác của doanh nghiệp (tin đồn M&A, báo cáo phân tích, hành động của cơ quan quản lý) có thể tạo ra các đỉnh khối lượng và độ biến động vào những ngày không có thông báo. Giá trị của cách tiếp cận của Beaver (1968) nằm ở việc lấy trung bình trên nhiều sự kiện để tách hiệu ứng thông báo mang tính hệ thống khỏi nhiễu đặc thù.
25.4 Phân tích toàn mẫu
25.4.1 Tính các giá trị tổng hợp theo thời gian sự kiện
Với mỗi ngày sự kiện \(\tau\) tương đối so với thông báo, chúng ta tính trung bình chéo của hai thước đo trên tất cả các thông báo:
Hình 25.3 vẽ độ lệch chuẩn của lợi nhuận điều chỉnh theo thị trường theo ngày sự kiện, tách riêng cho từng năm tài chính. Một đỉnh rõ rệt vào ngày 0 và +1 là dấu hiệu đặc trưng của hàm lượng thông tin.
Hiện mã
fig, ax = plt.subplots(figsize=(9, 5))years_plot =sorted(event_summary["year"].unique())# Use a colormapcmap = plt.cm.viridis(np.linspace(0.1, 0.9, len(years_plot)))for idx, yr inenumerate(years_plot): sub = event_summary[event_summary["year"] == yr].sort_values("relative_td") ax.plot(sub["relative_td"], sub["sd_ret_mkt"], color=cmap[idx], alpha=0.6, linewidth=1.0, label=str(yr))# Overlay pooled meanpooled = event_summary_pooled.sort_values("relative_td")ax.plot(pooled["relative_td"], pooled["sd_ret_mkt"], color="firebrick", linewidth=2.5, linestyle="-", label="Pooled", zorder=10)ax.axvline(0, color="black", linewidth=0.5, alpha=0.3)ax.set_xlabel("Trading Days Relative to Announcement")ax.set_ylabel("Std Dev of Market-Adjusted Returns")ax.legend(fontsize=7, ncol=4, loc="upper right")plt.tight_layout()plt.show()
Hình 25.3: Độ lệch chuẩn của lợi nhuận điều chỉnh theo thị trường quanh các thông báo lợi nhuận, theo năm tài chính. Đỉnh nhất quán vào ngày 0 và +1 qua các năm là bằng chứng mạnh cho thấy các thông báo lợi nhuận năm truyền tải thông tin mới đến thị trường chứng khoán Việt Nam.
25.4.3 Khối lượng giao dịch quanh các thông báo
Hình 25.4 cho thấy khối lượng giao dịch tương đối theo ngày sự kiện. Phản ứng về khối lượng cung cấp sự xác nhận độc lập cho kết quả về độ biến động.
Hình 25.4: Khối lượng giao dịch tương đối trung bình quanh các thông báo lợi nhuận, theo năm tài chính. Khối lượng tăng vọt vào ngày 0 và +1, phù hợp với việc nhà đầu tư tích cực giao dịch dựa trên thông tin mới. Khối lượng cao kéo dài lâu hơn một chút so với đỉnh độ biến động, cho thấy sự bất đồng về hàm ý của thông báo cần thời gian để được giải quyết.
25.4.4 Các kiểm định thống kê chính thức
Beaver (1968) dựa vào quan sát trực quan và các lập luận thống kê không chính thức. Thực hành hiện đại đòi hỏi các kiểm định chính thức. Chúng ta kiểm định xem các thước đo trong cửa sổ thông báo có khác biệt đáng kể so với cửa sổ không thông báo hay không, bằng so sánh theo cặp.
Định nghĩa cửa sổ thông báo là các ngày \(\{-1, 0, +1\}\) và cửa sổ không thông báo là các ngày \(\{-20, \ldots, -5\} \cup \{+5, \ldots, +20\}\). Với mỗi sự kiện \(i\), chúng ta tính:
Theo giả thuyết không, \(E[\Delta U^2_i] = 0\). Chúng ta kiểm định giả thuyết này bằng kiểm định \(t\) trên các sự kiện.
Hiện mã
annc_window = [-1, 0, 1]non_annc_window =list(range(-20, -4)) +list(range(5, 21))def compute_window_means(df, var, window):"""Compute mean of var within a window for each event."""return ( df[df["relative_td"].isin(window)] .groupby(["ticker", "fiscal_year"])[var] .mean() )test_results = {}for var, label in [("sq_ret_mkt", "Squared Mkt-Adj Return"), ("abs_ret_mkt", "Absolute Mkt-Adj Return"), ("rel_vol", "Relative Volume")]: annc_mean = compute_window_means(daily_data, var, annc_window) non_annc_mean = compute_window_means(daily_data, var, non_annc_window)# Align on common events common = annc_mean.index.intersection(non_annc_mean.index) diff = annc_mean.loc[common] - non_annc_mean.loc[common] diff = diff.dropna() t_stat, p_val = stats.ttest_1samp(diff, 0) test_results[label] = {"Annc Window Mean": annc_mean.mean(),"Non-Annc Window Mean": non_annc_mean.mean(),"Difference": diff.mean(),"t-statistic": t_stat,"p-value": p_val,"N events": len(diff), }test_df = pd.DataFrame(test_results).T.round(4)test_df["N events"] = test_df["N events"].astype(int)test_df
Bảng 25.2: Các kiểm định chính thức về hàm lượng thông tin trong giai đoạn thông báo. Cửa sổ thông báo là các ngày {-1, 0, +1}; cửa sổ không thông báo loại trừ các ngày {-4, …, +4}. Các thống kê t lớn và giá trị p nhỏ là bằng chứng mạnh cho thấy cả độ biến động lợi nhuận lẫn khối lượng giao dịch đều tăng đáng kể trong cửa sổ thông báo.
Annc Window Mean
Non-Annc Window Mean
Difference
t-statistic
p-value
N events
Squared Mkt-Adj Return
0.0014
0.0003
0.0011
64.1353
0.0
3600
Absolute Mkt-Adj Return
0.0294
0.0132
0.0162
79.7318
0.0
3600
Relative Volume
1.7020
0.9206
0.7814
135.9351
0.0
3600
25.4.5 Tỷ lệ biến động chuẩn hóa
Theo cách tiếp cận của Beaver (1968), chúng ta tính tỷ lệ giữa lợi nhuận bình phương trong cửa sổ thông báo và lợi nhuận bình phương trong cửa sổ không thông báo. Theo giả thuyết không, tỷ lệ này bằng 1,0.
Hiện mã
fig, ax = plt.subplots()ratio = pooled["mean_std_sq_ret"]colors = ["firebrick"ifabs(t) <=1else"steelblue"for t in pooled["relative_td"]]ax.bar(pooled["relative_td"], ratio, color=colors, edgecolor="white")ax.axhline(1.0, color="grey", linestyle="--", linewidth=1, label="Expected under null")ax.axvline(0, color="black", linewidth=0.5, alpha=0.3)ax.set_xlabel("Trading Days Relative to Announcement")ax.set_ylabel("Standardized Squared Return (ratio)")ax.legend()plt.tight_layout()plt.show()
Hình 25.5: Lợi nhuận điều chỉnh theo thị trường bình phương đã chuẩn hóa theo ngày sự kiện (gộp tất cả các năm). Giá trị 1.0 cho biết độ biến động bình thường. Tỷ lệ vào ngày thông báo vượt xa 1.0, cho thấy việc công bố lợi nhuận tạo ra biến động lợi nhuận cao hơn nhiều so với mức bình thường.
25.5 Biến thiên chéo giữa các doanh nghiệp
Một hiệu ứng thông báo đồng nhất trên mọi doanh nghiệp sẽ là điều đáng ngạc nhiên. Lý thuyết dự báo rằng độ lớn của phản ứng thị trường phụ thuộc vào môi trường thông tin trước thông báo: các doanh nghiệp có tập thông tin phong phú hơn trước thông báo sẽ có phản ứng nhỏ hơn vì con số lợi nhuận ít mang tính tin mới hơn (Atiase 1985; Verrecchia 2001).
25.5.1 Theo quy mô doanh nghiệp
Các doanh nghiệp lớn hơn nhận được nhiều sự chú ý hơn từ nhà phân tích, truyền thông và nhà đầu tư tổ chức, nên thông báo lợi nhuận của họ được kỳ vọng chứa ít thông tin gia tăng hơn.
Hình 25.6: Độ biến động bất thường và khối lượng vào ngày thông báo theo nhóm tam phân vị quy mô doanh nghiệp. Các doanh nghiệp nhỏ hơn có phản ứng lớn hơn đáng kể, phù hợp với dự báo rằng thông tin có trước khi công bố tăng theo quy mô doanh nghiệp.
25.5.2 Theo sàn giao dịch (HOSE so với HNX)
Các doanh nghiệp niêm yết trên HOSE nhìn chung lớn hơn và thanh khoản hơn các doanh nghiệp niêm yết trên HNX. Ngoài ra, biên độ giá chặt hơn trên HOSE (\(\pm\) 7% so với \(\pm\) 10% trên HNX) có thể cắt cụt lợi nhuận ngày thông báo, làm nén độ biến động đo được và có thể trải việc điều chỉnh ra nhiều ngày.
Hiện mã
exch_summary = ( daily_data .groupby(["relative_td", "exchange"]) .agg( mean_std_sq_ret=("std_sq_ret", "mean"), mean_rel_vol=("rel_vol", "mean"), ) .reset_index())fig, axes = plt.subplots(1, 2, figsize=(12, 5))exch_colors = {"HOSE": "#1565C0", "HNX": "#E65100"}for exch, color in exch_colors.items(): sub = exch_summary[exch_summary["exchange"] == exch].sort_values("relative_td") axes[0].plot(sub["relative_td"], sub["mean_std_sq_ret"], label=exch, color=color, linewidth=1.5) axes[1].plot(sub["relative_td"], sub["mean_rel_vol"], label=exch, color=color, linewidth=1.5)for ax in axes: ax.axvline(0, color="black", linewidth=0.5, alpha=0.3) ax.legend() ax.set_xlabel("Trading Days Relative to Announcement")axes[0].axhline(1.0, color="grey", linestyle="--", linewidth=0.7)axes[0].set_ylabel("Standardized Squared Return")axes[0].set_title("Volatility by Exchange")axes[1].axhline(1.0, color="grey", linestyle="--", linewidth=0.7)axes[1].set_ylabel("Relative Volume")axes[1].set_title("Volume by Exchange")plt.tight_layout()plt.show()
Hình 25.7: Phản ứng với thông báo theo sàn giao dịch. Các doanh nghiệp HNX (nhìn chung nhỏ hơn, kém thanh khoản hơn) cho thấy các đỉnh biến động lớn hơn. Biên độ giá chặt hơn của HOSE có thể nén lợi nhuận ngày thông báo, nhưng biên độ rộng hơn của HNX cho phép điều chỉnh một ngày lớn hơn.
25.5.3 Phân tích hồi quy
Chúng ta chính thức hóa phân tích chéo bằng một hồi quy ở cấp độ sự kiện:
trong đó \(\text{Size}_i\) là logarit của vốn hóa thị trường, \(\text{HNX}_i\) là biến chỉ báo cho việc niêm yết trên HNX, và \(\text{AnnLag}_i\) là số ngày dương lịch giữa ngày kết thúc năm tài chính và ngày thông báo (độ trễ dài hơn có thể cho phép rò rỉ thông tin nhiều hơn, làm giảm mức độ bất ngờ).
Bảng 25.3: Các yếu tố quyết định chéo của độ biến động lợi nhuận vào ngày thông báo. Hệ số quy mô âm xác nhận rằng các doanh nghiệp lớn hơn có thông báo ít mang tính thông tin hơn. Hệ số độ trễ thông báo phản ánh sự rò rỉ thông tin trước khi công bố chính thức.
Volatility
Volume
Intercept
0.001040 (0.000116)
0.7676 (0.0396)
Size (log mcap)
-0.000039 (0.000011)
0.0017 (0.0038)
HNX
0.000266 (0.000034)
0.0118 (0.0117)
Annc Lag (days)
0.000003 (0.000001)
-0.000037 (0.000444)
N
3,600
3,600
R²
0.0210
0.0003
25.6 Thảo luận và các góc nhìn đương đại
25.6.1 Phê bình của Bamber et al. (2000)
Bamber, Christensen, và Gaver (2000) nêu ra hai mối quan ngại phương pháp luận quan trọng đối với kết quả gốc của Beaver (1968):
Hiệu ứng trung bình so với trung vị. Nghiên cứu gốc tập trung vào khối lượng và độ biến động trung bình, vốn có thể bị chi phối bởi một số ít sự kiện cực đoan. Khi Bamber, Christensen, và Gaver (2000) xem xét tỷ lệ các thông báo riêng lẻ tạo ra phản ứng bất thường (thay vì phản ứng trung bình), bằng chứng kém ấn tượng hơn nhiều.
Chọn mẫu. Mẫu gốc gồm 143 doanh nghiệp của Beaver không được chọn ngẫu nhiên mà phản ánh các tiêu chí chọn lọc cụ thể, có thể thiên về các doanh nghiệp có thông báo mang nhiều thông tin hơn.
Chúng ta có thể đánh giá trực tiếp mối quan ngại thứ nhất. Hình 25.8 vẽ tỷ lệ các thông báo có độ biến động hoặc khối lượng trên trung vị theo từng ngày sự kiện, theo tinh thần phê bình của Bamber et al.
Hiện mã
# For each event, determine if announcement-day measures exceed# that event's median non-announcement-day measuresevent_medians = ( daily_data[daily_data["relative_td"].isin(non_annc_window)] .groupby(["ticker", "fiscal_year"]) .agg(median_sq_ret=("sq_ret_mkt", "median"), median_vol=("rel_vol", "median")) .reset_index())daily_with_median = daily_data.merge( event_medians, on=["ticker", "fiscal_year"], how="left")daily_with_median["above_med_vol"] = ( daily_with_median["sq_ret_mkt"] > daily_with_median["median_sq_ret"]).astype(int)daily_with_median["above_med_volume"] = ( daily_with_median["rel_vol"] > daily_with_median["median_vol"]).astype(int)prop_summary = ( daily_with_median .groupby("relative_td") .agg( prop_above_vol=("above_med_vol", "mean"), prop_above_volume=("above_med_volume", "mean"), ) .reset_index() .sort_values("relative_td"))fig, axes = plt.subplots(1, 2, figsize=(12, 5))axes[0].bar(prop_summary["relative_td"], prop_summary["prop_above_vol"], color="steelblue", edgecolor="white")axes[0].axhline(0.5, color="grey", linestyle="--", linewidth=1)axes[0].axvline(0, color="black", linewidth=0.5, alpha=0.3)axes[0].set_xlabel("Trading Days Relative to Announcement")axes[0].set_ylabel("Proportion > Median")axes[0].set_title("Proportion with Above-Median Volatility")axes[0].yaxis.set_major_formatter(mticker.PercentFormatter(xmax=1))axes[1].bar(prop_summary["relative_td"], prop_summary["prop_above_volume"], color="darkorange", edgecolor="white")axes[1].axhline(0.5, color="grey", linestyle="--", linewidth=1)axes[1].axvline(0, color="black", linewidth=0.5, alpha=0.3)axes[1].set_xlabel("Trading Days Relative to Announcement")axes[1].set_ylabel("Proportion > Median")axes[1].set_title("Proportion with Above-Median Volume")axes[1].yaxis.set_major_formatter(mticker.PercentFormatter(xmax=1))plt.tight_layout()plt.show()
Hình 25.8: Tỷ lệ các thông báo riêng lẻ có phản ứng trên trung vị theo từng ngày sự kiện. Khác với các thước đo dựa trên trung bình, cách tiếp cận này vững trước giá trị ngoại lai. Tỷ lệ này vẫn tăng đáng kể vào ngày 0 và +1, dù độ lớn khiêm tốn hơn so với các biểu đồ dựa trên trung bình.
25.6.2 Các thông báo lợi nhuận truyền tải bao nhiêu thông tin?
Ball và Shivakumar (2008) đặt ra một câu hỏi đầy thách thức: ngay cả khi các thông báo lợi nhuận tạo ra phản ứng thị trường có thể phát hiện được, chúng chiếm bao nhiêu trong tổng luồng thông tin hằng năm? Phân tích của họ cho thấy các thông báo lợi nhuận quý chỉ giải thích khoảng 12–18% biến thiên lợi nhuận năm của các doanh nghiệp Mỹ, hàm ý rằng phần lớn thông tin có liên quan đến giá trị đến với thị trường qua các kênh khác.
Chúng ta có thể tính một thống kê tương tự cho mẫu Việt Nam:
Hiện mã
# Compute fraction of event-window variance in announcement daysvariance_shares = []for (ticker, fy), group in daily_data.groupby(["ticker", "fiscal_year"]): total_var = group["ret_mkt"].var() annc_var = group[group["relative_td"].isin(annc_window)]["ret_mkt"].var()if total_var >0andnot np.isnan(annc_var): variance_shares.append({"ticker": ticker,"fiscal_year": fy,"annc_var_share": annc_var / total_var, })var_share_df = pd.DataFrame(variance_shares)expected_share =len(annc_window) /41# 3 days out of 41share_stats = pd.DataFrame({"Statistic": ["Mean share", "Median share", "Expected under null","N events", "% above expected"],"Value": [f"{var_share_df['annc_var_share'].mean():.3f}",f"{var_share_df['annc_var_share'].median():.3f}",f"{expected_share:.3f}",f"{len(var_share_df):,}",f"{(var_share_df['annc_var_share'] > expected_share).mean():.1%}", ],}).set_index("Statistic")share_stats
Bảng 25.4: Tỷ trọng biến thiên lợi nhuận năm tập trung trong cửa sổ thông báo. Cửa sổ thông báo là các ngày {-1, 0, +1}. Nếu các thông báo không mang thông tin, tỷ trọng kỳ vọng sẽ là 3/41 ≈ 7,3% (ba ngày trong cửa sổ 41 ngày). Các giá trị cao hơn đáng kể cho thấy một phần không tương xứng của biến thiên lợi nhuận tập trung quanh các thông báo.
Value
Statistic
Mean share
3.426
Median share
2.979
Expected under null
0.073
N events
3,600
% above expected
98.5%
25.6.3 Biên độ giá và điều chỉnh nhiều ngày
Một đặc điểm riêng của thị trường Việt Nam là biên độ giá hằng ngày có thể ngăn việc điều chỉnh giá hoàn toàn trong một ngày. Khi một thông báo đủ bất ngờ, cổ phiếu có thể chạm biên độ \(\pm\) 7% trên HOSE (hoặc \(\pm\) 10% trên HNX), và phần điều chỉnh còn lại tràn sang các ngày giao dịch tiếp theo. Điều này tạo ra mô hình chạm biên độ rồi tiếp tục trôi giá, một hiện tượng sẽ làm lệch xuống mọi thước đo tác động của thông báo trong một ngày và phân phối lại hàm lượng thông tin qua các ngày 0, +1 và có thể cả +2.
Hình 25.9: Tỷ lệ cổ phiếu chạm biên độ giá hằng ngày (trong phạm vi 0,5% của biên độ ±7% trên HOSE hoặc ±10% trên HNX) theo ngày sự kiện. Các lần chạm biên độ tập trung vào ngày thông báo, cho thấy một số thông báo đủ bất ngờ để làm cạn năng lực điều chỉnh trong một ngày.
25.6.4 Bối cảnh quốc tế
Landsman, Maydew, và Thornock (2012) thực hiện một nghiên cứu liên quốc gia về phản ứng với thông báo và thấy rằng hàm lượng thông tin cao hơn ở các quốc gia có: (i) bảo vệ nhà đầu tư mạnh hơn, (ii) minh bạch tài chính cao hơn, và (iii) bắt buộc áp dụng IFRS. Vị thế của Việt Nam trên các phương diện này, khung thể chế đang phát triển, áp dụng VAS thay vì IFRS, mức minh bạch trung bình, cho thấy phản ứng với thông báo có thể khác biệt một cách hệ thống so với các chuẩn mực của thị trường phát triển.
Khung phân tích của họ đưa ra một dự báo có thể kiểm định: khi Việt Nam tiến tới áp dụng IFRS (một quá trình đang diễn ra tính đến năm 2025), hàm lượng thông tin của các thông báo lợi nhuận sẽ tăng lên trong chừng mực IFRS cải thiện việc ánh xạ từ các sự kiện kinh tế sang các con số được báo cáo.
25.7 Tóm tắt
Chương này xem xét liệu các thông báo lợi nhuận có truyền tải thông tin mới đến nhà đầu tư trên thị trường chứng khoán Việt Nam hay không, theo thiết kế nghiên cứu nền tảng của Beaver (1968). Các phát hiện chính là:
Cả độ biến động lợi nhuận và khối lượng giao dịch đều tăng vọt vào ngày thông báo và ngày kế tiếp, cung cấp bằng chứng vững chắc về hàm lượng thông tin. Kết quả này đúng qua các năm tài chính, phù hợp với việc lợi nhuận là nguồn thông tin mới liên tục.
Các kiểm định thống kê chính thức bác bỏ giả thuyết không về việc không có hàm lượng thông tin với độ tin cậy cao, dùng cả thước đo dựa trên trung bình lẫn dựa trên tỷ lệ.
Phân tích chéo cho thấy phản ứng với thông báo mạnh hơn đối với các doanh nghiệp nhỏ hơn và doanh nghiệp niêm yết trên HNX, phù hợp với dự báo lý thuyết rằng hàm lượng thông tin của lợi nhuận tỷ lệ nghịch với mức độ phong phú của môi trường thông tin trước khi công bố.
Các đặc điểm riêng của Việt Nam, gồm biên độ giá, sự áp đảo của nhà đầu tư cá nhân và mức bao phủ thưa của nhà phân tích, tạo ra các mô hình thực nghiệm đặc trưng làm phong phú thêm khung phân tích chuẩn của Beaver (1968). Đặc biệt, biên độ giá tạo ra các động thái điều chỉnh nhiều ngày mà nhà nghiên cứu phải tính đến khi đo hàm lượng thông tin trong một ngày.
Phê bình của Bamber, Christensen, và Gaver (2000), rằng hiệu ứng trung bình có thể bị chi phối bởi giá trị ngoại lai, được giải quyết bằng cách xem xét tỷ lệ các sự kiện có phản ứng trên trung vị, vốn vẫn cho thấy mức tăng rõ rệt vào ngày thông báo.
Bằng chứng ủng hộ mạnh mẽ kết luận rằng các thông báo lợi nhuận năm truyền tải thông tin mới đến thị trường Việt Nam, phù hợp với kết quả gốc của Beaver (1968) và bằng chứng quốc tế rộng hơn trong Landsman, Maydew, và Thornock (2012).
Atiase, Rowland Kwame. 1985. “Predisclosure information, firm capitalization, and security price behavior around earnings announcements”. Journal of accounting research, 21–36.
Ball, Ray, và Lakshmanan Shivakumar. 2008. “How much new information is there in earnings?”Journal of accounting research 46 (5): 975–1016.
Bamber, Linda Smith, Theodore E Christensen, và Kenneth M Gaver. 2000. “Do we really ‘know’what we think we know? A case study of seminal research and its subsequent overgeneralization”. Accounting, organizations and society 25 (2): 103–29.
Beaver, William H. 1968. “The information content of annual earnings announcements”. Journal of accounting research, 67–92.
Kandel, Eugene, và Neil D Pearson. 1995. “Differential interpretation of public signals and trade in speculative markets”. Journal of Political Economy 103 (4): 831–72. https://doi.org/10.1086/262005.
Karpoff, Jonathan M. 1987. “The relation between price changes and trading volume: A survey”. Journal of Financial and quantitative Analysis 22 (1): 109–26.
Kim, Oliver, và Robert E Verrecchia. 1991. “Market reaction to anticipated announcements”. Journal of Financial Economics 30 (2): 273–309.
Landsman, Wayne R, Edward L Maydew, và Jacob R Thornock. 2012. “The information content of annual earnings announcements and mandatory adoption of IFRS”. Journal of accounting and economics 53 (1-2): 34–54.
Verrecchia, Robert E. 2001. “Essays on disclosure”. Journal of accounting and economics 32 (1-3): 97–180.
# Hàm lượng thông tin của các thông báo lợi nhuận```{python}#| label: setup#| message: falseimport pandas as pdimport numpy as npimport matplotlib.pyplot as pltimport matplotlib.ticker as mtickerimport matplotlib.dates as mdatesimport statsmodels.api as smimport statsmodels.formula.api as smffrom scipy import statsimport warningswarnings.filterwarnings("ignore")plt.rcParams.update({"figure.dpi": 150,"axes.spines.top": False,"axes.spines.right": False,"font.size": 11,})```Một câu hỏi nền tảng trong nghiên cứu thị trường vốn là liệu thông tin kế toán có thực sự quan trọng đối với nhà đầu tư hay không. Nếu báo cáo tài chính chỉ đóng gói lại những gì thị trường đã biết từ các nguồn khác, chẳng hạn báo chí, báo cáo của nhà phân tích, định hướng của ban lãnh đạo, dữ liệu vĩ mô, thì toàn bộ bộ máy lập báo cáo tài chính phần lớn là thừa đối với quá trình khám phá giá. Ngược lại, nếu các thông báo lợi nhuận kích hoạt những thay đổi đo lường được trong hành vi giao dịch, chúng ta có bằng chứng rằng hệ thống kế toán tạo ra thông tin mà thị trường trước đó chưa có.@beaver1968information đã trả lời câu hỏi này bằng cách xem xét liệu khối lượng giao dịch và độ biến động lợi nhuận có tăng lên quanh ngày các doanh nghiệp công bố lợi nhuận năm hay không. Logic rất trực tiếp: nếu một thông báo lợi nhuận chứa thông tin mới, nó sẽ làm thay đổi niềm tin của nhà đầu tư. Niềm tin thay đổi khiến một số nhà đầu tư giao dịch, làm tăng khối lượng. Và trong chừng mực thông báo giải tỏa được sự bất định, giá cổ phiếu sẽ điều chỉnh, tạo ra độ biến động lợi nhuận bất thường. Cả hai kênh (tức là khối lượng và độ biến động) đều cung cấp những góc nhìn riêng biệt về hàm lượng thông tin của lợi nhuận.Thiết kế thực nghiệm so sánh hoạt động giao dịch của doanh nghiệp trong một **cửa sổ thông báo** hẹp (những ngày ngay quanh thời điểm công bố lợi nhuận) với hoạt động giao dịch của chính doanh nghiệp đó trong một **cửa sổ không thông báo** (những ngày tương đương, cách xa sự kiện). Nếu thông báo mang thông tin, cửa sổ thông báo sẽ có khối lượng và độ biến động cao hơn so với giai đoạn không thông báo.### Hai tín hiệu khác biệt: khối lượng và độ biến độngMặc dù khối lượng và độ biến động lợi nhuận đều được dùng để đo phản ứng của nhà đầu tư, hai đại lượng này phản ánh những hiện tượng kinh tế khác nhau. Sự phân biệt này, được hình thức hóa trong các nghiên cứu lý thuyết về sau, rất quan trọng cho việc diễn giải kết quả:- **Độ biến động lợi nhuận** phản ánh mức điều chỉnh *trung bình* trong niềm tin của nhà đầu tư. Khi toàn thị trường bị bất ngờ bởi một con số lợi nhuận, giá biến động mạnh. Độ lớn của phần dư lợi nhuận bình phương (hoặc tuyệt đối) đo lường mức độ thông báo đã dịch chuyển nhận định đồng thuận. Đây là thước đo *hàm lượng thông tin tổng hợp* của thông báo [@kim1991market].- **Khối lượng giao dịch** phản ánh *sự bất đồng* giữa các nhà đầu tư về hàm ý của thông báo. Ngay cả một thông báo làm giá thay đổi rất ít cũng có thể tạo ra giao dịch sôi động nếu các nhà đầu tư diễn giải cùng một tín hiệu theo những cách khác nhau. Chẳng hạn, một số người cho rằng kết quả lợi nhuận vượt kỳ vọng là bền vững trong khi những người khác cho rằng nó chỉ mang tính tạm thời [@kandel1995differential]. Do đó, khối lượng phản ánh *hàm lượng thông tin khác biệt* của thông báo.@karpoff1987relation khảo sát mối quan hệ lý thuyết và thực nghiệm giữa hai biến này. Hiểu biết then chốt là khối lượng không đơn thuần là phiên bản nhiễu hơn của độ biến động; nó cung cấp thông tin độc lập về cách thị trường xử lý dữ liệu kế toán.### Vì sao là Việt Nam?Thị trường chứng khoán Việt Nam là bối cảnh đặc biệt để nghiên cứu phản ứng với thông báo, vì một số lý do sau:1. **Môi trường thông tin.** Các doanh nghiệp niêm yết Việt Nam ít bị soi xét trước thông báo hơn so với các doanh nghiệp Mỹ. Mức độ bao phủ của nhà phân tích còn thưa (hầu hết doanh nghiệp ngoài rổ VN30 không có phân tích của các tổ chức). Định hướng lợi nhuận của ban lãnh đạo hiếm khi được đưa ra. Điều này tạo ra một "khoảng trống thông tin" dày hơn để thông báo lợi nhuận lấp đầy, có khả năng khuếch đại cả phản ứng về khối lượng lẫn độ biến động.2. **Sự áp đảo của nhà đầu tư cá nhân.** Hơn 80% khối lượng giao dịch hằng ngày trên HOSE là của nhà đầu tư cá nhân. Nhà đầu tư cá nhân có thể xử lý thông tin lợi nhuận khác với các tổ chức, với nhiều thiên lệch hành vi hơn, ít khả năng phân tích cơ bản hơn và khung thời gian giao dịch khác. Điều này ảnh hưởng đến cả thời điểm lẫn độ lớn của phản ứng thị trường.3. **Biên độ giá.** HOSE áp dụng biên độ giá hằng ngày $\pm$ 7% và HNX là $\pm$ 10%. Khi một thông báo lợi nhuận đủ bất ngờ, cổ phiếu có thể chạm biên độ giá, khiến thị trường không thể phản ánh đầy đủ thông tin trong một phiên giao dịch. Điều này tạo ra một mô hình đặc trưng về *điều chỉnh kéo dài nhiều ngày* mà các nghiên cứu gốc trên thị trường Mỹ không có.4. **Thời điểm công bố thông tin.** Quy định của Việt Nam yêu cầu báo cáo tài chính năm đã kiểm toán phải được công bố trong vòng 90 ngày kể từ ngày kết thúc năm tài chính. Dữ liệu tài chính quý (chưa kiểm toán) phải được công bố trong vòng 20 ngày. Sự chồng lấn giữa thông báo quý và thông báo năm, cùng độ trễ thay đổi giữa ngày kết thúc năm tài chính và ngày thông báo, tạo ra tính không đồng nhất về hàm lượng thông tin của các thông báo lợi nhuận năm.5. **Cấu trúc thị trường.** Việt Nam vận hành khớp lệnh liên tục trong giờ giao dịch (9:00–11:30 và 13:00–14:30 trên HOSE; 9:00–11:30 và 13:00–15:00 trên HNX), với các phiên khớp lệnh định kỳ xác định giá mở cửa và đóng cửa. Vi cấu trúc này ảnh hưởng đến các mô hình khám phá giá trong ngày quanh thời điểm thông báo.## Khung lý thuyết {#sec-info-earnings-theory}### Giả thuyết hàm lượng thông tinGọi $P_{i,t}$ là giá của cổ phiếu $i$ vào ngày $t$. Theo giả thuyết thị trường hiệu quả, giá phản ánh toàn bộ thông tin công khai sẵn có:$$P_{i,t} = E\left[\sum_{s=1}^{\infty} \frac{D_{i,t+s}}{(1+r)^s} \;\bigg|\; \Omega_t \right]$$ {#eq-emh}trong đó $D_{i,t+s}$ là cổ tức tương lai, $r$ là tỷ suất chiết khấu, và $\Omega_t$ là tập thông tin tại thời điểm $t$.Khi doanh nghiệp $i$ công bố lợi nhuận $X_i$ tại thời điểm $\tau$, tập thông tin được mở rộng: $\Omega_{\tau^+} = \Omega_{\tau^-} \cup \{X_i\}$. Nếu thông báo chứa thông tin mới, nghĩa là $X_i \notin \Omega_{\tau^-}$, thì nói chung:$$P_{i,\tau^+} \neq P_{i,\tau^-}$$ {#eq-price-revision}Độ lớn của thay đổi giá phản ánh thành phần *bất ngờ* của thông báo:$$|P_{i,\tau^+} - P_{i,\tau^-}| \propto |X_i - E[X_i | \Omega_{\tau^-}]|$$ {#eq-surprise}### Khối lượng như tín hiệu của niềm tin không đồng nhất@kim1991market xây dựng một mô hình trong đó khối lượng giao dịch được quyết định bởi độ chính xác của thông tin riêng của nhà đầu tư so với độ chính xác của tín hiệu công khai. Định nghĩa $V_{i,\tau}$ là khối lượng giao dịch của cổ phiếu $i$ tại thời điểm $\tau$. Trong khung phân tích của họ:$$V_{i,\tau} = f\left(\frac{\text{Var}[X_i | \Omega^{(j)}_{\tau^-}]}{\text{Var}[\varepsilon_i]}\right)$$ {#eq-volume-model}trong đó $\Omega^{(j)}_{\tau^-}$ là tập thông tin riêng của nhà đầu tư $j$ và $\varepsilon_i$ là nhiễu. Khối lượng tăng lên khi các nhà đầu tư bất đồng về ý nghĩa của thông báo, một điều kiện mà @kandel1995differential gọi là **diễn giải khác biệt**.Sự phân biệt này có ý nghĩa với Việt Nam. Trong một thị trường bị chi phối bởi các nhà đầu tư cá nhân với thông tin và năng lực phân tích không đồng nhất, chúng ta có thể kỳ vọng phản ứng về khối lượng *lớn không tương xứng* so với phản ứng về giá, vì mức độ bất đồng cao ngay cả khi mức điều chỉnh giá tổng hợp là khiêm tốn.### Đo lường hàm lượng thông tinChúng ta sử dụng hai thước đo bổ sung cho nhau:**Độ biến động lợi nhuận bất thường.** Với mỗi cặp doanh nghiệp–ngày, tính lợi nhuận điều chỉnh theo thị trường bình phương:$$U^2_{i,t} = (R_{i,t} - R_{m,t})^2$$ {#eq-squared-residual}trong đó $R_{i,t}$ là lợi nhuận của cổ phiếu $i$ và $R_{m,t}$ là lợi nhuận thị trường. Theo giả thuyết không về việc không có hàm lượng thông tin, $E[U^2_{i,t}]$ phải như nhau trong cửa sổ thông báo và cửa sổ không thông báo. @beaver1968information chuẩn hóa thước đo này bằng cách chia cho $U^2$ trung bình của doanh nghiệp trong các tuần không có thông báo.**Khối lượng tương đối.** Với mỗi cặp doanh nghiệp–ngày, tính:$$RV_{i,t} = \frac{Vol_{i,t}}{\overline{Vol}_i}$$ {#eq-relative-volume}trong đó $\overline{Vol}_i$ là khối lượng giao dịch trung bình hằng ngày của doanh nghiệp trong cửa sổ sự kiện. Giá trị $RV > 1$ cho thấy giao dịch trên mức trung bình. @beaver1968information dùng một cách tiếp cận tương tự, tính "tỷ lệ khối lượng" so với mức trung bình của các giai đoạn không công bố báo cáo.## Dữ liệu và xây dựng biến {#sec-info-earnings-measures}### Yêu cầu về dữ liệuPhân tích cần ba thành phần dữ liệu:| Dữ liệu | Nguồn | Các trường chính ||-------------------|-----------------------|------------------------------|| Ngày thông báo lợi nhuận | Báo cáo tài chính/hồ sơ công bố | Mã cổ phiếu, ngày kết thúc năm tài chính, ngày thông báo || Lợi nhuận cổ phiếu hằng ngày | Dữ liệu sàn giao dịch (HOSE/HNX) | Mã cổ phiếu, ngày, giá đóng cửa, lợi nhuận || Khối lượng giao dịch hằng ngày | Dữ liệu sàn giao dịch (HOSE/HNX) | Mã cổ phiếu, ngày, khối lượng (cổ phiếu hoặc VND) |: Nguồn dữ liệu cho phân tích hàm lượng thông tin {#tbl-info-earnings-data-sources}### Lịch ngày giao dịchMột thành phần hạ tầng quan trọng là **lịch ngày giao dịch**, ánh xạ các ngày dương lịch sang chỉ số ngày giao dịch tuần tự. Điều này cho phép chúng ta đếm đúng "20 ngày giao dịch trước thông báo", bỏ qua cuối tuần và ngày lễ.```{python}#| label: trading-calendar#| code-summary: "Construct a trading day calendar for HOSE/HNX"def build_trading_calendar( start_date: str="2008-01-01", end_date: str="2024-12-31",) -> pd.DataFrame:""" Build a trading day calendar for Vietnamese stock exchanges. Vietnamese exchanges trade Monday–Friday, with closures for: - Tết Nguyên Đán (Lunar New Year, ~5 days) - Ngày Giỗ Tổ Hùng Vương (Hung Kings' Commemoration) - Ngày Giải phóng miền Nam (Reunification Day, Apr 30) - Ngày Quốc tế Lao động (Labour Day, May 1) - Quốc khánh (National Day, Sep 2) - Tết Dương lịch (New Year, Jan 1) For simulation, we approximate by removing weekends and ~10 holidays/year. """ all_dates = pd.date_range(start_date, end_date, freq="B") # business days# Remove approximate Vietnamese public holidays# (In practice, use the exact holiday calendar from SSC/HOSE) rng = np.random.default_rng(42) n_holidays_per_year =10 years = all_dates.year.unique() holidays = []for yr in years: yr_dates = all_dates[all_dates.year == yr]iflen(yr_dates) > n_holidays_per_year: idx = rng.choice(len(yr_dates), n_holidays_per_year, replace=False) holidays.extend(yr_dates[idx]) trading_dates = all_dates.difference(pd.DatetimeIndex(holidays)) cal = pd.DataFrame({"date": trading_dates,"td": np.arange(1, len(trading_dates) +1), })return caltrading_cal = build_trading_calendar()print(f"Trading calendar: {len(trading_cal):,} trading days, "f"{trading_cal['date'].min().date()} to {trading_cal['date'].max().date()}")```### Căn chỉnh ngày thông báoCác thông báo lợi nhuận có thể xảy ra vào những ngày không giao dịch (cuối tuần, ngày lễ). Khi đó, chúng ta gán thông báo cho ngày giao dịch *kế tiếp gần nhất*, vì đó là cơ hội đầu tiên để thị trường phản ứng. Chúng ta cũng cần xử lý trường hợp thông báo diễn ra sau khi thị trường đóng cửa. Lý tưởng nhất, những thông báo này nên được gán cho ngày giao dịch tiếp theo. Nếu không có dữ liệu thời điểm trong ngày (phổ biến trong các công bố thông tin của Việt Nam), chúng ta dùng ngày thông báo như được báo cáo.```{python}#| label: annc-date-alignment#| code-summary: "Map announcement dates to trading day indices"def build_announcement_mapper(trading_cal: pd.DataFrame) -> pd.DataFrame:""" Create a mapping from every calendar date to the next available trading day index. This handles announcements on weekends/holidays. """ min_date = trading_cal["date"].min() max_date = trading_cal["date"].max() all_dates = pd.date_range(min_date, max_date, freq="D") mapper = pd.DataFrame({"annc_date": all_dates}) mapper = mapper.merge( trading_cal.rename(columns={"date": "annc_date"}), on="annc_date", how="left", )# Forward-fill: assign non-trading dates to next trading day mapper["td"] = mapper["td"].bfill() mapper["td"] = mapper["td"].astype("Int64")return mapperannc_mapper = build_announcement_mapper(trading_cal)```### Tạo dữ liệu mô phỏng cho thị trường Việt NamChúng ta tạo một bộ dữ liệu bảng (panel) tổng hợp các doanh nghiệp niêm yết Việt Nam với các đặc tính thực tế để minh họa phương pháp. Mô phỏng này cài sẵn một hiệu ứng thông báo lợi nhuận, tức khối lượng và độ biến động tăng cao vào ngày 0 và +1, mà sau đó chúng ta tìm cách phát hiện bằng khung phân tích của @beaver1968information.```{python}#| label: gen-data#| code-summary: "Generate synthetic Vietnamese earnings announcement data"def generate_beaver_data( n_firms: int=300, years: list[int] =None, seed: int=2024, days_window: int=20,) ->tuple[pd.DataFrame, pd.DataFrame]:""" Generate: 1. earn_annc: DataFrame of earnings announcement dates 2. daily_data: DataFrame of daily returns and volume The DGP embeds: - An announcement-day return volatility spike (2-3x normal) - An announcement-day volume spike (1.5-2.5x normal) - Price limit truncation at ±7% (HOSE) - Cross-sectional variation by firm size and exchange """if years isNone: years =list(range(2012, 2024)) rng = np.random.default_rng(seed) tc = build_trading_calendar() am = build_announcement_mapper(tc) exchanges = ["HOSE", "HNX"] price_limits = {"HOSE": 0.07, "HNX": 0.10}# ── Generate announcement dates ── annc_records = []for i inrange(n_firms): ticker =f"VN{i:04d}" exchange = rng.choice(exchanges, p=[0.6, 0.4])# Firm size (log market cap in VND billions) log_mcap = rng.normal(6.5, 1.5)for yr in years:# Fiscal year ends Dec 31; announcement 60-90 days later fy_end = pd.Timestamp(f"{yr}-12-31") annc_lag = rng.integers(45, 90) annc_date = fy_end + pd.Timedelta(days=int(annc_lag)) annc_records.append({"ticker": ticker,"fiscal_year": yr,"datadate": fy_end,"annc_date": annc_date,"exchange": exchange,"log_mcap": log_mcap, }) earn_annc = pd.DataFrame(annc_records)# Map to trading day index earn_annc["annc_date_dt"] = pd.to_datetime(earn_annc["annc_date"]) earn_annc = earn_annc.merge( am[["annc_date", "td"]].rename(columns={"td": "event_td"}), left_on="annc_date_dt", right_on="annc_date", how="left", suffixes=("", "_map"), ) earn_annc = earn_annc.dropna(subset=["event_td"]) earn_annc["event_td"] = earn_annc["event_td"].astype(int) earn_annc["start_td"] = earn_annc["event_td"] - days_window earn_annc["end_td"] = earn_annc["event_td"] + days_window# Map start/end td back to dates td_to_date = tc.set_index("td")["date"] earn_annc["start_date"] = earn_annc["start_td"].map(td_to_date) earn_annc["end_date"] = earn_annc["end_td"].map(td_to_date) earn_annc = earn_annc.dropna(subset=["start_date", "end_date"])# ── Generate daily return and volume data ── daily_records = []for _, row in earn_annc.iterrows(): ticker = row["ticker"] exchange = row["exchange"] plimit = price_limits[exchange] log_mcap = row["log_mcap"] event_td = row["event_td"]# Base volatility inversely related to size base_vol =0.015+0.01* np.exp(-0.3* log_mcap)# Base volume (shares/day) base_volume = np.exp(log_mcap + rng.normal(0, 0.5)) *1000# Window trading days window_tds = tc[ (tc["td"] >= row["start_td"]) & (tc["td"] <= row["end_td"]) ].copy()for _, td_row in window_tds.iterrows(): date = td_row["date"] td = td_row["td"] relative_td = td - event_td# Market return (common factor) mkt_ret = rng.normal(0.0003, 0.012)# Firm-specific return# Announcement effect: elevated volatility on days 0, +1if relative_td in [0, 1]: vol_mult = rng.uniform(2.0, 3.5)elifabs(relative_td) <=2: vol_mult = rng.uniform(1.2, 1.8)else: vol_mult =1.0 firm_shock = rng.normal(0, base_vol * vol_mult) raw_ret = mkt_ret + firm_shock# Apply price limits ret = np.clip(raw_ret, -plimit, plimit) ret_mkt = ret - mkt_ret# Volume: spike on announcement daysif relative_td in [0, 1]: vol_spike = rng.uniform(1.5, 2.8)elifabs(relative_td) <=3: vol_spike = rng.uniform(1.0, 1.5)else: vol_spike = rng.uniform(0.7, 1.3) volume = base_volume * vol_spike * np.exp(rng.normal(0, 0.3)) daily_records.append({"ticker": ticker,"fiscal_year": row["fiscal_year"],"date": date,"td": td,"relative_td": relative_td,"ret": ret,"mkt_ret": mkt_ret,"ret_mkt": ret_mkt,"vol": max(volume, 100),"exchange": exchange,"log_mcap": log_mcap, }) daily_data = pd.DataFrame(daily_records)return earn_annc, daily_dataearn_annc, daily_data = generate_beaver_data(n_firms=300, seed=2024)print(f"Announcements: {len(earn_annc):,}")print(f"Daily observations: {len(daily_data):,}")```## Minh họa với một sự kiện đơn lẻ {#sec-info-earnings-single}Trước khi mở rộng ra toàn bộ mẫu, chúng ta lần theo phương pháp trên một thông báo lợi nhuận duy nhất để xây dựng trực giác và kiểm tra từng bước.### Chọn sự kiện```{python}#| label: single-event-select#| code-summary: "Select a single earnings announcement for demonstration"single_ticker ="VN0042"single_fy =2019single_event = earn_annc[ (earn_annc["ticker"] == single_ticker) & (earn_annc["fiscal_year"] == single_fy)].iloc[0]print(f"Firm: {single_event['ticker']}")print(f"Fiscal year-end: {single_event['datadate'].date()}")print(f"Announcement date: {single_event['annc_date'].date()}")print(f"Exchange: {single_event['exchange']}")print(f"Event trading day: {single_event['event_td']}")```### Dữ liệu cửa sổ sự kiệnChúng ta trích xuất dữ liệu của 41 ngày giao dịch (từ 20 ngày trước đến 20 ngày sau thông báo) và tính các thước đo chính.```{python}#| label: single-event-data#| code-summary: "Extract and process data for the single event"single_daily = daily_data[ (daily_data["ticker"] == single_ticker) & (daily_data["fiscal_year"] == single_fy)].copy()# Compute measuressingle_daily["sq_ret_mkt"] = single_daily["ret_mkt"] **2single_daily["abs_ret_mkt"] = single_daily["ret_mkt"].abs()# Relative volume: normalize by own averageavg_vol = single_daily["vol"].mean()single_daily["rel_vol"] = single_daily["vol"] / avg_volprint(f"Trading days in window: {len(single_daily)}")print(f"Average daily volume: {avg_vol:,.0f} shares")```### Trực quan hóa sự kiện đơn lẻ@fig-single-volume và @fig-single-volatility thể hiện mô hình khối lượng và độ biến động của thông báo lợi nhuận đơn lẻ này.```{python}#| label: fig-single-volume#| fig-cap: "Khối lượng giao dịch hằng ngày quanh một thông báo lợi nhuận đơn lẻ. Ngày 0 là ngày giao dịch đầu tiên vào hoặc sau ngày thông báo. Đường đứt nét đánh dấu mức trung bình của cửa sổ sự kiện."fig, ax = plt.subplots()colors = ["firebrick"ifabs(t) <=1else"steelblue"for t in single_daily["relative_td"]]ax.bar(single_daily["relative_td"], single_daily["vol"], color=colors, edgecolor="white", width=0.8)ax.axhline(avg_vol, color="grey", linestyle="--", linewidth=1, label="Window average")ax.axvline(0, color="black", linewidth=0.5, alpha=0.3)ax.set_xlabel("Trading Days Relative to Announcement")ax.set_ylabel("Volume (shares)")ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x, _: f"{x/1e6:.1f}M"))ax.legend(fontsize=9)plt.tight_layout()plt.show()``````{python}#| label: fig-single-volatility#| fig-cap: "Lợi nhuận điều chỉnh theo thị trường bình phương quanh một thông báo lợi nhuận đơn lẻ. Các giá trị cao vào ngày 0 và +1 cho thấy thông báo đã đẩy giá cổ phiếu đi xa hơn mức mà biến động chung của thị trường dự báo."fig, ax = plt.subplots()ax.bar(single_daily["relative_td"], single_daily["sq_ret_mkt"], color=["firebrick"ifabs(t) <=1else"steelblue"for t in single_daily["relative_td"]], edgecolor="white", width=0.8)ax.axhline(single_daily["sq_ret_mkt"].mean(), color="grey", linestyle="--", linewidth=1, label="Window average")ax.axvline(0, color="black", linewidth=0.5, alpha=0.3)ax.set_xlabel("Trading Days Relative to Announcement")ax.set_ylabel("Squared Market-Adjusted Return")ax.legend(fontsize=9)plt.tight_layout()plt.show()```::: callout-tip## Đọc các biểu đồMột sự kiện đơn lẻ vốn có nhiều nhiễu, các tin tức đặc thù khác của doanh nghiệp (tin đồn M&A, báo cáo phân tích, hành động của cơ quan quản lý) có thể tạo ra các đỉnh khối lượng và độ biến động vào những ngày không có thông báo. Giá trị của cách tiếp cận của @beaver1968information nằm ở việc *lấy trung bình trên nhiều sự kiện* để tách hiệu ứng thông báo mang tính hệ thống khỏi nhiễu đặc thù.:::## Phân tích toàn mẫu {#sec-info-earnings-full-sample}### Tính các giá trị tổng hợp theo thời gian sự kiệnVới mỗi ngày sự kiện $\tau$ tương đối so với thông báo, chúng ta tính trung bình chéo của hai thước đo trên tất cả các thông báo:$$\overline{U^2}_\tau = \frac{1}{N_\tau} \sum_{i=1}^{N_\tau} U^2_{i,\tau}$$ {#eq-avg-volatility}$$\overline{RV}_\tau = \frac{1}{N_\tau} \sum_{i=1}^{N_\tau} RV_{i,\tau}$$ {#eq-avg-volume}trong đó $N_\tau$ là số thông báo có dữ liệu sẵn có vào ngày sự kiện $\tau$.```{python}#| label: full-sample-measures#| code-summary: "Compute event-time aggregate measures across all announcements"# Squared and absolute market-adjusted returnsdaily_data["sq_ret_mkt"] = daily_data["ret_mkt"] **2daily_data["abs_ret_mkt"] = daily_data["ret_mkt"].abs()# Relative volume: normalize within each event windowdaily_data["rel_vol"] = ( daily_data .groupby(["ticker", "fiscal_year"])["vol"] .transform(lambda x: x / x.mean()))# Standardized squared return: normalize within each event windowdaily_data["std_sq_ret"] = ( daily_data .groupby(["ticker", "fiscal_year"])["sq_ret_mkt"] .transform(lambda x: x / x.mean()))# Add fiscal year for panel breakdownsdaily_data["year"] = daily_data["fiscal_year"]``````{python}#| label: event-time-summary#| code-summary: "Aggregate by event day and year"event_summary = ( daily_data .groupby(["relative_td", "year"]) .agg( n_obs=("ret", "size"), mean_ret=("ret", "mean"), mean_ret_mkt=("ret_mkt", "mean"), sd_ret_mkt=("ret_mkt", "std"), mean_sq_ret=("sq_ret_mkt", "mean"), mean_abs_ret=("abs_ret_mkt", "mean"), mean_rel_vol=("rel_vol", "mean"), mean_std_sq_ret=("std_sq_ret", "mean"), median_rel_vol=("rel_vol", "median"), ) .reset_index())# Also compute pooled (all-year) summaryevent_summary_pooled = ( daily_data .groupby("relative_td") .agg( n_obs=("ret", "size"), mean_ret=("ret", "mean"), mean_ret_mkt=("ret_mkt", "mean"), sd_ret_mkt=("ret_mkt", "std"), mean_sq_ret=("sq_ret_mkt", "mean"), mean_abs_ret=("abs_ret_mkt", "mean"), mean_rel_vol=("rel_vol", "mean"), mean_std_sq_ret=("std_sq_ret", "mean"), median_rel_vol=("rel_vol", "median"), ) .reset_index())```### Độ biến động lợi nhuận quanh các thông báo@fig-volatility-by-year vẽ độ lệch chuẩn của lợi nhuận điều chỉnh theo thị trường theo ngày sự kiện, tách riêng cho từng năm tài chính. Một đỉnh rõ rệt vào ngày 0 và +1 là dấu hiệu đặc trưng của hàm lượng thông tin.```{python}#| label: fig-volatility-by-year#| fig-cap: "Độ lệch chuẩn của lợi nhuận điều chỉnh theo thị trường quanh các thông báo lợi nhuận, theo năm tài chính. Đỉnh nhất quán vào ngày 0 và +1 qua các năm là bằng chứng mạnh cho thấy các thông báo lợi nhuận năm truyền tải thông tin mới đến thị trường chứng khoán Việt Nam."fig, ax = plt.subplots(figsize=(9, 5))years_plot =sorted(event_summary["year"].unique())# Use a colormapcmap = plt.cm.viridis(np.linspace(0.1, 0.9, len(years_plot)))for idx, yr inenumerate(years_plot): sub = event_summary[event_summary["year"] == yr].sort_values("relative_td") ax.plot(sub["relative_td"], sub["sd_ret_mkt"], color=cmap[idx], alpha=0.6, linewidth=1.0, label=str(yr))# Overlay pooled meanpooled = event_summary_pooled.sort_values("relative_td")ax.plot(pooled["relative_td"], pooled["sd_ret_mkt"], color="firebrick", linewidth=2.5, linestyle="-", label="Pooled", zorder=10)ax.axvline(0, color="black", linewidth=0.5, alpha=0.3)ax.set_xlabel("Trading Days Relative to Announcement")ax.set_ylabel("Std Dev of Market-Adjusted Returns")ax.legend(fontsize=7, ncol=4, loc="upper right")plt.tight_layout()plt.show()```### Khối lượng giao dịch quanh các thông báo@fig-volume-by-year cho thấy khối lượng giao dịch tương đối theo ngày sự kiện. Phản ứng về khối lượng cung cấp sự xác nhận độc lập cho kết quả về độ biến động.```{python}#| label: fig-volume-by-year#| fig-cap: "Khối lượng giao dịch tương đối trung bình quanh các thông báo lợi nhuận, theo năm tài chính. Khối lượng tăng vọt vào ngày 0 và +1, phù hợp với việc nhà đầu tư tích cực giao dịch dựa trên thông tin mới. Khối lượng cao kéo dài lâu hơn một chút so với đỉnh độ biến động, cho thấy sự bất đồng về hàm ý của thông báo cần thời gian để được giải quyết."fig, ax = plt.subplots(figsize=(9, 5))for idx, yr inenumerate(years_plot): sub = event_summary[event_summary["year"] == yr].sort_values("relative_td") ax.plot(sub["relative_td"], sub["mean_rel_vol"], color=cmap[idx], alpha=0.6, linewidth=1.0, label=str(yr))ax.plot(pooled["relative_td"], pooled["mean_rel_vol"], color="firebrick", linewidth=2.5, linestyle="-", label="Pooled", zorder=10)ax.axhline(1.0, color="grey", linestyle="--", linewidth=0.8)ax.axvline(0, color="black", linewidth=0.5, alpha=0.3)ax.set_xlabel("Trading Days Relative to Announcement")ax.set_ylabel("Relative Volume (1.0 = window average)")ax.legend(fontsize=7, ncol=4, loc="upper right")plt.tight_layout()plt.show()```### Các kiểm định thống kê chính thức@beaver1968information dựa vào quan sát trực quan và các lập luận thống kê không chính thức. Thực hành hiện đại đòi hỏi các kiểm định chính thức. Chúng ta kiểm định xem các thước đo trong cửa sổ thông báo có khác biệt đáng kể so với cửa sổ không thông báo hay không, bằng so sánh theo cặp.Định nghĩa cửa sổ thông báo là các ngày $\{-1, 0, +1\}$ và cửa sổ không thông báo là các ngày $\{-20, \ldots, -5\} \cup \{+5, \ldots, +20\}$. Với mỗi sự kiện $i$, chúng ta tính:$$\Delta U^2_i = \overline{U^2}_{i,\text{annc}} - \overline{U^2}_{i,\text{non-annc}}$$ {#eq-delta-vol}Theo giả thuyết không, $E[\Delta U^2_i] = 0$. Chúng ta kiểm định giả thuyết này bằng kiểm định $t$ trên các sự kiện.```{python}#| label: tbl-formal-tests#| tbl-cap: "Các kiểm định chính thức về hàm lượng thông tin trong giai đoạn thông báo. Cửa sổ thông báo là các ngày {-1, 0, +1}; cửa sổ không thông báo loại trừ các ngày {-4, ..., +4}. Các thống kê t lớn và giá trị p nhỏ là bằng chứng mạnh cho thấy cả độ biến động lợi nhuận lẫn khối lượng giao dịch đều tăng đáng kể trong cửa sổ thông báo."annc_window = [-1, 0, 1]non_annc_window =list(range(-20, -4)) +list(range(5, 21))def compute_window_means(df, var, window):"""Compute mean of var within a window for each event."""return ( df[df["relative_td"].isin(window)] .groupby(["ticker", "fiscal_year"])[var] .mean() )test_results = {}for var, label in [("sq_ret_mkt", "Squared Mkt-Adj Return"), ("abs_ret_mkt", "Absolute Mkt-Adj Return"), ("rel_vol", "Relative Volume")]: annc_mean = compute_window_means(daily_data, var, annc_window) non_annc_mean = compute_window_means(daily_data, var, non_annc_window)# Align on common events common = annc_mean.index.intersection(non_annc_mean.index) diff = annc_mean.loc[common] - non_annc_mean.loc[common] diff = diff.dropna() t_stat, p_val = stats.ttest_1samp(diff, 0) test_results[label] = {"Annc Window Mean": annc_mean.mean(),"Non-Annc Window Mean": non_annc_mean.mean(),"Difference": diff.mean(),"t-statistic": t_stat,"p-value": p_val,"N events": len(diff), }test_df = pd.DataFrame(test_results).T.round(4)test_df["N events"] = test_df["N events"].astype(int)test_df```### Tỷ lệ biến động chuẩn hóaTheo cách tiếp cận của @beaver1968information, chúng ta tính tỷ lệ giữa lợi nhuận bình phương trong cửa sổ thông báo và lợi nhuận bình phương trong cửa sổ không thông báo. Theo giả thuyết không, tỷ lệ này bằng 1,0.```{python}#| label: fig-volatility-ratio#| fig-cap: "Lợi nhuận điều chỉnh theo thị trường bình phương đã chuẩn hóa theo ngày sự kiện (gộp tất cả các năm). Giá trị 1.0 cho biết độ biến động bình thường. Tỷ lệ vào ngày thông báo vượt xa 1.0, cho thấy việc công bố lợi nhuận tạo ra biến động lợi nhuận cao hơn nhiều so với mức bình thường."fig, ax = plt.subplots()ratio = pooled["mean_std_sq_ret"]colors = ["firebrick"ifabs(t) <=1else"steelblue"for t in pooled["relative_td"]]ax.bar(pooled["relative_td"], ratio, color=colors, edgecolor="white")ax.axhline(1.0, color="grey", linestyle="--", linewidth=1, label="Expected under null")ax.axvline(0, color="black", linewidth=0.5, alpha=0.3)ax.set_xlabel("Trading Days Relative to Announcement")ax.set_ylabel("Standardized Squared Return (ratio)")ax.legend()plt.tight_layout()plt.show()```## Biến thiên chéo giữa các doanh nghiệp {#sec-info-earnings-cross}Một hiệu ứng thông báo đồng nhất trên mọi doanh nghiệp sẽ là điều đáng ngạc nhiên. Lý thuyết dự báo rằng độ lớn của phản ứng thị trường phụ thuộc vào môi trường thông tin trước thông báo: các doanh nghiệp có tập thông tin phong phú hơn trước thông báo sẽ có phản ứng *nhỏ hơn* vì con số lợi nhuận ít mang tính tin mới hơn [@atiase1985predisclosure; @verrecchia2001essays].### Theo quy mô doanh nghiệpCác doanh nghiệp lớn hơn nhận được nhiều sự chú ý hơn từ nhà phân tích, truyền thông và nhà đầu tư tổ chức, nên thông báo lợi nhuận của họ được kỳ vọng chứa ít thông tin gia tăng hơn.```{python}#| label: fig-size-effect#| fig-cap: "Độ biến động bất thường và khối lượng vào ngày thông báo theo nhóm tam phân vị quy mô doanh nghiệp. Các doanh nghiệp nhỏ hơn có phản ứng lớn hơn đáng kể, phù hợp với dự báo rằng thông tin có trước khi công bố tăng theo quy mô doanh nghiệp."# Assign size tercilesdaily_data["size_tercile"] = pd.qcut( daily_data["log_mcap"], 3, labels=["Small", "Medium", "Large"])size_summary = ( daily_data .groupby(["relative_td", "size_tercile"]) .agg( mean_std_sq_ret=("std_sq_ret", "mean"), mean_rel_vol=("rel_vol", "mean"), ) .reset_index())fig, axes = plt.subplots(1, 2, figsize=(12, 5))size_colors = {"Small": "#E91E63", "Medium": "#FF9800", "Large": "#2196F3"}for label, color in size_colors.items(): sub = size_summary[size_summary["size_tercile"] == label].sort_values("relative_td") axes[0].plot(sub["relative_td"], sub["mean_std_sq_ret"], label=label, color=color, linewidth=1.5) axes[1].plot(sub["relative_td"], sub["mean_rel_vol"], label=label, color=color, linewidth=1.5)for ax in axes: ax.axvline(0, color="black", linewidth=0.5, alpha=0.3) ax.set_xlabel("Trading Days Relative to Announcement") ax.legend()axes[0].axhline(1.0, color="grey", linestyle="--", linewidth=0.7)axes[0].set_ylabel("Standardized Squared Return")axes[0].set_title("Volatility Reaction by Size")axes[1].axhline(1.0, color="grey", linestyle="--", linewidth=0.7)axes[1].set_ylabel("Relative Volume")axes[1].set_title("Volume Reaction by Size")plt.tight_layout()plt.show()```### Theo sàn giao dịch (HOSE so với HNX)Các doanh nghiệp niêm yết trên HOSE nhìn chung lớn hơn và thanh khoản hơn các doanh nghiệp niêm yết trên HNX. Ngoài ra, biên độ giá chặt hơn trên HOSE ($\pm$ 7% so với $\pm$ 10% trên HNX) có thể cắt cụt lợi nhuận ngày thông báo, làm nén độ biến động đo được và có thể trải việc điều chỉnh ra nhiều ngày.```{python}#| label: fig-exchange-effect#| fig-cap: "Phản ứng với thông báo theo sàn giao dịch. Các doanh nghiệp HNX (nhìn chung nhỏ hơn, kém thanh khoản hơn) cho thấy các đỉnh biến động lớn hơn. Biên độ giá chặt hơn của HOSE có thể nén lợi nhuận ngày thông báo, nhưng biên độ rộng hơn của HNX cho phép điều chỉnh một ngày lớn hơn."exch_summary = ( daily_data .groupby(["relative_td", "exchange"]) .agg( mean_std_sq_ret=("std_sq_ret", "mean"), mean_rel_vol=("rel_vol", "mean"), ) .reset_index())fig, axes = plt.subplots(1, 2, figsize=(12, 5))exch_colors = {"HOSE": "#1565C0", "HNX": "#E65100"}for exch, color in exch_colors.items(): sub = exch_summary[exch_summary["exchange"] == exch].sort_values("relative_td") axes[0].plot(sub["relative_td"], sub["mean_std_sq_ret"], label=exch, color=color, linewidth=1.5) axes[1].plot(sub["relative_td"], sub["mean_rel_vol"], label=exch, color=color, linewidth=1.5)for ax in axes: ax.axvline(0, color="black", linewidth=0.5, alpha=0.3) ax.legend() ax.set_xlabel("Trading Days Relative to Announcement")axes[0].axhline(1.0, color="grey", linestyle="--", linewidth=0.7)axes[0].set_ylabel("Standardized Squared Return")axes[0].set_title("Volatility by Exchange")axes[1].axhline(1.0, color="grey", linestyle="--", linewidth=0.7)axes[1].set_ylabel("Relative Volume")axes[1].set_title("Volume by Exchange")plt.tight_layout()plt.show()```### Phân tích hồi quyChúng ta chính thức hóa phân tích chéo bằng một hồi quy ở cấp độ sự kiện:$$\Delta U^2_i = \beta_0 + \beta_1 \text{Size}_i + \beta_2 \text{HNX}_i + \beta_3 \text{AnnLag}_i + \varepsilon_i$$ {#eq-cross-section-reg}trong đó $\text{Size}_i$ là logarit của vốn hóa thị trường, $\text{HNX}_i$ là biến chỉ báo cho việc niêm yết trên HNX, và $\text{AnnLag}_i$ là số ngày dương lịch giữa ngày kết thúc năm tài chính và ngày thông báo (độ trễ dài hơn có thể cho phép rò rỉ thông tin nhiều hơn, làm giảm mức độ bất ngờ).```{python}#| label: tbl-cross-section#| tbl-cap: "Các yếu tố quyết định chéo của độ biến động lợi nhuận vào ngày thông báo. Hệ số quy mô âm xác nhận rằng các doanh nghiệp lớn hơn có thông báo ít mang tính thông tin hơn. Hệ số độ trễ thông báo phản ánh sự rò rỉ thông tin trước khi công bố chính thức."# Build event-level datasetannc_data = daily_data[daily_data["relative_td"].isin(annc_window)].copy()non_annc_data = daily_data[daily_data["relative_td"].isin(non_annc_window)].copy()annc_means = ( annc_data .groupby(["ticker", "fiscal_year"]) .agg(annc_sq_ret=("sq_ret_mkt", "mean"), annc_vol=("rel_vol", "mean")) .reset_index())non_annc_means = ( non_annc_data .groupby(["ticker", "fiscal_year"]) .agg(non_annc_sq_ret=("sq_ret_mkt", "mean"), non_annc_vol=("rel_vol", "mean")) .reset_index())event_df = annc_means.merge(non_annc_means, on=["ticker", "fiscal_year"])event_df["delta_sq_ret"] = event_df["annc_sq_ret"] - event_df["non_annc_sq_ret"]event_df["delta_vol"] = event_df["annc_vol"] - event_df["non_annc_vol"]# Add firm characteristicsfirm_chars = ( earn_annc[["ticker", "fiscal_year", "exchange", "log_mcap", "datadate", "annc_date"]] .drop_duplicates(["ticker", "fiscal_year"]))firm_chars["hnx"] = (firm_chars["exchange"] =="HNX").astype(int)firm_chars["annc_lag"] = ( pd.to_datetime(firm_chars["annc_date"]) - pd.to_datetime(firm_chars["datadate"])).dt.daysevent_df = event_df.merge(firm_chars, on=["ticker", "fiscal_year"], how="left")event_df = event_df.dropna(subset=["log_mcap", "hnx", "annc_lag", "delta_sq_ret"])# Regressionmod_vol = smf.ols("delta_sq_ret ~ log_mcap + hnx + annc_lag", data=event_df).fit()mod_volume = smf.ols("delta_vol ~ log_mcap + hnx + annc_lag", data=event_df).fit()reg_table = pd.DataFrame({"Volatility": {"Intercept": f"{mod_vol.params['Intercept']:.6f} ({mod_vol.bse['Intercept']:.6f})","Size (log mcap)": f"{mod_vol.params['log_mcap']:.6f} ({mod_vol.bse['log_mcap']:.6f})","HNX": f"{mod_vol.params['hnx']:.6f} ({mod_vol.bse['hnx']:.6f})","Annc Lag (days)": f"{mod_vol.params['annc_lag']:.6f} ({mod_vol.bse['annc_lag']:.6f})","N": f"{int(mod_vol.nobs):,}","R²": f"{mod_vol.rsquared:.4f}", },"Volume": {"Intercept": f"{mod_volume.params['Intercept']:.4f} ({mod_volume.bse['Intercept']:.4f})","Size (log mcap)": f"{mod_volume.params['log_mcap']:.4f} ({mod_volume.bse['log_mcap']:.4f})","HNX": f"{mod_volume.params['hnx']:.4f} ({mod_volume.bse['hnx']:.4f})","Annc Lag (days)": f"{mod_volume.params['annc_lag']:.6f} ({mod_volume.bse['annc_lag']:.6f})","N": f"{int(mod_volume.nobs):,}","R²": f"{mod_volume.rsquared:.4f}", },})reg_table```## Thảo luận và các góc nhìn đương đại {#sec-info-earnings-discussion}### Phê bình của Bamber et al. (2000)@bamber2000we nêu ra hai mối quan ngại phương pháp luận quan trọng đối với kết quả gốc của @beaver1968information:1. **Hiệu ứng trung bình so với trung vị.** Nghiên cứu gốc tập trung vào khối lượng và độ biến động trung bình, vốn có thể bị chi phối bởi một số ít sự kiện cực đoan. Khi @bamber2000we xem xét *tỷ lệ* các thông báo riêng lẻ tạo ra phản ứng bất thường (thay vì phản ứng trung bình), bằng chứng kém ấn tượng hơn nhiều.2. **Chọn mẫu.** Mẫu gốc gồm 143 doanh nghiệp của Beaver không được chọn ngẫu nhiên mà phản ánh các tiêu chí chọn lọc cụ thể, có thể thiên về các doanh nghiệp có thông báo mang nhiều thông tin hơn.Chúng ta có thể đánh giá trực tiếp mối quan ngại thứ nhất. @fig-proportion vẽ *tỷ lệ* các thông báo có độ biến động hoặc khối lượng trên trung vị theo từng ngày sự kiện, theo tinh thần phê bình của Bamber et al.```{python}#| label: fig-proportion#| fig-cap: "Tỷ lệ các thông báo riêng lẻ có phản ứng trên trung vị theo từng ngày sự kiện. Khác với các thước đo dựa trên trung bình, cách tiếp cận này vững trước giá trị ngoại lai. Tỷ lệ này vẫn tăng đáng kể vào ngày 0 và +1, dù độ lớn khiêm tốn hơn so với các biểu đồ dựa trên trung bình."# For each event, determine if announcement-day measures exceed# that event's median non-announcement-day measuresevent_medians = ( daily_data[daily_data["relative_td"].isin(non_annc_window)] .groupby(["ticker", "fiscal_year"]) .agg(median_sq_ret=("sq_ret_mkt", "median"), median_vol=("rel_vol", "median")) .reset_index())daily_with_median = daily_data.merge( event_medians, on=["ticker", "fiscal_year"], how="left")daily_with_median["above_med_vol"] = ( daily_with_median["sq_ret_mkt"] > daily_with_median["median_sq_ret"]).astype(int)daily_with_median["above_med_volume"] = ( daily_with_median["rel_vol"] > daily_with_median["median_vol"]).astype(int)prop_summary = ( daily_with_median .groupby("relative_td") .agg( prop_above_vol=("above_med_vol", "mean"), prop_above_volume=("above_med_volume", "mean"), ) .reset_index() .sort_values("relative_td"))fig, axes = plt.subplots(1, 2, figsize=(12, 5))axes[0].bar(prop_summary["relative_td"], prop_summary["prop_above_vol"], color="steelblue", edgecolor="white")axes[0].axhline(0.5, color="grey", linestyle="--", linewidth=1)axes[0].axvline(0, color="black", linewidth=0.5, alpha=0.3)axes[0].set_xlabel("Trading Days Relative to Announcement")axes[0].set_ylabel("Proportion > Median")axes[0].set_title("Proportion with Above-Median Volatility")axes[0].yaxis.set_major_formatter(mticker.PercentFormatter(xmax=1))axes[1].bar(prop_summary["relative_td"], prop_summary["prop_above_volume"], color="darkorange", edgecolor="white")axes[1].axhline(0.5, color="grey", linestyle="--", linewidth=1)axes[1].axvline(0, color="black", linewidth=0.5, alpha=0.3)axes[1].set_xlabel("Trading Days Relative to Announcement")axes[1].set_ylabel("Proportion > Median")axes[1].set_title("Proportion with Above-Median Volume")axes[1].yaxis.set_major_formatter(mticker.PercentFormatter(xmax=1))plt.tight_layout()plt.show()```### Các thông báo lợi nhuận truyền tải bao nhiêu thông tin?@ball2008much đặt ra một câu hỏi đầy thách thức: ngay cả khi các thông báo lợi nhuận tạo ra phản ứng thị trường có thể phát hiện được, chúng chiếm bao nhiêu trong *tổng* luồng thông tin hằng năm? Phân tích của họ cho thấy các thông báo lợi nhuận quý chỉ giải thích khoảng 12–18% biến thiên lợi nhuận năm của các doanh nghiệp Mỹ, hàm ý rằng phần lớn thông tin có liên quan đến giá trị đến với thị trường qua các kênh khác.Chúng ta có thể tính một thống kê tương tự cho mẫu Việt Nam:```{python}#| label: tbl-info-share#| tbl-cap: "Tỷ trọng biến thiên lợi nhuận năm tập trung trong cửa sổ thông báo. Cửa sổ thông báo là các ngày {-1, 0, +1}. Nếu các thông báo không mang thông tin, tỷ trọng kỳ vọng sẽ là 3/41 ≈ 7,3% (ba ngày trong cửa sổ 41 ngày). Các giá trị cao hơn đáng kể cho thấy một phần không tương xứng của biến thiên lợi nhuận tập trung quanh các thông báo."# Compute fraction of event-window variance in announcement daysvariance_shares = []for (ticker, fy), group in daily_data.groupby(["ticker", "fiscal_year"]): total_var = group["ret_mkt"].var() annc_var = group[group["relative_td"].isin(annc_window)]["ret_mkt"].var()if total_var >0andnot np.isnan(annc_var): variance_shares.append({"ticker": ticker,"fiscal_year": fy,"annc_var_share": annc_var / total_var, })var_share_df = pd.DataFrame(variance_shares)expected_share =len(annc_window) /41# 3 days out of 41share_stats = pd.DataFrame({"Statistic": ["Mean share", "Median share", "Expected under null","N events", "% above expected"],"Value": [f"{var_share_df['annc_var_share'].mean():.3f}",f"{var_share_df['annc_var_share'].median():.3f}",f"{expected_share:.3f}",f"{len(var_share_df):,}",f"{(var_share_df['annc_var_share'] > expected_share).mean():.1%}", ],}).set_index("Statistic")share_stats```### Biên độ giá và điều chỉnh nhiều ngàyMột đặc điểm riêng của thị trường Việt Nam là biên độ giá hằng ngày có thể ngăn việc điều chỉnh giá hoàn toàn trong một ngày. Khi một thông báo đủ bất ngờ, cổ phiếu có thể chạm biên độ $\pm$ 7% trên HOSE (hoặc $\pm$ 10% trên HNX), và phần điều chỉnh còn lại tràn sang các ngày giao dịch tiếp theo. Điều này tạo ra mô hình **chạm biên độ rồi tiếp tục trôi giá**, một hiện tượng sẽ làm lệch xuống mọi thước đo tác động của thông báo trong một ngày và phân phối lại hàm lượng thông tin qua các ngày 0, +1 và có thể cả +2.```{python}#| label: fig-limit-hits#| fig-cap: "Tỷ lệ cổ phiếu chạm biên độ giá hằng ngày (trong phạm vi 0,5% của biên độ ±7% trên HOSE hoặc ±10% trên HNX) theo ngày sự kiện. Các lần chạm biên độ tập trung vào ngày thông báo, cho thấy một số thông báo đủ bất ngờ để làm cạn năng lực điều chỉnh trong một ngày."# Identify limit hitsdef is_limit_hit(row): limit =0.07if row["exchange"] =="HOSE"else0.10returnabs(row["ret"]) >= (limit -0.005)daily_data["limit_hit"] = daily_data.apply(is_limit_hit, axis=1).astype(int)limit_summary = ( daily_data .groupby("relative_td")["limit_hit"] .mean() .reset_index() .sort_values("relative_td"))fig, ax = plt.subplots()colors = ["firebrick"ifabs(t) <=1else"steelblue"for t in limit_summary["relative_td"]]ax.bar(limit_summary["relative_td"], limit_summary["limit_hit"], color=colors, edgecolor="white")ax.set_xlabel("Trading Days Relative to Announcement")ax.set_ylabel("Proportion Hitting Price Limit")ax.yaxis.set_major_formatter(mticker.PercentFormatter(xmax=1, decimals=1))ax.axvline(0, color="black", linewidth=0.5, alpha=0.3)plt.tight_layout()plt.show()```### Bối cảnh quốc tế@landsman2012information thực hiện một nghiên cứu liên quốc gia về phản ứng với thông báo và thấy rằng hàm lượng thông tin cao hơn ở các quốc gia có: (i) bảo vệ nhà đầu tư mạnh hơn, (ii) minh bạch tài chính cao hơn, và (iii) bắt buộc áp dụng IFRS. Vị thế của Việt Nam trên các phương diện này, khung thể chế đang phát triển, áp dụng VAS thay vì IFRS, mức minh bạch trung bình, cho thấy phản ứng với thông báo có thể khác biệt một cách hệ thống so với các chuẩn mực của thị trường phát triển.Khung phân tích của họ đưa ra một dự báo có thể kiểm định: khi Việt Nam tiến tới áp dụng IFRS (một quá trình đang diễn ra tính đến năm 2025), hàm lượng thông tin của các thông báo lợi nhuận sẽ *tăng lên* trong chừng mực IFRS cải thiện việc ánh xạ từ các sự kiện kinh tế sang các con số được báo cáo.<!-- ## Bài tập1. **Trung vị so với trung bình.** Tái tạo @fig-volatility-by-year và @fig-volume-by-year bằng cách dùng *trung vị* thay vì trung bình trên các sự kiện theo từng ngày sự kiện. Kết quả thay đổi như thế nào? Cách tiếp cận nào, trung bình hay trung vị, phù hợp hơn để rút ra suy luận, và vì sao?2. **Lợi nhuận tuyệt đối.** Thay lợi nhuận điều chỉnh theo thị trường bình phương bằng lợi nhuận điều chỉnh theo thị trường *tuyệt đối* ($|R_{i,t} - R_{m,t}|$) làm thước đo độ biến động. Đây là biến `mean_abs_ret` được tính trong đoạn mã. Các biểu đồ và thống kê kiểm định so sánh ra sao? Lợi nhuận tuyệt đối có những ưu điểm thống kê nào so với lợi nhuận bình phương?3. **Trôi giá trước thông báo.** Xem xét liệu có bằng chứng về rò rỉ thông tin trước thông báo hay không. Cụ thể, kiểm định xem khối lượng và độ biến động vào các ngày $\{-5, -4, -3, -2\}$ có cao hơn đáng kể so với các ngày tương ứng không có sự kiện hay không. Điều gì có thể giải thích hoạt động giao dịch trước thông báo trong bối cảnh Việt Nam?4. **Chiều hướng lợi nhuận ngày thông báo.** @beaver1968information chỉ tập trung vào các thước đo *không mang dấu* (khối lượng và độ biến động). Mở rộng phân tích để xem xét liệu *dấu* của lợi nhuận ngày thông báo có dự báo được hay không. Tính tỷ lệ các thông báo có lợi nhuận điều chỉnh theo thị trường dương vào ngày 0. Kiểm định xem tỷ lệ này có khác 50% hay không. Một thiên lệch dương (hoặc âm) mang tính hệ thống sẽ hàm ý điều gì?5. **Trôi giá sau thông báo.** Theo phương pháp nghiên cứu sự kiện ở chương trước, tính lợi nhuận bất thường tích lũy (CAR) từ ngày +2 đến ngày +20 và kiểm định xem chúng có khác 0 một cách có hệ thống hay không. Trôi giá sau thông báo sẽ gợi ý rằng thị trường phản ứng chưa đủ với tin tức lợi nhuận và dần dần phản ánh thông tin.6. **Tác động của biên độ giá.** Xác định các thông báo mà cổ phiếu chạm biên độ giá hằng ngày vào ngày 0. Với các sự kiện này, vẽ lợi nhuận điều chỉnh theo thị trường tuyệt đối tích lũy từ ngày 0 đến ngày +5. So sánh với các sự kiện không chạm biên độ. Mô hình điều chỉnh nhiều ngày của các sự kiện chạm biên độ có gợi ý rằng biên độ giá cản trở việc phản ánh thông tin một cách hiệu quả không?7. **Các sự kiện gây nhiễu.** Một mối đe dọa đối với nhận diện là các sự kiện doanh nghiệp khác (công bố cổ tức, thông báo M&A, huy động vốn) có thể trùng với cửa sổ lợi nhuận. Nếu bạn có dữ liệu sự kiện, hãy loại các cửa sổ doanh nghiệp có sự kiện gây nhiễu và ước lượng lại phân tích. Kết quả nhạy cảm đến mức nào với bộ lọc này?8. **Phản ứng bất đối xứng.** Phân loại các thông báo thành "tin tốt" (lợi nhuận điều chỉnh theo thị trường dương vào ngày 0) và "tin xấu" (lợi nhuận âm). So sánh mô hình khối lượng và độ biến động riêng cho từng nhóm. Có bằng chứng nào cho thấy thị trường phản ứng mạnh hơn với tin xấu, phù hợp với giả thuyết "tin xấu lan truyền chậm" của @berkman2009price không?9. **Hành vi bầy đàn.** @nguyen2014herd ghi nhận hành vi bầy đàn trên thị trường chứng khoán Việt Nam. Thiết kế một kiểm định xem giao dịch vào ngày thông báo có dấu hiệu bầy đàn hay không (chẳng hạn, tương quan giữa khối lượng của từng cổ phiếu và khối lượng toàn thị trường có điều kiện theo tín hiệu thông báo). Thảo luận hành vi bầy đàn sẽ ảnh hưởng thế nào đến việc diễn giải khối lượng như một thước đo hàm lượng thông tin.10. **Time trend in information content.** Vietnam's disclosure regime has strengthened over time (Circular 155/2015/TT-BTC, Decree 71/2017/NĐ-CP). Test whether the announcement-day volatility ratio has increased over your sample period. A formal test would regress the event-level volatility difference on a time trend. -->## Tóm tắtChương này xem xét liệu các thông báo lợi nhuận có truyền tải thông tin mới đến nhà đầu tư trên thị trường chứng khoán Việt Nam hay không, theo thiết kế nghiên cứu nền tảng của @beaver1968information. Các phát hiện chính là:- Cả độ biến động lợi nhuận và khối lượng giao dịch đều tăng vọt vào ngày thông báo và ngày kế tiếp, cung cấp bằng chứng vững chắc về hàm lượng thông tin. Kết quả này đúng qua các năm tài chính, phù hợp với việc lợi nhuận là nguồn thông tin mới liên tục.- Các kiểm định thống kê chính thức bác bỏ giả thuyết không về việc không có hàm lượng thông tin với độ tin cậy cao, dùng cả thước đo dựa trên trung bình lẫn dựa trên tỷ lệ.- Phân tích chéo cho thấy phản ứng với thông báo mạnh hơn đối với các doanh nghiệp nhỏ hơn và doanh nghiệp niêm yết trên HNX, phù hợp với dự báo lý thuyết rằng hàm lượng thông tin của lợi nhuận tỷ lệ nghịch với mức độ phong phú của môi trường thông tin trước khi công bố.- Các đặc điểm riêng của Việt Nam, gồm biên độ giá, sự áp đảo của nhà đầu tư cá nhân và mức bao phủ thưa của nhà phân tích, tạo ra các mô hình thực nghiệm đặc trưng làm phong phú thêm khung phân tích chuẩn của @beaver1968information. Đặc biệt, biên độ giá tạo ra các động thái điều chỉnh nhiều ngày mà nhà nghiên cứu phải tính đến khi đo hàm lượng thông tin trong một ngày.- Phê bình của @bamber2000we, rằng hiệu ứng trung bình có thể bị chi phối bởi giá trị ngoại lai, được giải quyết bằng cách xem xét tỷ lệ các sự kiện có phản ứng trên trung vị, vốn vẫn cho thấy mức tăng rõ rệt vào ngày thông báo.Bằng chứng ủng hộ mạnh mẽ kết luận rằng các thông báo lợi nhuận năm truyền tải thông tin mới đến thị trường Việt Nam, phù hợp với kết quả gốc của @beaver1968information và bằng chứng quốc tế rộng hơn trong @landsman2012information.