29  Định giá doanh nghiệp, kiệt quệ tài chính và mức độ trưởng thành của công ty

Hiểu rõ về định giá doanh nghiệp, sức khỏe tài chính và mức độ trưởng thành của doanh nghiệp là nền tảng của phân tích tài chính và việc ra quyết định đầu tư. Ba thước đo được sử dụng rộng rãi (chẳng hạn Q của Tobin, điểm Z của Altman và tuổi công ty) phản ánh những khía cạnh khác nhau nhưng bổ sung cho nhau về vị thế kinh tế của doanh nghiệp. Q của Tobin phản ánh đánh giá của thị trường về giá trị doanh nghiệp so với cơ sở tài sản của nó, điểm Z của Altman dự báo khả năng xảy ra kiệt quệ tài chính, còn tuổi công ty là đại diện cho mức độ trưởng thành về tổ chức và sự ổn định hoạt động.

Mặc dù các thước đo này đã được nghiên cứu rộng rãi ở các thị trường phát triển, đặc biệt là Hoa Kỳ (xem Lindenberg và Ross 1981; Altman 1968; Gompers, Ishii, và Metrick 2003), việc áp dụng chúng ở các thị trường mới nổi như Việt Nam đặt ra những thách thức và cơ hội riêng. Thị trường chứng khoán Việt Nam tăng trưởng nhanh nhưng vẫn mang những đặc điểm cấu trúc, như mức độ tập trung sở hữu, sự chi phối của doanh nghiệp nhà nước, thị trường trái phiếu còn thiếu chiều sâu và chuẩn mực kế toán đang tiếp tục phát triển, đòi hỏi phải điều chỉnh cẩn trọng các thước đo định giá và kiệt quệ tài chính tiêu chuẩn.

29.1 Các gói cần thiết

Hiện mã
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import seaborn as sns
from matplotlib.colors import LinearSegmentedColormap
import warnings

warnings.filterwarnings("ignore")
plt.rcParams.update({
    "figure.figsize": (10, 6),
    "font.size": 12,
    "axes.titlesize": 14,
    "axes.labelsize": 12,
    "xtick.labelsize": 10,
    "ytick.labelsize": 10,
    "legend.fontsize": 10,
    "figure.dpi": 150,
    "axes.spines.top": False,
    "axes.spines.right": False,
})

# Color palette consistent with Tidy Finance style
colors = {
    "primary": "#1f77b4",
    "secondary": "#ff7f0e",
    "tertiary": "#2ca02c",
    "quaternary": "#d62728",
    "quinary": "#9467bd",
    "safe": "#2ca02c",
    "alert": "#ff7f0e",
    "danger": "#d62728",
    "distress": "#8b0000",
}

30 Nền tảng lý thuyết

30.1 Q của Tobin: Giá trị thị trường của doanh nghiệp

30.1.1 Khái niệm gốc

Q của Tobin được Tobin (1969) giới thiệu như một mối liên kết lý thuyết giữa thị trường tài chính và các quyết định đầu tư thực. Ý tưởng cốt lõi rất đơn giản mà tinh tế: nếu thị trường định giá tài sản của doanh nghiệp cao hơn chi phí thay thế của chúng, doanh nghiệp có động cơ đầu tư thêm vốn mới; nếu thị trường định giá thấp hơn, doanh nghiệp không nên đầu tư, thậm chí có thể có lợi khi bán bớt tài sản.

Một cách hình thức, Q của Tobin được định nghĩa như sau:

\[ Q = \frac{\text{Market Value of the Firm}}{\text{Replacement Cost of Assets}} \tag{30.1}\]

Khi \(Q > 1\), thị trường nhận thấy doanh nghiệp sở hữu các tài sản vô hình có giá trị, như giá trị thương hiệu, năng lực quản lý, công nghệ độc quyền hoặc cơ hội tăng trưởng, vượt quá chi phí của cơ sở tài sản hữu hình. Khi \(Q < 1\), thị trường thực chất định giá doanh nghiệp thấp hơn chi phí để xây dựng lại tài sản của nó, gợi ý khả năng bị định giá thấp hoặc sự hiện diện của chi phí đại diện và sự kém hiệu quả về tổ chức.

30.1.2 Phân rã giá trị thị trường

Tử số của Q của Tobin biểu diễn tổng giá trị thị trường của mọi quyền đòi đối với doanh nghiệp:

\[ MV = CS + PS + ST + LT \tag{30.2}\]

trong đó:

  • \(CS\) = Giá trị thị trường của cổ phiếu phổ thông (số cổ phiếu đang lưu hành \(\times\) giá thị trường)
  • \(PS\) = Giá trị thị trường của cổ phiếu ưu đãi
  • \(ST\) = Giá trị thị trường của nợ ngắn hạn
  • \(LT\) = Giá trị thị trường của nợ dài hạn

30.1.3 Chi phí thay thế của tài sản

Mẫu số phản ánh chi phí thay thế của cơ sở tài sản của doanh nghiệp:

\[ RC = TA + (RNP - HNP) + (RINV - HINV) \tag{30.3}\]

trong đó:

  • \(TA\) = Tổng tài sản theo báo cáo
  • \(RNP\) = Chi phí thay thế của nhà xưởng và thiết bị ròng
  • \(HNP\) = Giá trị lịch sử (sổ sách) của nhà xưởng và thiết bị ròng
  • \(RINV\) = Chi phí thay thế của hàng tồn kho
  • \(HINV\) = Giá trị lịch sử (sổ sách) của hàng tồn kho

Việc điều chỉnh chi phí thay thế đối với nhà xưởng và thiết bị đòi hỏi các phép tính đệ quy có tính đến tỷ lệ khấu hao và, trong các ước lượng chi tiết hơn, tỷ lệ tiến bộ kỹ thuật (Lindenberg và Ross 1981). Đối với hàng tồn kho, việc điều chỉnh phụ thuộc vào phương pháp kế toán hàng tồn kho: LIFO (nhập sau, xuất trước), FIFO (nhập trước, xuất trước), giá bình quân hoặc giá bán lẻ.

30.1.4 Q của Tobin giản lược

Trong thực tế, việc tính đầy đủ chi phí thay thế đòi hỏi dữ liệu thường không có sẵn, đặc biệt ở các thị trường mới nổi. Gompers, Ishii, và Metrick (2003) phổ biến một phiên bản giản lược:

\[ Q_{\text{simple}} = \frac{TA + ME - BE}{TA} \tag{30.4}\]

trong đó \(ME\) là giá trị thị trường của vốn chủ sở hữu và \(BE\) là giá trị sổ sách của vốn chủ sở hữu. Cách xây dựng này giả định rằng giá trị sổ sách của nợ và cổ phiếu ưu đãi xấp xỉ giá trị thị trường của chúng, và tổng tài sản xấp xỉ chi phí thay thế của cơ sở tài sản của doanh nghiệp. Dù đơn giản, thước đo này được chứng minh là tương quan tốt với các cách xây dựng phức tạp hơn và đã trở thành chuẩn mực trong nghiên cứu tài chính doanh nghiệp thực nghiệm.

30.1.5 Xấp xỉ của Chung và Pruitt

Chung và Pruitt (1994) đề xuất một phép xấp xỉ khác được sử dụng rộng rãi:

\[ Q_{\text{CP}} = \frac{ME + PS + DEBT}{TA} \tag{30.5}\]

trong đó \(DEBT = \text{Current Liabilities} - \text{Current Assets} + \text{Book Value of Inventories} + \text{Long-term Debt}\). Cách xây dựng này nắm bắt vị thế nợ ròng chính xác hơn so với phiên bản của Gompers, Ishii, và Metrick (2003).


30.2 Điểm Z của Altman: Dự báo kiệt quệ tài chính

30.2.1 Mô hình gốc

Điểm Z của Altman, do Altman (1968) phát triển, là một mô hình phân tích phân biệt đa biến dự báo xác suất phá sản của doanh nghiệp trong khoảng thời gian hai năm. Mô hình ban đầu được ước lượng bằng một mẫu ghép cặp gồm các doanh nghiệp sản xuất của Hoa Kỳ đã phá sản và chưa phá sản, và có dạng:

\[ Z = 3.3 \cdot X_1 + 0.999 \cdot X_2 + 0.6 \cdot X_3 + 1.2 \cdot X_4 + 1.4 \cdot X_5 \tag{30.6}\]

trong đó năm tỷ số tài chính được nêu trong Bảng 30.1

Bảng 30.1: Các thành phần của điểm Z của Altman
Biến Công thức Diễn giải
\(X_1\) \(\frac{\text{EBIT}}{\text{Total Assets}}\) Khả năng sinh lời của tài sản
\(X_2\) \(\frac{\text{Net Sales}}{\text{Total Assets}}\) Vòng quay tổng tài sản
\(X_3\) \(\frac{\text{Market Value of Equity}}{\text{Total Liabilities}}\) Tỷ số đòn bẩy (nghịch đảo)
\(X_4\) \(\frac{\text{Working Capital}}{\text{Total Assets}}\) Thanh khoản ngắn hạn
\(X_5\) \(\frac{\text{Retained Earnings}}{\text{Total Assets}}\) Khả năng sinh lời tích lũy

Các vùng diễn giải được nêu trong Bảng 30.2

Bảng 30.2: Các vùng diễn giải điểm Z của Altman
Khoảng điểm Z Diễn giải
\(Z > 2.99\) Vùng an toàn-xác suất kiệt quệ tài chính thấp
\(2.70 \leq Z \leq 2.99\) Vùng xám-cần cảnh giác, rủi ro trung bình
\(1.80 \leq Z < 2.70\) Vùng kiệt quệ-rủi ro phá sản đáng kể trong vòng 2 năm
\(Z < 1.80\) Kiệt quệ nghiêm trọng-xác suất thất bại tài chính rất cao

30.2.2 Mô hình điểm Z’ cho doanh nghiệp chưa niêm yết

Vì mô hình gốc dùng giá trị thị trường của vốn chủ sở hữu trong \(X_3\), Altman và Hotchkiss (2010) phát triển điểm Z’ cho các doanh nghiệp chưa niêm yết (không giao dịch công khai) bằng cách thay vốn hóa thị trường bằng giá trị sổ sách của vốn chủ sở hữu:

\[ Z' = 0.717 \cdot X_1' + 0.847 \cdot X_2' + 3.107 \cdot X_3' + 0.420 \cdot X_4' + 0.998 \cdot X_5' \tag{30.7}\]

trong đó \(X_4' = \frac{\text{Book Value of Equity}}{\text{Total Liabilities}}\), và các biến còn lại được định nghĩa như trong mô hình gốc nhưng với các hệ số được ước lượng lại.

30.2.3 Mô hình điểm Z’’ cho thị trường mới nổi

Phù hợp nhất với Việt Nam, Altman và Hotchkiss (2010) cũng phát triển điểm Z’’ cho các doanh nghiệp phi sản xuất và doanh nghiệp ở thị trường mới nổi:

\[ Z'' = 3.25 + 6.56 \cdot X_1'' + 3.26 \cdot X_2'' + 6.72 \cdot X_3'' + 1.05 \cdot X_4'' \tag{30.8}\]

trong đó:

  • \(X_1'' = \frac{\text{Working Capital}}{\text{Total Assets}}\)
  • \(X_2'' = \frac{\text{Retained Earnings}}{\text{Total Assets}}\)
  • \(X_3'' = \frac{\text{EBIT}}{\text{Total Assets}}\)
  • \(X_4'' = \frac{\text{Book Value of Equity}}{\text{Total Liabilities}}\)

Lưu ý rằng mô hình này loại bỏ tỷ số doanh thu/tổng tài sản (\(X_2\) trong mô hình gốc) để giảm thiểu hiệu ứng ngành và bổ sung một hệ số chặn. Các vùng phân loại được dịch chuyển tương ứng (Bảng 30.3).

Bảng 30.3: Các vùng diễn giải điểm Z’’ cho thị trường mới nổi
Khoảng điểm Z’’ Diễn giải
\(Z'' > 2.60\) Vùng an toàn
\(1.10 \leq Z'' \leq 2.60\) Vùng xám
\(Z'' < 1.10\) Vùng kiệt quệ

30.3 Tuổi công ty: Đo lường mức độ trưởng thành của doanh nghiệp

Tuổi công ty phản ánh kinh nghiệm tích lũy, uy tín và vốn tổ chức của doanh nghiệp. Các doanh nghiệp lâu đời thường có hoạt động ổn định hơn, lợi thế cạnh tranh vững chắc và dòng tiền dễ dự báo (Coad và c.s. 2018). Tuổi thường được dùng làm biến kiểm soát trong nghiên cứu tài chính doanh nghiệp, và mối quan hệ của nó với hiệu quả hoạt động là mơ hồ về mặt lý thuyết: doanh nghiệp lâu đời được hưởng lợi từ hiệu ứng học hỏi và uy tín nhưng có thể chịu sự cứng nhắc về tổ chức và suy giảm đổi mới (Huergo và Jaumandreu 2004).

Thước đo lý tưởng là số năm kể từ khi thành lập. Khi không có ngày thành lập, các đại diện thông dụng gồm:

  1. Tuổi niêm yết: Số năm kể từ lần IPO hoặc niêm yết đầu tiên trên một sở giao dịch chứng khoán.
  2. Tuổi dữ liệu: Số năm kể từ lần xuất hiện đầu tiên trong các cơ sở dữ liệu tài chính.
  3. Tuổi đăng ký thành lập: Số năm kể từ ngày đăng ký thành lập pháp lý.

Trong bối cảnh Việt Nam, chúng ta có thể sử dụng nhiều đại diện, bao gồm ngày niêm yết lần đầu trên HOSE hoặc HNX, ngày báo cáo tài chính đầu tiên có sẵn, và (khi có) ngày thành lập từ hồ sơ công ty.

31 Bối cảnh thị trường Việt Nam

31.1 Các đặc điểm thể chế ảnh hưởng đến thước đo định giá

Thị trường chứng khoán Việt Nam có một số đặc điểm thể chế quan trọng cần xem xét khi tính toán và diễn giải Q của Tobin, điểm Z của Altman và tuổi công ty.

31.1.1 Sở hữu nhà nước và cổ phần hóa

A significant proportion of Vietnamese listed firms are former state-owned enterprises (SOEs) that underwent equitization (cổ phần hóa). These firms often retain substantial state ownership, which can affect:

  • Q của Tobin: Sở hữu nhà nước có thể làm giảm Q nếu thị trường cho rằng sự can thiệp của chính phủ làm giảm hiệu quả, hoặc có thể làm tăng Q nếu các mối liên hệ với nhà nước mang lại khả năng tiếp cận ưu đãi đối với nguồn lực và hợp đồng.
  • Điểm Z: Các doanh nghiệp nhà nước (SOE) có thể có bảo lãnh ngầm của chính phủ làm giảm rủi ro phá sản thực tế xuống thấp hơn mức điểm Z dự báo.
  • Tuổi: Tuổi hoạt động thực của các SOE đã cổ phần hóa có thể vượt xa tuổi niêm yết của chúng, gây ra khó khăn trong đo lường.

31.1.2 Giới hạn sở hữu nước ngoài

Việt Nam áp đặt giới hạn sở hữu nước ngoài (FOL) đối với các công ty niêm yết, thường được giới hạn ở mức 49% đối với hầu hết các ngành (có một số ngoại lệ). Ràng buộc này có thể tạo ra phần bù giá đối với các cổ phiếu tiến gần trần FOL, có khả năng làm tăng Q của Tobin của các doanh nghiệp này (Vo 2015).

31.1.3 Chuẩn mực kế toán

Chuẩn mực Kế toán Việt Nam (VAS) khác với Chuẩn mực Báo cáo Tài chính Quốc tế (IFRS) ở một số điểm liên quan đến các thước đo của chúng ta:

  • Cơ sở giá gốc: VAS dựa nhiều hơn vào giá gốc, có thể khiến giá trị sổ sách lệch đáng kể so với chi phí thay thế, ảnh hưởng đến cả phép tính Q lẫn điểm Z.
  • Hạn chế đo lường theo giá trị hợp lý: Không như IFRS, VAS hạn chế việc đo lường theo giá trị hợp lý đối với nhiều loại tài sản, khiến các đại diện dựa trên giá trị sổ sách kém tin cậy hơn.
  • Phương pháp hàng tồn kho: Các doanh nghiệp Việt Nam dùng nhiều phương pháp định giá hàng tồn kho (FIFO, bình quân gia quyền), ảnh hưởng đến giá trị sổ sách của hàng tồn kho và do đó đến thành phần vốn lưu động của điểm Z.

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

Thị trường Việt Nam có biên độ giá hằng ngày (hiện là \(\pm\) 7% trên HOSE, \(\pm\) 10% trên HNX, \(\pm\) 15% trên UPCoM), có thể ngăn giá đạt tới giá trị cân bằng một cách nhanh chóng. Điều này có nghĩa là vốn hóa thị trường tại một thời điểm có thể không phản ánh đầy đủ thông tin sẵn có, đưa nhiễu vào các thước đo dựa trên giá trị thị trường như Q của Tobin.

32 Chuẩn bị dữ liệu

32.1 Tải và làm sạch dữ liệu báo cáo tài chính

Chúng ta bắt đầu bằng việc tải dữ liệu báo cáo tài chính hằng năm và xây dựng các biến cần thiết cho ba thước đo.

Simulate Vietnamese financial data (replace with DataCore.vn API calls)
# ============================================================================
# In practice, replace this section with actual DataCore.vn API calls:
#
#   from datacore import DataCoreClient
#   client = DataCoreClient(api_key="your_api_key")
#   financials = client.get_financial_statements(
#       frequency="annual",
#       start_date="2005-01-01",
#       end_date="2024-12-31"
#   )
#   market = client.get_market_data(frequency="daily")
#   profiles = client.get_company_profiles()
#
# For demonstration purposes, we simulate realistic Vietnamese market data.
# ============================================================================

np.random.seed(42)

n_firms = 300
years = range(2008, 2025)
exchanges = ["HOSE", "HNX", "UPCoM"]
industries = [
    "Bất động sản", "Ngân hàng", "Thực phẩm & Đồ uống",
    "Xây dựng & Vật liệu", "Công nghệ thông tin", "Bán lẻ",
    "Dầu khí", "Thép", "Dệt may", "Dược phẩm",
    "Điện lực", "Vận tải & Logistics", "Hóa chất",
    "Chứng khoán", "Bảo hiểm"
]

# Generate firm profiles
firm_ids = [f"VN{str(i).zfill(4)}" for i in range(1, n_firms + 1)]
firm_profiles = pd.DataFrame({
    "ticker": firm_ids,
    "exchange": np.random.choice(exchanges, n_firms, p=[0.5, 0.3, 0.2]),
    "industry": np.random.choice(industries, n_firms),
    "founding_year": np.random.randint(1975, 2015, n_firms),
    "listing_year": np.random.randint(2000, 2020, n_firms),
    "state_ownership_pct": np.clip(
        np.random.beta(2, 5, n_firms) * 100, 0, 75
    ).round(1),
})
firm_profiles["listing_year"] = np.maximum(
    firm_profiles["listing_year"],
    firm_profiles["founding_year"] + 1
)

# Generate panel data
records = []
for _, firm in firm_profiles.iterrows():
    start_year = max(firm["listing_year"], 2008)
    # Base financial characteristics (firm fixed effects)
    base_ta = np.exp(np.random.normal(14, 1.5))  # Total assets in VND millions
    base_profitability = np.random.normal(0.08, 0.05)
    base_leverage = np.random.beta(3, 3)
    base_turnover = np.random.gamma(2, 0.4)

    for year in years:
        if year < start_year:
            continue
        if np.random.random() < 0.02:  # 2% chance of delisting
            break

        # Time-varying components with persistence
        shock = np.random.normal(0, 0.15)
        growth = np.random.normal(0.08, 0.05)

        ta = base_ta * (1 + growth) ** (year - start_year) * np.exp(shock)
        profitability = np.clip(base_profitability + np.random.normal(0, 0.03), -0.3, 0.5)
        leverage = np.clip(base_leverage + np.random.normal(0, 0.05), 0.05, 0.95)

        lt = ta * leverage * np.random.uniform(0.4, 0.7)
        total_liabilities = ta * leverage
        ct_liabilities = total_liabilities - lt

        seq = ta * (1 - leverage)
        sale = ta * np.clip(base_turnover + np.random.normal(0, 0.1), 0.1, 5)
        ebit = ta * profitability
        ni = ebit * np.random.uniform(0.6, 0.9)
        re = seq * np.random.uniform(-0.2, 0.8)
        act = ta * np.random.uniform(0.2, 0.7)
        lct = ct_liabilities

        # Market value with noise and sentiment
        market_premium = np.random.lognormal(0, 0.4)
        me = seq * market_premium
        prcc = me / max(np.random.uniform(50, 500), 1)
        csho = me / max(prcc, 0.01)

        # Preferred stock (rare in Vietnam, mostly zero)
        pstk = 0 if np.random.random() > 0.05 else seq * np.random.uniform(0, 0.1)

        # Net plant and equipment
        ppent = ta * np.random.uniform(0.1, 0.6)
        invt = ta * np.random.uniform(0.05, 0.3)

        # Deferred taxes and investment tax credit (typically small in VN)
        txdb = ta * np.random.uniform(0, 0.02)
        itcb = 0

        records.append({
            "ticker": firm["ticker"],
            "year": year,
            "datadate": pd.Timestamp(year, 12, 31),
            "at": ta,            # Total Assets
            "seq": seq,          # Stockholders' Equity
            "lt": lt,            # Long-term Debt
            "lct": lct,          # Current Liabilities
            "tlb": total_liabilities,  # Total Liabilities
            "sale": sale,        # Net Sales/Revenue
            "ebit": ebit,        # EBIT
            "ni": ni,            # Net Income
            "re_var": re,        # Retained Earnings
            "act": act,          # Current Assets
            "ppent": ppent,      # Net Plant & Equipment
            "invt": invt,        # Inventories
            "txdb": txdb,        # Deferred Taxes
            "itcb": itcb,        # Investment Tax Credit
            "pstk": pstk,        # Preferred Stock
            "me": me,            # Market Value of Equity
            "prcc": prcc,        # Price at Calendar Year End
            "csho": csho,        # Shares Outstanding
        })

df = pd.DataFrame(records)
df = df.merge(firm_profiles[["ticker", "exchange", "industry",
                              "founding_year", "listing_year",
                              "state_ownership_pct"]],
              on="ticker", how="left")

print(f"Panel dimensions: {df.shape[0]:,} firm-year observations")
print(f"Number of unique firms: {df['ticker'].nunique()}")
print(f"Year range: {df['year'].min()}–{df['year'].max()}")
print(f"Exchanges: {df['exchange'].value_counts().to_dict()}")
Panel dimensions: 3,484 firm-year observations
Number of unique firms: 297
Year range: 2008–2024
Exchanges: {'HOSE': 1701, 'HNX': 1118, 'UPCoM': 665}

32.2 Định nghĩa biến và ánh xạ

Bảng 32.1 trình bày ánh xạ giữa tên biến chuẩn và các trường tương ứng.

Hiện mã
variable_map = pd.DataFrame({
    "Compustat Variable": [
        "AT", "SEQ", "LT", "LCT", "SALE", "EBIT", "NI",
        "RE", "ACT", "PPENT", "INVT", "TXDB", "ITCB",
        "PSTK/PSTKRV/PSTKL", "PRCC_C", "CSHO", "DLDTE", "DLRSN"
    ],
    "Description": [
        "Total Assets", "Stockholders' Equity", "Long-term Debt",
        "Current Liabilities", "Net Sales/Revenue",
        "Earnings Before Interest & Taxes", "Net Income",
        "Retained Earnings", "Current Assets",
        "Net Plant & Equipment", "Total Inventories",
        "Deferred Taxes", "Investment Tax Credit",
        "Preferred Stock (various)", "Price Close (Calendar Year)",
        "Common Shares Outstanding", "Delisting Date",
        "Delisting Reason"
    ],
    "DataCore.vn Equivalent": [
        "tong_tai_san", "von_chu_so_huu", "no_dai_han",
        "no_ngan_han", "doanh_thu_thuan",
        "loi_nhuan_truoc_thue_va_lai_vay", "loi_nhuan_sau_thue",
        "loi_nhuan_chua_phan_phoi", "tai_san_ngan_han",
        "tai_san_co_dinh_huu_hinh", "hang_ton_kho",
        "thue_thu_nhap_hoan_lai", "N/A (not applicable in VAS)",
        "co_phieu_uu_dai", "gia_dong_cua_cuoi_nam",
        "so_luong_co_phieu_luu_hanh", "ngay_huy_niem_yet",
        "ly_do_huy_niem_yet"
    ],
    "VAS Account": [
        "BS.100", "BS.400", "BS.330", "BS.310",
        "IS.10", "Computed", "IS.60",
        "BS.421", "BS.100", "BS.221", "BS.141",
        "BS.262", "—", "BS.411b", "Market", "Market",
        "Profile", "Profile"
    ]
})

variable_map.style.set_properties(**{
    "text-align": "left",
    "font-size": "10pt"
}).set_table_styles([
    {"selector": "th", "props": [
        ("background-color", "#1f77b4"),
        ("color", "white"),
        ("font-weight", "bold"),
        ("text-align", "left"),
        ("padding", "8px")
    ]},
    {"selector": "td", "props": [("padding", "6px")]},
])
Bảng 32.1: Ánh xạ biến
  Compustat Variable Description DataCore.vn Equivalent VAS Account
0 AT Total Assets tong_tai_san BS.100
1 SEQ Stockholders' Equity von_chu_so_huu BS.400
2 LT Long-term Debt no_dai_han BS.330
3 LCT Current Liabilities no_ngan_han BS.310
4 SALE Net Sales/Revenue doanh_thu_thuan IS.10
5 EBIT Earnings Before Interest & Taxes loi_nhuan_truoc_thue_va_lai_vay Computed
6 NI Net Income loi_nhuan_sau_thue IS.60
7 RE Retained Earnings loi_nhuan_chua_phan_phoi BS.421
8 ACT Current Assets tai_san_ngan_han BS.100
9 PPENT Net Plant & Equipment tai_san_co_dinh_huu_hinh BS.221
10 INVT Total Inventories hang_ton_kho BS.141
11 TXDB Deferred Taxes thue_thu_nhap_hoan_lai BS.262
12 ITCB Investment Tax Credit N/A (not applicable in VAS) —
13 PSTK/PSTKRV/PSTKL Preferred Stock (various) co_phieu_uu_dai BS.411b
14 PRCC_C Price Close (Calendar Year) gia_dong_cua_cuoi_nam Market
15 CSHO Common Shares Outstanding so_luong_co_phieu_luu_hanh Market
16 DLDTE Delisting Date ngay_huy_niem_yet Profile
17 DLRSN Delisting Reason ly_do_huy_niem_yet Profile

32.3 Kiểm tra chất lượng dữ liệu

Trước khi tính bất kỳ thước đo nào, chúng ta thực hiện các kiểm tra chất lượng dữ liệu thiết yếu, đặc biệt quan trọng đối với dữ liệu Việt Nam.

Data quality validation
def data_quality_report(df):
    """Generate a comprehensive data quality report."""
    report = {}

    # Check for negative total assets
    report["Negative total assets"] = (df["at"] < 0).sum()

    # Check for negative equity (acceptable but noteworthy)
    report["Negative equity"] = (df["seq"] <= 0).sum()

    # Check for missing critical variables
    critical_vars = ["at", "seq", "sale", "ebit", "me"]
    for var in critical_vars:
        report[f"Missing {var}"] = df[var].isna().sum()

    # Check for extreme values (potential data errors)
    report["Extreme leverage (>100%)"] = (df["tlb"] / df["at"] > 1.0).sum()
    report["Extreme sales/assets (>10)"] = (df["sale"] / df["at"] > 10).sum()

    return pd.Series(report, name="Count")

quality = data_quality_report(df)
print("Data Quality Report")
print("=" * 45)
for item, count in quality.items():
    status = "✓" if count == 0 else "⚠"
    print(f"  {status} {item}: {count:,}")

# Apply filters
df_clean = df.copy()
df_clean = df_clean[df_clean["at"] > 0]  # Positive total assets
df_clean = df_clean[df_clean["seq"] > 0]  # Positive equity (for BE calculation)
print(f"\nObservations after cleaning: {len(df_clean):,} "
      f"(dropped {len(df) - len(df_clean):,})")
Data Quality Report
=============================================
  ✓ Negative total assets: 0
  ✓ Negative equity: 0
  ✓ Missing at: 0
  ✓ Missing seq: 0
  ✓ Missing sale: 0
  ✓ Missing ebit: 0
  ✓ Missing me: 0
  ✓ Extreme leverage (>100%): 0
  ✓ Extreme sales/assets (>10): 0

Observations after cleaning: 3,484 (dropped 0)

33 Tính Q của Tobin

33.1 Giá trị sổ sách của vốn chủ sở hữu

Theo Daniel và Titman (1997), chúng ta tính giá trị sổ sách của vốn chủ sở hữu như sau:

\[ BE = SEQ + TXDB + ITCB - PREF \tag{33.1}\]

trong đó \(PREF\) là giá trị cổ phiếu ưu đãi, sử dụng giá trị hoàn lại nếu có, nếu không thì dùng giá trị thanh lý, và sau cùng là giá trị ghi sổ như phương án dự phòng cuối cùng. Trong bối cảnh Việt Nam, cổ phiếu ưu đãi tương đối hiếm trong các công ty niêm yết, nên \(PREF\) thường bằng không.

Book value of equity computation
def compute_book_equity(df):
    """
    Compute book value of equity following Daniel and Titman (1997).

    In Vietnam, preferred stock is uncommon among listed firms.
    The investment tax credit (ITCB) is not applicable under VAS,
    so we set it to zero when missing.
    """
    result = df.copy()

    # Preferred stock: use coalesce logic
    result["pref"] = result["pstk"].fillna(0)

    # Book equity = Shareholders' equity + Deferred taxes + ITC - Preferred
    result["be"] = (
        result["seq"]
        + result["txdb"].fillna(0)
        + result["itcb"].fillna(0)
        - result["pref"]
    )

    return result

df_clean = compute_book_equity(df_clean)

print("Book Equity Summary Statistics (VND millions)")
print(df_clean["be"].describe().apply(lambda x: f"{x:,.0f}"))
Book Equity Summary Statistics (VND millions)
count          3,484
mean       3,521,607
std        9,055,435
min            4,633
25%          386,441
50%        1,112,062
75%        3,210,269
max      247,845,397
Name: be, dtype: str

33.2 Giá trị thị trường của vốn chủ sở hữu

Giá trị thị trường của vốn chủ sở hữu được tính bằng giá cổ phiếu cuối năm dương lịch và số cổ phiếu đang lưu hành:

\[ ME = P_{\text{close}} \times \text{CSHO} \tag{33.2}\]

Đối với các doanh nghiệp Việt Nam, giá trị này lấy từ giá đóng cửa vào ngày giao dịch cuối cùng của năm tài chính. Hầu hết doanh nghiệp Việt Nam kết thúc năm tài chính vào ngày 31 tháng 12, dù một số (đặc biệt trong nông nghiệp và ngân hàng) có thể khác.

Market value of equity computation
# ME is already computed in our simulated data as prcc * csho
# In practice with DataCore.vn:
#   me = df["gia_dong_cua_cuoi_nam"] * df["so_luong_co_phieu_luu_hanh"]

df_clean["me"] = df_clean["prcc"] * df_clean["csho"]

print("Market Equity Summary Statistics (VND millions)")
print(df_clean["me"].describe().apply(lambda x: f"{x:,.0f}"))
Market Equity Summary Statistics (VND millions)
count          3,484
mean       3,653,445
std        9,747,318
min            3,379
25%          370,119
50%        1,106,646
75%        3,029,093
max      286,258,816
Name: me, dtype: str

33.3 Q của Tobin giản lược

Chúng ta triển khai phiên bản giản lược của Gompers, Ishii, và Metrick (2003):

\[ Q_{\text{simple}} = \frac{AT + ME - BE}{AT} \tag{33.3}\]

Công thức này có thể viết lại thành:

\[ Q_{\text{simple}} = 1 + \frac{ME - BE}{AT} \tag{33.4}\]

giúp cách diễn giải trở nên rõ ràng: Q của Tobin bằng một cộng với phần bù (hoặc chiết khấu) giữa giá trị thị trường và giá trị sổ sách, chia cho tổng tài sản.

Tobin’s Q computation with multiple variants
def compute_tobins_q(df):
    """
    Compute multiple variants of Tobin's Q.

    Variants:
    1. Simple Q (Gompers et al., 2003)
    2. Chung-Pruitt Q
    3. Market-to-Book ratio (for comparison)
    """
    result = df.copy()

    # --- Variant 1: Simple Q (Gompers, Ishii, Metrick 2003) ---
    result["tobin_q_simple"] = (result["at"] + result["me"] - result["be"]) / result["at"]

    # --- Variant 2: Chung-Pruitt Approximation ---
    # DEBT = Current Liabilities - Current Assets + Inventories + LT Debt
    result["debt_cp"] = (
        result["lct"].fillna(0)
        - result["act"].fillna(0)
        + result["invt"].fillna(0)
        + result["lt"].fillna(0)
    )
    result["tobin_q_cp"] = (
        (result["me"] + result["pstk"].fillna(0) + result["debt_cp"]) / result["at"]
    )

    # --- Market-to-Book Ratio ---
    result["mtb"] = np.where(result["be"] > 0, result["me"] / result["be"], np.nan)

    return result

df_clean = compute_tobins_q(df_clean)

# Summary statistics
q_vars = ["tobin_q_simple", "tobin_q_cp", "mtb"]
q_summary = df_clean[q_vars].describe(percentiles=[0.01, 0.05, 0.25, 0.5, 0.75, 0.95, 0.99])
q_summary = q_summary.round(3)
q_summary.columns = ["Simple Q", "Chung-Pruitt Q", "Market-to-Book"]

print("Tobin's Q Summary Statistics")
print(q_summary.to_string())
Tobin's Q Summary Statistics
       Simple Q  Chung-Pruitt Q  Market-to-Book
count  3484.000        3484.000        3484.000
mean      1.031           0.766           1.058
std       0.243           0.296           0.445
min       0.357           0.008           0.233
1%        0.602           0.189           0.382
5%        0.713           0.332           0.496
25%       0.886           0.568           0.742
50%       0.989           0.738           0.974
75%       1.132           0.926           1.287
95%       1.481           1.290           1.904
99%       1.916           1.668           2.492
max       2.804           2.433           3.189

33.4 Áp dụng winsorization cho các giá trị cực đoan

Giá trị Q của Tobin có thể bị ảnh hưởng nặng bởi các giá trị ngoại lai, đặc biệt ở các thị trường mới nổi nơi chất lượng dữ liệu có thể không đồng đều. Chúng ta thực hiện winsorization tại phân vị thứ 1 và thứ 99.

Winsorization of extreme values
def winsorize(series, lower=0.01, upper=0.99):
    """Winsorize a pandas Series at specified percentiles."""
    low = series.quantile(lower)
    high = series.quantile(upper)
    return series.clip(lower=low, upper=high)

# Winsorize Q measures
for var in ["tobin_q_simple", "tobin_q_cp", "mtb"]:
    df_clean[f"{var}_w"] = winsorize(df_clean[var])

print("Effect of Winsorization on Simple Tobin's Q:")
print(f"  Before: mean={df_clean['tobin_q_simple'].mean():.3f}, "
      f"std={df_clean['tobin_q_simple'].std():.3f}")
print(f"  After:  mean={df_clean['tobin_q_simple_w'].mean():.3f}, "
      f"std={df_clean['tobin_q_simple_w'].std():.3f}")
Effect of Winsorization on Simple Tobin's Q:
  Before: mean=1.031, std=0.243
  After:  mean=1.030, std=0.233

33.5 Phân phối chéo của Q của Tobin

Hiện mã
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Latest year distribution
latest_year = df_clean["year"].max()
latest_data = df_clean[df_clean["year"] == latest_year]

# Histogram
axes[0].hist(
    latest_data["tobin_q_simple_w"].dropna(),
    bins=50, color=colors["primary"], alpha=0.7, edgecolor="white"
)
axes[0].axvline(x=1, color=colors["quaternary"], linestyle="--", linewidth=2, label="Q = 1")
axes[0].set_xlabel("Tobin's Q (Simple)")
axes[0].set_ylabel("Number of Firms")
axes[0].set_title(f"Cross-Sectional Distribution ({latest_year})")
axes[0].legend()

# Box plot by exchange
exchange_data = latest_data[["exchange", "tobin_q_simple_w"]].dropna()
exchanges_sorted = exchange_data.groupby("exchange")["tobin_q_simple_w"].median().sort_values().index

box_data = [exchange_data[exchange_data["exchange"] == ex]["tobin_q_simple_w"].values
            for ex in exchanges_sorted]

bp = axes[1].boxplot(box_data, labels=exchanges_sorted, patch_artist=True,
                     medianprops=dict(color="black", linewidth=2))
box_colors = [colors["primary"], colors["secondary"], colors["tertiary"]]
for patch, color in zip(bp["boxes"], box_colors):
    patch.set_facecolor(color)
    patch.set_alpha(0.7)

axes[1].axhline(y=1, color=colors["quaternary"], linestyle="--", linewidth=1.5, alpha=0.7)
axes[1].set_ylabel("Tobin's Q (Simple)")
axes[1].set_title(f"Tobin's Q by Exchange ({latest_year})")

plt.tight_layout()
plt.show()
Hình 33.1: Phân phối của Q của Tobin trên các doanh nghiệp niêm yết Việt Nam (2024). Đường nét đứt thẳng đứng tại Q=1 biểu thị ngưỡng lý thuyết mà tại đó giá trị thị trường bằng chi phí thay thế.

33.6 Diễn biến chuỗi thời gian của Q của Tobin

Hiện mã
# Compute annual statistics by exchange
annual_q = (
    df_clean
    .groupby(["year", "exchange"])["tobin_q_simple_w"]
    .agg(["median", lambda x: x.quantile(0.25), lambda x: x.quantile(0.75)])
    .reset_index()
)
annual_q.columns = ["year", "exchange", "median", "q25", "q75"]

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

exchange_colors = {"HOSE": colors["primary"], "HNX": colors["secondary"],
                   "UPCoM": colors["tertiary"]}

for exchange, color in exchange_colors.items():
    data = annual_q[annual_q["exchange"] == exchange]
    ax.plot(data["year"], data["median"], color=color, linewidth=2.5,
            label=exchange, marker="o", markersize=4)
    ax.fill_between(data["year"], data["q25"], data["q75"],
                    color=color, alpha=0.15)

ax.axhline(y=1, color="gray", linestyle="--", linewidth=1, alpha=0.7, label="Q = 1")
ax.set_xlabel("Year")
ax.set_ylabel("Tobin's Q (Median)")
ax.set_title("Evolution of Tobin's Q in the Vietnamese Market")
ax.legend(loc="upper right")
ax.set_xlim(2008, latest_year)
ax.xaxis.set_major_locator(mticker.MultipleLocator(2))

plt.tight_layout()
plt.show()
Hình 33.2: Diễn biến của Q của Tobin trung vị trên các sàn giao dịch Việt Nam (2008–2024). Vùng tô bóng biểu thị khoảng tứ phân vị.

33.7 Q của Tobin theo ngành

Hiện mã
latest_industry_q = (
    latest_data
    .groupby("industry")["tobin_q_simple_w"]
    .agg(["median", "mean", "count"])
    .reset_index()
    .sort_values("median", ascending=True)
)

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

bar_colors = [colors["quaternary"] if m < 1 else colors["primary"]
              for m in latest_industry_q["median"]]

ax.barh(
    latest_industry_q["industry"],
    latest_industry_q["median"],
    color=bar_colors, alpha=0.8, edgecolor="white"
)
ax.axvline(x=1, color="gray", linestyle="--", linewidth=1.5, alpha=0.7)
ax.set_xlabel("Median Tobin's Q")
ax.set_title(f"Tobin's Q by Industry ({latest_year})")

# Add count annotations
for i, (_, row) in enumerate(latest_industry_q.iterrows()):
    ax.text(row["median"] + 0.02, i, f'n={int(row["count"])}',
            va="center", fontsize=9, color="gray")

plt.tight_layout()
plt.show()
Hình 33.3: Q của Tobin trung vị theo ngành ở Việt Nam (năm gần nhất có sẵn). Các ngành được sắp xếp theo giá trị Q trung vị.

34 Tính điểm Z của Altman

34.1 Điểm Z gốc cho doanh nghiệp niêm yết

Altman Z-Score computation (all three variants)
def compute_altman_z(df):
    """
    Compute three variants of the Altman Z-Score:
    1. Original Z-Score (for listed manufacturing firms)
    2. Z'-Score (for private firms, using book equity)
    3. Z''-Score (for emerging markets / non-manufacturing)
    """
    result = df.copy()

    # Common components
    result["wc"] = result["act"].fillna(0) - result["lct"].fillna(0)  # Working Capital

    # --- Original Z-Score ---
    # Z = 3.3*(EBIT/TA) + 0.999*(Sales/TA) + 0.6*(ME/TL) + 1.2*(WC/TA) + 1.4*(RE/TA)
    x1 = result["ebit"] / result["at"]
    x2 = result["sale"] / result["at"]
    x3 = np.where(result["tlb"] > 0, result["me"] / result["tlb"], np.nan)
    x4 = result["wc"] / result["at"]
    x5 = result["re_var"].fillna(0) / result["at"]

    result["z_x1"] = x1  # Earning power
    result["z_x2"] = x2  # Asset turnover
    result["z_x3"] = x3  # Leverage (inverse)
    result["z_x4"] = x4  # Liquidity
    result["z_x5"] = x5  # Cumulative profitability

    result["altman_z"] = (3.3 * x1 + 0.999 * x2 + 0.6 * x3 + 1.2 * x4 + 1.4 * x5)

    # --- Z'-Score (Private firms) ---
    x3_prime = np.where(result["tlb"] > 0, result["be"] / result["tlb"], np.nan)
    result["altman_z_prime"] = (
        0.717 * x4 + 0.847 * x5 + 3.107 * x1 + 0.420 * x3_prime + 0.998 * x2
    )

    # --- Z''-Score (Emerging Markets) ---
    result["altman_z_em"] = (
        3.25
        + 6.56 * x4   # Working Capital / TA
        + 3.26 * x5   # Retained Earnings / TA
        + 6.72 * x1   # EBIT / TA
        + 1.05 * x3_prime  # Book Equity / TL
    )

    # Classify risk zones
    result["z_zone"] = pd.cut(
        result["altman_z"],
        bins=[-np.inf, 1.80, 2.70, 2.99, np.inf],
        labels=["High Distress", "Distress", "Grey Zone", "Safe"]
    )

    result["z_em_zone"] = pd.cut(
        result["altman_z_em"],
        bins=[-np.inf, 1.10, 2.60, np.inf],
        labels=["Distress", "Grey Zone", "Safe"]
    )

    return result

df_clean = compute_altman_z(df_clean)

# Winsorize Z-scores
for var in ["altman_z", "altman_z_prime", "altman_z_em"]:
    df_clean[f"{var}_w"] = winsorize(df_clean[var])

print("Z-Score Summary Statistics")
z_summary = df_clean[["altman_z", "altman_z_prime", "altman_z_em"]].describe().round(3)
z_summary.columns = ["Original Z", "Z' (Private)", "Z'' (Emerging)"]
print(z_summary.to_string())
Z-Score Summary Statistics
       Original Z  Z' (Private)  Z'' (Emerging)
count    3484.000      3484.000        3484.000
mean        2.610         2.042           7.467
std         1.896         1.194           3.137
min         0.059         0.155           1.578
25%         1.595         1.314           5.670
50%         2.212         1.806           6.987
75%         3.028         2.410           8.549
max        28.640        11.119          29.686

34.2 Phân tích các thành phần của điểm Z

Việc hiểu thành phần nào chi phối điểm Z đặc biệt hữu ích trong bối cảnh Việt Nam, nơi một số tỷ số có thể hành xử khác với các thị trường phát triển.

Hiện mã
fig, axes = plt.subplots(2, 3, figsize=(15, 9))

components = [
    ("z_x1", 3.3, "$3.3 \\times$ EBIT/TA\n(Earning Power)"),
    ("z_x2", 0.999, "$0.999 \\times$ Sales/TA\n(Asset Turnover)"),
    ("z_x3", 0.6, "$0.6 \\times$ ME/TL\n(Leverage)"),
    ("z_x4", 1.2, "$1.2 \\times$ WC/TA\n(Liquidity)"),
    ("z_x5", 1.4, "$1.4 \\times$ RE/TA\n(Cum. Profitability)"),
]

latest_z = df_clean[df_clean["year"] == latest_year]

for idx, (var, weight, label) in enumerate(components):
    ax = axes[idx // 3, idx % 3]
    weighted_vals = (latest_z[var] * weight).dropna()
    weighted_vals = weighted_vals[weighted_vals.between(
        weighted_vals.quantile(0.01), weighted_vals.quantile(0.99)
    )]
    ax.hist(weighted_vals, bins=40, color=colors["primary"], alpha=0.7, edgecolor="white")
    ax.axvline(x=weighted_vals.median(), color=colors["quaternary"],
               linestyle="--", linewidth=2, label=f"Median: {weighted_vals.median():.2f}")
    ax.set_title(label, fontsize=11)
    ax.legend(fontsize=9)

# Remove empty subplot
axes[1, 2].set_visible(False)

plt.suptitle(f"Z-Score Component Distributions ({latest_year})", fontsize=14, y=1.02)
plt.tight_layout()
plt.show()
Hình 34.1: Phân phối các tỷ số thành phần của điểm Z của Altman đối với các doanh nghiệp niêm yết Việt Nam. Mỗi biểu đồ con thể hiện một thành phần được gia quyền theo hệ số của nó trong công thức điểm Z gốc.

34.3 Phân phối các vùng rủi ro

Hiện mã
fig, axes = plt.subplots(1, 2, figsize=(14, 6))

# --- Original Z-Score Zones ---
zone_counts = (
    df_clean
    .groupby(["year", "z_zone"])
    .size()
    .unstack(fill_value=0)
)
zone_pcts = zone_counts.div(zone_counts.sum(axis=1), axis=0) * 100

zone_colors = {
    "Safe": colors["safe"],
    "Grey Zone": colors["alert"],
    "Distress": colors["quaternary"],
    "High Distress": colors["distress"],
}

axes[0].stackplot(
    zone_pcts.index,
    *[zone_pcts[col] for col in ["Safe", "Grey Zone", "Distress", "High Distress"]
      if col in zone_pcts.columns],
    labels=[col for col in ["Safe", "Grey Zone", "Distress", "High Distress"]
            if col in zone_pcts.columns],
    colors=[zone_colors[col] for col in ["Safe", "Grey Zone", "Distress", "High Distress"]
            if col in zone_pcts.columns],
    alpha=0.8
)
axes[0].set_xlabel("Year")
axes[0].set_ylabel("Percentage of Firms")
axes[0].set_title("Original Z-Score Risk Zones")
axes[0].legend(loc="upper right", fontsize=9)
axes[0].set_ylim(0, 100)

# --- Emerging Market Z''-Score Zones ---
em_zone_counts = (
    df_clean
    .groupby(["year", "z_em_zone"])
    .size()
    .unstack(fill_value=0)
)
em_zone_pcts = em_zone_counts.div(em_zone_counts.sum(axis=1), axis=0) * 100

em_zone_colors = {"Safe": colors["safe"], "Grey Zone": colors["alert"],
                  "Distress": colors["quaternary"]}

axes[1].stackplot(
    em_zone_pcts.index,
    *[em_zone_pcts[col] for col in ["Safe", "Grey Zone", "Distress"]
      if col in em_zone_pcts.columns],
    labels=[col for col in ["Safe", "Grey Zone", "Distress"]
            if col in em_zone_pcts.columns],
    colors=[em_zone_colors[col] for col in ["Safe", "Grey Zone", "Distress"]
            if col in em_zone_pcts.columns],
    alpha=0.8
)
axes[1].set_xlabel("Year")
axes[1].set_ylabel("Percentage of Firms")
axes[1].set_title("Emerging Market Z''-Score Risk Zones")
axes[1].legend(loc="upper right", fontsize=9)
axes[1].set_ylim(0, 100)

plt.tight_layout()
plt.show()
Hình 34.2: Tỷ lệ các doanh nghiệp niêm yết Việt Nam trong từng vùng rủi ro của điểm Z của Altman theo thời gian. Hình thể hiện cả các vùng điểm Z gốc và các vùng điểm Z’’ cho thị trường mới nổi.

34.4 So sánh các biến thể của điểm Z

Một câu hỏi thực tiễn quan trọng là biến thể điểm Z nào phù hợp nhất với các doanh nghiệp Việt Nam. Mô hình gốc được hiệu chỉnh trên các doanh nghiệp sản xuất của Hoa Kỳ, trong khi điểm Z’’ được thiết kế riêng cho thị trường mới nổi và các ngành phi sản xuất.

Hiện mã
fig, ax = plt.subplots(figsize=(8, 8))

sample = latest_z.dropna(subset=["altman_z_w", "altman_z_em_w"]).sample(
    min(500, len(latest_z)), random_state=42
)

scatter = ax.scatter(
    sample["altman_z_w"], sample["altman_z_em_w"],
    c=sample["tobin_q_simple_w"], cmap="RdYlGn",
    alpha=0.6, s=30, edgecolor="white", linewidth=0.3
)

# Add reference lines
lims = [
    min(ax.get_xlim()[0], ax.get_ylim()[0]),
    max(ax.get_xlim()[1], ax.get_ylim()[1])
]
ax.plot(lims, lims, "k--", alpha=0.3, linewidth=1, label="45° line")

# Add zone boundaries
ax.axvline(x=1.80, color="red", linestyle=":", alpha=0.5, linewidth=1)
ax.axvline(x=2.99, color="green", linestyle=":", alpha=0.5, linewidth=1)
ax.axhline(y=1.10, color="red", linestyle=":", alpha=0.5, linewidth=1)
ax.axhline(y=2.60, color="green", linestyle=":", alpha=0.5, linewidth=1)

ax.set_xlabel("Original Z-Score")
ax.set_ylabel("Emerging Market Z''-Score")
ax.set_title("Z-Score vs Z''-Score for Vietnamese Firms")

cbar = plt.colorbar(scatter, ax=ax, shrink=0.8)
cbar.set_label("Tobin's Q")

ax.legend(loc="upper left")
plt.tight_layout()
plt.show()
Hình 34.3: So sánh điểm Z gốc và điểm Z’’ cho thị trường mới nổi đối với các doanh nghiệp niêm yết Việt Nam. Các điểm nằm trên (dưới) đường 45 độ có điểm Z’’ (điểm Z) cao hơn thước đo còn lại.

35 Tính tuổi công ty

35.1 Nhiều đại diện cho tuổi

Đối với các doanh nghiệp Việt Nam, chúng ta tính ba thước đo tuổi:

\[ \text{Age}_{\text{founding}} = \text{Current Year} - \text{Founding Year} \tag{35.1}\]

\[ \text{Age}_{\text{listing}} = \text{Current Year} - \text{Listing Year} \tag{35.2}\]

\[ \text{Age}_{\text{data}} = \text{Current Year} - \text{First Year with Data} \tag{35.3}\]

Company age computation with multiple proxies
def compute_company_age(df):
    """
    Compute multiple proxies for company age.

    For Vietnamese firms:
    - Founding age: based on founding/incorporation date
    - Listing age: based on first listing on HOSE/HNX/UPCoM
    - Data age: based on first year with available financial data
    """
    result = df.copy()

    # Founding age
    result["age_founding"] = result["year"] - result["founding_year"]

    # Listing age
    result["age_listing"] = result["year"] - result["listing_year"]
    result["age_listing"] = result["age_listing"].clip(lower=0)

    # Data age (years since first available financial data)
    first_year = result.groupby("ticker")["year"].transform("min")
    result["age_data"] = result["year"] - first_year

    # Log of age (commonly used in regressions)
    result["ln_age_founding"] = np.log1p(result["age_founding"])
    result["ln_age_listing"] = np.log1p(result["age_listing"])

    return result

df_clean = compute_company_age(df_clean)

print("Company Age Summary Statistics")
age_summary = df_clean[df_clean["year"] == latest_year][
    ["age_founding", "age_listing", "age_data"]
].describe().round(1)
age_summary.columns = ["Founding Age", "Listing Age", "Data Age"]
print(age_summary.to_string())
Company Age Summary Statistics
       Founding Age  Listing Age  Data Age
count         232.0        232.0     232.0
mean           30.1         13.9      12.3
std            11.6          5.8       3.9
min            10.0          5.0       5.0
25%            20.0          9.0       9.0
50%            30.0         13.0      13.0
75%            40.0         19.0      16.0
max            49.0         24.0      16.0

35.2 Phân phối tuổi

Hiện mã
fig, axes = plt.subplots(1, 3, figsize=(15, 5))

age_vars = [
    ("age_founding", "Founding Age (years)", colors["primary"]),
    ("age_listing", "Listing Age (years)", colors["secondary"]),
    ("age_data", "Data Age (years)", colors["tertiary"]),
]

latest = df_clean[df_clean["year"] == latest_year]

for ax, (var, label, color) in zip(axes, age_vars):
    data = latest[var].dropna()
    ax.hist(data, bins=30, color=color, alpha=0.7, edgecolor="white")
    ax.axvline(x=data.median(), color="black", linestyle="--",
               linewidth=2, label=f"Median: {data.median():.0f}")
    ax.set_xlabel(label)
    ax.set_ylabel("Number of Firms")
    ax.legend()

axes[0].set_title("Founding Age")
axes[1].set_title("Listing Age")
axes[2].set_title("Data Age")

plt.suptitle(f"Company Age Distributions ({latest_year})", fontsize=14, y=1.02)
plt.tight_layout()
plt.show()
Hình 35.1: Phân phối các thước đo tuổi công ty đối với các doanh nghiệp niêm yết Việt Nam. Tuổi thành lập phản ánh mức độ trưởng thành thực sự của tổ chức, trong khi tuổi niêm yết phản ánh kinh nghiệm trên thị trường vốn.

35.3 Tuổi và hiệu ứng cổ phần hóa

Một đặc điểm riêng của thị trường Việt Nam là việc cổ phần hóa các SOE. Nhiều doanh nghiệp có ngày thành lập sớm hơn thị trường chứng khoán hàng chục năm, tạo ra khoảng cách lớn giữa tuổi thành lập và tuổi niêm yết.

Hiện mã
fig, ax = plt.subplots(figsize=(8, 8))

latest = df_clean[df_clean["year"] == latest_year].copy()
latest["soe_flag"] = latest["state_ownership_pct"] > 30  # SOE proxy

for soe, label, color, marker in [
    (True, "SOE (>30% state)", colors["quaternary"], "s"),
    (False, "Non-SOE", colors["primary"], "o"),
]:
    subset = latest[latest["soe_flag"] == soe]
    ax.scatter(
        subset["age_listing"], subset["age_founding"],
        c=color, alpha=0.5, s=25, marker=marker, label=label, edgecolor="white"
    )

# 45-degree line
max_age = max(latest["age_founding"].max(), latest["age_listing"].max())
ax.plot([0, max_age], [0, max_age], "k--", alpha=0.3, label="Founding = Listing age")

ax.set_xlabel("Listing Age (years)")
ax.set_ylabel("Founding Age (years)")
ax.set_title("Founding vs. Listing Age: The Equitization Gap")
ax.legend()

plt.tight_layout()
plt.show()
Hình 35.2: Mối quan hệ giữa tuổi thành lập và tuổi niêm yết của các doanh nghiệp Việt Nam. Khoảng cách phản ánh số năm hoạt động trước khi niêm yết, đặc biệt lớn đối với các doanh nghiệp nhà nước đã cổ phần hóa.

36 Phân tích kết hợp: Định giá, kiệt quệ và mức độ trưởng thành

36.1 Mối quan hệ giữa Q, điểm Z và tuổi

Ba thước đo này phản ánh những khía cạnh khác nhau về vị thế tài chính của doanh nghiệp, nhưng chúng không độc lập với nhau. Về mặt lý thuyết, chúng ta kỳ vọng:

  • Q và Z: Các doanh nghiệp có nhiều cơ hội tăng trưởng (Q cao) thường khỏe mạnh hơn về tài chính (Z cao), nhưng mối quan hệ không đơn điệu, giá trị Q rất cao có thể cho thấy sự định giá quá cao mang tính đầu cơ.
  • Q và tuổi: Các doanh nghiệp trẻ có thể có Q cao hơn (kỳ vọng tăng trưởng) hoặc thấp hơn (mô hình kinh doanh chưa được kiểm chứng). Mối quan hệ phụ thuộc vào vòng đời của ngành.
  • Z và tuổi: Các doanh nghiệp lâu đời thường có lợi nhuận ổn định hơn và lợi nhuận giữ lại cao hơn, góp phần làm điểm Z cao hơn, dù các doanh nghiệp rất lâu đời có thể đối mặt với năng lực cạnh tranh suy giảm.
Hiện mã
fig, axes = plt.subplots(1, 3, figsize=(16, 5))

sample = latest.dropna(subset=["tobin_q_simple_w", "altman_z_w", "age_founding"])
sample = sample.sample(min(300, len(sample)), random_state=42)

# Q vs Z
axes[0].scatter(
    sample["altman_z_w"], sample["tobin_q_simple_w"],
    c=sample["age_founding"], cmap="viridis", alpha=0.6,
    s=np.log1p(sample["at"]) * 3, edgecolor="white", linewidth=0.3
)
axes[0].set_xlabel("Altman Z-Score")
axes[0].set_ylabel("Tobin's Q")
axes[0].set_title("Valuation vs. Distress Risk")
axes[0].axhline(y=1, color="gray", linestyle="--", alpha=0.5)
axes[0].axvline(x=1.80, color="red", linestyle=":", alpha=0.5)
axes[0].axvline(x=2.99, color="green", linestyle=":", alpha=0.5)

# Q vs Age
axes[1].scatter(
    sample["age_founding"], sample["tobin_q_simple_w"],
    c=sample["altman_z_w"], cmap="RdYlGn", alpha=0.6,
    s=np.log1p(sample["at"]) * 3, edgecolor="white", linewidth=0.3
)
axes[1].set_xlabel("Founding Age (years)")
axes[1].set_ylabel("Tobin's Q")
axes[1].set_title("Valuation vs. Maturity")
axes[1].axhline(y=1, color="gray", linestyle="--", alpha=0.5)

# Z vs Age
scatter = axes[2].scatter(
    sample["age_founding"], sample["altman_z_w"],
    c=sample["tobin_q_simple_w"], cmap="coolwarm", alpha=0.6,
    s=np.log1p(sample["at"]) * 3, edgecolor="white", linewidth=0.3
)
axes[2].set_xlabel("Founding Age (years)")
axes[2].set_ylabel("Altman Z-Score")
axes[2].set_title("Distress Risk vs. Maturity")
axes[2].axhline(y=1.80, color="red", linestyle=":", alpha=0.5)
axes[2].axhline(y=2.99, color="green", linestyle=":", alpha=0.5)

plt.tight_layout()
plt.show()
Hình 36.1: Mối quan hệ giữa Q của Tobin, điểm Z của Altman và tuổi công ty đối với các doanh nghiệp niêm yết Việt Nam. Kích thước bong bóng phản ánh tổng tài sản; màu sắc cho biết vùng rủi ro điểm Z’’ cho thị trường mới nổi.

36.2 Cấu trúc tương quan

Hiện mã
corr_vars = [
    "tobin_q_simple_w", "mtb_w", "altman_z_w", "altman_z_em_w",
    "age_founding", "age_listing",
    "z_x1", "z_x2", "z_x3", "z_x4", "z_x5"
]
corr_labels = [
    "Tobin's Q", "M/B Ratio", "Z-Score", "Z'' (EM)",
    "Age (Found.)", "Age (List.)",
    "EBIT/TA", "Sales/TA", "ME/TL", "WC/TA", "RE/TA"
]

corr_matrix = df_clean[corr_vars].corr()
corr_matrix.index = corr_labels
corr_matrix.columns = corr_labels

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

mask = np.triu(np.ones_like(corr_matrix, dtype=bool), k=1)
cmap = sns.diverging_palette(250, 15, s=75, l=40, n=9, center="light", as_cmap=True)

sns.heatmap(
    corr_matrix, mask=mask, cmap=cmap, center=0,
    annot=True, fmt=".2f", square=True,
    linewidths=0.5, cbar_kws={"shrink": 0.8},
    ax=ax, vmin=-1, vmax=1,
    annot_kws={"size": 9}
)
ax.set_title("Correlation Matrix of Key Financial Measures", fontsize=14, pad=20)

plt.tight_layout()
plt.show()
Hình 36.2: Ma trận tương quan của các thước đo chính về định giá, kiệt quệ và mức độ trưởng thành đối với các doanh nghiệp niêm yết Việt Nam.

36.3 Phân tích hồi quy dữ liệu chéo

Chúng ta ước lượng một mô hình hồi quy dữ liệu chéo đơn giản để xem xét các yếu tố quyết định Q của Tobin trên thị trường Việt Nam:

\[ Q_{i,t} = \alpha + \beta_1 \ln(\text{Age}_{i}) + \beta_2 Z''_{i,t} + \beta_3 \text{SOE}_{i} + \beta_4 \ln(\text{TA}_{i,t}) + \varepsilon_{i,t} \tag{36.1}\]

Cross-sectional regression of Tobin’s Q
from numpy.linalg import lstsq

# Prepare regression data
reg_data = latest.dropna(
    subset=["tobin_q_simple_w", "ln_age_founding", "altman_z_em_w", "at"]
).copy()

reg_data["ln_at"] = np.log(reg_data["at"])
reg_data["soe_dummy"] = (reg_data["state_ownership_pct"] > 30).astype(int)
reg_data["constant"] = 1.0

# OLS estimation
y = reg_data["tobin_q_simple_w"].values
X = reg_data[["constant", "ln_age_founding", "altman_z_em_w",
              "soe_dummy", "ln_at"]].values

betas, residuals, rank, sv = lstsq(X, y, rcond=None)
y_hat = X @ betas
resid = y - y_hat
n, k = X.shape

# Standard errors (heteroskedasticity-robust would be better)
sigma2 = np.sum(resid**2) / (n - k)
var_beta = sigma2 * np.linalg.inv(X.T @ X)
se = np.sqrt(np.diag(var_beta))
t_stats = betas / se
r_squared = 1 - np.sum(resid**2) / np.sum((y - y.mean())**2)
adj_r2 = 1 - (1 - r_squared) * (n - 1) / (n - k - 1)

# Display results
var_names = ["Constant", "ln(Age)", "Z''-Score (EM)", "SOE Dummy", "ln(Total Assets)"]
print("=" * 65)
print("  Cross-Sectional Regression: Determinants of Tobin's Q")
print(f"  Dependent Variable: Tobin's Q (Simple, Winsorized)")
print(f"  Sample: {n} firms ({latest_year})")
print("=" * 65)
print(f"  {'Variable':<22} {'Coef':>10} {'Std.Err':>10} {'t-stat':>10}")
print("-" * 65)
for name, b, s, t in zip(var_names, betas, se, t_stats):
    sig = "***" if abs(t) > 2.576 else "**" if abs(t) > 1.96 else "*" if abs(t) > 1.645 else ""
    print(f"  {name:<22} {b:>10.4f} {s:>10.4f} {t:>8.2f} {sig}")
print("-" * 65)
print(f"  R-squared: {r_squared:.4f}")
print(f"  Adjusted R-squared: {adj_r2:.4f}")
print(f"  Observations: {n}")
print("=" * 65)
print("  Significance: *** p<0.01, ** p<0.05, * p<0.10")
=================================================================
  Cross-Sectional Regression: Determinants of Tobin's Q
  Dependent Variable: Tobin's Q (Simple, Winsorized)
  Sample: 232 firms (2024)
=================================================================
  Variable                     Coef    Std.Err     t-stat
-----------------------------------------------------------------
  Constant                   0.9686     0.1959     4.94 ***
  ln(Age)                   -0.0064     0.0365    -0.18 
  Z''-Score (EM)             0.0067     0.0050     1.34 
  SOE Dummy                  0.0327     0.0315     1.04 
  ln(Total Assets)           0.0018     0.0096     0.19 
-----------------------------------------------------------------
  R-squared: 0.0126
  Adjusted R-squared: -0.0092
  Observations: 232
=================================================================
  Significance: *** p<0.01, ** p<0.05, * p<0.10

37 Quy trình hoàn chỉnh

37.1 Tổng hợp tất cả

Ở đây chúng tôi cung cấp một hàm gọn gàng duy nhất nhận dữ liệu tài chính thô từ DataCore.vn và tạo ra bộ dữ liệu hoàn chỉnh với cả ba thước đo đã được tính.

Complete pipeline for computing Tobin’s Q, Z-Score, and Company Age
def compute_valuation_distress_age(df, firm_profiles=None):
    """
    Complete pipeline to compute Tobin's Q, Altman Z-Score variants,
    and Company Age for Vietnamese listed firms.

    Parameters
    ----------
    df : pd.DataFrame
        Panel of firm-year financial data with columns:
        at, seq, lt, lct, tlb, sale, ebit, ni, re_var, act,
        ppent, invt, txdb, itcb, pstk, me, prcc, csho, year, ticker
    firm_profiles : pd.DataFrame, optional
        Company profiles with founding_year, listing_year

    Returns
    -------
    pd.DataFrame
        Input data augmented with computed measures
    """
    result = df.copy()

    # === Data Quality Filters ===
    result = result[result["at"] > 0]
    result = result[result["seq"] > 0]

    # === Book Value of Equity (Daniel & Titman 1997) ===
    result["pref"] = result["pstk"].fillna(0)
    result["be"] = (
        result["seq"]
        + result["txdb"].fillna(0)
        + result["itcb"].fillna(0)
        - result["pref"]
    )

    # === Market Value of Equity ===
    if "me" not in result.columns or result["me"].isna().all():
        result["me"] = result["prcc"] * result["csho"]

    # === Tobin's Q Variants ===
    # Simple Q (Gompers et al. 2003)
    result["tobin_q"] = (result["at"] + result["me"] - result["be"]) / result["at"]

    # Chung-Pruitt Q
    debt_cp = (
        result["lct"].fillna(0) - result["act"].fillna(0)
        + result["invt"].fillna(0) + result["lt"].fillna(0)
    )
    result["tobin_q_cp"] = (
        (result["me"] + result["pstk"].fillna(0) + debt_cp) / result["at"]
    )

    # Market-to-Book
    result["mtb"] = np.where(result["be"] > 0, result["me"] / result["be"], np.nan)

    # === Altman Z-Score Variants ===
    result["wc"] = result["act"].fillna(0) - result["lct"].fillna(0)

    x1 = result["ebit"] / result["at"]
    x2 = result["sale"] / result["at"]
    x3_market = np.where(result["tlb"] > 0, result["me"] / result["tlb"], np.nan)
    x3_book = np.where(result["tlb"] > 0, result["be"] / result["tlb"], np.nan)
    x4 = result["wc"] / result["at"]
    x5 = result["re_var"].fillna(0) / result["at"]

    # Original Z
    result["altman_z"] = 3.3 * x1 + 0.999 * x2 + 0.6 * x3_market + 1.2 * x4 + 1.4 * x5

    # Z' (private firms)
    result["altman_z_prime"] = (
        0.717 * x4 + 0.847 * x5 + 3.107 * x1 + 0.420 * x3_book + 0.998 * x2
    )

    # Z'' (emerging markets)
    result["altman_z_em"] = (
        3.25 + 6.56 * x4 + 3.26 * x5 + 6.72 * x1 + 1.05 * x3_book
    )

    # Risk zones
    result["z_zone"] = pd.cut(
        result["altman_z"],
        bins=[-np.inf, 1.80, 2.70, 2.99, np.inf],
        labels=["High Distress", "Distress", "Grey Zone", "Safe"]
    )
    result["z_em_zone"] = pd.cut(
        result["altman_z_em"],
        bins=[-np.inf, 1.10, 2.60, np.inf],
        labels=["Distress", "Grey Zone", "Safe"]
    )

    # === Company Age ===
    if firm_profiles is not None:
        result = result.merge(
            firm_profiles[["ticker", "founding_year", "listing_year"]],
            on="ticker", how="left", suffixes=("", "_profile")
        )
        result["age_founding"] = result["year"] - result["founding_year"]
        result["age_listing"] = (result["year"] - result["listing_year"]).clip(lower=0)

    first_year = result.groupby("ticker")["year"].transform("min")
    result["age_data"] = result["year"] - first_year

    # === Winsorize ===
    for var in ["tobin_q", "tobin_q_cp", "mtb", "altman_z", "altman_z_prime", "altman_z_em"]:
        if var in result.columns:
            result[f"{var}_w"] = winsorize(result[var])

    return result

# Demonstrate the pipeline
final_df = compute_valuation_distress_age(df, firm_profiles)
print(f"Final dataset: {final_df.shape[0]:,} observations, {final_df.shape[1]} variables")
print(f"Firms: {final_df['ticker'].nunique()}")
print(f"\nKey variables computed:")
for var in ["tobin_q", "tobin_q_cp", "mtb", "altman_z", "altman_z_prime",
            "altman_z_em", "age_founding", "age_listing", "age_data"]:
    if var in final_df.columns:
        non_null = final_df[var].notna().sum()
        print(f"  {var}: {non_null:,} non-null values")
Final dataset: 3,484 observations, 48 variables
Firms: 297

Key variables computed:
  tobin_q: 3,484 non-null values
  tobin_q_cp: 3,484 non-null values
  mtb: 3,484 non-null values
  altman_z: 3,484 non-null values
  altman_z_prime: 3,484 non-null values
  altman_z_em: 3,484 non-null values
  age_founding: 3,484 non-null values
  age_listing: 3,484 non-null values
  age_data: 3,484 non-null values

37.2 Xuất kết quả

Export computed measures to CSV
# Select key output variables
output_vars = [
    "ticker", "year", "datadate", "exchange", "industry",
    "at", "be", "me", "tobin_q", "tobin_q_w", "mtb", "mtb_w",
    "altman_z", "altman_z_w", "altman_z_em", "altman_z_em_w",
    "z_zone", "z_em_zone",
    "age_founding", "age_listing", "age_data",
    "state_ownership_pct"
]

export_cols = [v for v in output_vars if v in final_df.columns]
export_df = final_df[export_cols].copy()

# export_df.to_csv("vn_valuation_distress_age.csv", index=False)
# export_df.to_parquet("vn_valuation_distress_age.parquet", index=False)

print(f"Export dataset: {export_df.shape[0]:,} rows × {export_df.shape[1]} columns")
print(f"\nSample output (first 5 rows):")
display_cols = ["ticker", "year", "tobin_q", "altman_z", "altman_z_em",
                "z_zone", "age_founding"]
display_cols = [c for c in display_cols if c in export_df.columns]
print(export_df[display_cols].head().to_string(index=False))
Export dataset: 3,484 rows × 22 columns

Sample output (first 5 rows):
ticker  year  tobin_q  altman_z  altman_z_em z_zone  age_founding
VN0001  2017 0.905928  3.345941     5.605169   Safe             4
VN0001  2018 1.270114  5.324082     9.228846   Safe             5
VN0001  2019 1.866493  5.478529     6.264178   Safe             6
VN0001  2020 1.016979  5.612289    10.445449   Safe             7
VN0001  2021 0.805818  4.220869     7.588273   Safe             8

38 Các chủ đề đặc biệt cho thị trường Việt Nam

38.1 Sở hữu nhà nước và định giá

Sở hữu nhà nước là đặc điểm nổi bật của bức tranh doanh nghiệp Việt Nam. Chúng ta xem xét sở hữu nhà nước ảnh hưởng thế nào đến cả Q của Tobin lẫn rủi ro kiệt quệ tài chính.

Hiện mã
fig, axes = plt.subplots(1, 2, figsize=(14, 6))

latest = final_df[final_df["year"] == latest_year].dropna(
    subset=["tobin_q_w", "altman_z_em_w", "state_ownership_pct"]
)

# Q vs State Ownership
axes[0].scatter(
    latest["state_ownership_pct"], latest["tobin_q_w"],
    alpha=0.4, s=20, color=colors["primary"], edgecolor="white"
)

# Binned means
bins = pd.cut(latest["state_ownership_pct"], bins=10)
binned = latest.groupby(bins)["tobin_q_w"].mean()
bin_centers = [(b.left + b.right) / 2 for b in binned.index]
axes[0].plot(bin_centers, binned.values, color=colors["quaternary"],
             linewidth=3, label="Binned Mean")

axes[0].set_xlabel("State Ownership (%)")
axes[0].set_ylabel("Tobin's Q")
axes[0].set_title("State Ownership and Firm Valuation")
axes[0].axhline(y=1, color="gray", linestyle="--", alpha=0.5)
axes[0].legend()

# Z''-Score by SOE quartile
latest["soe_quartile"] = pd.qcut(
    latest["state_ownership_pct"],
    q=4, labels=["Q1 (Low)", "Q2", "Q3", "Q4 (High)"],
    duplicates="drop"
)

soe_groups = latest.groupby("soe_quartile")["altman_z_em_w"]
box_data = [group.values for name, group in soe_groups]
bp = axes[1].boxplot(
    box_data,
    labels=[name for name, _ in soe_groups],
    patch_artist=True,
    medianprops=dict(color="black", linewidth=2)
)

gradient_colors = plt.cm.Blues(np.linspace(0.3, 0.8, len(bp["boxes"])))
for patch, color in zip(bp["boxes"], gradient_colors):
    patch.set_facecolor(color)

axes[1].axhline(y=2.60, color="green", linestyle=":", alpha=0.5, label="Safe threshold")
axes[1].axhline(y=1.10, color="red", linestyle=":", alpha=0.5, label="Distress threshold")
axes[1].set_xlabel("State Ownership Quartile")
axes[1].set_ylabel("Z''-Score (Emerging Market)")
axes[1].set_title("Financial Health by State Ownership")
axes[1].legend(fontsize=9)

plt.tight_layout()
plt.show()
Hình 38.1: Mối quan hệ giữa tỷ lệ sở hữu nhà nước và các thước đo định giá/kiệt quệ của doanh nghiệp. Biểu đồ bên trái thể hiện Q của Tobin theo sở hữu nhà nước, với đường làm trơn LOWESS. Biểu đồ bên phải thể hiện điểm Z’’ theo tứ phân vị sở hữu nhà nước.

38.2 Phân tích theo sàn giao dịch

Hiện mã
exchange_summary = (
    latest
    .groupby("exchange")
    .agg(
        n_firms=("ticker", "nunique"),
        median_q=("tobin_q_w", "median"),
        mean_q=("tobin_q_w", "mean"),
        median_z=("altman_z_w", "median"),
        mean_z_em=("altman_z_em_w", "mean"),
        pct_distress_z=("z_zone", lambda x: (x == "High Distress").mean() * 100),
        pct_distress_em=("z_em_zone", lambda x: (x == "Distress").mean() * 100),
        median_age=("age_founding", "median"),
        median_soe=("state_ownership_pct", "median"),
    )
    .round(2)
)

exchange_summary.columns = [
    "N Firms", "Median Q", "Mean Q", "Median Z", "Mean Z'' (EM)",
    "% Distress (Z)", "% Distress (Z'')", "Median Age", "Median SOE %"
]

exchange_summary.style.set_properties(**{
    "text-align": "center",
    "font-size": "10pt"
}).set_table_styles([
    {"selector": "th", "props": [
        ("background-color", "#1f77b4"),
        ("color", "white"),
        ("text-align", "center"),
        ("padding", "8px")
    ]},
]).format({
    "Median Q": "{:.2f}", "Mean Q": "{:.2f}",
    "Median Z": "{:.2f}", "Mean Z'' (EM)": "{:.2f}",
    "% Distress (Z)": "{:.1f}%", "% Distress (Z'')": "{:.1f}%",
    "Median Age": "{:.0f}", "Median SOE %": "{:.1f}%"
})
Bảng 38.1: Thống kê mô tả theo sàn giao dịch đối với các doanh nghiệp niêm yết Việt Nam (năm gần nhất)
  N Firms Median Q Mean Q Median Z Mean Z'' (EM) % Distress (Z) % Distress (Z'') Median Age Median SOE %
exchange                  
HNX 77 0.99 1.02 2.07 7.25 32.5% 0.0% 33 22.1%
HOSE 113 1.00 1.04 2.06 7.24 40.7% 0.0% 29 26.8%
UPCoM 42 0.98 1.05 2.13 7.80 30.9% 0.0% 26 27.6%

38.3 Bản đồ nhiệt theo ngành: Định giá và kiệt quệ

Hiện mã
fig, axes = plt.subplots(1, 2, figsize=(16, 8))

# Tobin's Q heatmap
q_pivot = (
    final_df
    .groupby(["industry", "year"])["tobin_q_w"]
    .median()
    .unstack(fill_value=np.nan)
)

sns.heatmap(
    q_pivot, cmap="RdYlGn", center=1, ax=axes[0],
    cbar_kws={"label": "Median Q", "shrink": 0.8},
    linewidths=0.5, linecolor="white",
    xticklabels=2
)
axes[0].set_title("Tobin's Q by Industry and Year")
axes[0].set_xlabel("Year")
axes[0].set_ylabel("")

# Z''-Score heatmap
z_pivot = (
    final_df
    .groupby(["industry", "year"])["altman_z_em_w"]
    .median()
    .unstack(fill_value=np.nan)
)

sns.heatmap(
    z_pivot, cmap="RdYlGn", center=2.6, ax=axes[1],
    cbar_kws={"label": "Median Z''", "shrink": 0.8},
    linewidths=0.5, linecolor="white",
    xticklabels=2
)
axes[1].set_title("Z''-Score (EM) by Industry and Year")
axes[1].set_xlabel("Year")
axes[1].set_ylabel("")

plt.tight_layout()
plt.show()
Hình 38.2: Q của Tobin và điểm Z’’ trung vị theo ngành và năm đối với các doanh nghiệp niêm yết Việt Nam. Màu ấm hơn biểu thị giá trị cao hơn.

38.4 Xử lý các doanh nghiệp bị hủy niêm yết: Thiên kiến sống sót

Một vấn đề then chốt khi đo lường kiệt quệ tài chính là thiên kiến sống sót. Nếu chỉ phân tích các doanh nghiệp hiện đang niêm yết, chúng ta bỏ sót chính những doanh nghiệp mà điểm Z được thiết kế để nhận diện, tức những doanh nghiệp đã phá sản hoặc bị hủy niêm yết.

Analysis of delisted firms and survivorship bias
# Simulate delisting data (replace with DataCore.vn delisting records)
np.random.seed(123)
n_delisted = 50
delisted_firms = pd.DataFrame({
    "ticker": [f"VN{str(i).zfill(4)}" for i in range(n_firms + 1, n_firms + n_delisted + 1)],
    "delist_year": np.random.randint(2010, 2024, n_delisted),
    "delist_reason": np.random.choice(
        ["Bankruptcy/Liquidation", "Merger/Acquisition", "Voluntary Delisting",
         "Regulatory Non-compliance", "Below Minimum Requirements"],
        n_delisted,
        p=[0.15, 0.25, 0.20, 0.20, 0.20]
    )
})

# Summary of delisting reasons
print("Delisting Reasons Summary")
print("=" * 50)
reason_counts = delisted_firms["delist_reason"].value_counts()
for reason, count in reason_counts.items():
    print(f"  {reason}: {count} ({count/n_delisted*100:.0f}%)")
print(f"\nTotal delisted: {n_delisted}")
print(f"Currently listed: {final_df['ticker'].nunique()}")
print(f"\nSurvivorship rate: "
      f"{final_df['ticker'].nunique()/(final_df['ticker'].nunique()+n_delisted)*100:.1f}%")
Delisting Reasons Summary
==================================================
  Merger/Acquisition: 16 (32%)
  Voluntary Delisting: 14 (28%)
  Regulatory Non-compliance: 9 (18%)
  Below Minimum Requirements: 7 (14%)
  Bankruptcy/Liquidation: 4 (8%)

Total delisted: 50
Currently listed: 297

Survivorship rate: 85.6%

39 Kiểm tra độ vững và mở rộng

39.1 Các cách xác định Q của Tobin thay thế

Hiện mã
fig, ax = plt.subplots(figsize=(7, 7))

sample = latest.dropna(subset=["tobin_q_w", "tobin_q_cp"]).copy()
sample["tobin_q_cp_w"] = winsorize(sample["tobin_q_cp"])

ax.scatter(
    sample["tobin_q_w"], sample["tobin_q_cp_w"],
    alpha=0.4, s=15, color=colors["primary"], edgecolor="white"
)

# 45-degree line
lims = [
    min(ax.get_xlim()[0], ax.get_ylim()[0]),
    max(ax.get_xlim()[1], ax.get_ylim()[1])
]
ax.plot(lims, lims, "k--", alpha=0.3)

corr = sample["tobin_q_w"].corr(sample["tobin_q_cp_w"])
ax.text(0.05, 0.95, f"Correlation: {corr:.3f}",
        transform=ax.transAxes, fontsize=12,
        verticalalignment="top",
        bbox=dict(boxstyle="round", facecolor="wheat", alpha=0.5))

ax.set_xlabel("Simple Q (Gompers et al.)")
ax.set_ylabel("Chung-Pruitt Q")
ax.set_title("Comparison of Tobin's Q Variants")

plt.tight_layout()
plt.show()
Hình 39.1: So sánh các biến thể của Q của Tobin: Q đơn giản (Gompers et al. 2003) so với xấp xỉ Chung-Pruitt. Tương quan cao ủng hộ việc sử dụng thước đo đơn giản hơn.

39.2 Các thước đo điều chỉnh theo ngành

Q của Tobin và điểm Z thô có thể phản ánh đặc điểm ngành hơn là thuộc tính riêng của doanh nghiệp. Chúng ta tính các phiên bản điều chỉnh theo ngành:

\[ Q^{\text{adj}}_{i,t} = Q_{i,t} - \overline{Q}_{j(i),t} \tag{39.1}\]

trong đó \(\overline{Q}_{j(i),t}\) là Q của Tobin trung vị của ngành \(j\) mà doanh nghiệp \(i\) thuộc về trong năm \(t\).

Industry-adjusted measures
def industry_adjust(df, var, group_var="industry"):
    """Compute industry-adjusted measure (deviation from industry median)."""
    industry_median = df.groupby(["year", group_var])[var].transform("median")
    return df[var] - industry_median

final_df["tobin_q_adj"] = industry_adjust(final_df, "tobin_q_w")
final_df["altman_z_em_adj"] = industry_adjust(final_df, "altman_z_em_w")

latest_adj = final_df[final_df["year"] == latest_year]

print("Industry-Adjusted Measures (Latest Year)")
print(f"  Tobin's Q (adjusted): mean={latest_adj['tobin_q_adj'].mean():.4f}, "
      f"std={latest_adj['tobin_q_adj'].std():.3f}")
print(f"  Z''-Score (adjusted): mean={latest_adj['altman_z_em_adj'].mean():.4f}, "
      f"std={latest_adj['altman_z_em_adj'].std():.3f}")
Industry-Adjusted Measures (Latest Year)
  Tobin's Q (adjusted): mean=0.0352, std=0.226
  Z''-Score (adjusted): mean=0.4840, std=3.030

39.3 Hồi quy dữ liệu bảng với hiệu ứng cố định

Để phân tích chặt chẽ hơn, chúng ta ước lượng một mô hình dữ liệu bảng với hiệu ứng cố định theo doanh nghiệp và theo năm:

\[ Q_{i,t} = \alpha_i + \gamma_t + \beta_1 Z''_{i,t} + \beta_2 \ln(\text{Age}_{i,t}) + \beta_3 \text{Size}_{i,t} + \varepsilon_{i,t} \tag{39.2}\]

Panel regression with year and industry fixed effects
# Simplified within-estimator (year and industry demeaning)
panel = final_df.dropna(
    subset=["tobin_q_w", "altman_z_em_w", "age_founding", "at"]
).copy()

panel["ln_at"] = np.log(panel["at"])
panel["ln_age"] = np.log1p(panel["age_founding"])

# Demean by year-industry (proxy for fixed effects)
fe_vars = ["tobin_q_w", "altman_z_em_w", "ln_age", "ln_at"]
for var in fe_vars:
    group_mean = panel.groupby(["year", "industry"])[var].transform("mean")
    panel[f"{var}_dm"] = panel[var] - group_mean

# OLS on demeaned data
y = panel["tobin_q_w_dm"].values
X = np.column_stack([
    np.ones(len(panel)),
    panel["altman_z_em_w_dm"].values,
    panel["ln_age_dm"].values,
    panel["ln_at_dm"].values,
])

betas, _, _, _ = lstsq(X, y, rcond=None)
y_hat = X @ betas
resid = y - y_hat
n, k = X.shape

sigma2 = np.sum(resid**2) / (n - k)
var_beta = sigma2 * np.linalg.inv(X.T @ X)
se = np.sqrt(np.diag(var_beta))
t_stats = betas / se
r_squared = 1 - np.sum(resid**2) / np.sum((y - y.mean())**2)

var_names = ["Constant", "Z''-Score (EM)", "ln(Age)", "ln(Total Assets)"]
print("=" * 65)
print("  Panel Regression: Tobin's Q with Year-Industry FE")
print(f"  (Within estimator via year×industry demeaning)")
print("=" * 65)
print(f"  {'Variable':<22} {'Coef':>10} {'Std.Err':>10} {'t-stat':>10}")
print("-" * 65)
for name, b, s, t in zip(var_names, betas, se, t_stats):
    sig = "***" if abs(t) > 2.576 else "**" if abs(t) > 1.96 else "*" if abs(t) > 1.645 else ""
    print(f"  {name:<22} {b:>10.4f} {s:>10.4f} {t:>8.2f} {sig}")
print("-" * 65)
print(f"  R-squared (within): {r_squared:.4f}")
print(f"  Observations: {n:,}")
print("=" * 65)
=================================================================
  Panel Regression: Tobin's Q with Year-Industry FE
  (Within estimator via year×industry demeaning)
=================================================================
  Variable                     Coef    Std.Err     t-stat
-----------------------------------------------------------------
  Constant                   0.0000     0.0038     0.00 
  Z''-Score (EM)             0.0034     0.0014     2.45 **
  ln(Age)                   -0.0055     0.0063    -0.88 
  ln(Total Assets)          -0.0031     0.0026    -1.16 
-----------------------------------------------------------------
  R-squared (within): 0.0024
  Observations: 3,484
=================================================================

40 Giải quyết kiệt quệ: Chế độ pháp lý đằng sau điểm Z

40.1 Điểm Z thực sự dự báo điều gì

Hàm phân biệt của Altman (1968) được ước lượng trên một mẫu ghép cặp gồm các doanh nghiệp đã nộp và chưa nộp đơn phá sản. Do đó, sự kiện mà nó chấm điểm là việc bước vào một thủ tục pháp lý cụ thể, chứ không phải cái chết về mặt kinh tế, và càng không phải mức tổn thất khi xảy ra kiệt quệ. Sự phân biệt này dễ bị bỏ qua vì các vùng diễn giải trong Bảng 30.2 được diễn đạt bằng ngôn ngữ xác suất thất bại, nhưng đối với bất kỳ ai định giá một quyền đòi đối với doanh nghiệp, đối tượng liên quan là

\[ \mathbb{E}[\text{Loss}_i] \;=\; \underbrace{\Pr(\text{distress}_i)}_{\text{what } Z \text{ scores}} \times \underbrace{\mathbb{E}\!\left[\text{Loss}_i \mid \text{distress}_i\right]}_{\text{set by the insolvency regime}} . \tag{40.1}\]

Nhân tố thứ hai không phải là thuộc tính của doanh nghiệp. Nó là thuộc tính của các quy tắc quy định ai kiểm soát một doanh nghiệp kiệt quệ, chi phí tái cơ cấu là bao nhiêu, chủ nợ có thể buộc thanh lý hay không, và chủ sở hữu hiện hữu có thể giữ lại vốn chủ sở hữu hay không. Việc chuyển một hàm phân biệt được ước lượng ở một khu vực pháp lý sang nơi khác đồng nghĩa với việc chuyển theo một nhân tố thứ hai ngầm định. Việc nhân tố thứ hai thực sự thay đổi đã được ghi nhận rõ: Davydenko và Franks (2008) cho thấy các vụ vỡ nợ đối với các khoản vay tương tự ở Pháp, Đức và Anh được giải quyết khác nhau đến mức các ngân hàng điều chỉnh yêu cầu tài sản đảm bảo theo bộ luật, và Schoenherr và Starmans (2022) chứng minh cả về lý thuyết lẫn thực nghiệm rằng mức độ thân thiện tối ưu đối với con nợ phụ thuộc vào môi trường xung quanh chứ không phải là một hằng số phổ quát.

Đối với nghiên cứu về Việt Nam, điều này quan trọng gấp đôi. Các hệ số của điểm Z’’ được sử dụng trong Chương 34 được hiệu chỉnh bên ngoài Việt Nam, và phân phối kết cục mà chúng ngầm định lấy làm điều kiện được tạo ra bởi một hệ thống phá sản mà Việt Nam không có.

40.2 Thí nghiệm Subchapter V

Hotchkiss, Iverson, và Zheng (2026) cung cấp bằng chứng gần đây rõ ràng nhất rằng nhân tố thứ hai trong Phương trình 40.1 là một lựa chọn chính sách chứ không phải một hằng số công nghệ, và họ làm điều đó ở phân khúc mà câu hỏi này gay gắt nhất: các doanh nghiệp nhỏ.

Thực tế cách điệu hóa (stylized fact) làm động lực cho bài nghiên cứu lại đảo ngược thực tế quen thuộc. Đối với các công ty đại chúng lớn của Hoa Kỳ, các vụ thanh lý theo Chapter 11 hiếm khi xảy ra và hầu hết các doanh nghiệp nộp đơn thoát ra với một phần hoặc toàn bộ hoạt động còn nguyên vẹn. Đối với doanh nghiệp nhỏ, bức tranh đảo ngược: 70% doanh nghiệp nhỏ bước vào thủ tục phá sản giai đoạn 2010-2019 nộp đơn thẳng vào thanh lý theo Chapter 7, và trong số những doanh nghiệp đã vào Chapter 11, chỉ khoảng một phần ba được xác nhận kế hoạch. Phần còn lại bị chuyển sang Chapter 7 hoặc bị tòa bác đơn. Việc bị bác đơn không phải là một cú hạ cánh mềm, vì doanh nghiệp quay lại với các chủ nợ mà không được xóa nợ và thường ngừng hoạt động (Morrison 2007; Iverson 2018).

Ba ma sát giải thích khoảng cách này. Thứ nhất, các chi phí trực tiếp của Chapter 11 phần lớn là cố định, nên chúng trở nên quá nặng nề so với một cơ sở tài sản nhỏ (Bris, Welch, và Zhu 2006; Dou và c.s. 2021). Thứ hai, nguyên tắc ưu tiên tuyệt đối xóa sạch vốn chủ sở hữu trước phá sản, nhưng trong một doanh nghiệp nhỏ, các mối quan hệ của chủ sở hữu kiêm nhà quản lý với nhà cung cấp và khách hàng thường chính là giá trị hoạt động liên tục; các chủ nợ kiêm chủ sở hữu mới không thể dễ dàng thay thế những gì họ vừa tước đoạt, và các chủ nợ thương mại cùng các ngân hàng địa phương hiếm khi muốn nắm giữ vốn chủ sở hữu ngay từ đầu. Thứ ba, các chủ nợ phân tán với thứ tự ưu tiên khác nhau tạo ra các vấn đề phối hợp, và một chủ nợ có bảo đảm ưu tiên với khoản nợ nhỏ thường thích phát mại nhanh hơn một kế hoạch được thương lượng, ngay cả khi việc tiếp tục hoạt động có giá trị hơn xét trên tổng thể.

Đạo luật Tái cơ cấu Doanh nghiệp Nhỏ năm 2019 (Small Business Reorganization Act of 2019) đã tạo ra Subchapter V của Chapter 11, có hiệu lực từ ngày 19 tháng 2 năm 2020, giải quyết đồng thời cả ba ma sát này đối với các con nợ có tổng nợ không phụ thuộc điều kiện và đã xác định không vượt quá $7.5 triệu. Mức trần để đủ điều kiện ban đầu là $2.725 triệu và được CARES Act nâng lên $7.5 triệu vào ngày 27 tháng 3 năm 2020, một tháng sau khi thủ tục này mở ra. Ba thay đổi then chốt là: một kế hoạch có thể được xác nhận bất chấp sự phản đối của chủ nợ mà không xóa bỏ vốn chủ sở hữu của chủ doanh nghiệp, với điều kiện doanh nghiệp cam kết dành thu nhập khả dụng dự kiến của mình để trả nợ trong ba đến năm năm; ủy ban chủ nợ và bản công bố thông tin không còn bắt buộc; và một người được ủy thác Subchapter V được chỉ định để hòa giải thay vì thay thế ban quản lý.

Chính mức trần đủ điều kiện khiến đây trở thành một thí nghiệm chứ không phải một giai thoại. Bảng 40.1 tổng hợp các ước lượng chính.

Bảng 40.1: Tác động ước lượng của điều kiện đủ hưởng Subchapter V đối với kết cục Chapter 11 của doanh nghiệp nhỏ, từ Hotchkiss, Iverson, và Zheng (2026). “pp” là điểm phần trăm. Giai đoạn một là mức nhảy \(41.9\) pp trong việc sử dụng Subchapter V tại ngưỡng (\(F = 71.2\)) ở băng thông rộng và \(37.1\) pp (\(F = 19.7\)) ở băng thông hẹp.
Kết cục Thiết kế Ước lượng Mức tham chiếu
Kế hoạch được xác nhận OLS, 6,431 vụ dưới $15m \(+18.4\) pp tỷ lệ cơ sở \(31.9\%\)
Kế hoạch được xác nhận Fuzzy RDD, $4–11m (\(n=999\)) \(+32.0\) pp \(17.3\%\) đối với không thuộc SubV
Kế hoạch được xác nhận Fuzzy RDD, $6–9m \(+49.5\) pp \(17.3\%\) đối với không thuộc SubV
Kế hoạch được xác nhận DiD, ý định điều trị \(+12.5\) pp —
Số ngày đến khi xác nhận OLS / fuzzy RDD \(-24\%\) / \(-35\%\) trung bình 319 ngày
Tỷ lệ thu hồi của chủ nợ không bảo đảm Fuzzy RDD \(+11.9\) pp —
Khả năng tồn tại sau phá sản Fuzzy RDD, $4–11m \(+21\) pp không có ý nghĩa ở $6–9m

Hai đặc điểm của bảng này đáng được nhấn mạnh, ngoài các con số riêng lẻ.

Thứ nhất là tác động rất lớn so với tỷ lệ cơ sở. Một doanh nghiệp ngay dưới ngưỡng có xác suất tái cơ cấu cao gấp hơn hai lần so với một doanh nghiệp tương tự ngay trên ngưỡng. Trong khoảng này, việc một doanh nghiệp nhỏ kiệt quệ có tồn tại được hay không phụ thuộc vào thủ tục mà nó được phép sử dụng nhiều hơn là bất cứ điều gì trên bảng cân đối kế toán. Hệ quả đối với Phương trình 40.1 là nhân tố tổn thất có điều kiện là một tham số chính sách.

Thứ hai là phản bác hiển nhiên không đứng vững. Một chế độ cho phép chủ sở hữu giữ vốn chủ sở hữu bất chấp sự phản đối của chủ nợ, theo lập luận chuẩn, sẽ giữ cho các doanh nghiệp không khả thi tiếp tục tồn tại gây thiệt hại cho chủ nợ; hiệu quả hoạt động kém sau phá sản của các doanh nghiệp lớn đã thoát khỏi thủ tục phá sản từ lâu đã được xem là bằng chứng cho chính thiên lệch duy trì hoạt động đó (Hotchkiss 1995; Morrison 2007). Ở đây, tỷ lệ thu hồi kỳ vọng của chủ nợ không bảo đảm tăng 11.9 điểm phần trăm và khả năng tồn tại sau phá sản tăng lên chứ không giảm. Lợi ích từ việc tránh một cuộc thanh lý tốn kém dường như được chia sẻ chứ không bị tái phân phối, đúng như kỳ vọng nếu tỷ lệ thanh lý trước cải cách phản ánh chi phí giao dịch và thất bại phối hợp chứ không phải việc phân loại đúng giữa doanh nghiệp khả thi và không khả thi.

40.3 Đọc bằng chứng từ góc nhìn Việt Nam

Bốn hàm ý được áp dụng sang bối cảnh Việt Nam, nhưng không hàm ý nào áp dụng một cách máy móc.

Vùng kiệt quệ mang một ý nghĩa khác ở đây. Khung pháp lý chính thức về mất khả năng thanh toán của Việt Nam là Luật Phá sản số 51/2014/QH13, có hiệu lực từ ngày 1 tháng 1 năm 2015, quy định cả thủ tục phục hồi kinh doanh và thanh lý. Tuy nhiên, như đã trình bày trong Mục 13.5, các trường hợp rời khỏi nhóm doanh nghiệp niêm yết Việt Nam quan sát được chủ yếu là hủy niêm yết bắt buộc do lỗ vượt vốn điều lệ, không nộp báo cáo tài chính và xử lý vi phạm của cơ quan quản lý, chứ không phải các thủ tục tái cơ cấu dưới sự giám sát của tòa án tạo ra một tỷ lệ thu hồi quan sát được. Do đó, nội dung thực nghiệm của điểm Z’’ dưới 1.10 ở Việt Nam gần với “kỳ vọng bị hủy niêm yết và lợi nhuận cuối kỳ âm lớn” hơn là “kỳ vọng một quy trình có giám sát với tỷ lệ thu hồi của chủ nợ đo lường được”. Đây chính là lý do việc ước tính lợi nhuận hủy niêm yết trong Mục 13.5 không phải là một chi tiết kỹ thuật: đối với các doanh nghiệp Việt Nam, nó đang làm công việc mà hồ sơ vụ án của tòa án phá sản đảm nhiệm trong dữ liệu của Hoa Kỳ.

Việt Nam không có cơ sở dữ liệu tương đương FJC. Thiết kế của Hotchkiss–Iverson–Zheng dựa trên một cuộc điều tra toàn bộ công khai, ở cấp độ từng vụ việc, về các hồ sơ nộp đơn với nợ phải trả được ghi nhận tại thời điểm nộp đơn, bổ sung bằng các bảng kê thu thập thủ công từ hồ sơ tòa án. Việt Nam không có dữ liệu nào tương đương, và việc xây dựng nó sẽ là một đóng góp nghiên cứu độc lập. Cho đến khi nó tồn tại, nghiên cứu kiệt quệ tài chính ở Việt Nam phần lớn bị giới hạn trong nhóm doanh nghiệp niêm yết, vốn là lát cắt kém đại diện nhất của tổng thể doanh nghiệp nhỏ mà lý thuyết hướng đến.

Ma sát tái triển khai tài sản có thể lớn hơn. Bernstein, Colonnelli, và Iverson (2019) cho thấy tài sản của các doanh nghiệp nhỏ bị thanh lý ở Hoa Kỳ được tái triển khai kém và cuối cùng bị sử dụng dưới công suất, đó là cơ chế khiến việc thanh lý quá mức gây tốn kém cho xã hội. Thị trường thứ cấp mỏng đối với thiết bị vốn đã qua sử dụng và sự phức tạp về hành chính khi chuyển nhượng quyền sử dụng đất khiến ma sát này nhiều khả năng nghiêm trọng hơn ở Việt Nam, dù chúng tôi không biết có ước lượng trực tiếp nào.

Dư địa cải cách vẫn còn rộng mở. Hotchkiss, Iverson, và Zheng (2026) ghi nhận rằng ít nhất mười một quốc gia đã áp dụng các thủ tục mất khả năng thanh toán đơn giản hóa cho doanh nghiệp nhỏ trong thập kỷ qua, một số trong khu vực, và Gurrea-Martínez (2024) khảo sát cải cách mất khả năng thanh toán ở các nền kinh tế mới nổi một cách cụ thể. Việt Nam không nằm trong nhóm áp dụng. Đối với nhà nghiên cứu, điều này có hai mặt: không có thí nghiệm tự nhiên sẵn có kiểu này ở Việt Nam để khai thác, nhưng câu hỏi đánh giá ex-ante hoàn toàn bỏ ngỏ và khuôn mẫu nhận diện dưới đây có thể chuyển giao.

40.4 Quy tắc ngưỡng như thiết kế nghiên cứu

Nội dung phương pháp luận của Hotchkiss, Iverson, và Zheng (2026) đáng được tách khỏi nội dung thể chế, vì các ngưỡng đủ điều kiện có ở khắp nơi trong quy định của Việt Nam và thiết kế này có thể chuyển giao ngay cả khi nội dung cụ thể thì không.

40.4.1 Gián đoạn hồi quy mờ (Fuzzy Regression Discontinuity)

Điều kiện đủ hưởng là một hàm tất định của biến chạy, nhưng việc tham gia thì không: 39% doanh nghiệp đủ điều kiện trong mẫu của bài nghiên cứu vẫn chọn Chapter 11 truyền thống, và một số ít doanh nghiệp có vẻ không đủ điều kiện đã được chấp nhận sau khi tòa án phân loại lại một phần nợ phải trả của họ thành nợ phụ thuộc điều kiện. Do đó, việc điều trị nhảy tại ngưỡng mà không đi từ không lên một, đó là trường hợp mờ. Đặt \(s_i = L_i - c\) cho biến chạy đã được dời gốc về ngưỡng và \(B_i = \mathbf{1}\{s_i \leq 0\}\) cho điều kiện đủ hưởng. Thiết kế là bình phương tối thiểu hai giai đoạn với \(B_i\) là biến công cụ bị loại trừ:

\[ D_i = \alpha + \pi B_i + f(s_i) + B_i \cdot g(s_i) + X_i'\gamma + u_i \tag{40.2}\]

\[ y_i = \tilde{\alpha} + \beta \widehat{D}_i + \tilde{f}(s_i) + B_i \cdot \tilde{g}(s_i) + X_i'\tilde{\gamma} + \varepsilon_i \tag{40.3}\]

trong đó \(D_i\) chỉ thị việc sử dụng thực tế thủ tục đơn giản hóa, còn \(f\) và \(g\) là các đa thức bậc thấp được ước lượng riêng ở mỗi phía của ngưỡng (Imbens và Lemieux 2008; Lee và Lemieux 2010). Trong trường hợp vừa đủ nhận diện, \(\beta\) bằng tỷ số giữa mức nhảy dạng rút gọn của \(y\) và mức nhảy giai đoạn một \(\pi\), đó là dạng Wald của bộ ước lượng và là cách hữu ích nhất để đọc nó. Điều \(\beta\) nhận diện là một tác động điều trị trung bình cục bộ: tác động đối với các doanh nghiệp gần ngưỡng sử dụng thủ tục vì họ đủ điều kiện. Đó không phải là tác động đối với một doanh nghiệp có nợ phải trả $1 triệu, và cũng không phải là tác động của việc bắt buộc áp dụng thủ tục.

40.4.2 Điều gì làm thiết kế đáng tin cậy

Bốn kiểm tra đảm nhiệm vai trò này, và cả bốn đều được báo cáo trong bài nghiên cứu.

  1. Tính liên tục của mật độ. Nếu các doanh nghiệp thao túng biến chạy để đủ điều kiện, mật độ của \(s_i\) nhảy tại ngưỡng và phép so sánh diễn ra giữa các nhóm tự chọn. Kiểm định McCrary (2008) không tìm thấy sự gián đoạn (\(p = 0.25\)). Lập luận thể chế củng cố lập luận thống kê: một doanh nghiệp kiệt quệ không thể dễ dàng thanh toán nợ để xuống dưới một mức trần, và bản thân mức trần được nâng lên bất ngờ một tháng sau khi thủ tục mở ra.

  2. Tính trơn của các hiệp biến. Đòn bẩy, tỷ trọng nợ có bảo đảm, tổng tài sản, tình trạng nộp đơn chung và số lượng chủ nợ đều liên tục qua ngưỡng, cả riêng lẻ lẫn đồng thời.

  3. Độ vững theo đặc tả donut-hole. Loại bỏ các vụ việc trong dải $7–7.5 triệu, nơi mọi sự thao túng sẽ tập trung, hầu như không làm thay đổi các hệ số.

  4. Độ nhạy theo băng thông, được báo cáo trung thực. Tác động xác nhận là \(+32.0\) pp trong khoảng $4–11 triệu và \(+49.5\) pp trong khoảng $6–9 triệu. Thu hẹp cửa sổ củng cố nhận diện nhưng làm giảm sức mạnh thống kê, và bài nghiên cứu cho thấy cả hai mặt của sự đánh đổi đó: ước lượng điểm tăng, nhưng tác động lên khả năng tồn tại có ý nghĩa ở cửa sổ rộng lại không có ý nghĩa ở cửa sổ hẹp. Báo cáo đường đi của băng thông không phải là một nghi thức kiểm tra độ vững; nó là kết quả chính. Lựa chọn băng thông theo dữ liệu với suy luận hiệu chỉnh thiên lệch (Calonico, Cattaneo, và Titiunik 2014) là phần bổ sung tự nhiên, không phải là sự thay thế cho việc trình bày đường đi đó.

Có một mối đe dọa mà không kiểm tra nội bộ nào có thể giải quyết, đó là sự chọn lọc vào chính mẫu. Nếu điều kiện đủ hưởng khiến các doanh nghiệp lẽ ra sẽ tái cơ cấu ngoài tòa phải nộp đơn, thì phép so sánh bị nhiễm ngay cả khi có sự gián đoạn hoàn hảo. Phản hồi của bài nghiên cứu là một khuôn mẫu hữu ích: thay vì bác bỏ mối đe dọa bằng lập luận, nó định lượng mức độ chọn lọc cần thiết để lật ngược kết quả, và cho thấy việc loại bỏ ngẫu nhiên 29 vụ Subchapter V tái cơ cấu làm ước lượng chuyển từ 32.0 sang 29.1 điểm phần trăm, trong khi để đưa nó về không cần loại bỏ ít nhất 120 vụ.

40.4.3 Sai biệt trong sai biệt như ý định điều trị

Vì thủ tục này chưa tồn tại trước tháng 2 năm 2020, cùng một ngưỡng hỗ trợ một thiết kế thứ hai,

\[ y_i = \alpha + \beta_1 \text{Treated}_i + \beta_2 \text{Post}_t + \beta_3\,(\text{Treated}_i \times \text{Post}_t) + X_i'\gamma + \varepsilon_i \tag{40.4}\]

với các doanh nghiệp dưới mức trần là nhóm được điều trị và các doanh nghiệp ngay trên mức trần là nhóm đối chứng chưa bao giờ được điều trị. Ở đây \(\beta_3\) là tác động ý định điều trị, và nó nhỏ hơn ước lượng RDD vì hai lý do cộng dồn: không phải mọi doanh nghiệp đủ điều kiện đều sử dụng thủ tục, và mẫu trước năm 2020 chỉ có thể được chia theo tổng nợ phải trả chứ không phải nợ không phụ thuộc điều kiện, nên một số doanh nghiệp thực sự đủ điều kiện bị phân loại nhầm vào nhóm đối chứng. Sự đối chiếu này rất đáng học hỏi. Chia ITT là 12.5 điểm phần trăm cho giai đoạn một là 42 điểm phần trăm cho ra 29.8, gần với ước lượng RDD là 32.0. Một ITT và một LATE trông không nhất quán thường là cùng một tham số chia cho các mẫu số khác nhau.

40.4.4 Điều chỉnh thiết kế cho Việt Nam

Quy định của Việt Nam có rất nhiều quy tắc ranh giới rõ ràng: các điều kiện niêm yết và hủy niêm yết gắn với lỗ lũy kế so với vốn điều lệ, với số năm thua lỗ liên tiếp và với ý kiến kiểm toán; các định nghĩa dựa trên quy mô quyết định khả năng tiếp cận các chương trình hỗ trợ và ưu đãi thuế; trần sở hữu nước ngoài. Mỗi quy tắc đều là một ứng viên cho biến chạy (forcing variable).

Lời cảnh báo là kiểm định thao túng, vốn gần như một thủ tục hình thức trong Hotchkiss, Iverson, và Zheng (2026), trở thành yếu tố then chốt ở đây. Một doanh nghiệp Hoa Kỳ kiệt quệ không thể dễ dàng trả bớt nợ để lọt xuống dưới mức trần nợ phải trả. Một doanh nghiệp Việt Nam tiến gần ngưỡng được xác định theo lỗ lũy kế, doanh thu hoặc số lao động được báo cáo thường có thể làm được, và các động cơ quản trị dồn tích được ghi nhận trong chương quản trị lợi nhuận cung cấp cả động cơ lẫn phương tiện. Bất kỳ RDD nào của Việt Nam xây dựng trên một ngưỡng kế toán đều cần kiểm định mật độ, một đặc tả donut-hole (loại bỏ các quan sát sát ngưỡng) và một lập luận về lý do ngày báo cáo đi trước việc doanh nghiệp biết đến quy tắc — và cần sẵn sàng kết luận rằng thiết kế không hiệu quả. Các quy tắc ngưỡng cũng thay đổi; nội dung chính xác và ngày có hiệu lực của thông tư điều chỉnh phải được đối chiếu với giai đoạn mẫu chứ không được mặc định.

40.5 Triển khai: Fuzzy RDD từ đầu

Không tồn tại bảng dữ liệu phá sản cấp vụ việc nào của Việt Nam, vì vậy chúng ta mô phỏng một bảng mà quá trình sinh dữ liệu được hiệu chỉnh theo các ước lượng trong Bảng 40.1: tỷ lệ xác nhận cơ sở \(17\%\), tỷ lệ tham gia khoảng \(60\%\) trong số các doanh nghiệp đủ điều kiện, và tác động cục bộ thực là \(32\) điểm phần trăm. Vì biết trước giá trị thực, bộ ước lượng có thể được kiểm tra so với nó, và vấn đề chọn lọc thúc đẩy thiết kế này có thể được cố ý đưa vào. Độ phức tạp vụ việc không quan sát được làm tăng tỷ lệ tham gia và giảm xác suất xác nhận cơ sở, nên phép so sánh ngây thơ giữa doanh nghiệp sử dụng và không sử dụng thủ tục bị chệch xuống dưới — cùng chiều mà Hotchkiss, Iverson, và Zheng (2026) lập luận là áp dụng trong dữ liệu của họ, nơi các doanh nghiệp nộp đơn theo Subchapter V thậm chí còn kiệt quệ hơn các doanh nghiệp nộp đơn theo Chapter 11 truyền thống.

Simulate a threshold-eligibility insolvency regime
rng = np.random.default_rng(2026)
CUTOFF = 7.5        # eligibility ceiling, in millions of currency units
TAU = 0.32          # true local effect on the probability of confirmation

n_draw = 2000
liab = CUTOFF + rng.normal(0.0, 2.5, n_draw)
liab = liab[(liab > 2.0) & (liab < 13.0)]
n = liab.size

# Unobserved case complexity: raises take-up, lowers baseline success.
complexity = rng.normal(0.0, 1.0, n)
eligible = (liab <= CUTOFF).astype(float)

# Take-up is imperfect on both sides: some eligible firms decline the
# simplified track, and a few ineligible ones are admitted after the court
# reclassifies part of their liabilities as contingent.
p_take = np.where(eligible == 1.0,
                  np.clip(0.60 + 0.12 * complexity, 0.05, 0.95),
                  0.03)
simplified = (rng.random(n) < p_take).astype(float)

p_conf = np.clip(
    0.17 + 0.010 * (liab - CUTOFF) - 0.05 * complexity + TAU * simplified,
    0.01, 0.99,
)
confirmed = (rng.random(n) < p_conf).astype(float)

# A covariate that is smooth through the threshold, for the placebo check.
leverage = 3.0 + 0.15 * (liab - CUTOFF) + rng.normal(0.0, 1.2, n)

print(f"Cases simulated:                 {n:,}")
print(f"Eligible (below cutoff):         {eligible.mean():6.1%}")
print(f"Use simplified track | eligible: {simplified[eligible == 1].mean():6.1%}")
print(f"Use simplified track | above:    {simplified[eligible == 0].mean():6.1%}")
print(f"Plan confirmed (overall):        {confirmed.mean():6.1%}")
Cases simulated:                 1,940
Eligible (below cutoff):          50.8%
Use simplified track | eligible:  58.9%
Use simplified track | above:      2.5%
Plan confirmed (overall):         27.5%

Bộ máy ước lượng đủ ngắn để viết ra đầy đủ. Bình phương tối thiểu có trọng số với sai số chuẩn vững với phương sai thay đổi là khối xây dựng cơ bản; bình phương tối thiểu hai giai đoạn là cùng phép đại số được áp dụng cho phép chiếu của biến hồi quy nội sinh lên tập biến công cụ.

Weighted least squares, weighted 2SLS, and the fuzzy RD wrapper
from math import erfc, sqrt


def tri_weights(s, h):
    """Triangular kernel weights on the centred running variable."""
    return np.clip(1.0 - np.abs(s) / h, 0.0, None)


def robust_wls(X, y, w):
    """WLS coefficients with HC1 standard errors."""
    Xw = X * w[:, None]
    XtWX = X.T @ Xw
    beta = np.linalg.solve(XtWX, Xw.T @ y)
    e = y - X @ beta
    G = X * (w * e)[:, None]
    bread = np.linalg.inv(XtWX)
    n_obs, k = X.shape
    V = bread @ (G.T @ G) @ bread * (n_obs / max(n_obs - k, 1))
    return beta, np.sqrt(np.diag(V))


def robust_2sls(X, Z, y, w):
    """Weighted 2SLS with HC1 standard errors; Z must have as many columns as X."""
    Zw = Z * w[:, None]
    Xhat = Z @ np.linalg.solve(Z.T @ Zw, Zw.T @ X)
    A = Xhat.T @ (X * w[:, None])
    beta = np.linalg.solve(A, (Xhat * w[:, None]).T @ y)
    e = y - X @ beta
    G = Xhat * (w * e)[:, None]
    Ainv = np.linalg.inv(A)
    n_obs, k = X.shape
    V = Ainv @ (G.T @ G) @ Ainv.T * (n_obs / max(n_obs - k, 1))
    return beta, np.sqrt(np.diag(V))


def fuzzy_rd(running, treat, outcome, cutoff=CUTOFF, h=3.5, donut=0.0):
    """
    Local-linear fuzzy RD with separate slopes on each side of the cutoff.

    Returns the first stage, the reduced form (intent-to-treat), and the
    2SLS local average treatment effect, all estimated on the same sample.
    """
    s = running - cutoff
    m = (np.abs(s) <= h) & (np.abs(s) >= donut)
    s, d, y = s[m], treat[m], outcome[m]
    below = (s <= 0).astype(float)
    w = tri_weights(s, h)
    one = np.ones_like(s)

    Z = np.column_stack([one, below, s, below * s])   # instrument set
    X = np.column_stack([one, d, s, below * s])       # structural regressors

    b_fs, se_fs = robust_wls(Z, d, w)
    b_rf, se_rf = robust_wls(Z, y, w)
    b_iv, se_iv = robust_2sls(X, Z, y, w)

    return {
        "h": h, "n": int(m.sum()),
        "first_stage": b_fs[1], "first_stage_se": se_fs[1],
        "F": (b_fs[1] / se_fs[1]) ** 2,
        "itt": b_rf[1], "itt_se": se_rf[1],
        "late": b_iv[1], "late_se": se_iv[1],
        "wald": b_rf[1] / b_fs[1],
    }

Giờ đây có thể đặt phép so sánh ngây thơ và ước lượng RD cạnh nhau.

Naive OLS versus the fuzzy RD estimate
b_naive, se_naive = robust_wls(
    np.column_stack([np.ones(n), simplified]), confirmed, np.ones(n)
)
main = fuzzy_rd(liab, simplified, confirmed, h=3.5)

print("=" * 66)
print("  Effect on the probability of confirming a plan")
print("=" * 66)
print(f"  True local effect (by construction)      {TAU:>10.3f}")
print(f"  Naive OLS, users vs non-users            {b_naive[1]:>10.3f}"
      f"   (se {se_naive[1]:.3f})")
print("-" * 66)
print(f"  Fuzzy RD, bandwidth +/-{main['h']:.1f}, n = {main['n']:,}")
print(f"    First stage (eligibility -> use)       {main['first_stage']:>10.3f}"
      f"   (F = {main['F']:.1f})")
print(f"    Reduced form (intent-to-treat)         {main['itt']:>10.3f}"
      f"   (se {main['itt_se']:.3f})")
print(f"    2SLS local average treatment effect    {main['late']:>10.3f}"
      f"   (se {main['late_se']:.3f})")
print(f"    Wald ratio, ITT / first stage          {main['wald']:>10.3f}")
print("=" * 66)
==================================================================
  Effect on the probability of confirming a plan
==================================================================
  True local effect (by construction)           0.320
  Naive OLS, users vs non-users                 0.298   (se 0.023)
------------------------------------------------------------------
  Fuzzy RD, bandwidth +/-3.5, n = 1,690
    First stage (eligibility -> use)            0.562   (F = 253.7)
    Reduced form (intent-to-treat)              0.232   (se 0.043)
    2SLS local average treatment effect         0.414   (se 0.075)
    Wald ratio, ITT / first stage               0.414
==================================================================

Tỷ số Wald và hệ số 2SLS trùng khớp đến độ chính xác của máy tính, vì mô hình vừa đủ nhận diện. Phép so sánh ngây thơ đánh giá thấp tác động thực, đúng như câu chuyện chọn lọc dự báo.

Tiếp theo là độ nhạy theo băng thông và đặc tả donut-hole. Mục đích của bảng không phải là tìm đặc tả có hệ số lớn nhất mà là xem ước lượng và độ chính xác của nó biến đổi cùng nhau như thế nào.

Bandwidth path and donut-hole robustness
rows = []
for h in [1.5, 2.0, 2.5, 3.0, 3.5]:
    r = fuzzy_rd(liab, simplified, confirmed, h=h)
    rows.append({"Bandwidth": f"+/-{h:.1f}", "N": r["n"],
                 "First stage": r["first_stage"], "F": r["F"],
                 "LATE": r["late"], "SE": r["late_se"],
                 "t": r["late"] / r["late_se"]})

r_donut = fuzzy_rd(liab, simplified, confirmed, h=3.5, donut=0.5)
rows.append({"Bandwidth": "+/-3.5 donut", "N": r_donut["n"],
             "First stage": r_donut["first_stage"], "F": r_donut["F"],
             "LATE": r_donut["late"], "SE": r_donut["late_se"],
             "t": r_donut["late"] / r_donut["late_se"]})

bw_table = pd.DataFrame(rows)
print(f"Bandwidth path for the fuzzy RD estimate (true effect = {TAU:.2f})")
print(bw_table.to_string(index=False, float_format=lambda v: f"{v:8.3f}"))
Bandwidth path for the fuzzy RD estimate (true effect = 0.32)
   Bandwidth    N  First stage        F     LATE       SE        t
      +/-1.5  873        0.557  117.130    0.417    0.111    3.741
      +/-2.0 1148        0.563  160.669    0.418    0.095    4.401
      +/-2.5 1376        0.563  195.700    0.401    0.086    4.670
      +/-3.0 1546        0.562  226.385    0.407    0.080    5.107
      +/-3.5 1690        0.562  253.732    0.414    0.075    5.512
+/-3.5 donut 1369        0.559  104.089    0.363    0.111    3.284

Tiếp theo là hai kiểm tra tính hợp lệ. Kiểm định mật độ tìm sự dồn tụ (bunching) ngay dưới ngưỡng, vốn cho thấy các doanh nghiệp quản lý biến chạy để đủ điều kiện; kiểm tra hiệp biến ước lượng dạng rút gọn trên một biến mà việc điều trị không thể ảnh hưởng.

Density continuity test and covariate smoothness placebo
def density_jump(x, cutoff=CUTOFF, h=3.5, binw=0.25, n_boot=400, seed=7):
    """
    McCrary-style test: a local-linear density fitted separately on each side
    of the cutoff, with a bootstrap standard error for the log difference.
    """
    n_bins = int(round(2 * h / binw))
    edges = np.linspace(-h, h, n_bins + 1)
    mid = (edges[:-1] + edges[1:]) / 2.0

    def one_jump(sample):
        s = sample - cutoff
        s = s[np.abs(s) <= h]
        dens = np.histogram(s, bins=edges)[0] / (sample.size * binw)
        fitted = []
        for side in (-1.0, 1.0):
            m = (mid * side) > 0
            A = np.column_stack([np.ones(int(m.sum())), mid[m]])
            b, _ = robust_wls(A, dens[m], tri_weights(mid[m], h))
            fitted.append(max(b[0], 1e-12))
        return np.log(fitted[1]) - np.log(fitted[0])

    theta = one_jump(x)
    boot_rng = np.random.default_rng(seed)
    draws = np.array([
        one_jump(x[boot_rng.integers(0, x.size, x.size)]) for _ in range(n_boot)
    ])
    se = draws.std(ddof=1)
    z = theta / se if se > 0 else 0.0
    return theta, se, erfc(abs(z) / sqrt(2.0))


theta, se_theta, p_density = density_jump(liab)
cov_check = fuzzy_rd(liab, simplified, leverage, h=3.5)

print("Validity checks")
print("-" * 66)
print(f"  Density log-jump at the cutoff   {theta:>8.3f}"
      f"   (se {se_theta:.3f}, p = {p_density:.3f})")
print(f"  Leverage jump at the cutoff      {cov_check['itt']:>8.3f}"
      f"   (se {cov_check['itt_se']:.3f}, "
      f"t = {cov_check['itt'] / cov_check['itt_se']:.2f})")
print("-" * 66)
print("  Neither should be distinguishable from zero.")
Validity checks
------------------------------------------------------------------
  Density log-jump at the cutoff      0.034   (se 0.091, p = 0.706)
  Leverage jump at the cutoff        -0.088   (se 0.122, t = -0.72)
------------------------------------------------------------------
  Neither should be distinguishable from zero.
Hiện mã
bins = np.linspace(CUTOFF - 3.5, CUTOFF + 3.5, 29)
idx = np.digitize(liab, bins) - 1
mids = (bins[:-1] + bins[1:]) / 2.0

fig, axes = plt.subplots(1, 2, figsize=(14, 5))
panels = [
    (axes[0], simplified, "First stage: take-up of the simplified track",
     "Share using the procedure"),
    (axes[1], confirmed, "Reduced form: plan confirmation",
     "Share confirming a plan"),
]

for ax, series, title, ylab in panels:
    means = np.array([
        series[idx == b].mean() if (idx == b).sum() > 0 else np.nan
        for b in range(len(bins) - 1)
    ])
    ax.scatter(mids, means, s=34, color=colors["primary"], alpha=0.85,
               zorder=3, label="Binned mean")
    for side, col in [(-1.0, colors["tertiary"]), (1.0, colors["quaternary"])]:
        m = ((mids - CUTOFF) * side > 0) & ~np.isnan(means)
        A = np.column_stack([np.ones(int(m.sum())), mids[m] - CUTOFF])
        b, _ = robust_wls(A, means[m], tri_weights(mids[m] - CUTOFF, 3.5))
        grid = np.linspace(mids[m].min(), mids[m].max(), 50)
        ax.plot(grid, b[0] + b[1] * (grid - CUTOFF), color=col, linewidth=2)
    ax.axvline(CUTOFF, color=colors["distress"], linestyle="--", linewidth=2,
               label="Eligibility ceiling")
    ax.set_xlabel("Non-contingent liabilities at filing (millions)")
    ax.set_ylabel(ylab)
    ax.set_title(title)
    ax.legend(loc="best")

plt.tight_layout()
plt.show()
Hình 40.1: Gián đoạn hồi quy mờ tại một ngưỡng đủ điều kiện. Biểu đồ bên trái thể hiện giai đoạn một: mức tham gia thủ tục đơn giản hóa nhảy gián đoạn tại mức trần nhưng không đi từ không lên một, đó là điều làm cho thiết kế là mờ chứ không phải sắc nét. Biểu đồ bên phải thể hiện dạng rút gọn: xác suất xác nhận kế hoạch giảm mạnh đối với các doanh nghiệp ngay trên mức trần. Các điểm là trung bình theo nhóm; các đường là hồi quy tuyến tính cục bộ được ước lượng riêng ở mỗi phía.

Cuối cùng là phần tương ứng của sai biệt trong sai biệt. Nhóm giai đoạn trước nộp đơn trước khi thủ tục tồn tại, và biến chỉ thị điều trị của nó phải được xây dựng từ tổng nợ phải trả, vì sự phân chia nợ không phụ thuộc điều kiện không quan sát được trước khi quy tắc tạo ra lý do để ghi nhận nó. Sai số đo lường đó làm suy giảm ước lượng ý định điều trị, và việc điều chỉnh tỷ lệ theo giai đoạn một khôi phục tham số cấu trúc.

Difference-in-differences intent-to-treat, and rescaling to a LATE
did_rng = np.random.default_rng(20200219)


def make_cohort(size, post):
    """One filing cohort; the simplified track exists only when post is True."""
    nc_liab = CUTOFF + did_rng.normal(0.0, 2.5, size)
    total_liab = nc_liab + did_rng.gamma(1.5, 0.6, size)   # contingent claims
    cplx = did_rng.normal(0.0, 1.0, size)
    can_use = (nc_liab <= CUTOFF) & bool(post)
    p_use = np.where(can_use, np.clip(0.60 + 0.12 * cplx, 0.05, 0.95), 0.0)
    used = (did_rng.random(size) < p_use).astype(float)
    p_c = np.clip(0.17 + 0.010 * (nc_liab - CUTOFF) - 0.05 * cplx + TAU * used,
                  0.01, 0.99)
    return pd.DataFrame({
        "total_liab": total_liab,
        "used": used,
        "confirmed": (did_rng.random(size) < p_c).astype(float),
        "post": float(post),
    })


panel = pd.concat([make_cohort(4000, False), make_cohort(4000, True)],
                  ignore_index=True)
panel = panel[(panel["total_liab"] >= 4.0) & (panel["total_liab"] <= 11.0)].copy()
panel["treated"] = (panel["total_liab"] <= CUTOFF).astype(float)

D = np.column_stack([
    np.ones(len(panel)),
    panel["treated"].to_numpy(),
    panel["post"].to_numpy(),
    (panel["treated"] * panel["post"]).to_numpy(),
])
unit_w = np.ones(len(panel))
b_itt, se_itt = robust_wls(D, panel["confirmed"].to_numpy(), unit_w)
b_first, se_first = robust_wls(D, panel["used"].to_numpy(), unit_w)

print("=" * 66)
print(f"  Difference-in-differences, n = {len(panel):,}"
      f"   (true effect = {TAU:.2f})")
print("=" * 66)
print(f"  Treated x Post on confirmation (ITT)   {b_itt[3]:>8.3f}"
      f"   (se {se_itt[3]:.3f})")
print(f"  Treated x Post on take-up (1st stage)  {b_first[3]:>8.3f}"
      f"   (se {se_first[3]:.3f})")
print(f"  Rescaled: ITT / first stage            {b_itt[3] / b_first[3]:>8.3f}")
print("=" * 66)
==================================================================
  Difference-in-differences, n = 6,398   (true effect = 0.32)
==================================================================
  Treated x Post on confirmation (ITT)      0.111   (se 0.021)
  Treated x Post on take-up (1st stage)     0.457   (se 0.016)
  Rescaled: ITT / first stage               0.243
==================================================================

ITT rơi xa dưới tác động thực vì hầu hết các doanh nghiệp được điều trị trên danh nghĩa không bao giờ sử dụng thủ tục và một số bị phân loại nhầm theo tổng nợ phải trả thay vì nợ không phụ thuộc điều kiện. Chia cho giai đoạn một khôi phục tham số, đó là cùng phép đối chiếu mà Hotchkiss, Iverson, và Zheng (2026) báo cáo là \(12.5 / 0.42 = 29.8\) điểm phần trăm so với ước lượng RDD là \(32.0\).

Khuôn mẫu là điều có thể chuyển giao. Với một quy tắc ngưỡng của Việt Nam, một bảng dữ liệu cấp vụ việc hoặc cấp doanh nghiệp ghi nhận biến chạy theo cách quy tắc đo lường, và một lập luận đáng tin cậy rằng các doanh nghiệp không thể tự định vị quanh ngưỡng, các đoạn mã tương tự cho ra giai đoạn một, một LATE, một đường đi băng thông và hai kiểm tra tính hợp lệ quyết định liệu toàn bộ điều này có ý nghĩa hay không.

41 Các cân nhắc thực tiễn và hạn chế

41.1 Các hạn chế đã biết của Q của Tobin ở Việt Nam

Một số vấn đề ảnh hưởng đến độ tin cậy của các ước lượng Q của Tobin đối với doanh nghiệp Việt Nam:

  1. Giá trị sổ sách làm đại diện cho chi phí thay thế: Thước đo Q giản lược giả định rằng giá trị sổ sách của tài sản xấp xỉ chi phí thay thế. Với việc VAS dựa nhiều vào giá gốc, giả định này có thể gây vấn đề hơn so với các nước áp dụng IFRS, đặc biệt đối với các doanh nghiệp có quyền sử dụng đất đáng kể hoặc tài sản hữu hình tuổi thọ dài.

  2. Hiệu ứng vi cấu trúc thị trường: Biên độ giá hằng ngày của Việt Nam có thể ngăn giá thị trường đạt tới cân bằng, có khả năng làm méo mó thành phần giá trị thị trường. Giới hạn sở hữu nước ngoài có thể tạo ra phần bù giá nhân tạo đối với một số cổ phiếu.

  3. Sự hiếm gặp của cổ phiếu ưu đãi: Dù điều này đơn giản hóa phép tính (hầu hết doanh nghiệp Việt Nam không có cổ phiếu ưu đãi), nó có nghĩa là phép tính BE bị chi phối bởi vốn cổ phần phổ thông, có thể không nắm bắt hết mọi quyền đòi của chủ sở hữu.

  4. Hiệu ứng niêm yết chéo: Một số doanh nghiệp Việt Nam niêm yết trên nhiều nơi (HOSE, HNX, UPCoM, hoặc thậm chí các sàn nước ngoài). Cần thận trọng để sử dụng dữ liệu giá và số cổ phiếu nhất quán.

41.2 Các hạn chế đã biết của điểm Z của Altman ở Việt Nam

  1. Mẫu hiệu chỉnh: Điểm Z gốc được ước lượng trên các doanh nghiệp sản xuất của Hoa Kỳ giữa thế kỷ 20. Điểm Z’’ cho thị trường mới nổi phù hợp hơn nhưng vẫn được ước lượng trên một mẫu không phải của Việt Nam.

  2. Khác biệt kế toán: Các chuẩn mực kế toán VAS tạo ra các tỷ số tài chính có tính chất phân phối khác với dữ liệu US GAAP hoặc IFRS, có khả năng ảnh hưởng đến độ chính xác phân loại của hàm phân biệt.

  3. Ngân hàng và doanh nghiệp tài chính: Điểm Z không được thiết kế cho các định chế tài chính, vốn có cấu trúc bảng cân đối kế toán khác biệt căn bản. Ngân hàng, công ty bảo hiểm và công ty chứng khoán nên được loại trừ hoặc phân tích riêng.

  4. Bảo lãnh ngầm: Các SOE và doanh nghiệp liên kết với các tập đoàn kinh tế lớn có thể có sự hỗ trợ ngầm làm giảm rủi ro vỡ nợ thực tế xuống dưới dự báo của điểm Z.

41.3 Khuyến nghị cho các nhà nghiên cứu

Dựa trên phân tích của chúng tôi, chúng tôi đưa ra các khuyến nghị sau cho các nhà nghiên cứu làm việc với dữ liệu Việt Nam:

  1. Sử dụng biến thể điểm Z’’ (thị trường mới nổi) làm thước đo kiệt quệ chính cho các doanh nghiệp phi tài chính của Việt Nam.
  2. Báo cáo nhiều biến thể Q (đơn giản và Chung-Pruitt) để chứng minh độ vững.
  3. Winsorization tại 1%/99% để giảm thiểu tác động của lỗi dữ liệu và các giá trị ngoại lai cực đoan.
  4. Tính các thước đo điều chỉnh theo ngành khi thực hiện so sánh chéo.
  5. Sử dụng tuổi thành lập thay vì tuổi niêm yết khi có sẵn, nhưng báo cáo cả hai để phân biệt mức độ trưởng thành của tổ chức với kinh nghiệm trên thị trường vốn.
  6. Loại trừ các doanh nghiệp tài chính khỏi phân tích điểm Z.
  7. Tính đến sở hữu nhà nước như một biến điều tiết trong các nghiên cứu về định giá và kiệt quệ.
  8. Đừng đọc điểm Z như một dự báo thu hồi. Nó chấm điểm xác suất kiệt quệ, không phải điều mà hệ thống pháp luật làm với một doanh nghiệp kiệt quệ; xem Chương 40.

42 Tóm tắt

Chương này trình bày cách tiếp cận đối với ba thước đo cơ bản của tài chính doanh nghiệp (tức Q của Tobin, điểm Z của Altman và tuổi công ty). Chúng tôi đã trình bày nền tảng lý thuyết của từng thước đo và cung cấp nhiều hình minh họa về các mô thức cắt ngang và chuỗi thời gian.

Các phát hiện chính từ phân tích các doanh nghiệp niêm yết Việt Nam gồm:

  1. Q của Tobin biến thiên đáng kể giữa các ngành và các sàn giao dịch, với các ngành công nghệ và hướng đến người tiêu dùng thường có mức định giá cao hơn. Các doanh nghiệp niêm yết trên HOSE có xu hướng có giá trị Q cao hơn các doanh nghiệp trên HNX hoặc UPCoM, phản ánh cả sự khác biệt về chất lượng doanh nghiệp lẫn hiệu ứng thanh khoản.

  2. Điểm Z của Altman cho thấy một tỷ lệ đáng kể các doanh nghiệp Việt Nam hoạt động trong hoặc gần vùng kiệt quệ, dù biến thể phù hợp rất quan trọng; điểm Z’’ cho thị trường mới nổi cho phép phân loại tinh tế hơn mô hình gốc. Sức khỏe tài chính có biến thiên chuỗi thời gian đáng kể, với sự suy giảm rõ rệt trong các giai đoạn kinh tế suy thoái.

  3. Tuổi công ty ở Việt Nam đòi hỏi xử lý cẩn thận do việc cổ phần hóa các SOE, tạo ra khoảng cách lớn giữa tuổi thành lập và tuổi niêm yết. Sự phân biệt này có ý nghĩa quan trọng về bản chất để hiểu mối quan hệ giữa mức độ trưởng thành, định giá và sự ổn định tài chính.

  4. Giải quyết kiệt quệ là một câu hỏi tách biệt với dự báo kiệt quệ, và điểm Z chỉ nói về vế sau. Hotchkiss, Iverson, và Zheng (2026) cho thấy chỉ riêng một thay đổi trong thủ tục mất khả năng thanh toán đã làm xác suất một doanh nghiệp nhỏ của Hoa Kỳ tái cơ cấu thay vì thanh lý tăng hơn gấp đôi, mà không gây hại đến tỷ lệ thu hồi của chủ nợ. Vì các trường hợp rời khỏi thị trường ở Việt Nam chủ yếu là hủy niêm yết bắt buộc chứ không phải tái cơ cấu dưới sự giám sát của tòa án, một điểm Z’’ của Việt Nam trong vùng kiệt quệ ứng với một phân phối kết cục khác biệt đáng kể so với cùng điểm số đó trong mẫu mà các hệ số được ước lượng. Chương 40 phát triển luận điểm này và triển khai các thiết kế gián đoạn hồi quy mờ và sai biệt trong sai biệt mà các ngưỡng đủ điều kiện cho phép.