Quản trị công ty (tức là hệ thống các quy tắc, thông lệ và quy trình mà doanh nghiệp được điều hành và kiểm soát theo đó) là một trong những yếu tố quyết định lợi nhuận cổ phiếu được nghiên cứu sôi nổi nhất kể từ đầu những năm 2000. Ý tưởng cốt lõi tuy đơn giản nhưng rất mạnh mẽ: các doanh nghiệp trao cho cổ đông nhiều quyền hơn có xu hướng vượt trội so với những doanh nghiệp mà ban quản lý cố thủ nhờ các điều khoản chống thâu tóm. Chương này áp dụng ý tưởng đó vào thị trường Việt Nam, nơi chất lượng quản trị công ty khác biệt đáng kể giữa các doanh nghiệp niêm yết và sự phát triển thể chế vẫn đang tiếp diễn.

35.1 Cơ sở lý thuyết

35.1.1 Mối liên hệ giữa quản trị công ty và lợi nhuận

Động cơ lý thuyết cho mối liên hệ giữa quản trị công ty và lợi nhuận cổ phiếu dựa trên lý thuyết đại diện (Jensen và Meckling 1976). Các nhà quản lý, với tư cách là người đại diện của cổ đông, có thể theo đuổi lợi ích riêng gây tổn hại đến giá trị doanh nghiệp. Các điều khoản chống thâu tóm, hội đồng quản trị có nhiệm kỳ so le (staggered board), poison pill và những cơ chế phòng thủ khác che chắn ban quản lý khỏi sức ép kỷ luật của thị trường kiểm soát doanh nghiệp. Khi cổ đông không thể dễ dàng thay thế các nhà quản lý kém hiệu quả, chi phí đại diện tăng lên, hiệu quả đầu tư giảm xuống và giá trị doanh nghiệp suy giảm.

Gompers, Ishii, và Metrick (2003) đã chính thức hóa trực giác này bằng cách xây dựng Chỉ số quản trị (G-Index) dựa trên 24 điều khoản quản trị do Investor Responsibility Research Center (IRRC) theo dõi tại Hoa Kỳ. Mỗi điều khoản hạn chế quyền của cổ đông làm chỉ số tăng thêm một đơn vị. Như vậy:

\[ G\text{-Index}_i = \sum_{k=1}^{24} \mathbf{1}\{\text{Provision } k \text{ is present for firm } i\} \tag{35.1}\]

trong đó \(\mathbf{1}\{\cdot\}\) là hàm chỉ báo. Giá trị G-Index càng cao tương ứng với quyền cổ đông càng yếu.

Phát hiện thực nghiệm then chốt rất đáng chú ý: trong thập niên 1990, một danh mục mua các doanh nghiệp có quyền cổ đông mạnh nhất (G-Index \(\leq 5\), được gọi là Danh mục Dân chủ) và bán các doanh nghiệp có quyền cổ đông yếu nhất (G-Index \(\geq 14\), được gọi là Danh mục Độc tài) đã đạt lợi nhuận bất thường khoảng 8,5% mỗi năm.

35.1.2 Điều chỉnh khung phân tích cho Việt Nam

Bối cảnh quản trị công ty của Việt Nam khác biệt căn bản so với Hoa Kỳ. Thị trường Việt Nam có những đặc điểm sau:

  1. Sở hữu nhà nước: Nhiều doanh nghiệp niêm yết vẫn duy trì tỷ lệ sở hữu đáng kể của Nhà nước, tạo ra một vấn đề đại diện đặc thù trong đó Nhà nước đóng vai trò cổ đông kiểm soát, thay vì các cổ đông thiểu số phân tán đối mặt với ban quản lý cố thủ.

  2. Sở hữu tập trung: Sở hữu của gia đình và các nhóm kiểm soát khá phổ biến, khiến xung đột đại diện chủ yếu chuyển từ quan hệ nhà quản lý–cổ đông sang quan hệ giữa cổ đông kiểm soát (đa số) và cổ đông thiểu số (Claessens, Djankov, và Lang 2000).

  3. Khung pháp lý đang phát triển: Quy định về quản trị công ty của Việt Nam đã được tăng cường dần thông qua Nghị định 71/2017/ND-CP và các thông tư tiếp theo, nhưng việc thực thi vẫn chưa đồng đều.

  4. Niêm yết kép và giới hạn sở hữu nước ngoài: Các giới hạn sở hữu nước ngoài tạo ra những nhóm nhà đầu tư bị phân khúc, có thể có sở thích khác nhau về quản trị công ty.

Bất chấp những khác biệt này, logic kinh tế cốt lõi vẫn đúng: các doanh nghiệp có quản trị tốt hơn (tức là hội đồng quản trị độc lập hơn, ủy ban kiểm toán mạnh hơn, công bố thông tin minh bạch hơn, bảo vệ cổ đông thiểu số tốt hơn) sẽ được định giá cao hơn và mang lại lợi nhuận điều chỉnh theo rủi ro vượt trội, nếu các yếu tố khác không đổi.

Đối với thị trường Việt Nam, chúng ta xây dựng một chỉ số quản trị tương tự G-Index từ các điều khoản quản trị. Mặc dù các điều khoản cụ thể khác với 24 mục của IRRC dùng trong bối cảnh Hoa Kỳ, phương pháp luận là giống hệt: đếm số điều khoản hạn chế quyền cổ đông hoặc giúp ban quản lý cố thủ.

35.1.3 Chỉ số quản trị Việt Nam (VN-GIndex)

Chúng ta định nghĩa Chỉ số quản trị Việt Nam dựa trên các nhóm điều khoản sau (Bảng 35.1)

Bảng 35.1: Các nhóm điều khoản quản trị của VN-GIndex
Nhóm Điều khoản Hướng tác động
Cơ cấu hội đồng quản trị Hội đồng quản trị có nhiệm kỳ so le, kiêm nhiệm chủ tịch và CEO, quy mô hội đồng < 5 ↑ hạn chế quyền
Sở hữu Sở hữu nhà nước > 50%, không có thành viên độc lập ↑ hạn chế quyền
Quyền cổ đông Yêu cầu đa số tuyệt đối (supermajority), quyền biểu quyết hạn chế ↑ hạn chế quyền
Minh bạch Không có báo cáo thường niên bằng tiếng Anh, nộp báo cáo chậm ↑ hạn chế quyền
Chống thâu tóm Cơ chế tương đương poison pill, golden parachute ↑ hạn chế quyền
Kiểm toán Không có ủy ban kiểm toán độc lập, kiểm toán viên là bên liên quan ↑ hạn chế quyền

VN-GIndex của doanh nghiệp \(i\) tại thời điểm \(t\) là:

\[ \text{VN-GIndex}_{i,t} = \sum_{k=1}^{K} \mathbf{1}\{\text{Provision } k \text{ is present for firm } i \text{ at time } t\} \tag{35.2}\]

trong đó \(K\) là tổng số điều khoản quản trị được theo dõi. Giá trị càng cao cho thấy quản trị càng yếu.

35.2 Chuẩn bị dữ liệu

Chúng ta sử dụng ba bộ dữ liệu chính:

  1. Dữ liệu quản trị: Các điều khoản quản trị ở cấp doanh nghiệp, được cập nhật hằng năm hoặc khi có thay đổi trọng yếu.
  2. Dữ liệu thị trường chứng khoán: Lợi nhuận hằng tháng, giá và số cổ phiếu đang lưu hành của tất cả doanh nghiệp niêm yết trên HOSE và HNX.
  3. Dữ liệu nhân tố: Các nhân tố Fama-French của Việt Nam (thị trường, quy mô, giá trị) và một nhân tố động lượng.
Hiện mã
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import statsmodels.api as sm
from statsmodels.regression.linear_model import OLS
from scipy import stats
import warnings
import sqlite3

warnings.filterwarnings("ignore")

pd.options.display.float_format = "{:.4f}".format

tidy_finance = sqlite3.connect(
    database="data/tidy_finance_python.sqlite"
)

35.2.1 Tải dữ liệu quản trị

Bộ dữ liệu quản trị chứa các đặc điểm quản trị ở cấp doanh nghiệp. Mỗi quan sát tương ứng với một doanh nghiệp–năm, với các biến nhị phân cho biết sự hiện diện của từng điều khoản quản trị.

Hiện mã
governance_raw = pd.read_csv(
    "data/datacore_governance.csv",
    parse_dates=["date_effective", "date_expires"]
)

governance_raw.info()
Bảng 35.2: Các quan sát đầu tiên của bộ dữ liệu quản trị
Hiện mã
governance_raw.head(10)

Các biến chính gồm:

  • symbol: Mã cổ phiếu trên HOSE hoặc HNX.
  • date_effective: Ngày dữ liệu quản trị có hiệu lực (tương tự ngày tái cân bằng danh mục).
  • date_expires: Ngày cuối cùng mà phiên bản dữ liệu quản trị này còn hiệu lực.
  • year: Năm của phiên bản dữ liệu quản trị.

35.2.2 Tính VN-GIndex

Chúng ta tính chỉ số quản trị bằng cách cộng các biến nhị phân của các điều khoản quản trị. Các cột điều khoản được nhận diện qua tiền tố gov_.

Hiện mã
# Identify governance provision columns
gov_columns = [
    col for col in governance_raw.columns if col.startswith("gov_")
]

print(f"Number of governance provisions tracked: {len(gov_columns)}")
print(f"Provisions: {gov_columns}")

# Compute VN-GIndex as sum of all provision indicators
governance = governance_raw.assign(
    gindex=lambda x: x[gov_columns].sum(axis=1)
)

governance[["symbol", "year", "gindex"]].describe()

35.2.3 Phân phối của VN-GIndex

Hiểu được phân phối theo mặt cắt ngang (cross-sectional) của chỉ số quản trị là điều thiết yếu để xác định ngưỡng cắt cho các danh mục. Tại Hoa Kỳ, Gompers, Ishii, và Metrick (2003) dùng các ngưỡng cố định là \(\leq 5\) cho Dân chủ và \(\geq 14\) cho Độc tài. Đối với Việt Nam, chúng ta xem xét phân phối thực nghiệm và đặt các ngưỡng cắt tại các phân vị phù hợp.

Hiện mã
fig, axes = plt.subplots(
    2, 3, figsize=(12, 7), sharey=True, sharex=True
)
axes = axes.flatten()

years = sorted(governance["year"].unique())

for idx, yr in enumerate(years[:6]):
    ax = axes[idx]
    data_yr = governance.query(f"year == {yr}")["gindex"]
    
    p20 = data_yr.quantile(0.20)
    p80 = data_yr.quantile(0.80)
    
    ax.hist(data_yr, bins=range(0, int(data_yr.max()) + 2), 
            edgecolor="white", color="#2c5f8a", alpha=0.85)
    ax.axvline(p20, color="#d63e2a", linestyle="--", linewidth=1.5, 
               label=f"P20={p20:.0f}")
    ax.axvline(p80, color="#e8a317", linestyle="--", linewidth=1.5, 
               label=f"P80={p80:.0f}")
    ax.set_title(f"{yr}", fontsize=11, fontweight="bold")
    ax.legend(fontsize=8)
    ax.set_xlabel("VN-GIndex")

for idx in range(len(years), len(axes)):
    axes[idx].set_visible(False)

axes[0].set_ylabel("Number of Firms")
axes[3].set_ylabel("Number of Firms")

fig.suptitle(
    "Distribution of VN-GIndex by Governance Vintage Year",
    fontsize=13, fontweight="bold", y=1.01
)
plt.tight_layout()
plt.show()
Hình 35.1
Bảng 35.3: Thống kê mô tả của VN-GIndex theo năm của phiên bản dữ liệu
Hiện mã
gindex_summary = (
    governance
    .groupby("year")["gindex"]
    .describe()
    .round(2)
)

gindex_summary

35.2.4 Phân loại doanh nghiệp: Dân chủ, Trung lập và Độc tài

Thay vì dùng các ngưỡng cố định (có thể không phù hợp do phạm vi chỉ số khác nhau giữa các thị trường), chúng ta dùng cách phân loại dựa trên phân vị. Các doanh nghiệp thuộc nhóm ngũ phân vị thấp nhất của phân phối VN-GIndex trong mỗi năm được phân loại là doanh nghiệp Dân chủ, và các doanh nghiệp thuộc nhóm ngũ phân vị cao nhất được phân loại là doanh nghiệp Độc tài.

Hiện mã
def classify_governance(group):
    """Classify firms into Democracy, Neutral, or Dictatorship
    within each governance vintage year."""
    p20 = group["gindex"].quantile(0.20)
    p80 = group["gindex"].quantile(0.80)
    
    conditions = [
        group["gindex"] <= p20,
        group["gindex"] >= p80
    ]
    choices = ["Democracy", "Dictatorship"]
    
    group = group.assign(
        gx=np.select(conditions, choices, default="Neutral"),
        gx_code=np.select(
            conditions, [1, 3], default=2
        )
    )
    return group

governance_classified = (
    governance
    .groupby("year", group_keys=False)
    .apply(classify_governance)
)

# Summary of classification
classification_counts = (
    governance_classified
    .groupby(["year", "gx"])
    .size()
    .unstack(fill_value=0)
    [["Democracy", "Neutral", "Dictatorship"]]
)

classification_counts
Bảng 35.4: Số lượng doanh nghiệp trong mỗi nhóm quản trị theo năm của phiên bản dữ liệu
Hiện mã
classification_counts

Chúng ta loại các doanh nghiệp có cấu trúc cổ phiếu hai loại (dual-class), vì cấu trúc này tạo ra một cơ chế quản trị đặc thù làm lẫn lộn quyền biểu quyết với quyền sở hữu kinh tế. Tại Hoa Kỳ, Gompers, Ishii, và Metrick (2003) cũng loại các doanh nghiệp dual-class tương tự.

Hiện mã
# Exclude dual-class firms if flagged in the data
if "dual_class" in governance_classified.columns:
    governance_classified = governance_classified.query(
        "dual_class == 0"
    )
    print("Dual-class firms excluded.")
else:
    print("No dual_class indicator found; proceeding with all firms.")

# Keep only Democracy and Dictatorship firms for the long-short strategy
portfolio_firms = governance_classified.query(
    "gx in ['Democracy', 'Dictatorship']"
).copy()

print(f"\nFirms in portfolio universe: {len(portfolio_firms)}")
print(portfolio_firms["gx"].value_counts())

35.2.5 Tải dữ liệu thị trường chứng khoán

Chúng ta kết hợp (merge) các phân loại quản trị với dữ liệu lợi nhuận cổ phiếu hằng tháng.

Hiện mã
prices_monthly = pd.read_sql_query(
    sql="""
        SELECT symbol, date, ret, ret_excess, mktcap, mktcap_lag, risk_free
        FROM prices_monthly
    """,
    con=tidy_finance,
    parse_dates={"date"}
).dropna()

prices_monthly.info()
<class 'pandas.DataFrame'>
Index: 165499 entries, 1 to 209477
Data columns (total 7 columns):
 #   Column      Non-Null Count   Dtype         
---  ------      --------------   -----         
 0   symbol      165499 non-null  str           
 1   date        165499 non-null  datetime64[us]
 2   ret         165499 non-null  float64       
 3   ret_excess  165499 non-null  float64       
 4   mktcap      165499 non-null  float64       
 5   mktcap_lag  165499 non-null  float64       
 6   risk_free   165499 non-null  float64       
dtypes: datetime64[us](1), float64(5), str(1)
memory usage: 10.6 MB

Dữ liệu thị trường chứng khoán gồm:

  • symbol: Mã cổ phiếu.
  • date: Ngày cuối tháng.
  • ret: Tổng lợi nhuận hằng tháng (bao gồm cổ tức).
  • retx: Lợi nhuận hằng tháng không bao gồm cổ tức (chỉ lợi nhuận giá).
  • price: Giá đóng cửa cuối tháng (đã điều chỉnh).
  • shares_outstanding: Số cổ phiếu đang lưu hành (nghìn cổ phiếu).
Hiện mã
prices_monthly[["symbol", "date", "ret", "mktcap"]].describe()
date ret mktcap
count 165499 165499.0000 165499.0000
mean 2018-05-18 13:20:13.109444 0.0042 2183.1646
min 2010-02-28 00:00:00 -0.9900 0.3536
25% 2015-06-30 00:00:00 -0.0703 60.3728
50% 2018-12-31 00:00:00 0.0000 180.6224
75% 2021-07-31 00:00:00 0.0553 660.0000
max 2023-12-31 00:00:00 12.7500 463886.6454
std NaN 0.1862 13983.9977

35.2.6 Liên kết dữ liệu quản trị và dữ liệu cổ phiếu

Mỗi phiên bản dữ liệu quản trị có hiệu lực từ date_effective đến date_expires. Chúng ta gán lợi nhuận cổ phiếu hằng tháng cho phiên bản quản trị phù hợp. Đây là logic tái cân bằng danh mục: các danh mục được thiết lập lại khi có dữ liệu quản trị mới và được nắm giữ cho đến phiên bản tiếp theo.

Hiện mã
# Merge: each stock-month gets its governance classification
# if the month falls within [date_effective, date_expires]
merged = pd.merge(
    portfolio_firms[
        ["symbol", "year", "gindex", "gx", "gx_code",
         "date_effective", "date_expires"]
    ],
    prices_monthly[["symbol", "date", "ret", "retx", "mktcap"]],
    on="symbol",
    how="inner"
)

# Keep only months within the governance validity window
merged = merged.query(
    "date >= date_effective and date <= date_expires"
).sort_values(["symbol", "date"])

print(f"Total firm-month observations: {len(merged):,}")
merged.head()

35.2.7 Tính vốn hóa thị trường trễ làm trọng số

Lợi nhuận danh mục theo trọng số vốn hóa đòi hỏi gán trọng số cho từng cổ phiếu theo vốn hóa thị trường đầu kỳ. Chúng ta dùng vốn hóa thị trường của tháng trước làm trọng số. Với quan sát đầu tiên của mỗi cổ phiếu trong một phiên bản dữ liệu, chúng ta ước tính giá trị thị trường đầu kỳ bằng cách chia giá trị thị trường hiện tại cho \((1 + r_{x,t})\), trong đó \(r_{x,t}\) là lợi nhuận giá.

Lợi nhuận theo trọng số vốn hóa của danh mục \(p\) trong tháng \(t\) là:

\[ r_{p,t}^{vw} = \sum_{i \in p} w_{i,t-1} \cdot r_{i,t}, \quad w_{i,t-1} = \frac{\text{MV}_{i,t-1}}{\sum_{j \in p} \text{MV}_{j,t-1}} \tag{35.3}\]

trong đó \(\text{MV}_{i,t-1}\) là vốn hóa thị trường của cổ phiếu \(i\) vào cuối tháng \(t-1\).

Hiện mã
merged = merged.sort_values(["symbol", "date"])

# Lagged market value (previous month)
merged["mktcap_lag"] = merged.groupby("symbol")["mktcap"].shift(1)

# For first observation: estimate beginning-of-month market cap
first_obs = merged.groupby("symbol")["date"].transform("min")
mask_first = merged["date"] == first_obs

merged.loc[mask_first, "mktcap_lag"] = (
    merged.loc[mask_first, "mktcap"] 
    / (1 + merged.loc[mask_first, "retx"])
)

# Handle any remaining missing weights
mask_missing = merged["mktcap_lag"].isna()
merged.loc[mask_missing, "mktcap_lag"] = (
    merged.loc[mask_missing, "mktcap"]
    / (1 + merged.loc[mask_missing, "retx"])
)

# Drop observations with missing returns or weights
merged = merged.dropna(subset=["ret", "mktcap_lag"])
merged = merged.query("mktcap_lag > 0")

print(f"Clean firm-month observations: {len(merged):,}")

35.3 Xây dựng danh mục đầu tư

35.3.1 Lợi nhuận danh mục theo trọng số vốn hóa

Bây giờ chúng ta tính lợi nhuận hằng tháng theo trọng số vốn hóa cho các danh mục Dân chủ và Độc tài.

Hiện mã
def value_weighted_return(group):
    """Compute value-weighted return for a group of stocks."""
    weights = group["mktcap_lag"]
    total_weight = weights.sum()
    if total_weight == 0:
        return np.nan
    vw_ret = (group["ret"] * weights).sum() / total_weight
    return vw_ret

# Compute VW returns by date and governance group
portfolio_returns = (
    merged
    .groupby(["date", "gx"])
    .apply(value_weighted_return, include_groups=False)
    .reset_index()
    .rename(columns={0: "ret_vw"})
)

# Also compute equal-weighted returns for robustness
portfolio_returns_ew = (
    merged
    .groupby(["date", "gx"])["ret"]
    .mean()
    .reset_index()
    .rename(columns={"ret": "ret_ew"})
)

# Merge EW returns
portfolio_returns = portfolio_returns.merge(
    portfolio_returns_ew, on=["date", "gx"], how="left"
)

portfolio_returns.head(10)

35.3.2 Danh mục mua–bán khống: Dân chủ trừ Độc tài

Chiến lược giao dịch mua danh mục Dân chủ và bán khống danh mục Độc tài. Lợi nhuận hằng tháng của chiến lược mua–bán khống này là:

\[ r_{t}^{D-D} = r_{t}^{\text{Democracy}} - r_{t}^{\text{Dictatorship}} \tag{35.4}\]

Hiện mã
# Pivot to wide format
returns_wide = portfolio_returns.pivot(
    index="date", columns="gx", values=["ret_vw", "ret_ew"]
)

# Flatten column names
returns_wide.columns = [
    f"{val}_{grp}" for val, grp in returns_wide.columns
]
returns_wide = returns_wide.reset_index()

# Compute long-short returns
returns_wide = returns_wide.assign(
    ret_diff_vw=lambda x: (
        x["ret_vw_Democracy"] - x["ret_vw_Dictatorship"]
    ),
    ret_diff_ew=lambda x: (
        x["ret_ew_Democracy"] - x["ret_ew_Dictatorship"]
    )
)

returns_wide = returns_wide.sort_values("date").reset_index(drop=True)

print(f"Monthly return series: {len(returns_wide)} months")
returns_wide.head()

35.3.3 Đặc điểm danh mục theo thời gian

Trước khi xem xét lợi nhuận, chúng ta ghi nhận số lượng doanh nghiệp và điểm quản trị trung bình trong mỗi danh mục theo thời gian.

Bảng 35.5: Đặc điểm danh mục theo nhóm quản trị và năm
Hiện mã
portfolio_chars = (
    merged
    .assign(year_month=lambda x: x["date"].dt.to_period("Y"))
    .groupby(["year_month", "gx"])
    .agg(
        n_firms=("symbol", "nunique"),
        avg_gindex=("gindex", "mean"),
        avg_mktcap=("mktcap", "mean"),
        median_mktcap=("mktcap", "median")
    )
    .round(2)
)

portfolio_chars

35.4 Kết quả thực nghiệm

35.4.1 Thống kê mô tả lợi nhuận danh mục

Bảng 35.6: Thống kê mô tả lợi nhuận danh mục hằng tháng (đơn vị phần trăm)
Hiện mã
return_cols = [
    "ret_vw_Democracy", "ret_vw_Dictatorship", "ret_diff_vw",
    "ret_ew_Democracy", "ret_ew_Dictatorship", "ret_diff_ew"
]

summary_stats = (
    returns_wide[return_cols]
    .mul(100)  # Convert to percent
    .describe()
    .T
    .assign(
        skewness=returns_wide[return_cols].mul(100).skew(),
        sharpe=lambda x: x["mean"] / x["std"] * np.sqrt(12)
    )
    .round(3)
)

summary_stats.index = [
    "Democracy (VW)", "Dictatorship (VW)", "Long-Short (VW)",
    "Democracy (EW)", "Dictatorship (EW)", "Long-Short (EW)"
]

summary_stats[["mean", "std", "min", "25%", "50%", "75%", "max", 
               "skewness", "sharpe"]]

Tỷ số Sharpe được tính như sau:

\[ \text{SR} = \frac{\bar{r}_p}{\sigma_p} \times \sqrt{12} \tag{35.5}\]

trong đó \(\bar{r}_p\) và \(\sigma_p\) là trung bình mẫu và độ lệch chuẩn của lợi nhuận danh mục hằng tháng, và hệ số \(\sqrt{12}\) dùng để quy đổi tỷ số này theo năm.

35.4.2 Kiểm định t: Lợi nhuận mua–bán khống có ý nghĩa thống kê không?

Giả thuyết không là chênh lệch lợi nhuận trung bình hằng tháng giữa danh mục Dân chủ và Độc tài bằng không:

\[ H_0: \mathbb{E}[r_t^{\text{Democracy}} - r_t^{\text{Dictatorship}}] = 0 \tag{35.6}\]

Bảng 35.7: Kiểm định t cho chênh lệch trung bình giữa lợi nhuận danh mục Dân chủ và Độc tài
Hiện mã
def perform_ttest(series, label):
    """Perform a one-sample t-test and return results."""
    clean = series.dropna()
    t_stat, p_value = stats.ttest_1samp(clean, 0)
    return {
        "Portfolio": label,
        "Mean (%)": clean.mean() * 100,
        "Std (%)": clean.std() * 100,
        "T-statistic": t_stat,
        "P-value": p_value,
        "N months": len(clean),
        "Significant (5%)": "Yes" if p_value < 0.05 else "No"
    }

ttest_results = pd.DataFrame([
    perform_ttest(returns_wide["ret_diff_vw"], "VW Long-Short"),
    perform_ttest(returns_wide["ret_diff_ew"], "EW Long-Short"),
    perform_ttest(returns_wide["ret_vw_Democracy"], "VW Democracy"),
    perform_ttest(returns_wide["ret_vw_Dictatorship"], "VW Dictatorship")
])

ttest_results.round(4)

35.4.3 Lợi nhuận tích lũy: Bằng chứng trực quan cho quản trị công ty

Một trong những cách thuyết phục nhất để trình bày hiệu ứng quản trị là qua đồ thị tích lũy tài sản. Chúng ta theo dõi giá trị tăng trưởng của $1 đầu tư vào mỗi danh mục tại thời điểm đầu kỳ mẫu.

Lợi nhuận tích lũy tại thời điểm \(T\) là:

\[ W_T = \prod_{t=1}^{T} (1 + r_{p,t}) \tag{35.7}\]

Hiện mã
returns_wide = returns_wide.sort_values("date")

cum_democracy = (1 + returns_wide["ret_vw_Democracy"]).cumprod()
cum_dictatorship = (1 + returns_wide["ret_vw_Dictatorship"]).cumprod()

fig, ax = plt.subplots(figsize=(10, 6))

ax.plot(
    returns_wide["date"], cum_democracy,
    color="#1a6b3c", linewidth=2, label="Democracy Portfolio"
)
ax.plot(
    returns_wide["date"], cum_dictatorship,
    color="#c0392b", linewidth=1.5, linestyle="--",
    label="Dictatorship Portfolio"
)
ax.fill_between(
    returns_wide["date"],
    cum_democracy, cum_dictatorship,
    where=cum_democracy >= cum_dictatorship,
    alpha=0.15, color="#1a6b3c", label="Outperformance"
)
ax.fill_between(
    returns_wide["date"],
    cum_democracy, cum_dictatorship,
    where=cum_democracy < cum_dictatorship,
    alpha=0.15, color="#c0392b"
)

ax.set_xlabel("Date", fontsize=11)
ax.set_ylabel("Cumulative Value of $1 Invested", fontsize=11)
ax.set_title(
    "Democracy vs. Dictatorship Portfolios: Cumulative Returns",
    fontsize=13, fontweight="bold"
)
ax.legend(fontsize=10, loc="upper left")
ax.grid(True, alpha=0.3)
ax.set_xlim(returns_wide["date"].min(), returns_wide["date"].max())

plt.tight_layout()
plt.show()
Hình 35.2
Hiện mã
cum_longshort = (1 + returns_wide["ret_diff_vw"]).cumprod()

fig, ax = plt.subplots(figsize=(10, 4))

ax.plot(
    returns_wide["date"], cum_longshort,
    color="#2c5f8a", linewidth=2
)
ax.axhline(y=1.0, color="gray", linestyle=":", linewidth=1)
ax.fill_between(
    returns_wide["date"], 1.0, cum_longshort,
    where=cum_longshort >= 1.0, alpha=0.2, color="#2c5f8a"
)
ax.fill_between(
    returns_wide["date"], 1.0, cum_longshort,
    where=cum_longshort < 1.0, alpha=0.2, color="#c0392b"
)

ax.set_xlabel("Date", fontsize=11)
ax.set_ylabel("Cumulative Long-Short Return", fontsize=11)
ax.set_title(
    "Long-Short Governance Strategy: Cumulative Performance",
    fontsize=13, fontweight="bold"
)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Hình 35.3

35.4.4 Hiệu suất theo cửa sổ trượt

Phần bù quản trị có thể không cố định theo thời gian. Chúng ta xem xét lợi nhuận trung bình trượt 12 tháng và tỷ số Sharpe trượt để đánh giá tính ổn định.

Hiện mã
rolling_mean = (
    returns_wide["ret_diff_vw"]
    .rolling(window=12, min_periods=6)
    .mean() * 12 * 100  # Annualized
)

rolling_std = (
    returns_wide["ret_diff_vw"]
    .rolling(window=12, min_periods=6)
    .std() * np.sqrt(12) * 100
)

fig, axes = plt.subplots(2, 1, figsize=(10, 7), sharex=True)

# Rolling annualized return
axes[0].plot(
    returns_wide["date"], rolling_mean,
    color="#2c5f8a", linewidth=1.5
)
axes[0].axhline(0, color="gray", linestyle="--", linewidth=0.8)
axes[0].set_ylabel("Annualized Return (%)")
axes[0].set_title(
    "12-Month Rolling Annualized Return: Long-Short Governance Strategy",
    fontweight="bold"
)
axes[0].grid(True, alpha=0.3)

# Rolling annualized volatility
axes[1].plot(
    returns_wide["date"], rolling_std,
    color="#c0392b", linewidth=1.5
)
axes[1].set_ylabel("Annualized Volatility (%)")
axes[1].set_xlabel("Date")
axes[1].set_title(
    "12-Month Rolling Annualized Volatility",
    fontweight="bold"
)
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()
Hình 35.4

35.5 Hiệu suất điều chỉnh theo rủi ro: Phân tích mô hình nhân tố

35.5.1 Mô hình bốn nhân tố

Riêng lợi nhuận thô không cho chúng ta biết chiến lược quản trị có tạo ra lợi nhuận bất thường hay không (tức là phần lợi nhuận không thể giải thích bằng mức độ phơi nhiễm với các nhân tố rủi ro chung). Chúng ta ước lượng mô hình bốn nhân tố sau:

\[ r_{t}^{D-D} - r_{f,t} = \alpha + \beta_1 (r_{m,t} - r_{f,t}) + \beta_2 \text{SMB}_t + \beta_3 \text{HML}_t + \beta_4 \text{UMD}_t + \varepsilon_t \tag{35.8}\]

trong đó:

  • \(r_{t}^{D-D}\) là lợi nhuận của danh mục quản trị mua–bán khống,
  • \(r_{f,t}\) là lãi suất phi rủi ro,
  • \(r_{m,t} - r_{f,t}\) là lợi nhuận vượt mức của thị trường (MKTRF),
  • \(\text{SMB}_t\) là nhân tố quy mô (Small Minus Big),
  • \(\text{HML}_t\) là nhân tố giá trị (High Minus Low theo tỷ số giá trị sổ sách trên giá trị thị trường),
  • \(\text{UMD}_t\) là nhân tố động lượng (Up Minus Down),
  • \(\alpha\) là lợi nhuận bất thường (hệ số chặn mà chúng ta quan tâm).

Alpha (\(\alpha\)) biểu thị lợi nhuận trung bình hằng tháng không thể quy cho mức độ phơi nhiễm với bốn nhân tố rủi ro hệ thống. Một alpha dương có ý nghĩa thống kê cho thấy chiến lược quản trị tạo ra lợi nhuận bất thường thực sự.

35.5.2 Tải dữ liệu nhân tố

Hiện mã
factors_ff5_monthly = pd.read_sql_query(
    sql="SELECT * FROM factors_ff5_monthly",
    con=tidy_finance,
    parse_dates={"date"}
)

factors_ff5_monthly.info()
<class 'pandas.DataFrame'>
RangeIndex: 150 entries, 0 to 149
Data columns (total 7 columns):
 #   Column      Non-Null Count  Dtype         
---  ------      --------------  -----         
 0   date        150 non-null    datetime64[us]
 1   smb         150 non-null    float64       
 2   hml         150 non-null    float64       
 3   rmw         150 non-null    float64       
 4   cma         150 non-null    float64       
 5   mkt_excess  150 non-null    float64       
 6   risk_free   150 non-null    float64       
dtypes: datetime64[us](1), float64(6)
memory usage: 8.3 KB
Hiện mã
momentum_factor = pd.read_sql_query(
    sql="SELECT * FROM momentum_factor_monthly",
    con=tidy_finance,
    parse_dates={"date"}
)

factors_ff3_monthly = (
    factors_ff5_monthly
    .merge(momentum_factor, on="date")
    .rename(columns={"mkt_excess": "mktrf", "wml": "umd"})
)
Hiện mã
factor_cols = ["mktrf", "smb", "hml", "umd", "risk_free"]

(
    factors_ff3_monthly[factor_cols]
    .mul(100)
    .describe()
    .T
    .round(3)
)
Bảng 35.8: Thống kê mô tả các nhân tố Fama-French-Carhart của Việt Nam (hằng tháng, đơn vị phần trăm)
count mean std min 25% 50% 75% max
mktrf 150.0000 -1.0080 5.8580 -21.4910 -3.8030 -0.9510 2.1450 16.7680
smb 150.0000 0.7660 4.1900 -15.2200 -1.3730 1.0400 3.1610 12.8380
hml 150.0000 1.1460 5.1820 -12.8300 -1.2620 0.4610 3.2270 15.0970
umd 150.0000 -2.0910 4.8090 -16.3870 -4.8840 -2.1650 0.6190 15.6040
risk_free 150.0000 0.3330 0.0000 0.3330 0.3330 0.3330 0.3330 0.3330

35.5.3 Kết hợp lợi nhuận danh mục với dữ liệu nhân tố

Hiện mã
# Merge on year-month
returns_wide["ym"] = returns_wide["date"].dt.to_period("M")
factors_ff3_monthly["ym"] = factors_ff3_monthly["date"].dt.to_period("M")

analysis_data = returns_wide.merge(
    factors_ff3_monthly[["ym", "mktrf", "smb", "hml", "umd", "rf"]],
    on="ym",
    how="inner"
)

# Compute excess returns
analysis_data = analysis_data.assign(
    ret_excess_democracy=lambda x: x["ret_vw_Democracy"] - x["rf"],
    ret_excess_dictatorship=lambda x: x["ret_vw_Dictatorship"] - x["rf"],
    ret_excess_longshort=lambda x: x["ret_diff_vw"]
    # Long-short is already excess (self-financing)
)

print(f"Observations for regression: {len(analysis_data)}")

35.5.4 Hồi quy CAPM

Chúng ta bắt đầu với CAPM một nhân tố để hiểu mức độ phơi nhiễm thị trường của từng danh mục:

\[ r_{p,t} - r_{f,t} = \alpha_p + \beta_p (r_{m,t} - r_{f,t}) + \varepsilon_{p,t} \tag{35.9}\]

Bảng 35.9: Kết quả hồi quy CAPM cho các danh mục Dân chủ, Độc tài và mua–bán khống
Hiện mã
def run_regression(y, X, hac=True):
    """Run OLS regression with optional HAC standard errors."""
    X_const = sm.add_constant(X)
    model = OLS(y, X_const).fit(
        cov_type="HAC" if hac else "nonrobust",
        cov_kwds={"maxlags": 6} if hac else {}
    )
    return model

portfolios = {
    "Democracy": analysis_data["ret_excess_democracy"],
    "Dictatorship": analysis_data["ret_excess_dictatorship"],
    "Long-Short": analysis_data["ret_excess_longshort"]
}

capm_results = {}
for name, y in portfolios.items():
    X = analysis_data[["mktrf"]]
    model = run_regression(y, X, hac=True)
    capm_results[name] = {
        "Alpha (%)": model.params["const"] * 100,
        "Alpha t-stat": model.tvalues["const"],
        "Beta (MKTRF)": model.params["mktrf"],
        "Beta t-stat": model.tvalues["mktrf"],
        "R-squared": model.rsquared,
        "N": int(model.nobs)
    }

pd.DataFrame(capm_results).T.round(4)

35.5.5 Mô hình ba nhân tố Fama-French

Mô hình ba nhân tố (Fama và French 1993) bổ sung vào CAPM các nhân tố quy mô (SMB) và giá trị (HML):

\[ r_{p,t} - r_{f,t} = \alpha_p + \beta_1 \text{MKTRF}_t + \beta_2 \text{SMB}_t + \beta_3 \text{HML}_t + \varepsilon_{p,t} \tag{35.10}\]

Bảng 35.10: Kết quả mô hình ba nhân tố Fama-French với sai số chuẩn Newey-West
Hiện mã
ff3_results = {}
for name, y in portfolios.items():
    X = analysis_data[["mktrf", "smb", "hml"]]
    model = run_regression(y, X, hac=True)
    ff3_results[name] = {
        "Alpha (%)": model.params["const"] * 100,
        "Alpha t-stat": model.tvalues["const"],
        "MKTRF": model.params["mktrf"],
        "SMB": model.params["smb"],
        "HML": model.params["hml"],
        "R-squared": model.rsquared
    }

pd.DataFrame(ff3_results).T.round(4)

35.5.6 Mô hình bốn nhân tố Carhart

Việc bổ sung nhân tố động lượng (Carhart 1997) kiểm soát xu hướng đã được ghi nhận rõ là các cổ phiếu thắng cuộc trong quá khứ tiếp tục vượt trội các cổ phiếu thua cuộc trong quá khứ:

Bảng 35.11: Kết quả mô hình bốn nhân tố Carhart cho các danh mục quản trị. Sai số chuẩn được hiệu chỉnh Newey-West với 6 độ trễ.
Hiện mã
ff4_results = {}
for name, y in portfolios.items():
    X = analysis_data[["mktrf", "smb", "hml", "umd"]]
    model = run_regression(y, X, hac=True)
    ff4_results[name] = {
        "Alpha (%)": model.params["const"] * 100,
        "Alpha t-stat": model.tvalues["const"],
        "Alpha p-value": model.pvalues["const"],
        "MKTRF": model.params["mktrf"],
        "MKTRF t": model.tvalues["mktrf"],
        "SMB": model.params["smb"],
        "SMB t": model.tvalues["smb"],
        "HML": model.params["hml"],
        "HML t": model.tvalues["hml"],
        "UMD": model.params["umd"],
        "UMD t": model.tvalues["umd"],
        "R-squared": model.rsquared,
        "Adj R-sq": model.rsquared_adj,
        "N": int(model.nobs)
    }

ff4_df = pd.DataFrame(ff4_results).T
ff4_df.round(4)
Hiện mã
# Detailed regression output for the long-short portfolio
X = sm.add_constant(analysis_data[["mktrf", "smb", "hml", "umd"]])
y = analysis_data["ret_excess_longshort"]

model_ls = OLS(y, X).fit(
    cov_type="HAC", cov_kwds={"maxlags": 6}
)

print(model_ls.summary())

35.5.7 Diễn giải các hệ số tải nhân tố

Các hệ số tải nhân tố cho thấy những đặc điểm quan trọng của các danh mục quản trị:

  1. Beta thị trường (\(\beta_{\text{MKTRF}}\)): Nếu danh mục mua–bán khống có beta thị trường gần bằng không, chiến lược quản trị xấp xỉ trung lập với thị trường. Một beta dương cho thấy các doanh nghiệp Dân chủ nhạy cảm với biến động thị trường hơn các doanh nghiệp Độc tài.

  2. Nhân tố quy mô (\(\beta_{\text{SMB}}\)): Hệ số tải dương cho thấy chiến lược nghiêng về các doanh nghiệp nhỏ hơn. Nếu các doanh nghiệp Dân chủ có xu hướng nhỏ hơn (hoặc các doanh nghiệp Độc tài lớn hơn), nhân tố quy mô sẽ nắm bắt sự khác biệt này.

  3. Nhân tố giá trị (\(\beta_{\text{HML}}\)): Hệ số tải dương cho thấy chiến lược nghiêng về cổ phiếu giá trị. Quản trị và giá trị có thể liên quan với nhau nếu các doanh nghiệp quản trị kém cũng có xu hướng có tỷ số giá trị sổ sách trên giá trị thị trường cao (tức là chúng “rẻ” vì rủi ro quản trị).

  4. Nhân tố động lượng (\(\beta_{\text{UMD}}\)): Hệ số tải động lượng cho biết hiệu ứng quản trị có chồng lấn với động lượng giá hay không. Tại Hoa Kỳ, Gompers, Ishii, và Metrick (2003) tìm thấy hệ số tải động lượng gần bằng không, cho thấy hiệu ứng quản trị tách biệt với động lượng.

35.5.8 Kiểm tra độ vững: Sai số chuẩn vững với phương sai thay đổi của White

Như một kiểm tra độ vững, chúng ta cũng báo cáo kết quả với sai số chuẩn White (HC1), theo đúng phương pháp luận ban đầu:

Bảng 35.12: Mô hình bốn nhân tố với sai số chuẩn vững với phương sai thay đổi của White (HC1)
Hiện mã
white_results = {}
for name, y in portfolios.items():
    X = sm.add_constant(analysis_data[["mktrf", "smb", "hml", "umd"]])
    model = OLS(y, X).fit(cov_type="HC1")
    white_results[name] = {
        "Alpha (%)": model.params["const"] * 100,
        "Alpha t-stat": model.tvalues["const"],
        "Alpha p-value": model.pvalues["const"],
        "R-squared": model.rsquared
    }

pd.DataFrame(white_results).T.round(4)

35.6 Phân tích sâu hơn

35.6.1 Danh mục theo ngũ phân vị quản trị

Thay vì chỉ tập trung vào các danh mục cực đoan, chúng ta xem xét lợi nhuận qua cả năm nhóm ngũ phân vị quản trị. Điều này cho phép đánh giá mối quan hệ giữa quản trị và lợi nhuận có đơn điệu hay không.

Hiện mã
def assign_quintile(group):
    """Assign governance quintile within each vintage year."""
    group = group.copy()
    group["gindex_quintile"] = pd.qcut(
        group["gindex"], q=5, labels=[1, 2, 3, 4, 5],
        duplicates="drop"
    )
    return group

governance_quintiles = (
    governance
    .groupby("year", group_keys=False)
    .apply(assign_quintile)
)

# Merge with stock data
if "dual_class" in governance_quintiles.columns:
    governance_quintiles = governance_quintiles.query("dual_class == 0")

merged_q = pd.merge(
    governance_quintiles[
        ["symbol", "year", "gindex", "gindex_quintile",
         "date_effective", "date_expires"]
    ],
    prices_monthly[["symbol", "date", "ret", "retx", "mktcap"]],
    on="symbol",
    how="inner"
)

merged_q = merged_q.query(
    "date >= date_effective and date <= date_expires"
).sort_values(["symbol", "date"])

# Compute lagged market value
merged_q = merged_q.sort_values(["symbol", "date"])
merged_q["mktcap_lag"] = merged_q.groupby("symbol")["mktcap"].shift(1)

first_obs_q = merged_q.groupby("symbol")["date"].transform("min")
mask_first_q = merged_q["date"] == first_obs_q
merged_q.loc[mask_first_q, "mktcap_lag"] = (
    merged_q.loc[mask_first_q, "mktcap"]
    / (1 + merged_q.loc[mask_first_q, "retx"])
)

mask_missing_q = merged_q["mktcap_lag"].isna()
merged_q.loc[mask_missing_q, "mktcap_lag"] = (
    merged_q.loc[mask_missing_q, "mktcap"]
    / (1 + merged_q.loc[mask_missing_q, "retx"])
)

merged_q = merged_q.dropna(subset=["ret", "mktcap_lag"])
merged_q = merged_q.query("mktcap_lag > 0")
Hiện mã
# Compute VW returns by quintile and date
quintile_returns = (
    merged_q
    .groupby(["date", "gindex_quintile"])
    .apply(
        lambda g: np.average(g["ret"], weights=g["mktcap_lag"])
        if g["mktcap_lag"].sum() > 0 else np.nan,
        include_groups=False
    )
    .reset_index()
    .rename(columns={0: "ret_vw"})
)

# Average across time
quintile_avg = (
    quintile_returns
    .groupby("gindex_quintile")["ret_vw"]
    .agg(["mean", "std", "count"])
    .assign(
        se=lambda x: x["std"] / np.sqrt(x["count"]),
        ci95=lambda x: 1.96 * x["std"] / np.sqrt(x["count"])
    )
)

fig, ax = plt.subplots(figsize=(8, 5))

quintiles = quintile_avg.index.astype(int)
means = quintile_avg["mean"] * 100 * 12  # Annualized
ci = quintile_avg["ci95"] * 100 * 12

bars = ax.bar(
    quintiles, means, yerr=ci,
    color=["#1a6b3c", "#5fa35f", "#b0b0b0", "#d4836a", "#c0392b"],
    edgecolor="white", linewidth=0.5, capsize=5,
    error_kw={"linewidth": 1.5}
)

ax.axhline(0, color="gray", linestyle="--", linewidth=0.8)
ax.set_xlabel("VN-GIndex Quintile\n(1 = Strongest Rights → 5 = Weakest Rights)",
              fontsize=10)
ax.set_ylabel("Annualized Return (%)", fontsize=10)
ax.set_title(
    "Average Annualized Returns by Governance Quintile",
    fontsize=12, fontweight="bold"
)
ax.set_xticks(quintiles)
ax.grid(axis="y", alpha=0.3)

plt.tight_layout()
plt.show()
Hình 35.5
Bảng 35.13: Lợi nhuận trung bình hằng tháng và alpha bốn nhân tố theo nhóm ngũ phân vị quản trị
Hiện mã
# Merge quintile returns with factors
quintile_wide = quintile_returns.pivot(
    index="date", columns="gindex_quintile", values="ret_vw"
)
quintile_wide.columns = [f"Q{int(c)}" for c in quintile_wide.columns]
quintile_wide = quintile_wide.reset_index()

quintile_wide["ym"] = quintile_wide["date"].dt.to_period("M")
quintile_analysis = quintile_wide.merge(
    factors_ff3_monthly[["ym", "mktrf", "smb", "hml", "umd", "rf"]],
    on="ym", how="inner"
)

quintile_factor_results = {}
for q in range(1, 6):
    col = f"Q{q}"
    if col in quintile_analysis.columns:
        y = quintile_analysis[col] - quintile_analysis["rf"]
        X = sm.add_constant(
            quintile_analysis[["mktrf", "smb", "hml", "umd"]]
        )
        model = OLS(y.dropna(), X.loc[y.dropna().index]).fit(
            cov_type="HAC", cov_kwds={"maxlags": 6}
        )
        quintile_factor_results[f"Quintile {q}"] = {
            "Mean Return (%)": y.mean() * 100,
            "Alpha (%)": model.params["const"] * 100,
            "Alpha t-stat": model.tvalues["const"],
            "MKTRF Beta": model.params["mktrf"],
            "R-squared": model.rsquared
        }

# Add 5-1 spread
if "Q1" in quintile_analysis.columns and "Q5" in quintile_analysis.columns:
    y_spread = quintile_analysis["Q1"] - quintile_analysis["Q5"]
    X = sm.add_constant(
        quintile_analysis[["mktrf", "smb", "hml", "umd"]]
    )
    model_spread = OLS(
        y_spread.dropna(), X.loc[y_spread.dropna().index]
    ).fit(cov_type="HAC", cov_kwds={"maxlags": 6})
    quintile_factor_results["Q1 - Q5"] = {
        "Mean Return (%)": y_spread.mean() * 100,
        "Alpha (%)": model_spread.params["const"] * 100,
        "Alpha t-stat": model_spread.tvalues["const"],
        "MKTRF Beta": model_spread.params["mktrf"],
        "R-squared": model_spread.rsquared
    }

pd.DataFrame(quintile_factor_results).T.round(4)

35.6.2 Phân tích mẫu con

Phần bù quản trị có thể thay đổi giữa các trạng thái thị trường. Chúng ta chia mẫu tại điểm giữa và xem xét liệu hiệu ứng có tập trung ở một giai đoạn con cụ thể hay không.

Bảng 35.14: Alpha bốn nhân tố của chiến lược quản trị mua–bán khống qua các giai đoạn con
Hiện mã
midpoint = analysis_data["date"].quantile(0.5)

subperiods = {
    "Full Sample": analysis_data,
    "First Half": analysis_data.query(f"date <= '{midpoint}'"),
    "Second Half": analysis_data.query(f"date > '{midpoint}'")
}

subsample_results = {}
for period_name, df in subperiods.items():
    if len(df) < 12:
        continue
    y = df["ret_excess_longshort"]
    X = sm.add_constant(df[["mktrf", "smb", "hml", "umd"]])
    model = OLS(y, X).fit(
        cov_type="HAC", cov_kwds={"maxlags": 6}
    )
    subsample_results[period_name] = {
        "Alpha (% monthly)": model.params["const"] * 100,
        "Alpha (% annual)": model.params["const"] * 100 * 12,
        "T-statistic": model.tvalues["const"],
        "P-value": model.pvalues["const"],
        "N months": int(model.nobs),
        "Date Range": f"{df['date'].min().strftime('%Y-%m')} to "
                      f"{df['date'].max().strftime('%Y-%m')}"
    }

pd.DataFrame(subsample_results).T

35.6.3 Kiểm tra độ vững với trọng số bằng nhau

Việc dùng trọng số theo vốn hóa có thể khiến kết quả bị chi phối bởi một vài doanh nghiệp lớn. Chúng ta kiểm tra độ vững với các danh mục trọng số bằng nhau.

Bảng 35.15: Kết quả mô hình bốn nhân tố: Danh mục mua–bán khống trọng số bằng nhau so với trọng số theo vốn hóa
Hiện mã
ew_y = analysis_data["ret_diff_ew"]
vw_y = analysis_data["ret_excess_longshort"]
X = sm.add_constant(analysis_data[["mktrf", "smb", "hml", "umd"]])

model_ew = OLS(ew_y, X).fit(cov_type="HAC", cov_kwds={"maxlags": 6})
model_vw = OLS(vw_y, X).fit(cov_type="HAC", cov_kwds={"maxlags": 6})

comparison = pd.DataFrame({
    "Value-Weighted": {
        "Alpha (% monthly)": model_vw.params["const"] * 100,
        "Alpha t-stat": model_vw.tvalues["const"],
        "MKTRF": model_vw.params["mktrf"],
        "SMB": model_vw.params["smb"],
        "HML": model_vw.params["hml"],
        "UMD": model_vw.params["umd"],
        "R-squared": model_vw.rsquared
    },
    "Equal-Weighted": {
        "Alpha (% monthly)": model_ew.params["const"] * 100,
        "Alpha t-stat": model_ew.tvalues["const"],
        "MKTRF": model_ew.params["mktrf"],
        "SMB": model_ew.params["smb"],
        "HML": model_ew.params["hml"],
        "UMD": model_ew.params["umd"],
        "R-squared": model_ew.rsquared
    }
}).T

comparison.round(4)

35.6.4 Tính ổn định của hệ số tải nhân tố: Hồi quy trượt

Hiện mã
window = 24
rolling_alpha = []

for i in range(window, len(analysis_data)):
    subset = analysis_data.iloc[i - window:i]
    y = subset["ret_excess_longshort"]
    X = sm.add_constant(subset[["mktrf", "smb", "hml", "umd"]])
    try:
        model = OLS(y, X).fit()
        rolling_alpha.append({
            "date": subset["date"].iloc[-1],
            "alpha": model.params["const"] * 100 * 12,
            "alpha_se": model.bse["const"] * 100 * 12,
            "t_stat": model.tvalues["const"]
        })
    except Exception:
        pass

rolling_alpha_df = pd.DataFrame(rolling_alpha)

fig, ax = plt.subplots(figsize=(10, 5))

ax.plot(
    rolling_alpha_df["date"],
    rolling_alpha_df["alpha"],
    color="#2c5f8a", linewidth=1.5
)
ax.fill_between(
    rolling_alpha_df["date"],
    rolling_alpha_df["alpha"] - 1.96 * rolling_alpha_df["alpha_se"],
    rolling_alpha_df["alpha"] + 1.96 * rolling_alpha_df["alpha_se"],
    alpha=0.2, color="#2c5f8a"
)
ax.axhline(0, color="gray", linestyle="--", linewidth=0.8)

ax.set_xlabel("Date", fontsize=11)
ax.set_ylabel("Annualized Alpha (%)", fontsize=11)
ax.set_title(
    "24-Month Rolling Four-Factor Alpha: Governance Long-Short Strategy",
    fontsize=12, fontweight="bold"
)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Hình 35.6

35.7 So sánh với bằng chứng quốc tế

35.7.1 Kinh nghiệm của Hoa Kỳ

Tại Hoa Kỳ, Gompers, Ishii, và Metrick (2003) ghi nhận lợi nhuận bất thường quy đổi theo năm khoảng 8,5% cho chiến lược Dân chủ trừ Độc tài trong thập niên 1990. Các nghiên cứu sau đó đã bổ sung những sắc thái quan trọng:

  • L. Bebchuk, Cohen, và Ferrell (2009) tinh gọn G-Index thành Chỉ số cố thủ (Entrenchment Index, E-Index) chỉ dựa trên sáu điều khoản quan trọng nhất đối với giá trị doanh nghiệp: hội đồng quản trị có nhiệm kỳ so le, giới hạn việc cổ đông sửa đổi quy chế nội bộ (bylaws), poison pill, golden parachute, và yêu cầu đa số tuyệt đối đối với sáp nhập và sửa đổi điều lệ. E-Index giải thích mối quan hệ giữa quản trị và lợi nhuận ít nhất tốt như toàn bộ G-Index.

  • Cremers và Nair (2005) phát hiện rằng hiệu ứng quản trị mạnh nhất ở các doanh nghiệp mà các cơ chế quản trị bên ngoài (thị trường thâu tóm) hoạt động sôi động, gợi ý sự bổ trợ giữa quản trị nội bộ và bên ngoài.

  • Phần bù quản trị tại Hoa Kỳ phần lớn đã biến mất trong các giai đoạn gần đây (L. A. Bebchuk, Cohen, và Wang 2013), có thể vì thị trường đã học cách định giá sự khác biệt về quản trị. “Giả thuyết học hỏi” này có hàm ý về việc liệu các chiến lược tương tự có thể tồn tại ở những thị trường kém hiệu quả hơn như Việt Nam hay không.

35.7.2 Bối cảnh thị trường mới nổi

Mối quan hệ giữa quản trị và lợi nhuận ở các thị trường mới nổi vẫn là một lĩnh vực nghiên cứu sôi động. Một số đặc điểm của thị trường mới nổi cho thấy phần bù có thể lớn hơn và bền vững hơn:

  1. Bất đối xứng thông tin: Yêu cầu công bố thông tin yếu hơn và ít phân tích viên theo dõi hơn khiến chất lượng quản trị khó quan sát hơn, tạo ra các sai lệch định giá lớn hơn (Klapper và Love 2004).

  2. Thực thi pháp luật yếu hơn: Khi sự bảo vệ pháp lý đối với cổ đông thiểu số yếu, quản trị ở cấp doanh nghiệp trở nên quan trọng hơn như một yếu tố thay thế (La Porta và c.s. 2000).

  3. Sở hữu tập trung: Sự hiện diện của các cổ đông kiểm soát tạo cơ hội cho việc chuyển giá trị sang bên liên quan (tunneling) và các giao dịch với bên liên quan, mà các cơ chế quản trị tốt có thể giảm thiểu (Johnson và c.s. 2000).

Việt Nam, với tư cách là một thị trường cận biên đang tiến tới mới nổi và có đủ cả ba đặc điểm này, là một phòng thí nghiệm đặc biệt thú vị để kiểm tra liệu quản trị có tạo ra lợi nhuận bất thường hay không.

35.8 Phân tích sụt giảm và rủi ro

35.8.1 Mức sụt giảm tối đa

Hiện mã
def compute_drawdown(cumulative_returns):
    """Compute drawdown series from cumulative returns."""
    running_max = cumulative_returns.cummax()
    drawdown = (cumulative_returns - running_max) / running_max
    return drawdown

cum_dem = (1 + returns_wide["ret_vw_Democracy"]).cumprod()
cum_dic = (1 + returns_wide["ret_vw_Dictatorship"]).cumprod()

dd_dem = compute_drawdown(cum_dem)
dd_dic = compute_drawdown(cum_dic)

fig, ax = plt.subplots(figsize=(10, 5))

ax.fill_between(
    returns_wide["date"], dd_dem * 100, 0,
    alpha=0.4, color="#1a6b3c", label="Democracy"
)
ax.fill_between(
    returns_wide["date"], dd_dic * 100, 0,
    alpha=0.4, color="#c0392b", label="Dictatorship"
)
ax.set_xlabel("Date", fontsize=11)
ax.set_ylabel("Drawdown (%)", fontsize=11)
ax.set_title("Portfolio Drawdowns", fontsize=12, fontweight="bold")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Hình 35.7
Bảng 35.16: Các thước đo rủi ro của các danh mục quản trị
Hiện mã
def compute_risk_metrics(returns, name, rf=None):
    """Compute standard risk metrics."""
    r = returns.dropna()
    if rf is not None:
        excess = r - rf
    else:
        excess = r
    
    ann_return = (1 + r.mean()) ** 12 - 1
    ann_vol = r.std() * np.sqrt(12)
    sharpe = excess.mean() / r.std() * np.sqrt(12) if r.std() > 0 else np.nan
    
    cum = (1 + r).cumprod()
    max_dd = compute_drawdown(cum).min()
    
    # Sortino ratio (downside deviation)
    downside = r[r < 0].std() * np.sqrt(12)
    sortino = excess.mean() * 12 / downside if downside > 0 else np.nan
    
    # Skewness and kurtosis
    skew = r.skew()
    kurt = r.kurtosis()
    
    return {
        "Portfolio": name,
        "Ann. Return (%)": ann_return * 100,
        "Ann. Volatility (%)": ann_vol * 100,
        "Sharpe Ratio": sharpe,
        "Sortino Ratio": sortino,
        "Max Drawdown (%)": max_dd * 100,
        "Skewness": skew,
        "Excess Kurtosis": kurt,
        "% Positive Months": (r > 0).mean() * 100
    }

rf_series = analysis_data.set_index("date")["rf"].reindex(
    returns_wide["date"]
).fillna(0)

risk_table = pd.DataFrame([
    compute_risk_metrics(
        returns_wide["ret_vw_Democracy"], "Democracy (VW)",
        rf_series.values
    ),
    compute_risk_metrics(
        returns_wide["ret_vw_Dictatorship"], "Dictatorship (VW)",
        rf_series.values
    ),
    compute_risk_metrics(
        returns_wide["ret_diff_vw"], "Long-Short (VW)"
    )
]).set_index("Portfolio")

risk_table.round(3)

35.9 Thảo luận và diễn giải

35.9.1 Vì sao quản trị có thể dự báo lợi nhuận tại Việt Nam?

Một số kênh có thể giải thích phần bù quản trị trên thị trường Việt Nam:

Kênh rủi ro: Các doanh nghiệp quản trị kém có thể rủi ro hơn theo những cách mà các mô hình nhân tố tiêu chuẩn không nắm bắt được. Nhà đầu tư đòi hỏi lợi nhuận kỳ vọng cao hơn để nắm giữ các doanh nghiệp này, nhưng chênh lệch lợi nhuận đảo chiều (tức là phần bù quản trị dương đối với các doanh nghiệp quản trị tốt) nếu thị trường đánh giá quá cao rủi ro của các doanh nghiệp quản trị kém hoặc nếu rủi ro quản trị có thể được đa dạng hóa một phần.

Kênh định giá sai: Nếu thị trường chậm phản ánh thông tin quản trị vào giá, có thể vì chất lượng quản trị tốn kém để đánh giá hoặc vì nhiều nhà đầu tư Việt Nam là nhà đầu tư cá nhân với năng lực phân tích hạn chế, thì một chiến lược có hệ thống mua doanh nghiệp quản trị tốt và bán doanh nghiệp quản trị kém có thể thu lợi từ việc điều chỉnh dần các sai lệch định giá.

Kênh dòng tiền: Các doanh nghiệp quản trị tốt hơn có thể tạo ra dòng tiền thực sự cao hơn vì ít lãng phí vào xây dựng đế chế (empire-building), giao dịch với bên liên quan và các hoạt động hủy hoại giá trị khác. Trong chừng mực những dòng tiền vượt trội này không được thị trường dự đoán đầy đủ, các doanh nghiệp quản trị tốt mang lại những bất ngờ dương về lợi nhuận.

35.9.2 Sở hữu nhà nước và hiệu ứng quản trị

Một đặc điểm nổi bật của thị trường Việt Nam là sự phổ biến của các doanh nghiệp nhà nước (SOE). Tương tác giữa sở hữu nhà nước và chất lượng quản trị tạo ra một động lực thú vị:

  • Các SOE có thể có quản trị yếu theo các tiêu chí thông thường (hội đồng quản trị ít độc lập, bổ nhiệm mang tính chính trị) nhưng lại được hưởng lợi từ các bảo lãnh ngầm của Chính phủ và khả năng tiếp cận ưu đãi đối với đất đai, vốn và hợp đồng.
  • Do đó, phần bù quản trị có thể khác nhau giữa SOE và doanh nghiệp tư nhân. Chúng tôi khuyến khích bạn đọc mở rộng phân tích bằng cách cho chỉ số quản trị tương tác với một biến chỉ báo sở hữu nhà nước.

35.9.3 Hạn chế

Phân tích này có một số lưu ý sau:

  1. Thiên kiến sống sót: Nếu các doanh nghiệp quản trị kém có khả năng bị hủy niêm yết cao hơn (do khó khăn tài chính hoặc xử lý của cơ quan quản lý), lợi nhuận của danh mục Độc tài có thể bị chệch lên, làm suy giảm phần bù quản trị thực sự.

  2. Chi phí giao dịch: Chiến lược mua–bán khống đòi hỏi bán khống, vốn bị hạn chế tại Việt Nam. Việc triển khai bằng cách nghiêng danh mục chỉ mua (long-only) về các doanh nghiệp Dân chủ có thể thực tế hơn.

  3. Tần suất dữ liệu quản trị: Không giống giá cổ phiếu hằng ngày, dữ liệu quản trị được cập nhật không thường xuyên (thường là hằng năm). Do đó tần suất tái cân bằng danh mục thấp, làm hạn chế khả năng phản ứng của chiến lược trước các thay đổi về quản trị.

  4. Cách xây dựng chỉ số: Các điều khoản cụ thể được đưa vào VN-GIndex và việc gán trọng số bằng nhau cho chúng có thể không nắm bắt tối ưu chất lượng quản trị. Các nghiên cứu tương lai có thể khám phá các chỉ số có trọng số, theo L. Bebchuk, Cohen, và Ferrell (2009).

35.10 Bài tập

  1. Chỉ số cố thủ: Theo L. Bebchuk, Cohen, và Ferrell (2009), hãy xác định tập con các điều khoản quản trị trong dữ liệu Việt Nam có mối liên hệ mạnh nhất với giá trị doanh nghiệp (đo bằng Tobin’s Q hoặc tỷ số giá thị trường trên giá trị sổ sách). Xây dựng một E-Index của Việt Nam chỉ dùng các điều khoản này và so sánh khả năng dự báo lợi nhuận của nó với VN-GIndex đầy đủ.

  2. Hồi quy Fama-MacBeth: Thay vì phân loại doanh nghiệp vào các danh mục, hãy ước lượng mối quan hệ giữa quản trị và lợi nhuận bằng hồi quy mặt cắt ngang Fama-MacBeth (Fama và MacBeth 1973):

\[ r_{i,t} - r_{f,t} = \gamma_{0,t} + \gamma_{1,t} \text{VN-GIndex}_{i,t-1} + \gamma_{2,t} \mathbf{X}_{i,t-1} + \epsilon_{i,t} \]

trong đó \(\mathbf{X}_{i,t-1}\) gồm các biến kiểm soát về quy mô, tỷ số giá trị sổ sách trên giá trị thị trường và động lượng. Hãy báo cáo trung bình chuỗi thời gian của \(\hat{\gamma}_{1,t}\) với sai số chuẩn Newey-West.

  1. Tương tác với sở hữu nhà nước: Chia mẫu thành các SOE (sở hữu nhà nước > 50%) và doanh nghiệp tư nhân. Phần bù quản trị có khác nhau giữa hai nhóm này không? Hãy ước lượng:

\[ r_{t}^{D-D} = \alpha + \beta_1 \text{MKTRF}_t + \beta_2 \text{SMB}_t + \beta_3 \text{HML}_t + \beta_4 \text{UMD}_t + \varepsilon_t \] riêng cho các mẫu con SOE và phi SOE.

  1. Phân tích chi phí giao dịch: Tính tỷ lệ vòng quay danh mục tại mỗi ngày tái cân bằng (khi có dữ liệu quản trị mới). Ước tính phần lợi nhuận bất thường bị chi phí giao dịch hấp thụ ở mức chênh lệch giá mua–bán thực tế của cổ phiếu Việt Nam.

  2. Thay đổi quản trị: Các doanh nghiệp cải thiện quản trị (VN-GIndex giảm) có đạt lợi nhuận cao hơn trong giai đoạn sau so với các doanh nghiệp có quản trị xấu đi không? Hãy xây dựng các danh mục dựa trên \(\Delta \text{VN-GIndex}\) và kiểm định.

  3. So sánh với các cải cách quản trị toàn thị trường: Việt Nam đã triển khai một số đợt cải cách quản trị (ví dụ Thông tư 121/2012/TT-BTC, Nghị định 71/2017/ND-CP). Hãy kiểm định xem phần bù quản trị có thu hẹp sau các thay đổi quy định này hay không, bằng cách dùng điểm gãy cấu trúc hoặc phương pháp tương tác.

35.11 Tóm tắt

Chương này điều chỉnh chiến lược đầu tư quản trị có ảnh hưởng lớn của Gompers, Ishii, và Metrick (2003) cho thị trường chứng khoán Việt Nam. Các bước phương pháp chính là:

  1. Xây dựng chỉ số quản trị (VN-GIndex) từ các điều khoản quản trị ở cấp doanh nghiệp có sẵn qua DataCore.vn.
  2. Phân loại doanh nghiệp vào các danh mục Dân chủ (quyền mạnh) và Độc tài (quyền yếu) dựa trên phân phối theo mặt cắt ngang của chỉ số.
  3. Tính lợi nhuận danh mục hằng tháng theo trọng số vốn hóa, tái cân bằng khi có dữ liệu quản trị mới.
  4. Đánh giá chiến lược mua–bán khống (Dân chủ trừ Độc tài) bằng kiểm định t và hồi quy bốn nhân tố Fama-French-Carhart.
  5. Xem xét độ vững qua các giai đoạn con, các danh mục ngũ phân vị và các cách gán trọng số thay thế.