39  Động lực của tỷ giá hối đoái

Ghi chú

Trong chương này, chúng ta xây dựng một cơ sở dữ liệu tỷ giá hối đoái toàn diện cho đồng Việt Nam (VND) so với các đồng tiền chính, phân tích các tính chất thống kê của lợi nhuận VND, kiểm định ngang giá lãi suất có phòng ngừa và không phòng ngừa, ước lượng mức độ phơi nhiễm tiền tệ của các doanh nghiệp niêm yết Việt Nam, mô hình hóa các chế độ biến động bằng GARCH, và đánh giá các chiến lược phòng ngừa rủi ro cho nhà đầu tư quốc tế.

Tỷ giá hối đoái nằm ở giao điểm của kinh tế vĩ mô và tài chính. Đối với các nhà đầu tư quốc tế đang cân nhắc cổ phiếu Việt Nam, tỷ giá VND/USD không phải là mối quan tâm thứ yếu; nó là yếu tố quyết định bậc nhất đối với lợi nhuận tính bằng đô la. Một danh mục cổ phiếu Việt Nam sinh lời 15% tính theo VND nhưng trùng với mức mất giá 5% của VND chỉ mang lại khoảng 10% tính theo USD. Ngược lại, các giai đoạn VND ổn định hoặc lên giá làm tăng lợi nhuận cho nhà đầu tư nước ngoài. Do đó, hiểu động lực của tỷ giá hối đoái là điều thiết yếu đối với bất kỳ ai làm việc với dữ liệu tài chính Việt Nam trong bối cảnh quốc tế.

Việt Nam có một chế độ tỷ giá đặc thù. Không giống các đồng tiền thả nổi tự do như USD/EUR hoặc USD/JPY, VND vận hành theo cơ chế thả nổi có quản lý. Ngân hàng Nhà nước Việt Nam (NHNN) công bố tỷ giá tham chiếu hằng ngày và cho phép giao dịch trong một biên độ (hiện là \(\pm\) 3% trên thị trường liên ngân hàng và \(\pm\) 5% so với tỷ giá chính thức đối với các ngân hàng thương mại). Chế độ này tạo ra những mô thức đặc thù trong lợi nhuận VND, khác biệt căn bản với những gì quan sát được ở các cặp tiền thả nổi tự do: các biến động tập trung gần các mép của biên độ, các điều chỉnh rời rạc khi NHNN dịch chuyển tỷ giá tham chiếu, và các giai đoạn biến động gần như bằng không bị ngắt quãng bởi những cú dịch chuyển mạnh khi chính sách thay đổi.

Chương này phát triển các công cụ để làm việc với những động lực đó. Chúng ta đi từ xây dựng dữ liệu, qua mô tả đặc điểm thống kê, đến các ứng dụng kinh tế: kiểm định ngang giá lãi suất, ước lượng mức độ phơi nhiễm cấp doanh nghiệp, mô hình hóa biến động GARCH và đánh giá chiến lược phòng ngừa rủi ro.

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

39.1.1 Xác định tỷ giá hối đoái

Lý thuyết hiện đại về xác định tỷ giá hối đoái dựa trên một số khối nền tảng. Dornbusch (1976) đưa ra mô hình “vọt lố” (overshooting) kinh điển: vì giá hàng hóa cứng nhắc trong khi giá tài sản điều chỉnh tức thời, một cú sốc tiền tệ khiến tỷ giá vọt quá mức cân bằng dài hạn của nó. Frankel (1979) mở rộng điều này thành mô hình chênh lệch lãi suất thực, trong đó tỷ giá phụ thuộc vào cung tiền tương đối, mức thu nhập và lãi suất giữa các quốc gia.

Bất chấp lý thuyết thanh lịch, Meese và Rogoff (1983) đưa ra một kết quả thực nghiệm gây thất vọng nặng nề: không mô hình cấu trúc nào của tỷ giá hối đoái liên tục vượt trội bước ngẫu nhiên trong dự báo ngoài mẫu. Phát hiện này, được xác nhận lặp đi lặp lại suốt bốn thập kỷ và được Rossi (2013) tổng quan, có nghĩa là các thay đổi của tỷ giá hối đoái, xét theo xấp xỉ bậc nhất, là không thể dự báo. Về mặt thực tiễn, dự báo tốt nhất cho tỷ giá VND/USD ngày mai là tỷ giá hôm nay.

39.1.2 Ngang giá lãi suất

Hai điều kiện ngang giá liên kết tỷ giá hối đoái với lãi suất:

Ngang giá lãi suất có phòng ngừa (CIP): Hoạt động chênh lệch giá (arbitrage) giữa thị trường giao ngay và kỳ hạn phải làm cân bằng phần bù kỳ hạn với chênh lệch lãi suất:

\[ F_{t,t+k} = S_t \cdot \frac{(1 + r^{VND}_{t,k})}{(1 + r^{USD}_{t,k})} \tag{39.1}\]

trong đó \(S_t\) là tỷ giá giao ngay (VND trên mỗi USD), \(F_{t,t+k}\) là tỷ giá kỳ hạn \(k\) kỳ, và \(r^{VND}_{t,k}\) và \(r^{USD}_{t,k}\) là lãi suất trong nước và nước ngoài cho kỳ hạn \(k\). CIP phải đúng chính xác khi không có chi phí giao dịch, rủi ro tín dụng và kiểm soát vốn. Du, Tepper, và Verdelhan (2018) ghi nhận rằng các vi phạm CIP đã nới rộng đáng kể ở các cặp tiền chính kể từ năm 2008, do các ràng buộc bảng cân đối kế toán của ngân hàng sau khủng hoảng. Ở Việt Nam, kiểm soát vốn và thanh khoản hạn chế của thị trường kỳ hạn tạo ra thêm các độ lệch CIP.

Ngang giá lãi suất không phòng ngừa (UIP): Mức mất giá kỳ vọng phải bằng chênh lệch lãi suất:

\[ \mathbb{E}_t[\Delta s_{t+k}] = r^{VND}_{t,k} - r^{USD}_{t,k} \tag{39.2}\]

trong đó \(\Delta s_{t+k} = \ln(S_{t+k}/S_t)\). UIP là một điều kiện cân bằng, không phải điều kiện chênh lệch giá, vì nó đòi hỏi nhà đầu tư trung lập với rủi ro. Fama (1984) đã chỉ ra một cách nổi tiếng rằng phần bù kỳ hạn dự báo các thay đổi tỷ giá với dấu sai: các đồng tiền có lãi suất cao có xu hướng lên giá thay vì mất giá, trái với UIP. “Nghịch lý phần bù kỳ hạn” này là nền tảng của giao dịch carry.

39.1.3 Giao dịch carry

Giao dịch carry khai thác các vi phạm UIP bằng cách vay bằng đồng tiền có lãi suất thấp và đầu tư vào đồng tiền có lãi suất cao. Lustig và Verdelhan (2007) cho thấy một danh mục các vị thế giao dịch carry đem lại một phần bù rủi ro đáng kể, mà họ gắn với rủi ro tăng trưởng tiêu dùng. Menkhoff và c.s. (2012) xác định biến động ngoại hối toàn cầu là nhân tố rủi ro then chốt: giao dịch carry thua lỗ đúng vào lúc biến động toàn cầu tăng vọt, một “rủi ro sụp đổ” được Brunnermeier, Nagel, và Pedersen (2008) ghi nhận.

Việt Nam, với lãi suất thường cao hơn lãi suất Mỹ 3-8 điểm phần trăm, là ứng viên tự nhiên cho dòng vốn giao dịch carry. Chế độ tỷ giá được quản lý mang lại thêm một sức hấp dẫn: việc NHNN ngầm bảo vệ biên độ làm giảm biến động ngắn hạn, nâng tỷ số Sharpe của vị thế carry, cho đến khi một lần điều chỉnh biên độ hoặc phá giá xóa sạch khoản lãi tích lũy chỉ trong một lần dịch chuyển.

39.1.4 Bộ ba bất khả thi

Obstfeld, Shambaugh, và Taylor (2005) chính thức hóa “bộ ba bất khả thi”: một quốc gia không thể đồng thời duy trì (i) tỷ giá cố định, (ii) chính sách tiền tệ độc lập và (iii) dòng vốn tự do. Việt Nam xử lý bộ ba này bằng cách duy trì kiểm soát vốn một phần song song với cơ chế thả nổi có quản lý, cho phép một mức độ độc lập nhất định của chính sách tiền tệ. Hiểu Việt Nam đang đứng ở đâu trong bộ ba này tại bất kỳ thời điểm nào là điều thiết yếu để diễn giải dữ liệu tỷ giá: các giai đoạn kiểm soát vốn chặt hơn tạo ra biến động tỷ giá thấp một cách giả tạo, không phản ánh sự bất định kinh tế thực sự.

39.2 Chế độ tỷ giá VND

39.2.1 Diễn tiến lịch sử

Tỷ giá VND/USD đã trải qua nhiều giai đoạn riêng biệt:

Bảng 39.1: Diễn tiến của chế độ tỷ giá VND/USD.
Giai đoạn Chế độ Đặc điểm chính
Trước 1999 Biên độ hẹp NHNN ấn định tỷ giá với độ linh hoạt tối thiểu
1999-2007 Trượt dần Mất giá chậm, có kiểm soát (~1-2% mỗi năm)
2008-2011 Điều chỉnh trong khủng hoảng Nhiều lần phá giá rời rạc (2008, 2010, 2011)
2012-2015 Ổn định hóa Biên độ chặt, hiếm điều chỉnh, tích lũy dự trữ
2016-nay Cơ chế tỷ giá tham chiếu Tỷ giá tham chiếu hằng ngày dựa trên một rổ tiền tệ; biên độ \(\pm\) 3%

Cải cách năm 2016, chuyển từ neo cố định sang tỷ giá tham chiếu hằng ngày chịu ảnh hưởng của một rổ tiền tệ, là một thay đổi cấu trúc quan trọng. Tỷ giá tham chiếu hiện nay bao gồm tỷ giá liên ngân hàng đóng cửa của ngày hôm trước, điều kiện cung cầu và biến động của các đồng tiền của các đối tác thương mại lớn (USD, EUR, CNY, JPY, KRW).

39.2.2 Cơ chế biên độ

NHNN công bố tỷ giá tham chiếu mỗi buổi sáng. Các ngân hàng thương mại có thể niêm yết tỷ giá trong một biên độ quanh mức tham chiếu này. Thị trường liên ngân hàng vận hành với biên độ hẹp hơn là \(\pm\) 3%. Khi tỷ giá giao ngay tiến gần trần biên độ, NHNN có thể can thiệp bằng cách bán USD từ dự trữ, nới rộng biên độ hoặc điều chỉnh tỷ giá tham chiếu.

Chế độ này tạo ra các tính chất thống kê đặc thù:

  1. Lợi nhuận tập trung gần không: Trong hầu hết các ngày, tỷ giá VND/USD hầu như không dịch chuyển, tạo ra một đỉnh nhọn tại không trong phân phối lợi nhuận.
  2. Các cú nhảy rời rạc: Việc điều chỉnh tỷ giá tham chiếu tạo ra những dịch chuyển lớn trong một ngày, xuất hiện như các giá trị ngoại lai.
  3. Biến động bị chặn: Biên độ giới hạn các dịch chuyển hằng ngày, tạo ra phân phối lợi nhuận có đuôi mỏng hơn so với một đồng tiền thả nổi tự do.
  4. Điều chỉnh bất đối xứng: Áp lực mất giá (VND yếu đi) phổ biến hơn áp lực lên giá, phản ánh chênh lệch lạm phát dai dẳng.

39.3 Xây dựng dữ liệu

39.3.1 Nạp các thư viện

Import libraries and configure environment
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
import statsmodels.formula.api as smf
from scipy import stats
from arch import arch_model
from linearmodels.panel import PanelOLS
import warnings
warnings.filterwarnings('ignore')

plt.rcParams.update({
    'figure.figsize': (12, 6),
    'figure.dpi': 150,
    'font.size': 11,
    'axes.spines.top': False,
    'axes.spines.right': False
})

39.3.2 Truy xuất dữ liệu tỷ giá hối đoái

Chúng ta trích xuất tỷ giá giao ngay hằng ngày của VND so với tất cả các đồng tiền chính, cùng với tỷ giá tham chiếu của NHNN và tỷ giá kỳ hạn khi có sẵn.

Load exchange rate data
from datacore import DataCoreClient

client = DataCoreClient()

# Spot exchange rates: VND per unit of foreign currency
fx_spot = client.get_exchange_rates(
    base_currency='VND',
    quote_currencies=['USD', 'EUR', 'JPY', 'CNY', 'KRW',
                      'SGD', 'THB', 'GBP', 'AUD'],
    start_date='2005-01-01',
    end_date='2024-12-31',
    fields=['date', 'from_currency', 'to_currency', 'rate',
            'bid', 'ask', 'reference_rate']
)

# Forward rates (VND/USD)
fx_forward = client.get_fx_forwards(
    pair='VND/USD',
    start_date='2010-01-01',
    end_date='2024-12-31',
    tenors=['1M', '3M', '6M', '12M'],
    fields=['date', 'tenor', 'forward_rate', 'forward_points']
)

# Interest rates for parity tests
interest_rates = client.get_interest_rates(
    countries=['VN', 'US'],
    start_date='2005-01-01',
    end_date='2024-12-31',
    instruments=['interbank_overnight', 'deposit_3m',
                 'government_bond_1y', 'government_bond_10y']
)

print(f"Spot observations: {fx_spot.shape[0]:,}")
print(f"Forward observations: {fx_forward.shape[0]:,}")
print(f"Interest rate observations: {interest_rates.shape[0]:,}")

39.3.3 Xây dựng chuỗi thời gian VND/USD

Chúng ta tập trung chủ yếu vào cặp VND/USD, tỷ giá song phương chi phối của Việt Nam, đồng thời dùng các cặp khác cho phân tích tỷ giá chéo.

Construct clean VND/USD spot rate series with log returns
# Filter VND/USD
vnd_usd = fx_spot[
    (fx_spot['from_currency'] == 'VND') &
    (fx_spot['to_currency'] == 'USD')
].copy()

vnd_usd['date'] = pd.to_datetime(vnd_usd['date'])
vnd_usd = vnd_usd.sort_values('date').set_index('date')

# Log returns: positive = VND depreciation, negative = VND appreciation
vnd_usd['log_return'] = np.log(vnd_usd['rate'] / vnd_usd['rate'].shift(1))

# Bid-ask spread as fraction of mid rate
vnd_usd['spread_pct'] = (vnd_usd['ask'] - vnd_usd['bid']) / vnd_usd['rate']

# Distance from reference rate (in %)
vnd_usd['deviation_from_ref'] = (
    (vnd_usd['rate'] - vnd_usd['reference_rate'])
    / vnd_usd['reference_rate'] * 100
)

# Rolling volatility measures
vnd_usd['vol_30d'] = vnd_usd['log_return'].rolling(30).std() * np.sqrt(252)
vnd_usd['vol_90d'] = vnd_usd['log_return'].rolling(90).std() * np.sqrt(252)

vnd_usd = vnd_usd.dropna(subset=['log_return'])

print(f"VND/USD series: {len(vnd_usd)} daily observations")
print(f"Date range: {vnd_usd.index.min()} to {vnd_usd.index.max()}")
print(f"\nSummary of daily log returns:")
print(vnd_usd['log_return'].describe().round(6))

39.3.4 Bảng dữ liệu đa tiền tệ

Cho phân tích tỷ giá chéo và so sánh với ASEAN, chúng ta xây dựng một bảng dữ liệu (panel) gồm lợi nhuận logarit của tất cả các cặp tiền tệ.

Construct multi-currency return panel
# Pivot to wide format: one column per currency pair
fx_wide = fx_spot.pivot_table(
    index='date', columns='to_currency', values='rate'
)
fx_wide.index = pd.to_datetime(fx_wide.index)
fx_wide = fx_wide.sort_index()

# Compute log returns for all pairs
fx_returns = np.log(fx_wide / fx_wide.shift(1)).dropna()

# Summary statistics
fx_summary = pd.DataFrame({
    'Mean (ann.)': fx_returns.mean() * 252,
    'Vol (ann.)': fx_returns.std() * np.sqrt(252),
    'Skewness': fx_returns.skew(),
    'Kurtosis': fx_returns.kurtosis(),
    'Min': fx_returns.min(),
    'Max': fx_returns.max()
})
print("VND Cross-Rate Return Statistics (Daily):")
print(fx_summary.round(4))

39.4 Tính chất thống kê của lợi nhuận VND

39.4.1 Phân phối lợi nhuận

Phân phối của lợi nhuận logarit VND/USD lệch rõ rệt khỏi phân phối chuẩn. Chế độ thả nổi có quản lý tạo ra phân phối lợi nhuận có đuôi dày (leptokurtic) và tập trung gần không, kèm theo thỉnh thoảng những cú nhảy rời rạc lớn do điều chỉnh tỷ giá tham chiếu.

Plot VND/USD return distribution with normal overlay
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Panel A: Histogram with normal overlay
returns = vnd_usd['log_return']
mu, sigma = returns.mean(), returns.std()

axes[0].hist(returns, bins=100, density=True, color='#2C5F8A',
             alpha=0.7, edgecolor='white', label='Empirical')
x_range = np.linspace(returns.min(), returns.max(), 200)
axes[0].plot(x_range, stats.norm.pdf(x_range, mu, sigma),
             color='#C0392B', linewidth=2, label='Normal fit')
axes[0].set_xlabel('Daily Log Return')
axes[0].set_ylabel('Density')
axes[0].set_title('Panel A: Return Distribution')
axes[0].legend()

# Panel B: QQ plot
stats.probplot(returns, dist='norm', plot=axes[1])
axes[1].set_title('Panel B: Normal Q-Q Plot')
axes[1].get_lines()[0].set_color('#2C5F8A')
axes[1].get_lines()[0].set_markersize(3)
axes[1].get_lines()[1].set_color('#C0392B')

plt.tight_layout()
plt.show()

# Formal tests
jb_stat, jb_p = stats.jarque_bera(returns)
print(f"\nJarque-Bera test: statistic = {jb_stat:.1f}, p-value = {jb_p:.2e}")
print(f"Skewness: {returns.skew():.4f}")
print(f"Excess Kurtosis: {returns.kurtosis():.4f}")
Hình 39.1

39.4.2 Cấu trúc tự tương quan

Lợi nhuận của các tỷ giá thả nổi tự do xấp xỉ không tương quan (phù hợp với kết quả bước ngẫu nhiên của Meese và Rogoff (1983)). Tuy nhiên, các tỷ giá được quản lý có thể thể hiện tương quan chuỗi vì ngân hàng trung ương làm mượt các điều chỉnh qua nhiều ngày.

Plot ACF of returns and squared returns
from statsmodels.graphics.tsaplots import plot_acf

fig, axes = plt.subplots(2, 1, figsize=(12, 8))

# ACF of returns
plot_acf(vnd_usd['log_return'].dropna(), lags=40, ax=axes[0],
         color='#2C5F8A', vlines_kwargs={'color': '#2C5F8A'})
axes[0].set_title('Autocorrelation of Daily Returns')
axes[0].set_ylabel('ACF')

# ACF of squared returns (volatility clustering)
plot_acf(vnd_usd['log_return'].dropna() ** 2, lags=40, ax=axes[1],
         color='#C0392B', vlines_kwargs={'color': '#C0392B'})
axes[1].set_title('Autocorrelation of Squared Returns (Volatility Clustering)')
axes[1].set_ylabel('ACF')

plt.tight_layout()
plt.show()

# Ljung-Box test
from statsmodels.stats.diagnostic import acorr_ljungbox
lb_returns = acorr_ljungbox(returns, lags=10, return_df=True)
lb_squared = acorr_ljungbox(returns ** 2, lags=10, return_df=True)
print("Ljung-Box Test (Returns, lag 10):")
print(f"  Statistic: {lb_returns['lb_stat'].iloc[-1]:.2f}, "
      f"p-value: {lb_returns['lb_pvalue'].iloc[-1]:.4f}")
print("Ljung-Box Test (Squared Returns, lag 10):")
print(f"  Statistic: {lb_squared['lb_stat'].iloc[-1]:.2f}, "
      f"p-value: {lb_squared['lb_pvalue'].iloc[-1]:.4f}")
Hình 39.2

39.4.3 Điều chỉnh tỷ giá tham chiếu và các điểm gãy cấu trúc

NHNN định kỳ điều chỉnh tỷ giá tham chiếu hoặc độ rộng biên độ giao dịch, những sự kiện tạo ra các điểm gãy cấu trúc trong chuỗi tỷ giá. Việc xác định các điểm gãy này là thiết yếu để mô hình hóa chính xác.

Identify and plot major exchange rate adjustments
# Detect large daily moves (proxy for policy adjustments)
threshold = vnd_usd['log_return'].std() * 3
large_moves = vnd_usd[vnd_usd['log_return'].abs() > threshold].copy()

# Known major adjustment dates (approximate)
policy_events = pd.DataFrame({
    'date': pd.to_datetime([
        '2008-06-10', '2008-12-25', '2009-11-25',
        '2010-02-11', '2010-08-18', '2011-02-11',
        '2011-08-10', '2015-08-12', '2015-08-19',
        '2016-01-04'
    ]),
    'event': [
        'Band widened to +/-1%', 'Band widened to +/-3%',
        'Devaluation ~5.4%', 'Devaluation ~3.4%',
        'Band narrowed to +/-1%', 'Devaluation ~8.5%',
        'Band widened to +/-1%', 'Devaluation ~1%',
        'Devaluation ~1%', 'New reference rate mechanism'
    ]
})

fig, ax = plt.subplots(figsize=(14, 6))
ax.plot(vnd_usd.index, vnd_usd['rate'], color='#2C5F8A',
        linewidth=1.5, label='VND/USD Spot')

if 'reference_rate' in vnd_usd.columns:
    ref_clean = vnd_usd['reference_rate'].dropna()
    if len(ref_clean) > 0:
        ax.plot(ref_clean.index, ref_clean, color='#E67E22',
                linewidth=1, alpha=0.7, label='SBV Reference Rate')

# Mark policy events
for _, event in policy_events.iterrows():
    if event['date'] >= vnd_usd.index.min():
        rate_at_event = vnd_usd['rate'].asof(event['date'])
        ax.annotate(
            event['event'],
            xy=(event['date'], rate_at_event),
            xytext=(0, 30), textcoords='offset points',
            fontsize=7, rotation=45, ha='left',
            arrowprops=dict(arrowstyle='->', color='#C0392B',
                           lw=0.8),
            color='#C0392B'
        )

ax.set_ylabel('VND per USD')
ax.set_title('VND/USD Exchange Rate with Policy Events')
ax.legend(loc='upper left')
plt.tight_layout()
plt.show()
Hình 39.3

39.4.4 Tương quan giữa các đồng tiền

Hiểu VND đồng biến động thế nào với các đồng tiền khác là điều quan trọng cho đa dạng hóa và cho việc xác định các động lực chung (ví dụ, sức mạnh của USD ảnh hưởng đến tất cả các đồng tiền châu Á).

Compute and visualize cross-currency return correlations
# Correlation matrix of daily returns
corr_matrix = fx_returns.corr()

fig, ax = plt.subplots(figsize=(9, 8))
mask = np.triu(np.ones_like(corr_matrix, dtype=bool), k=1)
sns.heatmap(
    corr_matrix, mask=mask, annot=True, fmt='.2f',
    cmap='RdBu_r', center=0, vmin=-1, vmax=1,
    square=True, linewidths=0.5, ax=ax,
    cbar_kws={'label': 'Correlation'}
)
ax.set_title('Cross-Rate Return Correlations (VND Base)')
plt.tight_layout()
plt.show()
Hình 39.4

39.5 Kiểm định ngang giá lãi suất

39.5.1 Kiểm định ngang giá lãi suất có phòng ngừa

Độ lệch CIP được đo bằng khoảng chênh giữa phần bù kỳ hạn và chênh lệch lãi suất:

\[ \text{CIP Deviation}_t = f_{t,k} - s_t - (r^{VND}_{t,k} - r^{USD}_{t,k}) \tag{39.3}\]

trong đó \(f_{t,k} = \ln F_{t,t+k}\) và \(s_t = \ln S_t\) là các tỷ giá kỳ hạn và giao ngay dạng logarit. Khi CIP đúng chính xác, độ lệch này bằng không.

Compute CIP deviations for VND/USD
# Merge spot, forward, and interest rate data
vnd_usd_monthly = vnd_usd['rate'].resample('M').last().to_frame('spot')
vnd_usd_monthly.index = vnd_usd_monthly.index.to_period('M').to_timestamp()

# Forward rates (3-month tenor)
fwd_3m = fx_forward[fx_forward['tenor'] == '3M'].copy()
fwd_3m['date'] = pd.to_datetime(fwd_3m['date'])
fwd_3m = fwd_3m.set_index('date').resample('M').last()

# Interest rate differential
vn_rates = interest_rates[
    (interest_rates['country'] == 'VN') &
    (interest_rates['instrument'] == 'deposit_3m')
].set_index('date')['rate'].resample('M').last()

us_rates = interest_rates[
    (interest_rates['country'] == 'US') &
    (interest_rates['instrument'] == 'deposit_3m')
].set_index('date')['rate'].resample('M').last()

# Align and compute CIP deviation
cip_data = pd.DataFrame({
    'spot': vnd_usd_monthly['spot'],
    'forward': fwd_3m['forward_rate'],
    'r_vnd': vn_rates / 100,
    'r_usd': us_rates / 100
}).dropna()

# CIP deviation (annualized, in basis points)
cip_data['log_spot'] = np.log(cip_data['spot'])
cip_data['log_forward'] = np.log(cip_data['forward'])
cip_data['forward_premium'] = (
    (cip_data['log_forward'] - cip_data['log_spot']) * 4
)
cip_data['rate_diff'] = cip_data['r_vnd'] - cip_data['r_usd']
cip_data['cip_deviation'] = (
    (cip_data['forward_premium'] - cip_data['rate_diff']) * 10000
)

print("CIP Deviation Summary (basis points, annualized):")
print(cip_data['cip_deviation'].describe().round(1))
print(f"\nMean deviation: {cip_data['cip_deviation'].mean():.1f} bps")
t_stat, p_val = stats.ttest_1samp(cip_data['cip_deviation'].dropna(), 0)
print(f"One-sample t-test: t = {t_stat:.2f}, p = {p_val:.4f}")
Plot CIP deviations over time
fig, axes = plt.subplots(2, 1, figsize=(14, 8), height_ratios=[2, 1])

# Panel A: CIP deviation time series
axes[0].fill_between(
    cip_data.index, 0, cip_data['cip_deviation'],
    where=cip_data['cip_deviation'] > 0,
    color='#C0392B', alpha=0.4, label='Positive (VND forward expensive)'
)
axes[0].fill_between(
    cip_data.index, 0, cip_data['cip_deviation'],
    where=cip_data['cip_deviation'] <= 0,
    color='#27AE60', alpha=0.4, label='Negative'
)
axes[0].axhline(y=0, color='black', linewidth=0.5)
axes[0].set_ylabel('CIP Deviation (bps)')
axes[0].set_title('Panel A: CIP Deviations for VND/USD')
axes[0].legend()

# Panel B: Interest rate differential
axes[1].plot(cip_data.index, cip_data['r_vnd'] * 100,
             color='#C0392B', label='Vietnam 3M')
axes[1].plot(cip_data.index, cip_data['r_usd'] * 100,
             color='#2C5F8A', label='US 3M')
axes[1].set_ylabel('Interest Rate (%)')
axes[1].set_xlabel('Date')
axes[1].set_title('Panel B: Interest Rate Differential')
axes[1].legend()

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

39.5.2 Kiểm định ngang giá lãi suất không phòng ngừa

Hồi quy UIP chuẩn là:

\[ \Delta s_{t+k} = \alpha + \beta (r^{VND}_{t,k} - r^{USD}_{t,k}) + \varepsilon_{t+k} \tag{39.4}\]

Theo UIP, \(\alpha = 0\) và \(\beta = 1\). Nghịch lý của Fama (1984) tương ứng với \(\beta < 1\) (thường là \(\beta < 0\)), hàm ý rằng các đồng tiền có lãi suất cao lên giá thay vì mất giá.

Fama regression test of UIP for VND/USD
# Monthly exchange rate changes
cip_data['delta_s'] = (
    np.log(cip_data['spot'].shift(-3) / cip_data['spot'])
)

# Fama regression
uip_data = cip_data[['delta_s', 'rate_diff']].dropna()

uip_model = sm.OLS(
    uip_data['delta_s'],
    sm.add_constant(uip_data['rate_diff'])
).fit(cov_type='HAC', cov_kwds={'maxlags': 4})

print("UIP (Fama) Regression: delta_s_{t+3m} = alpha + beta(r_VND - r_USD) + eps")
print(f"\nalpha = {uip_model.params['const']:.4f} "
      f"(t = {uip_model.tvalues['const']:.2f})")
print(f"beta = {uip_model.params['rate_diff']:.4f} "
      f"(t = {uip_model.tvalues['rate_diff']:.2f})")
print(f"R-squared = {uip_model.rsquared:.4f}")
print(f"\nH0: beta = 1 (Wald test):")
wald_stat = ((uip_model.params['rate_diff'] - 1) /
             uip_model.bse['rate_diff']) ** 2
print(f"  Chi2 = {wald_stat:.2f}, p = {1 - stats.chi2.cdf(wald_stat, 1):.4f}")
Diễn giải các thất bại của UIP ở Việt Nam

Một tỷ giá được quản lý làm phức tạp các kiểm định UIP. Khi NHNN bảo vệ thành công VND trong một biên độ hẹp, các thay đổi tỷ giá thực hiện bị nén lại một cách giả tạo bất kể chênh lệch lãi suất. Ước lượng beta thu được phản ánh độ tin cậy của cơ chế neo cũng nhiều như phản ánh phần bù rủi ro. Các giai đoạn NHNN can thiệp mạnh nên được xử lý riêng so với các giai đoạn tương đối linh hoạt.

39.5.3 Lợi nhuận giao dịch carry

Chúng ta xây dựng một chiến lược giao dịch carry VND đơn giản: vay USD theo lãi suất ngắn hạn của Mỹ, đổi sang VND, đầu tư theo lãi suất ngắn hạn của Việt Nam, rồi đổi ngược lại khi đáo hạn. Lợi nhuận vượt mức là:

\[ \text{Carry}_{t \to t+k} = (r^{VND}_{t,k} - r^{USD}_{t,k}) - \Delta s_{t+k} \tag{39.5}\]

Lợi nhuận carry dương nghĩa là chênh lệch lãi suất đã bù đắp nhiều hơn mức mất giá của VND.

Compute and analyze VND carry trade returns
carry = cip_data.copy()
carry['carry_return'] = carry['rate_diff'] / 4 - carry['delta_s']
carry = carry.dropna(subset=['carry_return'])

# Annualize
ann_carry = carry['carry_return'].mean() * 4
ann_vol = carry['carry_return'].std() * 2
sharpe = ann_carry / ann_vol

print("VND/USD Carry Trade Performance (3-Month Rolling):")
print(f"Annualized return: {ann_carry:.4f} ({ann_carry*100:.2f}%)")
print(f"Annualized volatility: {ann_vol:.4f} ({ann_vol*100:.2f}%)")
print(f"Sharpe ratio: {sharpe:.2f}")
print(f"Skewness: {carry['carry_return'].skew():.2f}")
print(f"Kurtosis: {carry['carry_return'].kurtosis():.2f}")
print(f"\nMax quarterly loss: {carry['carry_return'].min():.4f}")
print(f"Max quarterly gain: {carry['carry_return'].max():.4f}")
Plot cumulative carry trade returns
fig, axes = plt.subplots(2, 1, figsize=(14, 8), height_ratios=[2, 1])

# Panel A: Cumulative return
cum_carry = (1 + carry['carry_return']).cumprod()
axes[0].plot(carry.index, cum_carry, color='#2C5F8A', linewidth=2)
axes[0].fill_between(carry.index, 1, cum_carry,
                     where=cum_carry > 1, color='#27AE60', alpha=0.2)
axes[0].fill_between(carry.index, 1, cum_carry,
                     where=cum_carry < 1, color='#C0392B', alpha=0.2)
axes[0].axhline(y=1, color='gray', linewidth=0.5)
axes[0].set_ylabel('Cumulative Return (VND 1 invested)')
axes[0].set_title('Panel A: VND/USD Carry Trade Cumulative Return')

# Panel B: Quarterly returns
axes[1].bar(carry.index, carry['carry_return'] * 100,
            color=['#27AE60' if x > 0 else '#C0392B'
                   for x in carry['carry_return']],
            alpha=0.7, width=60)
axes[1].axhline(y=0, color='black', linewidth=0.5)
axes[1].set_ylabel('Quarterly Return (%)')
axes[1].set_xlabel('Date')
axes[1].set_title('Panel B: Quarterly Carry Returns')

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

39.6 Mô hình hóa biến động

39.6.1 Ước lượng GARCH

Tự tương quan mạnh trong lợi nhuận bình phương VND/USD (Hình 39.2) là động lực để mô hình hóa GARCH (Bollerslev 1986). Chúng ta ước lượng mô hình GARCH(1,1) cho lợi nhuận logarit hằng ngày VND/USD:

\[ r_t = \mu + \varepsilon_t, \quad \varepsilon_t = \sigma_t z_t, \quad z_t \sim D(0,1) \tag{39.6}\]

\[ \sigma_t^2 = \omega + \alpha \varepsilon_{t-1}^2 + \beta \sigma_{t-1}^2 \tag{39.7}\]

trong đó \(\alpha\) phản ánh hiệu ứng ARCH (tác động của cú sốc hôm qua lên phương sai hôm nay) và \(\beta\) phản ánh tính bền vững (biến động quay về trung bình dài hạn chậm đến mức nào). Phương sai vô điều kiện là \(\bar{\sigma}^2 = \omega / (1 - \alpha - \beta)\).

Estimate GARCH(1,1) models with normal and Student-t innovations
returns_bps = vnd_usd['log_return'] * 10000  # Scale to basis points

# GARCH(1,1) with normal innovations
garch_norm = arch_model(
    returns_bps, vol='Garch', p=1, q=1, dist='normal'
)
res_norm = garch_norm.fit(disp='off')

# GARCH(1,1) with Student-t innovations
garch_t = arch_model(
    returns_bps, vol='Garch', p=1, q=1, dist='t'
)
res_t = garch_t.fit(disp='off')

# GJR-GARCH (asymmetric: depreciation shocks may increase vol more)
gjr = arch_model(
    returns_bps, vol='Garch', p=1, o=1, q=1, dist='t'
)
res_gjr = gjr.fit(disp='off')

print("=" * 60)
print("GARCH(1,1) - Normal Innovations")
print("=" * 60)
print(res_norm.summary().tables[1])

print("\n" + "=" * 60)
print("GARCH(1,1) - Student-t Innovations")
print("=" * 60)
print(res_t.summary().tables[1])

print("\n" + "=" * 60)
print("GJR-GARCH(1,1,1) - Student-t Innovations")
print("=" * 60)
print(res_gjr.summary().tables[1])

# Model comparison
print("\nModel Comparison:")
print(f"{'Model':<25} {'AIC':>10} {'BIC':>10} {'LogLik':>12}")
for name, res in [('GARCH-Normal', res_norm),
                   ('GARCH-t', res_t),
                   ('GJR-GARCH-t', res_gjr)]:
    print(f"{name:<25} {res.aic:>10.1f} {res.bic:>10.1f} "
          f"{res.loglikelihood:>12.1f}")
Plot GARCH conditional volatility versus realized volatility
fig, ax = plt.subplots(figsize=(14, 6))

# GARCH conditional volatility (annualized, from basis points)
cond_vol = res_t.conditional_volatility / 10000 * np.sqrt(252)

ax.plot(vnd_usd.index[-len(cond_vol):], cond_vol,
        color='#C0392B', linewidth=1.2, alpha=0.9,
        label='GARCH(1,1) Conditional Vol')
ax.plot(vnd_usd.index, vnd_usd['vol_30d'],
        color='#2C5F8A', linewidth=1, alpha=0.6,
        label='30-Day Realized Vol')

ax.set_ylabel('Annualized Volatility')
ax.set_xlabel('Date')
ax.set_title('VND/USD Volatility: GARCH vs Realized')
ax.legend()
plt.tight_layout()
plt.show()
Hình 39.7

39.6.2 Nhận diện chế độ biến động

Chúng ta phân loại thị trường VND/USD thành các chế độ biến động bằng biến động có điều kiện của GARCH:

Classify volatility regimes and analyze their properties
# Merge conditional volatility back to main DataFrame
vol_series = pd.Series(
    cond_vol.values,
    index=vnd_usd.index[-len(cond_vol):]
)
vnd_usd['cond_vol'] = vol_series

# Classify regimes based on percentiles
vnd_usd['vol_regime'] = pd.cut(
    vnd_usd['cond_vol'],
    bins=[0, vnd_usd['cond_vol'].quantile(0.33),
          vnd_usd['cond_vol'].quantile(0.67), np.inf],
    labels=['Low', 'Medium', 'High']
)

# Regime statistics
regime_stats = (
    vnd_usd.groupby('vol_regime')
    .agg(
        n_days=('log_return', 'count'),
        mean_return=('log_return', lambda x: x.mean() * 252),
        volatility=('log_return', lambda x: x.std() * np.sqrt(252)),
        mean_spread=('spread_pct', 'mean'),
        skewness=('log_return', 'skew')
    )
    .round(4)
)
print("Volatility Regime Characteristics:")
print(regime_stats)
Scatter plot of returns vs conditional volatility by regime
fig, ax = plt.subplots(figsize=(10, 6))

colors_regime = {'Low': '#27AE60', 'Medium': '#F1C40F', 'High': '#C0392B'}
for regime in ['Low', 'Medium', 'High']:
    subset = vnd_usd[vnd_usd['vol_regime'] == regime]
    ax.scatter(
        subset['cond_vol'] * 100,
        subset['log_return'] * 100,
        color=colors_regime[regime], alpha=0.3, s=8, label=regime
    )

ax.axhline(y=0, color='gray', linewidth=0.5)
ax.set_xlabel('Conditional Volatility (% annualized)')
ax.set_ylabel('Daily Return (%)')
ax.set_title('VND/USD Returns by Volatility Regime')
ax.legend(title='Regime')
plt.tight_layout()
plt.show()
Hình 39.8

39.7 Mức độ phơi nhiễm tỷ giá của các doanh nghiệp Việt Nam

39.7.1 Khung phân tích của Jorion

Jorion (1990) giới thiệu mô hình hai nhân tố chuẩn để ước lượng mức độ phơi nhiễm tỷ giá hối đoái cấp doanh nghiệp:

\[ R_{i,t} = \alpha_i + \beta_i^{MKT} R_{m,t} + \gamma_i \Delta s_t + \varepsilon_{i,t} \tag{39.8}\]

trong đó \(R_{i,t}\) là lợi nhuận cổ phiếu của doanh nghiệp \(i\), \(R_{m,t}\) là lợi nhuận thị trường, và \(\Delta s_t\) là thay đổi tỷ giá hối đoái (lợi nhuận logarit VND/USD, với dương = VND mất giá). Hệ số \(\gamma_i\) là mức phơi nhiễm tỷ giá còn lại, tức độ nhạy của lợi nhuận doanh nghiệp \(i\) với các biến động tiền tệ sau khi kiểm soát các hiệu ứng toàn thị trường.

Một \(\gamma_i\) dương nghĩa là doanh nghiệp hưởng lợi khi VND mất giá (điển hình với các nhà xuất khẩu), trong khi \(\gamma_i\) âm nghĩa là doanh nghiệp bị thiệt hại bởi mất giá (điển hình với các nhà nhập khẩu hoặc doanh nghiệp có nợ bằng USD).

Estimate firm-level exchange rate exposure using the Jorion model
# Load equity returns
equity = client.get_monthly_returns(
    exchanges=['HOSE', 'HNX'],
    start_date='2012-01-01',
    end_date='2024-12-31',
    fields=['ticker', 'month_end', 'monthly_return', 'market_cap']
)

# Market return (VW)
market_ret = (
    equity
    .groupby('month_end')
    .apply(lambda g: np.average(g['monthly_return'],
                                 weights=g['market_cap']))
    .reset_index(name='market_return')
)

# Monthly VND/USD returns
fx_monthly = (
    vnd_usd['log_return']
    .resample('M').sum()
    .to_frame('fx_return')
)
fx_monthly.index = fx_monthly.index.to_period('M').to_timestamp()

# Merge all data
exposure_data = (
    equity
    .merge(market_ret, on='month_end')
    .merge(fx_monthly, left_on='month_end', right_index=True, how='inner')
)

# Estimate exposure for each firm
def estimate_exposure(group, min_obs=36):
    """Estimate Jorion exposure model for a single firm."""
    if len(group) < min_obs:
        return None
    
    y = group['monthly_return']
    X = sm.add_constant(group[['market_return', 'fx_return']])
    
    try:
        model = sm.OLS(y, X).fit(cov_type='HC1')
        return pd.Series({
            'gamma': model.params['fx_return'],
            'gamma_se': model.bse['fx_return'],
            'gamma_t': model.tvalues['fx_return'],
            'gamma_p': model.pvalues['fx_return'],
            'beta_mkt': model.params['market_return'],
            'r_squared': model.rsquared,
            'n_obs': model.nobs
        })
    except Exception:
        return None

exposures = (
    exposure_data
    .groupby('ticker')
    .apply(estimate_exposure)
    .dropna()
)

print(f"Estimated exposure for {len(exposures)} firms")
print(f"\nExposure (gamma) Summary:")
print(exposures['gamma'].describe().round(4))
print(f"\nSignificant at 5%: "
      f"{(exposures['gamma_p'] < 0.05).sum()} / {len(exposures)} "
      f"({(exposures['gamma_p'] < 0.05).mean():.1%})")
Plot distribution of exchange rate exposures
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Panel A: Distribution of gamma
sig = exposures['gamma_p'] < 0.05
axes[0].hist(exposures.loc[~sig, 'gamma'], bins=40, color='#BDC3C7',
             alpha=0.7, label='Insignificant', edgecolor='white')
axes[0].hist(exposures.loc[sig & (exposures['gamma'] > 0), 'gamma'],
             bins=20, color='#27AE60', alpha=0.8,
             label='Significant positive', edgecolor='white')
axes[0].hist(exposures.loc[sig & (exposures['gamma'] < 0), 'gamma'],
             bins=20, color='#C0392B', alpha=0.8,
             label='Significant negative', edgecolor='white')
axes[0].axvline(x=0, color='black', linewidth=0.8)
axes[0].set_xlabel('Exchange Rate Exposure (gamma)')
axes[0].set_ylabel('Number of Firms')
axes[0].set_title('Panel A: Cross-Sectional Distribution')
axes[0].legend(fontsize=9)

# Panel B: Exposure vs market beta
axes[1].scatter(exposures['beta_mkt'], exposures['gamma'],
                c=exposures['gamma_t'].clip(-3, 3),
                cmap='RdBu_r', s=15, alpha=0.6)
axes[1].axhline(y=0, color='gray', linewidth=0.5)
axes[1].axvline(x=1, color='gray', linewidth=0.5, linestyle='--')
axes[1].set_xlabel('Market Beta')
axes[1].set_ylabel('FX Exposure (gamma)')
axes[1].set_title('Panel B: Market Beta vs FX Exposure')

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

39.7.2 Mức phơi nhiễm cấp ngành

Mức phơi nhiễm tỷ giá hối đoái khác nhau một cách có hệ thống giữa các ngành. Các ngành hướng xuất khẩu (dệt may, thủy sản, điện tử) phải có mức phơi nhiễm dương (hưởng lợi khi VND yếu), trong khi các ngành phụ thuộc nhập khẩu (dầu khí, máy móc, hàng tiêu dùng) phải có mức phơi nhiễm âm.

Compute and compare exposure by industry
# Get industry classification
industry = client.get_firm_info(
    exchanges=['HOSE', 'HNX'],
    fields=['ticker', 'icb_sector', 'icb_industry']
)

exposure_industry = exposures.reset_index().merge(
    industry, on='ticker', how='left'
)

# Industry-level average exposure
industry_avg = (
    exposure_industry
    .groupby('icb_sector')
    .agg(
        n_firms=('gamma', 'count'),
        mean_gamma=('gamma', 'mean'),
        median_gamma=('gamma', 'median'),
        pct_significant=('gamma_p', lambda x: (x < 0.05).mean())
    )
    .sort_values('mean_gamma', ascending=False)
    .round(3)
)

print("Exchange Rate Exposure by Industry:")
print(industry_avg.to_string())
Bar chart of industry-level FX exposure
fig, ax = plt.subplots(figsize=(12, 6))

colors_bar = ['#27AE60' if x > 0 else '#C0392B'
              for x in industry_avg['mean_gamma']]
bars = ax.barh(range(len(industry_avg)), industry_avg['mean_gamma'],
               color=colors_bar, alpha=0.85)
ax.set_yticks(range(len(industry_avg)))
ax.set_yticklabels(industry_avg.index, fontsize=9)
ax.axvline(x=0, color='black', linewidth=0.8)
ax.set_xlabel('Average Exchange Rate Exposure (gamma)')
ax.set_title('Exchange Rate Exposure by Industry')

for i, (_, row) in enumerate(industry_avg.iterrows()):
    label = f"({row['pct_significant']:.0%} sig.)"
    ax.text(row['mean_gamma'] + 0.01 * np.sign(row['mean_gamma']),
            i, label, va='center', fontsize=8, color='gray')

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

39.7.3 Mức phơi nhiễm biến đổi theo thời gian

Mức phơi nhiễm tỷ giá hối đoái không cố định, vì các doanh nghiệp điều chỉnh chiến lược phòng ngừa rủi ro, cơ cấu thương mại thay đổi và bản thân chế độ tỷ giá cũng tiến triển. Chúng ta ước lượng mức phơi nhiễm trượt 36 tháng:

Estimate rolling 36-month exchange rate exposures
def rolling_exposure(group, window=36, min_obs=24):
    """Estimate rolling FX exposure with 36-month windows."""
    results = []
    group = group.sort_values('month_end')
    
    for i in range(window, len(group) + 1):
        sub = group.iloc[i - window:i]
        if len(sub) < min_obs:
            continue
        
        y = sub['monthly_return']
        X = sm.add_constant(sub[['market_return', 'fx_return']])
        
        try:
            model = sm.OLS(y, X).fit()
            results.append({
                'month_end': sub['month_end'].iloc[-1],
                'gamma': model.params['fx_return'],
                'gamma_se': model.bse['fx_return']
            })
        except Exception:
            pass
    
    return pd.DataFrame(results)

# Compute for a sample of large firms
large_firms = (
    equity.groupby('ticker')['market_cap']
    .last().nlargest(20).index
)

rolling_results = {}
for ticker in large_firms:
    firm_data = exposure_data[exposure_data['ticker'] == ticker]
    result = rolling_exposure(firm_data)
    if len(result) > 0:
        rolling_results[ticker] = result

print(f"Rolling exposures computed for {len(rolling_results)} firms")
Plot rolling FX exposures for selected firms
fig, ax = plt.subplots(figsize=(14, 6))

plot_colors = plt.cm.Set1(np.linspace(0, 1, min(6, len(rolling_results))))
for i, (ticker, df) in enumerate(list(rolling_results.items())[:6]):
    ax.plot(pd.to_datetime(df['month_end']), df['gamma'],
            linewidth=1.5, label=ticker, color=plot_colors[i])
    if i == 0:
        ax.fill_between(
            pd.to_datetime(df['month_end']),
            df['gamma'] - 1.96 * df['gamma_se'],
            df['gamma'] + 1.96 * df['gamma_se'],
            alpha=0.1, color=plot_colors[i]
        )

ax.axhline(y=0, color='gray', linewidth=0.8)
ax.set_ylabel('Exchange Rate Exposure (gamma)')
ax.set_xlabel('Date')
ax.set_title('Rolling 36-Month Exchange Rate Exposure')
ax.legend(ncol=3, fontsize=9)
plt.tight_layout()
plt.show()
Hình 39.11

39.8 Phòng ngừa rủi ro tiền tệ cho nhà đầu tư quốc tế

39.8.1 Quyết định phòng ngừa rủi ro

Đối với một nhà đầu tư nước ngoài nắm giữ cổ phiếu Việt Nam, lợi nhuận tính bằng USD không phòng ngừa là:

\[ R^{USD}_{i,t} \approx R^{VND}_{i,t} + \Delta s_t + R^{VND}_{i,t} \cdot \Delta s_t \tag{39.9}\]

trong đó số hạng tích chéo thường nhỏ. Khi phòng ngừa toàn phần bằng hợp đồng kỳ hạn, lợi nhuận trở thành:

\[ R^{USD,\text{hedged}}_{i,t} \approx R^{VND}_{i,t} + (f_t - s_t) \tag{39.10}\]

trong đó \(f_t - s_t\) là phần bù kỳ hạn (mà theo CIP xấp xỉ bằng \(r^{USD}_t - r^{VND}_t\)). Ở Việt Nam, nơi lãi suất VND cao hơn lãi suất USD, phần bù kỳ hạn là âm, nên phòng ngừa rủi ro tốn tiền vì nhà đầu tư từ bỏ chênh lệch lãi suất.

Campbell, Serfaty-De Medeiros, và Viceira (2010) cho thấy tỷ lệ phòng ngừa tối ưu phụ thuộc vào tương quan giữa lợi nhuận cổ phiếu và lợi nhuận tiền tệ. Khi tương quan dương (VND mất giá trùng với khoản lỗ cổ phiếu), phòng ngừa rủi ro làm giảm phương sai tổng thể của danh mục hiệu quả hơn.

Compare hedged vs unhedged portfolio returns for a foreign investor
# Construct VW equity index in VND
equity_index = (
    equity
    .groupby('month_end')
    .apply(lambda g: np.average(g['monthly_return'],
                                 weights=g['market_cap']))
    .reset_index(name='equity_vnd')
)
equity_index['month_end'] = pd.to_datetime(equity_index['month_end'])

# Merge with FX returns and forward premium
hedge_data = (
    equity_index
    .merge(fx_monthly, left_on='month_end', right_index=True, how='inner')
)

# Forward premium
if len(cip_data) > 0:
    fwd_premium_monthly = (
        cip_data['forward_premium']
        .resample('M').last() / 12
    )
    hedge_data = hedge_data.merge(
        fwd_premium_monthly.to_frame('fwd_premium'),
        left_on='month_end', right_index=True, how='left'
    )
    hedge_data['fwd_premium'] = hedge_data['fwd_premium'].fillna(
        hedge_data['fwd_premium'].mean()
    )
else:
    hedge_data['fwd_premium'] = -0.003

# Unhedged USD return
hedge_data['return_unhedged'] = (
    hedge_data['equity_vnd'] + hedge_data['fx_return']
    + hedge_data['equity_vnd'] * hedge_data['fx_return']
)

# Fully hedged USD return
hedge_data['return_hedged'] = (
    hedge_data['equity_vnd'] + hedge_data['fwd_premium']
)

# 50% hedge ratio
hedge_data['return_50pct'] = (
    0.5 * hedge_data['return_hedged']
    + 0.5 * hedge_data['return_unhedged']
)

# Performance comparison
for strategy, col in [('VND (Local)', 'equity_vnd'),
                       ('USD Unhedged', 'return_unhedged'),
                       ('USD 50% Hedged', 'return_50pct'),
                       ('USD Fully Hedged', 'return_hedged')]:
    r = hedge_data[col]
    ann_ret = (1 + r).prod() ** (12 / len(r)) - 1
    ann_vol = r.std() * np.sqrt(12)
    sharpe = r.mean() / r.std() * np.sqrt(12)
    print(f"{strategy:<22} Return: {ann_ret:>7.2%}  Vol: {ann_vol:>7.2%}  "
          f"Sharpe: {sharpe:>5.2f}")
Plot cumulative returns for different hedging strategies
fig, ax = plt.subplots(figsize=(12, 6))

strategies = {
    'VND (Local)': ('equity_vnd', '#2C5F8A', '-'),
    'USD Unhedged': ('return_unhedged', '#C0392B', '-'),
    'USD 50% Hedged': ('return_50pct', '#E67E22', '--'),
    'USD Fully Hedged': ('return_hedged', '#27AE60', '-'),
}

for label, (col, color, ls) in strategies.items():
    cum = (1 + hedge_data.set_index('month_end')[col]).cumprod()
    ax.plot(cum.index, cum, label=label, color=color,
            linewidth=2, linestyle=ls)

ax.set_ylabel('Cumulative Wealth')
ax.set_xlabel('Date')
ax.set_title('Vietnamese Equity Returns: Hedging Comparison')
ax.legend()
ax.set_yscale('log')
plt.tight_layout()
plt.show()
Hình 39.12

39.8.2 Tỷ lệ phòng ngừa tối ưu

Tỷ lệ phòng ngừa phương sai tối thiểu là:

\[ h^* = \frac{\text{Cov}(R^{VND}_t, \Delta s_t)}{\text{Var}(\Delta s_t)} \tag{39.11}\]

Đây là hệ số góc OLS từ hồi quy lợi nhuận cổ phiếu trong nước trên các thay đổi tỷ giá hối đoái. Khi tương quan giữa cổ phiếu và tiền tệ dương (bất lợi cho nhà đầu tư không phòng ngừa), \(h^* > 0\) và phòng ngừa rủi ro làm giảm phương sai.

Estimate optimal hedge ratio and rolling hedge ratio
# Full-sample optimal hedge ratio
hedge_reg = sm.OLS(
    hedge_data['equity_vnd'],
    sm.add_constant(hedge_data['fx_return'])
).fit()

h_star = hedge_reg.params['fx_return']
print(f"Optimal hedge ratio (full sample): {h_star:.3f}")
print(f"Equity-currency correlation: "
      f"{hedge_data['equity_vnd'].corr(hedge_data['fx_return']):.3f}")

# Rolling 36-month optimal hedge ratio
rolling_cov = hedge_data['equity_vnd'].rolling(36).cov(
    hedge_data['fx_return']
)
rolling_var = hedge_data['fx_return'].rolling(36).var()
hedge_data['h_star_rolling'] = rolling_cov / rolling_var

print(f"\nRolling hedge ratio range: "
      f"[{hedge_data['h_star_rolling'].min():.2f}, "
      f"{hedge_data['h_star_rolling'].max():.2f}]")

39.9 So sánh tiền tệ ASEAN

Để đặt VND vào bối cảnh, chúng ta so sánh các tính chất của nó với các đồng tiền ASEAN khác và đồng Nhân dân tệ.

Compare VND with ASEAN and Asian currency pairs
# Compute annual statistics for each currency
comparison = pd.DataFrame()
for currency in fx_returns.columns:
    r = fx_returns[currency].dropna()
    if len(r) > 252:
        comparison.loc[currency, 'Ann. Depreciation (%)'] = r.mean() * 252 * 100
        comparison.loc[currency, 'Ann. Volatility (%)'] = r.std() * np.sqrt(252) * 100
        comparison.loc[currency, 'Skewness'] = r.skew()
        comparison.loc[currency, 'Kurtosis'] = r.kurtosis()
        comparison.loc[currency, 'Max Daily Loss (%)'] = r.max() * 100
        comparison.loc[currency, 'Sharpe (Carry)'] = (
            r.mean() * 252 / (r.std() * np.sqrt(252))
        )

print("ASEAN + Asian Currency Comparison (VND base, vs USD):")
print(comparison.round(2).to_string())

# Risk-return scatter
fig, ax = plt.subplots(figsize=(9, 7))
for currency in comparison.index:
    ax.scatter(
        comparison.loc[currency, 'Ann. Volatility (%)'],
        comparison.loc[currency, 'Ann. Depreciation (%)'],
        s=120, zorder=5
    )
    ax.annotate(
        f'VND/{currency}',
        (comparison.loc[currency, 'Ann. Volatility (%)'] + 0.15,
         comparison.loc[currency, 'Ann. Depreciation (%)']),
        fontsize=10
    )

ax.axhline(y=0, color='gray', linewidth=0.5)
ax.set_xlabel('Annualized Volatility (%)')
ax.set_ylabel('Annualized Depreciation (%)')
ax.set_title('ASEAN Currency Risk-Return Characteristics')
plt.tight_layout()
plt.show()
Hình 39.13

39.10 Đồng biến động giữa tỷ giá hối đoái và thị trường cổ phiếu

Mối quan hệ giữa các biến động VND/USD và thị trường cổ phiếu Việt Nam là trung tâm của việc xây dựng danh mục đầu tư. Chúng ta xem xét điều này ở cả cấp độ tổng hợp lẫn cấp độ có điều kiện.

Analyze equity-FX co-movement at the aggregate level
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Panel A: Scatter plot
axes[0].scatter(
    hedge_data['fx_return'] * 100,
    hedge_data['equity_vnd'] * 100,
    c=hedge_data.index, cmap='viridis', s=20, alpha=0.7
)
z = np.polyfit(hedge_data['fx_return'], hedge_data['equity_vnd'], 1)
x_line = np.linspace(hedge_data['fx_return'].min(),
                     hedge_data['fx_return'].max(), 100)
axes[0].plot(x_line * 100, np.polyval(z, x_line) * 100,
             color='#C0392B', linewidth=2)
axes[0].axhline(y=0, color='gray', linewidth=0.5)
axes[0].axvline(x=0, color='gray', linewidth=0.5)
axes[0].set_xlabel('VND/USD Change (%)')
axes[0].set_ylabel('VN-Index Return (%)')
axes[0].set_title('Panel A: Monthly Equity-FX Relationship')

rho = hedge_data['equity_vnd'].corr(hedge_data['fx_return'])
axes[0].text(0.05, 0.95, f'rho = {rho:.3f}', transform=axes[0].transAxes,
             fontsize=12, verticalalignment='top',
             bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))

# Panel B: Rolling 36-month correlation
rolling_corr = (
    hedge_data['equity_vnd']
    .rolling(36)
    .corr(hedge_data['fx_return'])
)
axes[1].plot(hedge_data['month_end'], rolling_corr,
             color='#2C5F8A', linewidth=2)
axes[1].axhline(y=0, color='gray', linewidth=0.8, linestyle='--')
axes[1].fill_between(
    hedge_data['month_end'], 0, rolling_corr,
    where=rolling_corr < 0, color='#C0392B', alpha=0.2
)
axes[1].fill_between(
    hedge_data['month_end'], 0, rolling_corr,
    where=rolling_corr > 0, color='#27AE60', alpha=0.2
)
axes[1].set_xlabel('Date')
axes[1].set_ylabel('Rolling Correlation')
axes[1].set_title('Panel B: 36-Month Rolling Equity-FX Correlation')

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

39.11 Tóm tắt

Chương này đã phát triển một bộ công cụ toàn diện để làm việc với dữ liệu tỷ giá hối đoái trong bối cảnh Việt Nam. Các phát hiện và phương pháp chính nằm trong Bảng 39.2.

Bảng 39.2: Tóm tắt các phát hiện về động lực tỷ giá hối đoái VND.
Chủ đề Phát hiện Hàm ý
Chế độ VND Thả nổi có quản lý với biên độ +/-3%, tỷ giá tham chiếu hằng ngày Lợi nhuận không tuân theo phân phối chuẩn; tập trung gần không với các cú nhảy rời rạc
Phân phối lợi nhuận Đuôi dày, độ nhọn vượt mức, độ xiên dương Các mô hình rủi ro dựa trên phân phối chuẩn thông thường đánh giá thấp rủi ro đuôi
CIP Độ lệch dương dai dẳng (~50-200 bps) Kiểm soát vốn và chênh lệch giá hạn chế; phòng ngừa rủi ro tốn kém
UIP beta < 1 trong hồi quy Fama Giao dịch carry có lãi trung bình; rủi ro sụp đổ khi phá giá
Giao dịch carry Sharpe dương nhưng độ xiên âm Lãi đều đặn bị ngắt quãng bởi các khoản lỗ mạnh khi phá giá
GARCH Tính bền vững mạnh (alpha + beta xấp xỉ 0.99); ưu tiên Student-t Tập trung biến động; chuyển chế độ quanh các sự kiện chính sách
Mức phơi nhiễm doanh nghiệp ~15-25% có ý nghĩa; mô thức ngành có hệ thống Nhà xuất khẩu hưởng lợi từ mất giá; nhà nhập khẩu bị thiệt
Phòng ngừa rủi ro Phòng ngừa toàn phần loại bỏ rủi ro ngoại hối nhưng tốn ~3-5% mỗi năm Mức phòng ngừa một phần tối ưu phụ thuộc vào tương quan cổ phiếu-ngoại hối
So sánh ASEAN VND có biến động thấp nhất nhưng mất giá đều đặn Thả nổi có quản lý nén biến động; không loại bỏ rủi ro xu hướng

Đối với các nhà nghiên cứu sử dụng dữ liệu cổ phiếu Việt Nam, khía cạnh tỷ giá hối đoái không phải là tùy chọn. Bất kỳ nghiên cứu nào quy đổi lợi nhuận VND sang USD, dùng các mô hình nhân tố quốc tế, hoặc xem xét các doanh nghiệp có phơi nhiễm thương mại đều phải tính đến các tính chất đặc thù của chế độ VND. Các công cụ phát triển trong chương này, từ mô hình hóa biến động GARCH đến ước lượng mức phơi nhiễm trượt, cung cấp các khối nền tảng để đưa động lực tỷ giá hối đoái vào công việc thực nghiệm định giá tài sản rộng hơn.