MA2830 — Portfolio Hedging (Tableau Assignment)
Last updated: Nov 5, 2025, 23:00 SGTFinance Crash Course (Start Here)
Open/Close Crash Course
Motivation — Why hedge?
Ticker choice: Any liquid portfolio ticker with a sensible hedge is acceptable. Any tickers shown are examples only, not required.
- Big losses kill compounding. A hedge aims to reduce the worst falls so you can stay invested.
- We compare three paths: Unhedged, Static (fixed ratio), Dynamic (rolling beta).
Key ideas
- Return = today/prev − 1; Volatility = day‑to‑day wiggle; Drawdown = drop from prior peak.
- Correlation (−1→+1) and Beta (slope) tell how portfolio moves vs hedge.
- Static hedge: one fixed ratio daily (e.g., 1.0). Dynamic hedge: use recent beta each day.
- Sharpe proxy = Annual Return / Annual Volatility (return per unit of wiggle).
Math you’ll implement
- Daily return:
today/prev - 1 - Static hedged:
port_r - ratio * hedge_r - Dynamic hedged:
port_r - beta90_clamped * hedge_r - Equity:
100 * EXP(RUNNING_SUM(LOG(1 + r))) - Drawdown:
equity / WINDOW_MAX(equity) - 1 - Annual return:
EXP(AVG(LOG(1+r))*252)-1; Annual vol:STDEV(r)*SQRT(252)
Static vs Dynamic — When each wins / loses
Quick idea: Static = fixed hedge size (your Hedge Ratio parameter). Dynamic = use the recent Rolling Beta (90d) (clamped 0–1.5) as the hedge size.
Static tends to win when…
- Regime is steady: Relationship between portfolio & hedge is stable (beta ~ constant).
- Transaction costs / friction matter: You prefer no daily resizing.
- Signal is noisy: Rolling beta is jumpy; fixed size avoids over‑reacting.
Dynamic tends to win when…
- Regime drifts: Beta trends up/down across months; dynamic tracks it.
- Market mix changes: Correlation/volatility shift (e.g., rotations, macro shocks).
- Risk focus: You want the hedge to scale automatically with current sensitivity.
Dynamic can lose when…
- Very fast shocks: Beta estimate lags reality → mis‑sized hedge in the moment.
- Beta flips sign briefly: Noise can produce negative/huge betas — clamp (0–1.5) prevents blow‑ups.
- Overfitting short windows: Too small a window chases noise; keep ~90 days.
Read your dashboards: Use D2 Rolling Beta to spot regime drift; compare KPIs in D3 Static vs Dynamic; confirm protection in shock windows via D5 Stress.
Tiny example: If 90‑day beta ~0.6, Dynamic hedges less than Static 1.0, keeping more upside in calm periods. If beta rises toward 1.2, Dynamic hedges more, cutting drawdowns.
Upfront Assessment (Exploratory Visuals)
Goal: Build quick intuition about your chosen Portfolio and Hedge before hedging.
- Monthly Return Heatmap (Highlight Table) — rows = Year, cols = Month, color = Avg Monthly Return of Portfolio.
- Daily Return Box Plot by Month — distribution of Portfolio daily returns by Month.
- Daily Return Histogram — frequency of Portfolio daily returns.
- Rolling 90‑day Correlation Heatmap — Portfolio Return vs Hedge Return (−1→+1).
Upfront EDA — Guiding Questions (answer in captions)
- Which months are typically strongest/weakest for the Portfolio? (Heatmap)
- Which months show the widest spread or most outliers? (Box plot)
- Is the return distribution symmetric or skewed? (Histogram)
- When does correlation with the hedge strengthen or weaken? (Rolling correlation)
- Given these patterns, what hedge ratio might work better: lower, ~1.0, or higher?
Get Live Data (choose one)
A. Google Sheets (no code)
- Create tabs:
PORT,HEDGE,MERGE. Put tickers inPORT!A1andHEDGE!A1. - In
PORT!A3paste:=QUERY(GOOGLEFINANCE($A$1,"close",EDATE(TODAY(),-36),TODAY()), "select Col1,Col2 where Col2 is not null label Col1 'Date', Col2 'Close'",1) - Repeat same in
HEDGE!A3. - In
MERGE!A2paste:
Download MERGE as=QUERY({PORT!A3:B, ARRAYFORMULA(IFNA(VLOOKUP(PORT!A3:A, HEDGE!A3:B, 2, FALSE)))}, "select Col1,Col2,Col3 where Col3 is not null label Col1 'Date', Col2 'PortfolioClose', Col3 'HedgeClose'",0)prices_clean.csv.
B. Python (yfinance, ~10 lines)
import yfinance as yf, pandas as pd
PORT, HEDGE = "QQQ", "SPY"
end = pd.Timestamp.today().normalize(); start = end - pd.DateOffset(months=36)
def pull(t):
df = yf.download(t, start=start, end=end, interval="1d", auto_adjust=True)[["Close"]]
return df.reset_index().rename(columns={"Close":t})
df = pull(PORT).merge(pull(HEDGE), on="Date", how="inner")
df = df.rename(columns={PORT:"PortfolioClose", HEDGE:"HedgeClose"})
df.to_csv("prices_clean.csv", index=False)
Tableau Steps (calculated fields)
- Portfolio Return:
( SUM([PortfolioClose]) / LOOKUP(SUM([PortfolioClose]), -1) ) - 1 - Hedge Return:
( SUM([HedgeClose]) / LOOKUP(SUM([HedgeClose]), -1) ) - 1 - Parameter: Hedge Ratio (0.00–1.50, step 0.10)
- Hedged Return (Static):
[Portfolio Return] - [Hedge Ratio] * [Hedge Return] - Rolling Beta (90d):
WINDOW_COVAR([Portfolio Return],[Hedge Return], -89, 0) / WINDOW_VAR([Hedge Return], -89, 0) - Beta Clamped:
MIN(1.5, MAX(0, [Rolling Beta (90d)])) - Hedged Return (Dynamic):
[Portfolio Return] - [Beta Clamped] * [Hedge Return] - Equity (per series):
100 * EXP(RUNNING_SUM(LOG(1+[Return Series]))) - Drawdown (per series):
[Equity] / WINDOW_MAX([Equity]) - 1 - KPIs (per series): Ann Return, Ann Vol, Max DD, Sharpe = Return/Vol. Compute Using: Date.
Implementation (Tableau‑ready)
Purpose: exact fields and settings so your Tableau workbook works on first try.
0) Expected columns
- Date (continuous, ascending)
- PortfolioClose (your chosen asset’s close)
- HedgeClose (hedge benchmark’s close)
1) Daily returns
[Portfolio Return] = ( SUM([PortfolioClose]) / LOOKUP(SUM([PortfolioClose]), -1) ) - 1
[Hedge Return] = ( SUM([HedgeClose]) / LOOKUP(SUM([HedgeClose]), -1) ) - 1
Compute Using: Date (table down). First row null is expected.
2) Rolling beta (90‑day)
[Rolling Beta (90d)] =
WINDOW_COVAR( [Portfolio Return], [Hedge Return], -89, 0 )
/
WINDOW_VAR ( [Hedge Return], -89, 0 )
Place on Rows vs Date. Compute Using: Date. Optional regime color:
IF [Rolling Beta (90d)] > 1.2 THEN "High (>1.2)" ELSE "OK" END
3) Strategy returns
[Hedged Return (Static)] = [Portfolio Return] - [Hedge Ratio] * [Hedge Return]
[Hedged Return (Dynamic)] = [Portfolio Return] - [Rolling Beta (90d)] * [Hedge Return]
Create a numeric parameter Hedge Ratio (default 1.0), show parameter control.
4) Equity indexing (per series)
Equity = 100 * EXP( RUNNING_SUM( LOG( 1 + [Return Series] ) ) )
PortEquity→[Return Series] = [Portfolio Return]StaticEquity→[Return Series] = [Hedged Return (Static)]DynamicEquity→[Return Series] = [Hedged Return (Dynamic)]
Compute Using: Date. Ensure 1 + [Return Series] > 0.
5) What your dashboards should show
- D1 Overview: 3 equity lines (indexed to 100).
- D2 Rolling Beta: β line with regime highlight (>1.2 = red).
- D3 Static vs Dynamic: equity lines + KPIs; call out wins/losses.
- D4 Drawdown: 3 drawdown lines via
[Equity]/RUNNING_MAX([Equity]) − 1. - D5 Rolling Vol & Sharpe: 30‑day stdev and return/σ per series.
- D6 Decision: one clincher chart + three bullets you can defend.
- D7 Worst 5‑day loss:
Roll5d = WINDOW_SUM([Ret], −4, 0); chart the worst per series.
6) Common mistakes
- “Calculation contains errors” → add
SUM()when usingWINDOW_*with base fields. - Flat line at 100 → table calc not set to Date.
- Nulls across chart → missing closes or Date not ascending.
Deliverables
- Upfront EDA Dashboard — Monthly Return Heatmap, Box Plot by Month, Histogram, Rolling 90‑day Correlation Heatmap (captions answer EDA questions).
- Tableau Storyboard (5–7 story points = 5–7 dashboards). Set Story as default tab; turn off “Show Sheets as Tabs”. Export a PowerPoint (.pptx) of the Story.
- Required Dashboards (used as story points):
- D1 — Overview — Equity curves: Unhedged vs Static vs Dynamic; Hedge Ratio visible
- D2 — Rolling Beta — 90‑day beta line + callouts when beta < 0.3 or > 1.2
- D3 — Static vs Dynamic — KPIs table + one example where dynamic helps/hurts
- D4 — Sensitivity (Static) — ratios 0.5, 0.8, 1.0, 1.2 (small multiples or parameter view)
- D5 — Stress Windows — pick 2–3 shock windows; per‑window bars or mini‑curves
- D6 — Decision — one key chart + 3 bullets: Benefit, Cost, Limitation
- D7 — Optional — Worst 5‑day loss per strategy (bar chart)
- Workbook — .twbx (Tableau Packaged Workbook) ONLY; no Tableau Public links. The .twbx must contain all dashboards and the Story and open offline.
- Data file —
prices_clean.csv(Date, PortfolioClose, HedgeClose) - Write‑up — 1,500–2,000 words covering context, definitions, method, KPIs, sensitivity, stress windows, decision, and ≥3 credible references
Grading Rubric (100 pts)
| Component | Pts | Evidence |
|---|---|---|
| Live data pipeline | 15 | Working Sheet/script; correct 36‑month window. |
| Clean CSV | 10 | Correct columns; aligned dates; no blanks. |
| Upfront EDA visuals | 15 | Heatmap, box plot, histogram, rolling correlation heatmap; captions answer EDA questions. |
| Tableau calcs & settings | 25 | All fields; Compute Using = Date; Rolling window = 90; parameter visible. |
| Dashboard quality | 15 | Readable lines; % formatting; consistent legends; coherent design. |
| Storyboard (5–7 dashboards as story points) | 10 | Clear titles; concise captions; logical flow; Story default; PowerPoint (.pptx) exported; .twbx submitted. |
| Write‑up (1,500–2,000 words) | 10 | Context & definitions, method justified, sensitivity & stress test, numeric evidence, ≥3 credible references. |