Drawdown Backtest
Fetch live portfolio from Hiro, classify holdings, backtest against major market crises, and generate an interactive dashboard + markdown summary.
Arguments
| Argument |
Default |
Description |
--periods LIST |
dotcom,gfc,covid |
Comma-separated crisis periods to test |
--skip-dashboard |
false |
Skip HTML dashboard generation and browser open |
Output Structure
Each run creates a timestamped folder in the current working directory:
drawdown-backtest-YYYY-MM-DD-HHMM/
├── drawdown-backtest-YYYY-MM-DD-HHMM-dashboard.html # Interactive Plotly dashboard
├── drawdown-backtest-YYYY-MM-DD-HHMM-summary.md # Markdown analysis
├── portfolio.json # Input snapshot for reproducibility
└── drawdown-backtest-YYYY-MM-DD-HHMM-data.json # Raw results for downstream use
Prerequisites
- Hiro MCP server connected to Claude with linked brokerage/investment accounts
- Python 3 with dependencies:
pip3 install yfinance pandas numpy plotly pytest
Workflow
Execute these 6 phases sequentially and autonomously. Do NOT ask the user for guidance between phases.
Phase 1: Fetch Portfolio from Hiro
- Call
mcp__hiro__list_accounts — filter to investment/brokerage accounts only
- For each investment account, call
mcp__hiro__list_holdings — paginate fully (check for cursor/next)
- For each holding, call
mcp__hiro__get_security using the security_id to get ticker, name, type
- Calculate weights:
holding_value / total_portfolio_value
- Handle edge cases:
- Cash positions: Exclude from backtest (weight=0)
- Negative cash / margin: Note in metadata but exclude from positions
- Mutual funds: Map to ETF equivalents (e.g., VITNX -> VTI, VFIAX -> VOO)
- Missing tickers: Use security name to infer, or flag for manual review
Write down every holding's ticker, name, value, and weight as you go — tool results may be cleared from context later.
Phase 2: Auto-Classify Holdings
Classify each holding into an asset class using ticker + security name. Use these patterns:
| Pattern |
Asset Class |
| SHV/BIL/SGOV, "Treasury Bill", "Money Market" |
Short-term bonds |
| TLT/SPTL/VGLT, "Long Treasury" |
Long-term bonds |
| TIP/LTPZ/VTIP, "TIPS", "Inflation Protected" |
TIPS |
| GLD/GLDM/SGOL/IAU, "Gold" |
Gold |
| DBC/PDBC/GSG, "Commodity" |
Commodities |
| GUNR/XLE, "Natural Resource" |
Natural resources |
| VEA/EFA/IEFA, "Intl Developed", "International Equity" |
Intl developed equity |
| IGOV/BWX, "Intl Bond", "International Treasury" |
Intl bonds |
| EEM/IEMG/DEM/DGS, "Emerging Market Equity" |
EM equity |
| EMB/EMLC/EBND, "Emerging Market Bond" |
EM bonds |
| VTI/SPY/VOO/VFIAX/VITNX, "US Equity", "Total Stock", "S&P 500" |
US equity |
Ticker ends in .T (8xxx.T = sogo shosha) |
Japanese equity |
| BTC-USD/ETH-USD, "Bitcoin", "Ethereum" |
Crypto |
| CCJ/SRUUF/URA, "Uranium" |
Uranium |
For ambiguous holdings, reason about them using the security name, type, and any other available context. This is where Claude adds value over a static mapping.
Phase 3: Build Proxy Mappings
Assign proxies per asset class per crisis period. Many securities didn't exist during earlier crises, so proxies provide historical approximations.
| Asset Class |
Dot-com (2000-2003) |
GFC (2007-2009) |
COVID (2020) |
| Short-term bonds |
_TBILL |
_TBILL |
_TBILL |
| Long-term bonds |
_LT_TREASURY |
TLT |
actual |
| TIPS |
_TIPS_PROXY |
_TIPS_PROXY |
actual |
| Gold |
GC=F |
GC=F |
actual or GC=F |
| Commodities |
_GSCI_PROXY |
DBC |
actual |
| Natural resources |
XLE |
XLE |
actual |
| Intl developed equity |
EFA |
EFA |
actual |
| Intl bonds |
_INTL_BOND_PROXY |
_INTL_BOND_PROXY |
actual |
| EM equity |
_EM_EQUITY_PROXY |
EEM |
actual |
| EM bonds |
_EM_BOND_PROXY |
_EM_BOND_PROXY |
actual |
| US equity |
SPY |
actual or SPY |
actual |
| Japanese equity |
actual (TSE tickers go back to 1990s) |
actual |
actual |
| Crypto |
_NO_DATA |
_NO_DATA |
actual |
| Uranium |
CCJ |
CCJ |
actual or CCJ |
Rules for "actual or PROXY": Use actual ticker if it existed during the period (check inception date from security metadata). Otherwise fall back to the proxy.
Phase 4: Run Backtest Script
Assemble the portfolio JSON from Phases 1-3:
{
"positions": [
{
"name": "SGOV (0-3M Treasury)",
"ticker": "SGOV",
"weight": 0.163,
"asset_class": "Short-term bonds",
"proxy_map": {"dotcom": "_TBILL", "gfc": "_TBILL", "covid": "_TBILL"}
}
],
"metadata": {
"source": "hiro",
"fetched_at": "2026-03-08T12:00:00",
"total_value": 9350000
}
}
Create the output directory:
OUTPUT_DIR="./drawdown-backtest-$(date +%Y-%m-%d-%H%M)"
mkdir -p "$OUTPUT_DIR"
Write portfolio JSON:
# Write the portfolio JSON to the output directory (use Write tool)
Parse --periods argument (default: dotcom,gfc,covid)
Run the backtest:
python3 ${CLAUDE_SKILL_DIR}/portfolio_drawdown_backtest.py \
--portfolio-json "$OUTPUT_DIR/portfolio.json" \
--output-dir "$OUTPUT_DIR" \
--periods dotcom,gfc,covid
Use a 5-minute timeout (yfinance can be slow).
Verify output: check that data.json and dashboard.html exist in the output directory.
Phase 5: Write Markdown Summary
- Read
data.json from the output directory
- Write
summary.md in the same output directory with these sections:
Structure of summary.md:
- Title + date
- Summary table: Crisis | Portfolio Max DD | S&P 500 Max DD | DD Reduction | Portfolio Return | S&P 500 Return
- Key Insights per period: Drawdown reduction, best/worst performers, best/worst asset classes
- Portfolio Resilience Assessment: Dynamically generated based on actual allocation weights — do NOT hardcode percentages. Calculate actual weights per asset class from the portfolio data and describe the defensive/risk characteristics based on what's actually in the portfolio.
- Methodology Notes:
- Buy-and-hold only — no rebalancing during the period
- Margin/leverage excluded — actual drawdowns would be slightly worse
- Simulated series use fixed seeds but are uncorrelated — may understate portfolio drawdown
- Point-in-time weights applied retroactively to historical data
- Proxy annotations shown per-security in the HTML dashboard tables
- Link to HTML dashboard
Phase 6: Open Dashboard (unless --skip-dashboard)
Open the HTML dashboard in the default browser:
open "$OUTPUT_DIR/dashboard.html"
Display completion summary to the user with:
- Output directory path
- Key numbers: portfolio max DD vs S&P 500 for each period
- Number of positions backtested
- Any data gaps or warnings
Important Notes
- Don't ask for workflow guidance — proceed through all 6 phases autonomously
- Paginate all Hiro API calls — always check for cursor/next and fetch ALL pages
- Be precise with numbers — never round amounts in data files
- Write down important data — Hiro tool results may be cleared from context. Record ticker, name, value, and weight for each holding immediately after fetching.
- Handle yfinance failures gracefully — some tickers may fail to download. The script handles this internally, but if the entire script fails, check for missing dependencies (
pip3 install yfinance pandas numpy plotly) and retry.
1---2name: drawdown-backtest3description: Fetches portfolio from Hiro, backtests against major market crises (dot-com, GFC, COVID), generates interactive dashboard + markdown summary.4---56# Drawdown Backtest78Fetch live portfolio from Hiro, classify holdings, backtest against major market crises, and generate an interactive dashboard + markdown summary.910## Arguments1112| Argument | Default | Description |13|----------|---------|-------------|14| `--periods LIST` | `dotcom,gfc,covid` | Comma-separated crisis periods to test |15| `--skip-dashboard` | false | Skip HTML dashboard generation and browser open |1617## Output Structure1819Each run creates a timestamped folder in the current working directory:2021```22drawdown-backtest-YYYY-MM-DD-HHMM/23├── drawdown-backtest-YYYY-MM-DD-HHMM-dashboard.html # Interactive Plotly dashboard24├── drawdown-backtest-YYYY-MM-DD-HHMM-summary.md # Markdown analysis25├── portfolio.json # Input snapshot for reproducibility26└── drawdown-backtest-YYYY-MM-DD-HHMM-data.json # Raw results for downstream use27```2829## Prerequisites3031- **Hiro MCP server** connected to Claude with linked brokerage/investment accounts32- **Python 3** with dependencies: `pip3 install yfinance pandas numpy plotly pytest`3334## Workflow3536Execute these 6 phases sequentially and autonomously. Do NOT ask the user for guidance between phases.3738### Phase 1: Fetch Portfolio from Hiro39401. Call `mcp__hiro__list_accounts` — filter to investment/brokerage accounts only412. For each investment account, call `mcp__hiro__list_holdings` — paginate fully (check for cursor/next)423. For each holding, call `mcp__hiro__get_security` using the security_id to get ticker, name, type434. Calculate weights: `holding_value / total_portfolio_value`445. Handle edge cases:45 - **Cash positions**: Exclude from backtest (weight=0)46 - **Negative cash / margin**: Note in metadata but exclude from positions47 - **Mutual funds**: Map to ETF equivalents (e.g., VITNX -> VTI, VFIAX -> VOO)48 - **Missing tickers**: Use security name to infer, or flag for manual review4950Write down every holding's ticker, name, value, and weight as you go — tool results may be cleared from context later.5152### Phase 2: Auto-Classify Holdings5354Classify each holding into an asset class using ticker + security name. Use these patterns:5556| Pattern | Asset Class |57|---------|-------------|58| SHV/BIL/SGOV, "Treasury Bill", "Money Market" | Short-term bonds |59| TLT/SPTL/VGLT, "Long Treasury" | Long-term bonds |60| TIP/LTPZ/VTIP, "TIPS", "Inflation Protected" | TIPS |61| GLD/GLDM/SGOL/IAU, "Gold" | Gold |62| DBC/PDBC/GSG, "Commodity" | Commodities |63| GUNR/XLE, "Natural Resource" | Natural resources |64| VEA/EFA/IEFA, "Intl Developed", "International Equity" | Intl developed equity |65| IGOV/BWX, "Intl Bond", "International Treasury" | Intl bonds |66| EEM/IEMG/DEM/DGS, "Emerging Market Equity" | EM equity |67| EMB/EMLC/EBND, "Emerging Market Bond" | EM bonds |68| VTI/SPY/VOO/VFIAX/VITNX, "US Equity", "Total Stock", "S&P 500" | US equity |69| Ticker ends in `.T` (8xxx.T = sogo shosha) | Japanese equity |70| BTC-USD/ETH-USD, "Bitcoin", "Ethereum" | Crypto |71| CCJ/SRUUF/URA, "Uranium" | Uranium |7273For ambiguous holdings, reason about them using the security name, type, and any other available context. This is where Claude adds value over a static mapping.7475### Phase 3: Build Proxy Mappings7677Assign proxies per asset class per crisis period. Many securities didn't exist during earlier crises, so proxies provide historical approximations.7879| Asset Class | Dot-com (2000-2003) | GFC (2007-2009) | COVID (2020) |80|-------------|---------------------|------------------|--------------|81| Short-term bonds | `_TBILL` | `_TBILL` | `_TBILL` |82| Long-term bonds | `_LT_TREASURY` | `TLT` | actual |83| TIPS | `_TIPS_PROXY` | `_TIPS_PROXY` | actual |84| Gold | `GC=F` | `GC=F` | actual or `GC=F` |85| Commodities | `_GSCI_PROXY` | `DBC` | actual |86| Natural resources | `XLE` | `XLE` | actual |87| Intl developed equity | `EFA` | `EFA` | actual |88| Intl bonds | `_INTL_BOND_PROXY` | `_INTL_BOND_PROXY` | actual |89| EM equity | `_EM_EQUITY_PROXY` | `EEM` | actual |90| EM bonds | `_EM_BOND_PROXY` | `_EM_BOND_PROXY` | actual |91| US equity | `SPY` | actual or `SPY` | actual |92| Japanese equity | actual (TSE tickers go back to 1990s) | actual | actual |93| Crypto | `_NO_DATA` | `_NO_DATA` | actual |94| Uranium | `CCJ` | `CCJ` | actual or `CCJ` |9596**Rules for "actual or PROXY":** Use actual ticker if it existed during the period (check inception date from security metadata). Otherwise fall back to the proxy.9798### Phase 4: Run Backtest Script991001. Assemble the portfolio JSON from Phases 1-3:101 ```json102 {103 "positions": [104 {105 "name": "SGOV (0-3M Treasury)",106 "ticker": "SGOV",107 "weight": 0.163,108 "asset_class": "Short-term bonds",109 "proxy_map": {"dotcom": "_TBILL", "gfc": "_TBILL", "covid": "_TBILL"}110 }111 ],112 "metadata": {113 "source": "hiro",114 "fetched_at": "2026-03-08T12:00:00",115 "total_value": 9350000116 }117 }118 ```1191202. Create the output directory:121 ```bash122 OUTPUT_DIR="./drawdown-backtest-$(date +%Y-%m-%d-%H%M)"123 mkdir -p "$OUTPUT_DIR"124 ```1251263. Write portfolio JSON:127 ```bash128 # Write the portfolio JSON to the output directory (use Write tool)129 ```1301314. Parse `--periods` argument (default: `dotcom,gfc,covid`)1321335. Run the backtest:134 ```bash135 python3 ${CLAUDE_SKILL_DIR}/portfolio_drawdown_backtest.py \136 --portfolio-json "$OUTPUT_DIR/portfolio.json" \137 --output-dir "$OUTPUT_DIR" \138 --periods dotcom,gfc,covid139 ```140 Use a 5-minute timeout (yfinance can be slow).1411426. Verify output: check that `data.json` and `dashboard.html` exist in the output directory.143144### Phase 5: Write Markdown Summary1451461. Read `data.json` from the output directory1472. Write `summary.md` in the same output directory with these sections:148149**Structure of summary.md:**150- **Title + date**151- **Summary table**: Crisis | Portfolio Max DD | S&P 500 Max DD | DD Reduction | Portfolio Return | S&P 500 Return152- **Key Insights per period**: Drawdown reduction, best/worst performers, best/worst asset classes153- **Portfolio Resilience Assessment**: Dynamically generated based on actual allocation weights — do NOT hardcode percentages. Calculate actual weights per asset class from the portfolio data and describe the defensive/risk characteristics based on what's actually in the portfolio.154- **Methodology Notes**:155 1. Buy-and-hold only — no rebalancing during the period156 2. Margin/leverage excluded — actual drawdowns would be slightly worse157 3. Simulated series use fixed seeds but are uncorrelated — may understate portfolio drawdown158 4. Point-in-time weights applied retroactively to historical data159 5. Proxy annotations shown per-security in the HTML dashboard tables160- **Link to HTML dashboard**161162### Phase 6: Open Dashboard (unless `--skip-dashboard`)1631641. Open the HTML dashboard in the default browser:165 ```bash166 open "$OUTPUT_DIR/dashboard.html"167 ```1681692. Display completion summary to the user with:170 - Output directory path171 - Key numbers: portfolio max DD vs S&P 500 for each period172 - Number of positions backtested173 - Any data gaps or warnings174175## Important Notes176177- **Don't ask for workflow guidance** — proceed through all 6 phases autonomously178- **Paginate all Hiro API calls** — always check for cursor/next and fetch ALL pages179- **Be precise with numbers** — never round amounts in data files180- **Write down important data** — Hiro tool results may be cleared from context. Record ticker, name, value, and weight for each holding immediately after fetching.181- **Handle yfinance failures gracefully** — some tickers may fail to download. The script handles this internally, but if the entire script fails, check for missing dependencies (`pip3 install yfinance pandas numpy plotly`) and retry.