Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Methods for acquiring, cleaning, and analyzing financial datasets for research
.claude/skills/brycewang-stanford-financial-data-analysis/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | -3% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 21% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 15% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 83% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 33% | 0% |
A practical skill for sourcing, processing, and analyzing financial data in academic research contexts. Covers data acquisition from public APIs, cleaning workflows, and standard analytical techniques used in empirical finance research.
| Source | Data Type | Access | Python Package | |--------|-----------|--------|---------------| | Yahoo Finance | Prices, fundamentals | Free | yfinance | | FRED (St. Louis Fed) | Macroeconomic indicators | Free (API key) | fredapi | | SEC EDGAR | Company filings (10-K, 10-Q) | Free | sec-edgar-downloader | | WRDS (Wharton) | CRSP, Compustat, IBES | University subscription | wrds | | Alpha Vantage | Real-time and historical prices | Free tier | alpha_vantage |
pythonimport yfinance as yf import pandas as pd def fetch_stock_data(tickers: list[str], start: str, end: str) -> pd.DataFrame: """ Fetch adjusted close prices for a list of tickers. Args: tickers: List of ticker symbols (e.g., ['AAPL', 'MSFT']) start: Start date (YYYY-MM-DD) end: End date (YYYY-MM-DD) Returns: DataFrame with adjusted close prices """ data = yf.download(tickers, start=start, end=end, auto_adjust=True) prices = data['Close'] if len(tickers) > 1 else data[['Close']] prices.columns = tickers if len(tickers) > 1 else tickers return prices # Fetch 5 years of data prices = fetch_stock_data(['AAPL', 'MSFT', 'GOOGL'], '2020-01-01', '2025-01-01') print(prices.head())
pythonfrom fredapi import Fred fred = Fred(api_key=os.environ["FRED_API_KEY"]) # Common series for finance research series_ids = { 'GDP': 'GDP', 'CPI': 'CPIAUCSL', 'Fed_Funds_Rate': 'FEDFUNDS', 'Unemployment': 'UNRATE', '10Y_Treasury': 'DGS10', 'VIX': 'VIXCLS' } macro_data = pd.DataFrame() for name, sid in series_ids.items(): macro_data[name] = fred.get_series(sid, observation_start='2000-01-01')
Financial data requires careful cleaning before analysis:
pythondef clean_financial_data(df: pd.DataFrame) -> pd.DataFrame: """Standard cleaning pipeline for financial time series.""" cleaned = df.copy() # 1. Handle missing values missing_pct = cleaned.isnull().sum() / len(cleaned) * 100 print(f"Missing data:\n{missing_pct}") # 2. Forward-fill for market holidays (max 5 days) cleaned = cleaned.ffill(limit=5) # 3. Remove remaining NaN rows cleaned = cleaned.dropna() # 4. Detect and flag outliers (>5 sigma daily returns) returns = cleaned.pct_change() z_scores = (returns - returns.mean()) / returns.std() outliers = (z_scores.abs() > 5).any(axis=1) print(f"Outlier days flagged: {outliers.sum()}") # 5. Verify data integrity assert cleaned.index.is_monotonic_increasing, "Index must be sorted" assert not cleaned.duplicated().any(), "No duplicate rows allowed" return cleaned
pythondef compute_returns(prices: pd.DataFrame) -> dict: """Compute standard return metrics.""" simple_returns = prices.pct_change().dropna() log_returns = np.log(prices / prices.shift(1)).dropna() annualized_return = simple_returns.mean() * 252 annualized_vol = simple_returns.std() * np.sqrt(252) sharpe_ratio = annualized_return / annualized_vol # Maximum drawdown cumulative = (1 + simple_returns).cumprod() rolling_max = cumulative.cummax() drawdown = (cumulative - rolling_max) / rolling_max max_drawdown = drawdown.min() return { 'annualized_return': annualized_return, 'annualized_volatility': annualized_vol, 'sharpe_ratio': sharpe_ratio, 'max_drawdown': max_drawdown }
A common methodology in empirical finance research:
Always report both raw and risk-adjusted results, and perform robustness checks with different estimation windows and benchmark models.
Store all data processing steps in version-controlled scripts. Use pandas.DataFrame.to_parquet() for efficient storage of intermediate datasets, and document data provenance including download dates, API versions, and any filters applied.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-04 | pass→pass | 11,267 | 9,572 | -15% | 1 | 1 | 0% | 1,990 | 2,648 | +33% | 0 | 0 | — |
case-01 | fail→pass | 27,915 | 31,401 | +12% | 1 | 1 | 0% | 5,003 | 4,877 | -3% | 0 | 0 | — |
case-02 | fail→fail | 20,896 | 19,987 | -4% | 1 | 1 | 0% | 4,026 | 5,353 | +33% | 0 | 0 | — |
case-03 | fail→fail | 36,353 | 38,173 | +5% | 1 | 1 | 0% | 6,131 | 8,487 | +38% | 0 | 0 | — |
case-05 | pass→pass | 8,064 | 6,721 | -17% | 1 | 1 | 0% | 1,182 | 2,475 | +109% | 0 | 0 | — |
case-06 | pass→pass | 10,869 | 7,023 | -35% | 1 | 1 | 0% | 1,868 | 2,288 | +22% | 0 | 0 | — |
case-07 | pass→pass | 7,877 | 5,044 | -36% | 1 | 1 | 0% | 1,295 | 2,033 | +57% | 0 | 0 | — |
case-08 | fail→pass | 14,613 | 10,301 | -30% | 1 | 1 | 0% | 2,673 | 3,237 | +21% | 0 | 0 | — |
case-09 | fail→pass | 14,155 | 6,877 | -51% | 1 | 1 | 0% | 2,134 | 2,459 | +15% | 0 | 0 | — |
case-10 | fail→fail | 20,666 | 14,085 | -32% | 1 | 1 | 0% | 3,096 | 3,619 | +17% | 0 | 0 | — |
case-11 | pass→pass | 20,244 | 10,268 | -49% | 1 | 1 | 0% | 3,285 | 2,962 | -10% | 0 | 0 | — |
case-12 | fail→fail | 10,726 | 8,085 | -25% | 1 | 1 | 0% | 1,908 | 2,778 | +46% | 0 | 0 | — |
case-21 | pass→pass | 15,523 | 16,511 | +6% | 1 | 1 | 0% | 3,017 | 4,542 | +51% | 0 | 0 | — |
case-13 | pass→pass | 12,416 | 11,085 | -11% | 1 | 1 | 0% | 1,940 | 3,402 | +75% | 0 | 0 | — |
case-14 | pass→pass | 10,595 | 5,113 | -52% | 1 | 1 | 0% | 1,488 | 2,187 | +47% | 0 | 0 | — |
case-15 | pass→pass | 11,222 | 15,716 | +40% | 1 | 1 | 0% | 2,143 | 3,366 | +57% | 0 | 0 | — |
case-16 | fail→pass | 9,795 | 9,070 | -7% | 1 | 1 | 0% | 1,534 | 2,810 | +83% | 0 | 0 | — |
case-17 | pass→pass | 12,001 | 7,526 | -37% | 1 | 1 | 0% | 1,936 | 2,605 | +35% | 0 | 0 | — |
case-18 | pass→pass | 17,470 | 9,844 | -44% | 1 | 1 | 0% | 2,666 | 2,837 | +6% | 0 | 0 | — |
case-19 | fail→fail | 23,717 | 20,873 | -12% | 1 | 1 | 0% | 3,684 | 4,495 | +22% | 0 | 0 | — |
case-20 | pass→pass | 9,754 | 13,930 | +43% | 1 | 1 | 0% | 1,897 | 4,128 | +118% | 0 | 0 | — |
case-22 | pass→pass | 23,663 | 19,736 | -17% | 1 | 1 | 0% | 3,286 | 5,257 | +60% | 0 | 0 | — |
case-23 | pass→pass | 5,210 | 3,152 | -40% | 1 | 1 | 0% | 827 | 1,873 | +126% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +17 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.