</>
#
WEALTH CODE CURRICULUM

บทที่ 7: Break the Strategy
ทดสอบความเครียดโมเดลการลงทุน

"กลยุทธ์ที่แข็งแกร่งที่สุด คือกลยุทธ์ที่รอดพ้นจากสถานการณ์ที่เลวร้ายที่สุดได้"

เรียนรู้การทำ Stress Test ประเมินความเสี่ยงเชิงลึกของกลยุทธ์เมื่อต้องเจอกับเหตุการณ์ที่ไม่คาดฝัน (Black Swan)

{}

เส้นทางการเรียนรู้ (120 นาที)

10m

CASE

หายนะของโมเดลที่ดู "สมบูรณ์แบบ"

20m

CONCEPT

Stress Testing และ Black Swan

25m

CODE ALONG

การจำลองข้อมูลแบบสุ่ม (Randomness)

45m

BUILD

สร้าง Stress Test Dashboard

15m

DEFEND

ประเมินความเปราะบางของพอร์ต

05m

EXIT

ไม่มีใครทำนายอนาคตได้

!
CASE STUDY

หายนะของโมเดลที่ดู "สมบูรณ์แบบ"

ทำไมผล Backtest สวยหรู ถึงมักจะพังเมื่อเจอตลาดจริง?

นายทุนเชื่อมั่นใน Backtest

โมเดลนี้ชนะตลาดมาตลอด 10 ปีที่ผ่านมา กราฟพุ่งขึ้นสวยงาม ไม่มีทางเจ๊ง!

เกิดวิกฤต "หงส์ดำ" (Black Swan)

จู่ๆ ก็มีโรคระบาดระดับโลกเกิดขึ้น! (เหตุการณ์ที่ไม่เคยมีในข้อมูลอดีต) โมเดลรับมือไม่ทัน พอร์ตพังพินาศ

อดีตไม่ได้การันตีอนาคต! เราต้องทดสอบโมเดลกับ "สิ่งที่แย่กว่าที่เคยเจอ"

%
CONCEPT

Stress Testing และ Black Swan

Stress Testing

การจำลองสถานการณ์ตลาดที่รุนแรงกว่าปกติ เพื่อดูว่าพอร์ตฟอลิโอหรือกลยุทธ์ของเราจะ "พัง" หรือไม่ เช่น ตลาดหุ้นตก 50% รวดเดียว

Black Swan (หงส์ดำ)

เหตุการณ์ที่คาดไม่ถึงอย่างรุนแรง มีโอกาสเกิดน้อยมาก (Rare Event) แต่เมื่อเกิดแล้วจะส่งผลกระทบมหาศาล และเรามักจะหาเหตุผลอธิบายได้หลังเกิดไปแล้ว

</>
CODE ALONG

การจำลองข้อมูลแบบสุ่ม (Random Shocks)

สร้างไฟล์ lesson7_intro.py ลองใช้ Numpy จำลองความผันผวนแบบสุ่ม

lesson7_intro.py
import numpy as np
import pandas as pd

# ตั้งค่าฐาน: พอร์ตเริ่ม 100 บาท, ต้องการจำลอง 12 เดือน
initial_capital = 100
months = 12

# np.random.normal(เฉลี่ย, ความผันผวน, จำนวน) สร้างข้อมูลแบบสุ่ม (ระฆังคว่ำ)
# จำลองผลตอบแทนเฉลี่ย 1% ต่อเดือน ผันผวน 5%
random_returns = np.random.normal(0.01, 0.05, months)

# คำนวณมูลค่าพอร์ต: (1 + return) ทบต้นไปเรื่อยๆ
portfolio_values = initial_capital * (1 + random_returns).cumprod()

print(portfolio_values)
*
BUILD PROJECT (Step 1/5)

เตรียมโครงสร้าง Dashboard

สร้างไฟล์ stress_dash.py สร้างส่วนรับค่าพารามิเตอร์สำหรับทดสอบความเครียด

stress_dash.py
import streamlit as st
import pandas as pd
import numpy as np

st.set_page_config(layout="wide")
st.title("⚡ Stress Test Dashboard")

st.sidebar.header("ตั้งค่าสถานการณ์จำลอง")
# ให้ผู้ใช้เลือกระดับความรุนแรงของวิกฤต (ความผันผวน)
volatility_shock = st.sidebar.slider("ระดับความผันผวนของตลาด (%)", 5, 50, 15) / 100
# จำลองเหตุการณ์ Black Swan: ราคาตกฮวบในเดือนเดียว
black_swan_drop = st.sidebar.slider("Black Swan Drop (%)", 0, 80, 20) / 100
# เลือกว่าจะให้เกิดวิกฤตในเดือนไหน (สมมติ 12 เดือน)
crash_month = st.sidebar.slider("เดือนที่เกิดวิกฤต", 1, 12, 6)
*
BUILD PROJECT (Step 2/5)

สร้างข้อมูลจำลองพื้นฐาน

พิมพ์ต่อ: จำลองผลตอบแทนตลาด 12 เดือน แบบมีทั้งขึ้นและลง

stress_dash.py (cont.)
# --- 1. สร้าง Base Case (ตลาดปกติ) ---
months = 12
# สมมติว่าตลาดปกติให้ผลตอบแทนเฉลี่ย 1% ต่อเดือน ผันผวนน้อย
base_returns = np.random.normal(0.01, 0.02, months)

df = pd.DataFrame({"Month": list(range(1, months + 1))})
df["Base_Return"] = base_returns

# --- 2. นำข้อมูลจำลองไปใส่ใน DataFrame ---
# คำนวณการเติบโตแบบทบต้น (เริ่มที่ 100)
df["Base_Portfolio"] = 100 * (1 + df["Base_Return"]).cumprod()
{}
BUILD PROJECT (Step 3/5)

ใส่ Stress Shocks ลงไป

พิมพ์ต่อ: ตอนนี้เราจะเอาพารามิเตอร์ความเครียดที่ปรับใน Sidebar มาใส่ในโมเดล

stress_dash.py (cont.)
# --- 3. สร้าง Stress Case (ตลาดโหดร้าย) ---
# จำลองตลาดผันผวนสูง (ตามค่า volatility_shock ที่ปรับไว้)
stress_returns = np.random.normal(0.00, volatility_shock, months)

# แทรก Black Swan Drop ลงในเดือนที่กำหนด
# เดือนในโค้ดเริ่มที่ 0 ดังนั้นต้อง -1
stress_returns[crash_month - 1] = -black_swan_drop

df["Stress_Return"] = stress_returns
df["Stressed_Portfolio"] = 100 * (1 + df["Stress_Return"]).cumprod()
[]
BUILD PROJECT (Step 4/5)

หา Drawdown แบบเร็วๆ

พิมพ์ต่อ: เราอยากรู้ว่าจุดต่ำสุดของพอร์ต Stressed Portfolio ลงไปลึกแค่ไหน

stress_dash.py (cont.)
# --- 4. หา Max Drawdown ของ Stress Case ---
df["Peak"] = df["Stressed_Portfolio"].cummax()
df["Drawdown"] = (df["Stressed_Portfolio"] - df["Peak"]) / df["Peak"]
max_dd = df["Drawdown"].min()

# --- 5. แสดงผล UI ---
st.subheader("📉 เปรียบเทียบ: ตลาดปกติ VS ตลาดวิกฤต")
df_chart = df.set_index("Month")[["Base_Portfolio", "Stressed_Portfolio"]]
st.line_chart(df_chart)
*
BUILD PROJECT (Step 5/5)

แสดง Metrics สุดท้าย

พิมพ์โค้ดชุดสุดท้าย: สรุปตัวเลขเพื่อตัดสินความแข็งแกร่งของพอร์ต

stress_dash.py (cont.)
c1, c2, c3 = st.columns(3)
final_base = df["Base_Portfolio"].iloc[-1]
final_stress = df["Stressed_Portfolio"].iloc[-1]

c1.metric("มูลค่าสุดท้าย (Base)", f"฿{final_base:.2f}")
c2.metric("มูลค่าสุดท้าย (Stress)", f"฿{final_stress:.2f}",
          delta=f"{(final_stress - 100):.2f}%")
c3.metric("Max Drawdown (วิกฤต)", f"{max_dd*100:.2f}%")

if final_stress < 80:
    st.error("💥 พอร์ตนี้เปราะบางเกินไป! มูลค่าลดลงอย่างมีนัยสำคัญเมื่อเจอวิกฤต")
else:
    st.success("🛡️ พอร์ตมีความทนทาน (Resilient) แม้เจอตลาดโหดก็ยังรอด!")
*
BUILD PROJECT (Result)

ผลลัพธ์: Stress Test Dashboard

localhost:8501

Stress Test Dashboard

📉 เปรียบเทียบ: ตลาดปกติ VS ตลาดวิกฤต

Base_Portfolio vs Stressed_Portfolio
มูลค่าสุดท้าย (Base)
฿112.45
มูลค่าสุดท้าย (Stress)
฿68.30
-31.70%
Max Drawdown (วิกฤต)
-45.20%
💥 พอร์ตนี้เปราะบางเกินไป! มูลค่าลดลงอย่างมีนัยสำคัญเมื่อเจอวิกฤต
@
DEFEND

ประเมินความเปราะบางของพอร์ต

เราจะใช้ Stress Test เพื่อปรับปรุงกลยุทธ์ได้อย่างไร?

ปรับแก้กลยุทธ์ (Tweaking)

ถ้า Stress Test บอกว่าพอร์ตเราจะพังเมื่อตลาดหุ้นตก 40% เราก็ต้องกลับไปที่ Asset Allocation (บทที่ 5) เพื่อลดสัดส่วนหุ้นลง หรือหา Tactical Rule (บทที่ 6) ที่ไวขึ้น

เตรียมใจรับมือ (Psychology)

อย่างน้อยถ้าวิกฤตเกิดขึ้นจริง เราก็ "รู้ล่วงหน้า" แล้วว่าพอร์ตเราจะติดลบได้มากสุดแค่ไหน ทำให้เราไม่ตกใจกลัว (Panic Sell) เหมือนนาย A ในเคสก่อนๆ

%
EXIT

ไม่มีใครทำนายอนาคตได้

The Purpose of Models

"โมเดลทางการเงินทุกอัน 'ผิด' หมด
แต่บางอันก็มีประโยชน์"

(All models are wrong, but some are useful)
- George E. P. Box

"เราใช้โค้ดและข้อมูล ไม่ใช่เพื่อเดาว่าพรุ่งนี้หุ้นจะขึ้นหรือลง
แต่เพื่อสร้างพอร์ตที่พร้อมรับมือกับทุกสถานการณ์!"