Bài 5: Dự báo giá cổ phiếu với GBM

Dự báo giá cổ phiếu 90 ngày bằng mô hình GBM. Cách dùng dữ liệu thực tế để 'đọc vị' thị trường.

Chúng ta đã đi qua một hành trình dài từ những bước đi ngẫu nhiên của Random Walk đến dòng chảy liên tục của Brownian Motion. Nhưng lý thuyết sẽ chỉ là lý thuyết nếu chúng ta không thể áp dụng vào thực tế sinh động. Liệu chỉ số S&P 500 hay giá cổ phiếu Apple có thực sự tuân theo quy luật mà các nhà toán học đã đặt ra?

Trong bài viết này, chúng ta sẽ tạm rời xa các công thức khô khan để bắt tay vào làm việc với dữ liệu thực tế. Chúng ta sẽ kiểm chứng xem thị trường có “chuẩn” như chúng ta nghĩ, học cách đo lường “linh hồn” của cổ phiếu qua bộ tham số \(\mu, \sigma\), và cuối cùng là dùng Python/Excel để dự đoán tương lai ngắn hạn trong 90 ngày tới.

Trong trang này:

1. Kiểm chứng phân phối chuẩn — Normality Test

Trước khi áp dụng mô hình vào thực tế, chúng ta cần trả lời một câu hỏi nền tảng: dữ liệu thực tế có “chuẩn” như lý thuyết yêu cầu không? Bachelier giả định lợi nhuận tuyệt đối (absolute return), còn GBM thì yêu cầu lợi nhuận (log-return) phải tuân theo phân phối chuẩn. Hãy cùng kiểm chứng điều này với dữ liệu lịch sử.

1.1. Thu thập dữ liệu

Để thực hiện, chúng ta cần dữ liệu lịch sử chất lượng, tốt nhất là giá đóng cửa điều chỉnh (adjusted close) đã loại bỏ tác động của chia tách (split) và cổ tức (dividend).

Có rất nhiều nguồn cho phép bạn truy cập miễn phí dữ liệu lịch sử giao dịch chứng khoán. Các lựa chọn này trải dài từ việc tải file .csv thủ công trên trang web cho đến các API mạnh mẽ giúp tự động lấy dữ liệu dành cho lập trình viên. Dưới đây là một vài nguồn dữ liệu miễn phí tốt nhất năm 2026 để bạn tham khảo.

Tải xuống thủ công cho người mới

Nếu bạn chỉ cần một file .csv để mở trong Excel hoặc Google Sheets, đây là những lựa chọn tốt nhất:

  • Yahoo Finance: Vẫn được coi là “tiêu chuẩn vàng” cho việc tải dữ liệu nhờ tính bao quát thị trường và hoàn toàn miễn phí. Nó cung cấp dữ liệu của hầu hết các sàn chứng khoán lớn trên thế giới và cả ở Việt Nam. Cách làm: tìm kiếm mã chứng khoán, ví dụ AAPL (Apple Inc.), nhấp vào tab Historical Data, chọn khoảng thời gian và nhấn Download.

  • Investing.com: Cung cấp cơ sở dữ liệu khổng lồ về cổ phiếu toàn cầu, các chỉ số, cũng như hàng hóa. Cách làm: tìm kiếm mã chứng khoán, ví dụ JPM (JPMorgan Chase & Co.), đi đến tab Historical Data và bạn có thể Download dữ liệu trực tiếp.

API cho lập trình viên

  • Alpha Vantage: Đây là API phổ biến nhất vì nó cung cấp hầu hết mọi thứ từ giá cổ phiếu, ngoại hối (Forex), tiền điện tử (Crypto) đến các chỉ báo kỹ thuật (RSI, MACD…). Gói miễn phí giới hạn khoảng 25 yêu cầu/ngày, phù hợp để làm các dự án nhỏ, học tập hoặc nghiên cứu.

  • Polygon.io: Nếu bạn cần dữ liệu cực nhanh và chính xác cho thị trường chứng khoán Mỹ, Polygon là lựa chọn hàng đầu. Gói miễn phí cho phép truy cập dữ liệu lịch sử trong vòng 2 năm với giới hạn 5 yêu cầu/phút.

Nên lấy dữ liệu trong khoảng 2 đến 5 năm để có đủ cỡ mẫu (sample size) cho các kiểm định thống kê, nhưng không quá xa để tránh việc cấu trúc thị trường đã thay đổi quá nhiều.

1.2. Mô tả dữ liệu

Để thuận tiện cho bài viết, tôi đã chuẩn bị sẵn dữ liệu thực tế của 4 mã cổ phiếu blue-chips trên sàn S&P500 như AAPL (Apple Inc.), JPM (JPMorgan Chase & Co.), JNJ (Johnson & Johnson), XOM (Exxon Mobil Corp.) rất phù hợp để so sánh hai mô hình BachelierGBM. Bạn có thể download dữ liệu tại đây 5_market_data.zip.

market_data bao gồm dữ liệu theo ngày (daily) trong 2 năm gần nhất, với giá mở cửa (open), cao nhất (high), thấp nhất (low), đóng cửa (close), khối lượng giao dịch (volume) và một số thông tin khác.


Dữ liệu lịch sử giá cổ phiếu AAPL (Apple Inc.), JPM (JPMorgan Chase & Co.), JNJ (Johnson & Johnson), XOM (Exxon Mobil Corp.)
Hình 1: Dữ liệu lịch sử giá cổ phiếu AAPL (Apple Inc.), JPM (JPMorgan Chase & Co.), JNJ (Johnson & Johnson), XOM (Exxon Mobil Corp.), giai đoạn 01/2024 — 01/2026.

Nhìn vào biểu đồ thực tế, bạn sẽ thấy những đoạn giá bỗng dưng “dốc đứng” như vách núi, hay thậm chí là “đứt đoạn” tạo thành một khoảng trống. Xin chúc mừng: bạn vừa tận mắt chứng kiến những “bước nhảy giá” (jump) trong thị trường tài chính đấy!

Những bước nhảy này từ đâu mà ra? Đó có thể là một bản báo cáo lợi nhuận “khủng” khiến nhà đầu tư phấn khích. Một tin tức vĩ mô bất ngờ từ chính phủ. Hay đơn giản là một sự kiện đột xuất của doanh nghiệp khiến mọi người “trở tay không kịp” vào sáng hôm sau. Hãy để ý các cột volume (xanh/đỏ) ở dưới. Một “bước nhảy giá” thường đi kèm với một cột volume cao ngất ngưởng. Nó giống như việc bạn hét lớn và có cả một đám đông hàng ngàn người cùng hò reo theo vậy – đó chính là sự “xác nhận” cho một xu hướng mới đã bắt đầu.

Hãy thử dùng GBM để mô phỏng giá cổ phiếu Apple, từ đó xem liệu mô hình này có bắt được những “cú nhảy giá” đột ngột như thực tế hay không.


So sánh mô phỏng GBM và thực tế giá cổ phiếu AAPL (Apple Inc.)
Hình 2: So sánh mô phỏng GBM và thực tế giá cổ phiếu AAPL (Apple Inc.), giai đoạn 01/2024 — 01/2026.

Khi nhìn vào biểu đồ, bạn sẽ thấy một sự thật phũ phàng nhưng đầy thú vị. Đường GBM (màu cam đứt đoạn) sẽ trông rất “hiền”, nó dao động ngẫu nhiên “êm đềm” (smooth) như một dòng sông, tăng giảm nhẹ nhàng từng chút một. Đường giá thực tế AAPL (màu xanh) trông rất “gai góc” với những đoạn gãy khúc hoặc vọt lên/xuống rất “gắt” (jump).

Bạn thấy đó giữa “lý thuyết màu hồng” và “thực tế màu xám” có những khoảng cách rất thú vị. GBM bảo vệ bạn tốt khi “trời quang mây tạnh”, nhưng khi có “bão” (những cú nhảy giá đột ngột), nó thường bị “đơ ra” vì những cú nhảy đó không nằm trong công thức toán học của nó. Đó là lý do tại sao các chuyên gia tài chính sau này phải “tô điểm” thêm cho lý thuyết bằng các mô hình như Jump-Diffusion (GBM cộng thêm các cú nhảy) để thực tế bớt “xám” và lý thuyết bớt “hồng” đi một chút.

1.3. Kiểm chứng với Python

Để kiểm tra dữ liệu có “chuẩn” không, chúng ta sẽ kiểm tra nhanh bằng trực quan qua biểu đồ Histogram, Q-Q Plot sau đó bằng các chỉ số định lượng Skewness, Kurtosis.

Kiểm chứng với cổ phiếu AAPL (Apple Inc.)

Bạn có thể chạy code trên Google Colab cho cổ phiếu AAPL (Apple Inc.).

Open In Colab

Nếu chạy local trên máy tính của bạn, market_data cần đặt cùng thư mục chứa code Python. Bạn có thể thay thế ticker = "AAPL" bằng "JPM", "JNJ", "XOM" để chạy kiểm định với các mã cổ phiếu khác.

Hàm pd.read_csv(file_path) giúp đọc file .csv và gán dữ liệu vào dataframe — một container trong thư viện pandas. Hàm pd.to_datetime giúp chuẩn hóa dữ liệu thời gian sang định dạng ngày giờ.

import numpy as np
import pandas as pd
import scipy.stats as stats
import matplotlib.pyplot as plt
import os

# --- CONFIGURATION ---
ticker = "AAPL"
folder_name = "market_data"

# --- 1. Load Historical Data ---
# This code for run in local
file_path = os.path.join(folder_name, f"{ticker}.csv")

try:
    # Read Csv file from a local
    print("Loading data from local CSV...")
    df = pd.read_csv(file_path)
    # Ensure Date is datetime if loading from CSV
    df['timestamp'] = pd.to_datetime(df['timestamp'])
    print("Success!")
except Exception as e:
    print(f"Error loading file: {e}")

Kiểm tra mẫu (sample) đầu vào, ta có 502 dữ liệu theo ngày (daily) tương đương với gần 2 năm dữ liệu (mỗi năm có 252 ngày giao dịch).

# Inspect data
df
	open	high	low	    close	volume	    vwap	    timestamp	        transactions	otc
0	182.160	184.26	180.934	183.63	65603041.0	182.8825	2024-01-16 05:00:00	767284	NaN
1	181.270	182.93	180.300	182.68	47317433.0	181.9201	2024-01-17 05:00:00	594632	NaN
2	186.090	189.14	185.830	188.63	78005754.0	187.9375	2024-01-18 05:00:00	787235	NaN
3	189.330	191.95	188.820	191.56	68902985.0	190.6151	2024-01-19 05:00:00	682664	NaN
4	192.300	195.33	192.260	193.89	60133852.0	193.9891	2024-01-22 05:00:00	718108	NaN
...	...	    ...	    ...	    ...	    ...	        ...	        ...	                ...	    ...
497	257.020	259.29	255.700	259.04	50419337.0	257.4496	2026-01-08 05:00:00	764090	NaN
498	259.075	260.21	256.220	259.37	39996967.0	258.6804	2026-01-09 05:00:00	649745	NaN
499	259.160	261.30	256.800	260.25	45263767.0	260.0061	2026-01-12 05:00:00	664531	NaN
500	258.720	261.81	258.390	261.05	45601262.0	260.3489	2026-01-13 05:00:00	599894	NaN
501	259.490	261.82	256.710	259.96	39934470.0	259.2345	2026-01-14 05:00:00	611582	NaN
502 rows × 9 columns

Ta tính lợi nhuận tuyệt đối price_diffs cho Bachelier bằng giá ngày hôm sau trừ giá ngày hôm trước \(\Delta S_t = S_t - S_{t-1}\) — hàm diff() trong code. Với GBM, lợi nhuận log_returns được tính theo công thức \(\Delta S_t = \ln\left(\frac{S_t}{S_{t-1}}\right)\) — hàm np.log trong code.

Hàm dropna() giúp loại bỏ dữ liệu NaN trong mẫu. Dòng đầu tiên sẽ bị loại bỏ do không có dữ liệu trước đó để tính lợi nhuận.

# --- 2. Calculate Return ---
# Create a new DataFrame with only 'timestamp' and 'close'
df_prices = df[['timestamp', 'close']].copy()

# GBM Model (Log-Returns)
# Formula: ln(P_t / P_{t-1})
log_returns = np.log(df_prices['close'] / df_prices['close'].shift(1)).dropna()
df_prices['log_returns'] = log_returns

# Bachelier Model (Absolute Price Changes)
# Formula: P_t - P_{t-1}
price_diffs = df_prices['close'].diff().dropna()
df_prices['price_diffs'] = price_diffs

Ta thử kiểm tra tính toán lợi nhuận tuyệt đối price_diffs và lợi nhuận log_returns.

# Inspect the result
df_prices
	timestamp	        close	log_returns	price_diffs
0	2024-01-16 05:00:00	183.63	NaN	        NaN
1	2024-01-17 05:00:00	182.68	-0.005187	-0.95
2	2024-01-18 05:00:00	188.63	0.032051	5.95
3	2024-01-19 05:00:00	191.56	0.015414	2.93
4	2024-01-22 05:00:00	193.89	0.012090	2.33
...	...	                ...	    ...	        ...
497	2026-01-08 05:00:00	259.04	-0.004968	-1.29
498	2026-01-09 05:00:00	259.37	0.001273	0.33
499	2026-01-12 05:00:00	260.25	0.003387	0.88
500	2026-01-13 05:00:00	261.05	0.003069	0.80
501	2026-01-14 05:00:00	259.96	-0.004184	-1.09

Tiếp theo, ta vẽ biểu đồ HistogramQ-Q Plot để kiểm tra phân phối chuẩn một cách trực quan.

# --- 3. Plot Histograms, Q-Q Plot ---
# Create a figure with 2 rows and 2 columns
plt.figure(figsize=(12, 10))

# --- GBM MODEL (LOG RETURNS) ---
# Plot 1: Histogram for Log Returns
plt.subplot(2, 2, 1)
x_vals_log = np.linspace(log_returns.min(), log_returns.max(), 100)
# Use mu and sigma calculated from log_returns
mu_l, sigma_l = stats.norm.fit(log_returns)
pdf_vals_log = stats.norm.pdf(x_vals_log, mu_l, sigma_l)

plt.hist(log_returns, bins=50, density=True, alpha=0.6, color='blue', label="Empirical Log Returns")
plt.plot(x_vals_log, pdf_vals_log, 'r--', lw=2, label='Normal Fit (GBM)')
plt.title(f"GBM Model: Log Returns Distribution ({ticker})", fontsize=12)
plt.xlabel("Log Returns", fontsize=10)
plt.ylabel("Density", fontsize=10)
plt.legend()
plt.grid(axis='y', alpha=0.3)

# Plot 2: Q-Q Plot for Log Returns
plt.subplot(2, 2, 2)
log_returns_std = (log_returns - log_returns.mean()) / log_returns.std()
stats.probplot(log_returns_std, dist="norm", plot=plt)
plt.title("Q-Q Plot: Log Returns (Check for Fat Tails)", fontsize=12)
plt.xlabel("Theoretical Quantiles", fontsize=10)
plt.ylabel("Ordered Values", fontsize=10)
plt.grid(True, alpha=0.3)

# --- BACHELIER MODEL (PRICE DIFFERENCES) ---
# Plot 3: Histogram for Price Differences
plt.subplot(2, 2, 3)
x_vals_abs = np.linspace(price_diffs.min(), price_diffs.max(), 100)
# Use mu and sigma calculated from price_diffs
mu_a, sigma_a = stats.norm.fit(price_diffs)
pdf_vals_abs = stats.norm.pdf(x_vals_abs, mu_a, sigma_a)

plt.hist(price_diffs, bins=50, density=True, alpha=0.6, color='green', label="Empirical Price Diffs")
plt.plot(x_vals_abs, pdf_vals_abs, 'r--', lw=2, label='Normal Fit (Bachelier)')
plt.title(f"Bachelier Model: Absolute Price Diffs ({ticker})", fontsize=12)
plt.xlabel("Price Difference ($)", fontsize=10)
plt.ylabel("Density", fontsize=10)
plt.legend()
plt.grid(axis='y', alpha=0.3)

# Plot 4: Q-Q Plot for Price Differences
plt.subplot(2, 2, 4)
price_diffs_std = (price_diffs - price_diffs.mean()) / price_diffs.std()
stats.probplot(price_diffs_std, dist="norm", plot=plt)
plt.title("Q-Q Plot: Price Diffs (Check for Fat Tails)", fontsize=12)
plt.xlabel("Theoretical Quantiles", fontsize=10)
plt.ylabel("Ordered Values", fontsize=10)
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

Kiểm định phân phối chuẩn bằng Histogram và Q-Q Plot cho cổ phiếu AAPL (Apple Inc.)
Hình 3: Kiểm định phân phối chuẩn bằng Histogram và Q-Q Plot cho cổ phiếu AAPL (Apple Inc.), giai đoạn 01/2024 — 01/2026.

Biểu đồ cho thấy dữ liệu thực tế của Apple (cả absolute returnlog-return) không tuân theo phân phối chuẩn một cách hoàn hảo: ta thấy hiện tượng “đuôi béo” (Fat Tails)“đỉnh nhọn” (Leptokurtosis).

Biểu đồ Histogram cho thấy phần đỉnh vọt lên cao và nhọn nhiều hơn đường màu đỏ. Quan trọng hơn, phần đuôi ở biểu đồ Q-Q Plot bị uốn cong ra xa đường chéo \(45^\circ\) ở cả hai đầu, minh chứng cho việc các biến động cực đoan (sập sàn hoặc tăng trần) xảy ra thường xuyên hơn so với dự báo lý thuyết.

Cuối cùng, ta tính toán các chỉ số thống kê để kiểm tra phân phối chuẩn.

# --- 4. Calculate Statistics ---
def display_stats(data, label):
    s = stats.skew(data)
    k = stats.kurtosis(data)
    jb_stat, p_val = stats.jarque_bera(data)
    
    print(f"--- Statistics: {label} ---")
    print(f"Skewness:            {s:.4f}")
    print(f"Kurtosis:            {k:.4f}")
    print(f"Jarque-Bera P-value: {p_val:.4e}")
    print("-" * 40)

# Print metrics for comparison
print(f"--- Normality Test: {ticker} ---")
display_stats(log_returns, "GBM (Lognormal Fit)")
display_stats(price_diffs, "Bachelier (Normal Fit)")
--- Normality Test: AAPL ---
--- Statistics: GBM (Lognormal Fit) ---
Skewness:            0.6364
Kurtosis:            11.7107
Jarque-Bera P-value: 0.0000e+00
----------------------------------------
--- Statistics: Bachelier (Normal Fit) ---
Skewness:            0.2830
Kurtosis:            8.0102
Jarque-Bera P-value: 0.0000e+00
----------------------------------------

Cả hai mô hình đều cho thấy:

  • Skewness lệch dương: GBM \((0.6364)\) có độ lệch dương rõ rệt, Bachelier \((0.2830)\) đối xứng hơn một chút nhưng vẫn lệch dương. Điều này cho thấy trong giai đoạn này, Apple có xu hướng xuất hiện các phiên tăng điểm đột biến mạnh nhiều hơn là các phiên giảm sâu.

  • Kurtosis cực cao: Chỉ số Kurtosis của GBM là \(11.71\) và Bachelier là \(8.01\). Trong phân phối chuẩn lý tưởng, chỉ số này (excess kurtosis) bằng \(0\). Điều này xác nhận hiện tượng “đỉnh nhọn” mà ta quan sát được trong Histogram.

  • Kiểm định Jarque-Bera: \(P-value = 0.0000e+00\) gần như bằng \(0\), tương đương việc bác bỏ hoàn toàn giả thuyết cho rằng lợi nhuận của Apple tuân theo phân phối chuẩn.

Kiểm chứng với cổ phiếu khác

Không có một mô hình nào (dù là GBM hay Bachelier) hoàn toàn đúng cho mọi loại tài sản. Cổ phiếu công nghệ có thể vi phạm các giả định phân phối chuẩn do biến động cực đoan, nhưng cổ phiếu năng lượng, tài chính hay y tế thì sao? Chúng ta cùng thực hiện kiểm định cho các mã cổ phiếu còn lại.


Kiểm định phân phối chuẩn bằng Histogram cho cổ phiếu AAPL (Apple Inc.), JPM (JPMorgan Chase & Co.), JNJ (Johnson & Johnson), XOM (Exxon Mobil Corp.)
Hình 4: Kiểm định phân phối chuẩn bằng Histogram cho cổ phiếu AAPL (Apple Inc.), JPM (JPMorgan Chase & Co.), JNJ (Johnson & Johnson), XOM (Exxon Mobil Corp.), giai đoạn 01/2024 — 01/2026.

Nhìn vào biểu đồ, ta thấy cả 4 cổ phiếu đều có hiện tượng chung “đuôi béo” và “đỉnh nhọn”.

  • AAPL (Apple Inc.): Cổ phiếu công nghệ (Technology) tăng trưởng mạnh. AAPL có đỉnh nhọn nhất, và độ lệch dương lớn cho thấy có nhiều phiên tăng điểm bùng nổ do tin tức sản phẩm hoặc kết quả kinh doanh.

  • JPM (JPMorgan Chase & Co.): Cổ phiếu tài chính (Financials) nhạy cảm với lãi suất và các chu kỳ kinh tế. Histogram của JPM có đuôi lệch trái so với AAPL, phản ánh các đợt sụt giảm mạnh khi thị trường lo ngại về lãi suất hoặc khủng hoảng tài chính.

  • JNJ (Johnson & Johnson): Cổ phiếu y tế (Healthcare) biến động thấp, cổ tức ổn định. JNJ là cổ phiếu phòng thủ, thường được kỳ vọng bám sát đường cong phân phối chuẩn nhất (đỉnh bớt nhọn và đuôi ngắn hơn).

  • XOM (Exxon Mobil Corp.): Cổ phiếu năng lượng (Energy) phụ thuộc vào giá dầu thô, biến động rất cao và không ổn định. Histogram của XOM mặc dù ít nhọn nhưng lại có độ lệch trái lớn nhất \((Skewness = -0.63)\) phản ánh sự biến động (volatility) rất lớn.

Việc cả 4 mã cổ phiếu đều vi phạm giả thuyết phân phối chuẩn cảnh báo các nhà đầu tư rằng: thị trường thực tế luôn có rủi ro và các cú sốc ngẫu nhiên mà các mô hình toán học không thể bao quát hết được. Nếu chỉ dựa vào phân phối chuẩn để quản trị rủi ro, họ sẽ đánh giá thấp khả năng xảy ra các sự kiện sụp đổ hoặc tăng trưởng đột biến.

1.4. Kiểm chứng với Excel/VBA

Bạn có thể download file Excel bên dưới để bắt đầu thực hành.

Download Excel workbook (.xlsx/.xlsm)

Worksheet 1 “Normality Test”: Trang tính này phân tích lợi nhuận của cổ phiếu Apple để kiểm tra tính phân phối chuẩn thông qua biểu đồ Histogram, các chỉ số Skewness, Kurtosis và kiểm định Jarque-Bera.

Công thức tại ô C8D8 như sau.

=B8 - B7
=LN(B8/B7)

Trong đó B8 là giá ngày hôm nay, B7 là giá ngày hôm trước. Đây chính là công thức tính absolute returnlog-return theo ngày (daily) cho cổ phiếu Apple.

Từ dữ liệu lợi nhuận, ta vẽ Histogram thủ công trong Excel.


Kiểm định phân phối chuẩn bằng Histogram cho cổ phiếu AAPL (Apple Inc.) trong Excel
Hình 5: Kiểm định phân phối chuẩn bằng Histogram cho cổ phiếu AAPL (Apple Inc.), giai đoạn 01/2024 — 01/2026 trong Excel.

Biểu đồ cho thấy log-return của Apple không tuân theo phân phối chuẩn một cách hoàn hảo mà có “đỉnh nhọn” và “đuôi béo” về hai phía.

Trong Excel, để tính Skewness, Kurtosis ta chỉ cần gọi trực tiếp hàm SKEW()KURT(). Kiểm định Jarque-Bera phức tạp hơn, nhưng ta có thể tính đại lượng thống kê (test statistic) theo công thức \(JB = \frac{n}{6} \left( S^2 + \frac{1}{4}(K)^2 \right)\) trong đó \(n\) là số lượng mẫu, \(S\) là Skewness, \(K\) là Excess Kurtosis; và dựa vào giá trị này để đưa ra kết luận. Khác với \(P-value\) như trong phần Python, \(B\) càng gần \(0\), dữ liệu càng gần với phân phối chuẩn.


Công thức Excel tính Skewness, Kurtosis, Jarque-Bera cho cổ phiếu AAPL (Apple Inc.), giai đoạn 01/2024 — 01/2026
Hình 6: Công thức Excel tính Skewness, Kurtosis, Jarque-Bera cho cổ phiếu AAPL (Apple Inc.), giai đoạn 01/2024 — 01/2026.

Từ Hình 6, ta bác bỏ giả thuyết dữ liệu thực tế của Apple là chuẩn, với Skewness, Kurtosis, và đại lượng thống kê Jarque-Bera đều lệch xa \(0\).

2. Hiệu chuẩn mô hình — Calibration

2.1. Hiệu chuẩn là gì?

Sau khi đã “khám sức khỏe” cho dữ liệu ở Phần 1 và chấp nhận những sai số nhỏ, chúng ta sẽ bước vào giai đoạn quan trọng nhất hiệu chuẩn (calibration).

Hiệu chuẩn thực chất là việc chúng ta nhìn vào dữ liệu quá khứ để tìm ra bộ thông số cốt lõi nhằm thiết lập “động cơ” cho mô phỏng tương lai. Hãy tưởng tượng mỗi cổ phiếu là một vận động viên chạy marathon. Để dự đoán vận động viên đó sẽ chạy được bao xa trong tương lai, bạn cần biết “tốc độ trung bình” và “nhịp thở/độ bền” của họ dựa trên các cuộc đua trước đó. Trong mô hình BachelierGBM, chúng ta cần tìm \(\mu\) (drift) và \(\sigma\) (volatility).

  • Drift \((\mu)\) – “động cơ” đẩy giá đi lên. Drift đại diện cho xu hướng tăng trưởng trung bình của cổ phiếu. Nếu bạn giữ cổ phiếu này trong dài hạn, bạn kỳ vọng nó sinh lời bao nhiêu mỗi năm? Nếu \(\mu\) dương, giá cổ phiếu có xu hướng tăng trong dài hạn và ngược lại.

  • Volatility \((\sigma)\) – “sóng” rung lắc. Volatility chính là thước đo của rủi ro, cho biết giá cổ phiếu thường xuyên “nhảy múa” quanh mức trung bình mạnh đến mức nào. \(\sigma\) càng cao, các đường mô phỏng sẽ càng “zic-zac” và lan tỏa rộng, thể hiện sự bất ổn cao của thị trường.

2.2. Phương pháp hiệu chuẩn

Trong bài toán này, chúng ta có hai hướng tiếp cận chính để tìm ra \(\mu\) và \(\sigma\). Một bên dựa trên tư duy tài chính thực dụng (Financial Approach), một bên dựa trên nền tảng thống kê toán học khắt khe (MLE).

Trung bình & độ lệch chuẩn (Financial Approach)

Đây là cách tiếp cận phổ biến nhất trong giới tài chính truyền thống vì nó trực quan, dễ hiểu và dễ thực hiện. Các con số \(\mu, \sigma\) đầu ra khớp với những gì nhà đầu tư nhìn thấy trên các trang tin chứng khoán như Bloomberg hay Reuters.

Cách tiếp cận này coi absolute return (Bachelier) hay log-return (GBM) như một chuỗi thời gian (time series) thông thường và áp dụng các công cụ thống kê mô tả cơ bản để tính trung bìnhđộ lệch chuẩn.

Các bước thực hiện:

  • Tính lợi nhuận hàng ngày: \(r_i = S_i - S_{i-1}\) hoặc \(r_i = \ln(S_i / S_{i-1})\).
  • Tính trung bình mẫu \((\bar{r})\): Đại diện cho lợi nhuận kỳ vọng hàng ngày của nhà đầu tư.
  • Tính độ lệch chuẩn mẫu \((s)\): Đại diện cho rủi ro hàng ngày của nhà đầu tư.
  • Annualization: Đây là bước rất dễ nhầm lẫn. Dữ liệu chúng ta vừa tính là theo ngày (daily), nhưng trong mô hình các tham số thường mặc định theo chuẩn đơn vị năm (annually). Để chuyển đổi, chúng ta cần một chút “phép thuật” toán học (\(252\) thể hiện số ngày giao dịch trong một năm):
\[\mu_{financial} = \text{Average}(r_i) \times 252\] \[\sigma_{financial} = \text{Stdev}(r_i) \times \sqrt{252}\]

Ước lượng hợp lý tối đa (MLE Approach)

MLE (Maximum Likelihood Estimation) là một phương pháp thống kê nhằm tìm ra các tham số sao cho “xác suất xảy ra dữ liệu đã quan sát được là cao nhất”.

Các bước thực hiện:

  • Thiết lập hàm Log-Likelihood: Chúng ta xây dựng một hàm toán học dựa trên hàm mật độ xác suất (Probability Density Function) của phân phối chuẩn cho toàn bộ chuỗi dữ liệu.
  • Tối ưu hóa: Bằng cách lấy đạo hàm và giải phương trình, MLE đưa ra các ước lượng “không chệch” (unbiased) nhất về mặt toán học.

Quá trình MLE khớp hình chuông lý thuyết với dữ liệu thực tế của cổ phiếu AAPL (Apple Inc.)
Hình 7: Minh họa quá trình MLE "khớp" hình chuông lý thuyết với dữ liệu thực tế của cổ phiếu AAPL (Apple Inc.)

Bạn hãy tưởng tượng bạn có dữ liệu giá chứng khoán với phân phối thực tế như một dải núi nhấp nhô. Bạn biết dải núi đó gần với phân phối chuẩn (hình chuông), nhưng bạn không biết cái chuông này nên rộng bao nhiêu và nằm ở đâu.

Tham số \(\mu\) điều chỉnh cái chuông sang trái hoặc sang phải. Tham số \(\sigma\) điều chỉnh cái chuông béo ra hoặc gầy lại. MLE chính là quá trình bạn “xoay núm vặn” \(\mu\) và \(\sigma\) sao cho cái hình chuông lý thuyết “khớp” nhất với dữ liệu thực tế bạn đang có (Hình 7).

2.3. Hiệu chuẩn với Python

Hàm data.mean()data.std() là cách tiếp cận “số học” đơn giản mà chúng ta đã học ở trường với mean() để tính trung bìnhstd() để tính độ lệch chuẩn.

Hàm stats.norm.fit(data) chính là nơi phép màu MLE xảy ra. Hàm này sẽ tự động “xoay núm vặn” để tìm ra \(\mu\) và \(\sigma\) sao cho đường cong lý thuyết khớp nhất với biểu đồ thực tế của bạn.

# --- 5. Calibrattion ---
def display_stats(data, label):
    # --- Financial Approach ---
    mean_annual = data.mean() * 252
    std_annual = data.std() * np.sqrt(252)
    
    # ---  MLE Approach ---
    mu, sigma = stats.norm.fit(data)
    mu_annual = mu * 252
    sigma_annual = sigma * np.sqrt(252)
    
    results = pd.DataFrame({
        'Metric': ['Annual Drift (mu)', 'Annual Volatility (sigma)'],
        'Financial': [mean_annual, std_annual],
        'MLE': [mu_annual, sigma_annual]
    })
    
    print(f"--- {label} ---")
    print(results.to_string(index=False))
    print("-" * 48)

# Print metrics for comparison
print(f"--- Calibrattion: {ticker} ---")
display_stats(log_returns, "GBM (Lognormal Fit)")
display_stats(price_diffs, "Bachelier (Normal Fit)")
--- Calibrattion: AAPL ---
--- GBM (Lognormal Fit) ---
                    Metric  Financial       MLE
         Annual Drift (mu)   0.174843  0.174843
 Annual Volatility (sigma)   0.276458  0.276182
------------------------------------------------
--- Bachelier (Normal Fit) ---
                    Metric  Financial        MLE
         Annual Drift (mu)  38.393533  38.393533
 Annual Volatility (sigma)  58.344192  58.285935
------------------------------------------------

Kết quả cho thấy sự tương đồng rất lớn giữa phương pháp tài chính (Financial)ước lượng hợp lý tối đa (MLE). Sự chênh lệch \(\sigma\) cực nhỏ giữa hai phương pháp (chỉ \(0.0003\) với GBM, và \(0.058\) với Bachelier) cho thấy dữ liệu thực tế đang tuân thủ khá tốt quy luật toán học mà chúng ta đặt ra. Điều này cho phép chúng ta tự tin sử dụng mô hình mà không lo ngại sai số quá lớn.

Với drift \((\mu)\) đạt mức \(17.48\%\) và volatility \((\sigma)\) hàng năm khoảng \(27.6\%\), chúng ta đã có một bộ “thông số” đáng tin cậy để thiết lập “động cơ” cho các kịch bản mô phỏng Monte-Carlo ở bước tiếp theo.

2.4. Hiệu chuẩn với Excel/VBA

Worksheet 2 “Normality Test”: Trang tính này ước lượng các tham số drift \((\mu)\) và volatility \((\sigma)\) từ dữ liệu thực tế của Apple.

Phương pháp tài chính (Financial), để tính \(\mu\), \(\sigma\) ta chỉ cần gọi trực tiếp hàm AVERAGE()STDEV.S() trong Excel. Đây chính là trung bìnhđộ lệch chuẩn mẫu theo ngày, ta cần chuyển về đơn vị theo năm.


Công thức Excel chuyển đổi lợi nhuận kỳ vọng từ ngày sang năm Công thức Excel chuyển đổi độ biến động từ ngày sang năm
Hình 8: Công thức Excel chuyển mu, sigma từ ngày (daily) sang năm (annually).

Phương pháp ước lượng hợp lý tối đa (MLE) yêu cầu chạy tính toán nhiều lần để tìm ra bộ tham số tối ưu. Lập trình là bắt buộc để thực thi. Tuy nhiên, Excel có công cụ Solver có thể làm điều này.

Đây là một quá trình “thử và sai” có hệ thống.

  • Với mỗi dữ liệu thực tế của Apple \((-1.2\%, 0.5\%...)\), Excel dùng hàm mật độ xác suất (Probability Density Function) để tính xem điểm đó “cao” bao nhiêu trên đường cong lý thuyết.

  • Chúng ta cộng dồn LN() của chúng. Nếu tổng này càng lớn, nghĩa là đường cong lý thuyết của bạn đang “giải thích” rất tốt dữ liệu thực tế.

  • Solver trong Excel sẽ liên tục thay đổi \(\mu\), \(\sigma\) cho đến khi tổng log-chiều cao (Log-Likelihood) đạt mức cao nhất có thể. Đó chính là lúc đường cong “khớp” nhất.

Ta sử dụng hàm mật độ xác suất (Probability Density Function) tại ô N4Q4 như sau.

=NORM.DIST(C4, $K$4, $K$5, FALSE)
=NORM.DIST(D4, $L$4, $L$5, FALSE)

Ta thiết lập giá trị dự đoán ban đầu của \(\mu\), \(\sigma\) tại K4:K5 cho BachelierL4:L5 cho GBM (ô màu cam). Tổng Log-Likelihood được tính sẵn với bộ tham số dự đoán tại ô K6L6.


Hiệu chuẩn mu, sigma bằng phương pháp MLE với Solver trong Excel Hiệu chuẩn mu, sigma bằng phương pháp MLE với Solver trong Excel
Hình 9: Giá trị dự đoán mu, sigma và kết quả sau khi chạy Solver bằng phương pháp MLE trong Excel.

Để chạy Solver:

  • Vào thẻ Data > Solver (nếu chưa có, bạn cần kích hoạt trong File > Options > Add-ins).
  • Set Objective: Chọn ô $L$6 (chứa Log-Likelihood tính sẵn).
  • To : Chọn Max.
  • By Changing Variable Cells: Chọn vùng $IK$4:$K$5 (chứa \(\mu\) và \(\sigma\) dự đoán).
  • Constraints: Nhấn Add thêm điều kiện $K$5 >= 0.0001 (vì biến động không thể bằng \(0\) hoặc âm).
  • Nhấn Solve.

Cả hai phương pháp hiệu chuẩn cho ta kết quả ước lượng \(\mu\), \(\sigma\) gần như giống hệt với phần Python.

3. Dự báo — Predicting

Nếu ở Phần 1 và 2 chúng ta đã hiểu về quá khứ, thì ở Phần 3 này, chúng ta sẽ dùng dữ liệu đó để xây dựng một “cỗ máy dự báo tương lai”.

Tại sao không dự báo một con số duy nhất cho giá cổ phiếu Apple vào ngày mai?. Câu trả lời là thị trường không phải là một bài toán cộng giản đơn. Giá cổ phiếu là sự kết hợp của xu hướng dài hạn và những cú va đập ngẫu nhiên đến từ tin tức, tâm lý đám đông, và các sự kiện vĩ mô.

Thay vì đoán mò một con số, chúng ta giả lập 10 000 “kịch bản tương lai” khác nhau: có kịch bản AAPL tăng vọt vì ra sản phẩm mới, có kịch bản AAPL lại giảm sâu vì suy thoái kinh tế. Kết quả là bạn không chỉ có một giá mục tiêu, mà là một “phễu xác suất” giúp bạn biết được vùng giá nào dễ xảy ra nhất.

3.1. Dự báo với Python

Trong phần này ta chỉ tập trung vào mô hình GBM thay vì cả hai mô hình như các phần trước. Tham số hiệu chuẩn được tính lại nhờ hàm stats.norm.fit và được chuyển về đơn vị năm mu_paramm, sigma_param.

# --- 6 Simulation Parameters ---
S_initial = df_prices['close'].iloc[-1]
dt = 1 / 252                 # Time step (1 day)
n_steps = 90                 # 90 trading days
n_simulations = 10000        # 10000 scenarios

# Parameters from calibration section
mu, sigma = stats.norm.fit(log_returns) # MLE
mu_param = mu * 252        
sigma_param = sigma * np.sqrt(252)   

Để mô phỏng bước đi của giá cổ phiếu trong 90 ngày tới, chúng ta chia nhỏ thành từng bước nhỏ (mỗi bước là 1 ngày). Ta áp dụng công thức tính giá như trong Bài 4. Brownian Motion.

\[S_{t+\Delta t} = S_t e^{(\mu - \frac{\sigma^2}{2})t + \sigma \Delta W_t}\]

Tuy nhiên, chúng ta không chỉ mô phỏng giá ở vị trí cuối cùng \(S_T\) mà cần mô phỏng toàn bộ đường đi của giá cổ phiếu. Bước thời gian ở đây là \(\Delta t\) (1 ngày) thay vì cả khoảng thời gian \(T\).

# --- 7. Projection Random Paths ---
dW_t = np.random.normal(loc=0, scale=np.sqrt(dt), size=(n_steps, n_simulations))

# Calculate daily growth factors using GBM discretized formula
# Formula: exp((mu - 0.5 * sigma^2) * dt + sigma * sqrt(dt) * Z)
daily_growth = np.exp((mu_param - 0.5 * sigma_param**2) * dt + 
                       sigma_param * dW_t)

# Accumulate growth over time to get price paths
simulated_paths = S_initial * np.cumprod(daily_growth, axis=0)

# Prepend the starting price S0 to each simulation path
simulated_paths = np.vstack([np.full(n_simulations, S_initial), simulated_paths])

Trước khi muốn biết tương lai như nào, bạn phải biết mình đang đứng ở đâu. Chúng ta vẽ biểu đồ giá quá khứ cho history_window = 252 ngày trước để có cái nhìn trực quan nhất.

# --- 8. Visualization ---
plt.figure(figsize=(10, 6))

# Plot historical data
history_window = 252    #252 days of history
historical_segment = df_prices['close'].iloc[-history_window:]

history_index = np.arange(-(history_window - 1), 1) #index from -59 to 0  

plt.plot(history_index, historical_segment, color='blue', lw=1.5, label='Historical Price')

Thay vì chỉ vẽ một đường dự báo duy nhất, chúng ta vẽ ra 100 kịch bản ngẫu nhiên với vòng lặp for i in range(100). Ý nghĩa ở đây là: tôi không biết chính xác giá sẽ đi đường nào, nhưng đây là tất cả những con đường khả thi mà AAPL có thể đi qua.

# Plot simulation paths
future_index = np.arange(0, n_steps + 1) # Align projection with history

for i in range(100):
    plt.plot(future_index, simulated_paths[:, i], color='red', lw=0.5, alpha=0.15)

# Plot confidence intervals
median_path = np.percentile(simulated_paths, 50, axis=1)
p5 = np.percentile(simulated_paths, 5, axis=1)
p95 = np.percentile(simulated_paths, 95, axis=1)

plt.plot(future_index, median_path, color='red', lw=1, linestyle='--', label='Forecast Median (P50)')
plt.fill_between(future_index, p5, p95, color='red', alpha=0.1, label='90% Probability Cone')

plt.axvline(x=0, color='grey', linestyle='--', alpha=0.5, label='Today')
plt.title(f"Historical Data and Monte Carlo Projection")
plt.xlabel("Days relative to Today")
plt.ylabel("Price")
plt.legend()
plt.show()

Dự đoán giá cổ phiếu AAPL (Apple Inc.) 90 ngày bằng mô hình GBM với Python
Hình 10: Minh họa dự đoán giá cổ phiếu AAPL (Apple Inc.) trong 90 ngày bằng mô hình GBM.

Biểu đồ phía trên là một bức tranh toàn cảnh về cổ phiếu Apple: từ thực tế trần trụi quá khứ (đường màu xanh) đến một tương lai đầy biến động được mô phỏng qua 10 000 kịch bản (những đường hồng nhạt).

  • Vạch đứt đoạn (Today): Đây là cột mốc phân chia giữa sự thật (bên trái) và giả thuyết (bên phải). Chúng ta chỉ có một quá khứ duy nhất, nhưng lại có vô vàn tương lai có thể xảy ra.

  • Phễu xác suất (90% Probability Cone): Vùng tô màu ghi nhạt chính là nơi mà mô hình tin rằng giá cổ phiếu Apple sẽ nằm trong đó với xác suất 90%. Bạn có thể thấy chiếc phễu này càng lúc càng loe rộng ra. Điều này phản ánh một quy luật tự nhiên trong tài chính: thời gian càng xa, sự bất định càng lớn.

  • Đường trung vị (Median): Đây là kịch bản “trung dung” nhất. Nó không quá lạc quan cũng không quá bi quan.

  • Đường ngoại lệ (Outliers): Hãy để ý có một vài đường hồng nhạt nằm hẳn ra ngoài vùng tô màu đậm. Đây chính là những kịch bản “thiên nga đen” (Black Swan) hoặc những cú đột phá bất ngờ. Dù xác suất thấp, nhưng mô hình nhắc chúng ta rằng: những điều không tưởng vẫn có thể xảy ra.

Giữa hàng ngàn kịch bản hỗn loạn, bạn sẽ chọn đặt cược vào đường trung vị ổn định hay chuẩn bị cho những kịch bản nằm ở rìa của chiếc phễu?

3.2. Dự báo với Excel/VBA

Worksheet 3 “Predicting”: Trang tính này thực hiện mô phỏng Monte Carlo dựa trên mô hình GBM để dự báo các kịch bản giá tiềm năng của cổ phiếu Apple trong tương lai.

Tham số \(\mu\), \(\sigma\) của mô hình GBM cùng giá cổ phiếu tại ngày cuối \(S_0\) được lấy từ trang tính “Calibration”. Từ các cột B:AY ta tạo 50 kịch bản đường đi khác nhau của giá cổ phiếu theo mô hình GBM.

Kịch bản đầu tiên, công thức tại ô B25 như sau.

=B24 * EXP(($B$2 - 0.5 * $B$3^2) * $B$5 + $B$3 * SQRT($B$5) * NORM.S.INV(RAND()))

Hàm NORM.S.INV(RAND()) chỉ giúp sinh số ngẫu nhiên tuân theo phân phối chuẩn hóa (Standard Normal Distribution) với \(\mu = 0\), \(\sigma = 1\), chứ không phải phân phối chuẩn (Normal Distribution) với \(\mu\), \(\sigma\) bất kỳ.

Để tạo \(\Delta W_t \sim N(0, \Delta t)\) — phân phối chuẩn với độ lệch chuẩn \(\sqrt{\Delta t}\), ta cần viết lại công thức như sau \(\Delta W_t \sim \sqrt{\Delta t} \times N(0, 1)\). Số ngẫu nhiên \(Z \sim N(0, 1)\) được tạo ra nhờ Excel, và khi nhân với \(\sqrt{\Delta t}\) ta sẽ được \(\Delta W_t\). Ta có công thức tính giá GBM:

\[S_{t+\Delta t} = S_t e^{(\mu - \frac{\sigma^2}{2})t + \sigma \Delta W_t} = S_t e^{(\mu - \frac{\sigma^2}{2})t + \sigma \Delta t Z}\]

Dưới đây là biểu đồ 50 kịch bản ngẫu nhiên của giá cổ phiếu Apple. Bạn có thể nhấn phím F9 trong Excel để thay đổi các kịch bản ngẫu nhiên khác.


Dự đoán giá cổ phiếu AAPL (Apple Inc.) 90 ngày bằng mô hình GBM với Excel
Hình 11: Minh họa dự đoán giá cổ phiếu AAPL (Apple Inc.) trong 90 ngày bằng mô hình GBM trong Excel.

4. Tóm tắt và thảo luận

Trong bài viết này, chúng ta đã đưa mô hình BachelierGBM ra khỏi những trang sách giáo khoa để đối mặt với dữ liệu thực tế nghiệt ngã của thị trường.

  • Thực tế không bao giờ hoàn hảo: Bài học lớn nhất là dữ liệu thực tế luôn vi phạm giả định của mô hình. BachelierGBM giả định dữ liệu tuân theo phân phối chuẩn, nhưng thị trường luôn có hiện tượng “đuôi béo” (Fat Tails) — các sự kiện cực đoan xảy ra quá mức thường xuyên, và “đỉnh nhọn” (Leptokurtosis) — lợi nhuận thực tế thường bị lệch về một phía.

  • Dữ liệu là “DNA” của mô hình: Việc hiệu chuẩn (calibration) các tham số \(\mu\) (drift) và \(\sigma\) (volatility) từ dữ liệu thực tế giúp chúng ta tạo ra mô phỏng mang đậm “tính cách” riêng của cổ phiếu thay vì một con số ngẫu nhiên vô nghĩa.

  • Định lượng rủi ro: Thay vì dự đoán một mức giá mục tiêu duy nhất, phương pháp Monte Carlo cho phép chúng ta nhìn thấy hàng ngàn tương lai có thể xảy ra. “Phễu xác suất” (Probability Cone) giúp chúng ta xác định được “vùng an toàn” nơi 90% các kịch bản có thể xảy ra.

Chúng ta chấp nhận rằng BachelierGBM không hoàn hảo để đổi lấy sự đơn giản và khả năng tính toán nhanh chóng. Hiểu được rằng thực tế không tuân theo phân phối chuẩn chính là bước đầu tiên để bạn trở thành một nhà đầu tư thận trọng và thông minh hơn.

Hãy để lại câu hỏi tại phần bình luận hoặc trên FORUM để nhận được câu trả lời sớm nhất. Bạn đọc có thể ủng hộ blog qua BUY ME A COFFEE ở góc trên bên phải. Cảm ơn bạn đã tiếp thêm năng lượng để mình hoàn thiện những bài viết tâm huyết hơn mỗi ngày.

Cusdis

Đừng ngần ngại để lại bình luận nhé!
Bạn không cần tạo tài khoản, chỉ cần nhập tên và nội dung tin nhắn.