480 lines
17 KiB
Python
480 lines
17 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Download all ML4T datasets.
|
|
|
|
Usage:
|
|
python download_all.py # Download core datasets (free)
|
|
python download_all.py --all # Download everything (incl. paid)
|
|
python download_all.py --update # Update all datasets to present
|
|
python download_all.py --estimate-only # Show cost estimates
|
|
|
|
Datasets:
|
|
FREE (no API key):
|
|
- ETF Universe (Yahoo Finance) - Case study primary
|
|
- Crypto Premium Index (Binance Public) - Case study primary
|
|
- FX Pairs (Yahoo Finance)
|
|
- Fama-French Factors (Ken French Library)
|
|
- AQR Factors (AQR Research)
|
|
|
|
FREE (requires free API key):
|
|
- Treasury Yields (FRED)
|
|
- US Equities (NASDAQ Data Link) - FROZEN, ends 2018
|
|
- Yahoo S&P 500 (for survivorship bias demo)
|
|
|
|
PAID:
|
|
- CME Futures (Databento - $125 free credit)
|
|
- NASDAQ ITCH (5-6 GB download)
|
|
|
|
Update Mode:
|
|
The --update flag extends all updateable datasets from their configured
|
|
end date (e.g., 2025-12-31) to the present. Use this in 2026+ to get
|
|
the latest data for strategies.
|
|
|
|
Updateable: ETFs, Crypto, Macro, FX, FF Factors, AQR Factors
|
|
Frozen: US Equities (ends 2018), AlgoSeek (licensed snapshots)
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from utils.downloading import load_dotenv, resolve_data_dir
|
|
|
|
# Script name to path mapping (new directory structure)
|
|
DOWNLOAD_SCRIPTS = {
|
|
# Asset-class market data
|
|
"etfs.py": "etfs/market/download.py",
|
|
"crypto.py": "crypto/market/download.py",
|
|
"cme_futures.py": "futures/market/download.py",
|
|
"fx_pairs.py": "fx/market/download.py",
|
|
"us_equities.py": "equities/market/us_equities/download.py",
|
|
"mbo_data.py": "equities/market/microstructure/mbo_download.py",
|
|
"nasdaq_itch.py": "equities/market/microstructure/nasdaq_itch_download.py",
|
|
"iex_hist.py": "equities/market/microstructure/iex_download.py",
|
|
# Positioning
|
|
"cot.py": "futures/positioning/cot_download.py",
|
|
"institutional_13f.py": "equities/positioning/13f_download.py",
|
|
"sec_form4.py": "equities/positioning/form4_download.py",
|
|
# Fundamentals (SEC filings + XBRL)
|
|
"sec_filings.py": "equities/fundamentals/filings_download.py",
|
|
"sec_xbrl.py": "equities/fundamentals/xbrl_download.py",
|
|
# Standalone packaged datasets
|
|
"firm_characteristics.py": "equities/firm_characteristics/download.py",
|
|
# Cross-asset macro / factors
|
|
"macro.py": "macro/download.py",
|
|
"ff_factors.py": "factors/ff_download.py",
|
|
"aqr_factors.py": "factors/aqr_download.py",
|
|
# Prediction markets
|
|
"prediction_markets.py": "prediction_markets/download.py",
|
|
# Alternative (cross-asset third-party)
|
|
"fnspid.py": "alternative/news/fnspid_download.py",
|
|
"bloomberg_news.py": "alternative/news/bloomberg_download.py",
|
|
"onchain.py": "crypto/onchain/download.py",
|
|
}
|
|
|
|
|
|
def run_download_script(script_name: str, extra_args: list | None = None) -> bool:
|
|
"""Run a download script from the appropriate dataset directory."""
|
|
# Map old script name to new path
|
|
relative_path = DOWNLOAD_SCRIPTS.get(script_name, script_name)
|
|
script_path = Path(__file__).parent / relative_path
|
|
if not script_path.exists():
|
|
print(f" Script not found: {script_path}")
|
|
return False
|
|
|
|
cmd = [sys.executable, str(script_path)]
|
|
if extra_args:
|
|
cmd.extend(extra_args)
|
|
|
|
result = subprocess.run(cmd, cwd=str(Path(__file__).parent))
|
|
return result.returncode == 0
|
|
|
|
|
|
def download_etfs(data_path: Path, force: bool = False):
|
|
"""Download ETF data from Yahoo Finance (free)."""
|
|
print("\n" + "=" * 60)
|
|
print("ETF UNIVERSE (Free - Yahoo Finance)")
|
|
print("=" * 60)
|
|
|
|
extra_args = ["--data-path", str(data_path)]
|
|
if force:
|
|
extra_args.append("--force")
|
|
return run_download_script("etfs.py", extra_args)
|
|
|
|
|
|
def download_crypto(data_path: Path, force: bool = False):
|
|
"""Download crypto perpetuals and premium index from Binance Public (free)."""
|
|
print("\n" + "=" * 60)
|
|
print("CRYPTO PERPS + PREMIUM (Free - Binance Public)")
|
|
print("=" * 60)
|
|
|
|
extra_args = ["--data-path", str(data_path)]
|
|
if force:
|
|
extra_args.append("--force")
|
|
return run_download_script("crypto.py", extra_args)
|
|
|
|
|
|
def download_macro(data_path: Path, _force: bool = False):
|
|
"""Download macro indicators from FRED."""
|
|
print("\n" + "=" * 60)
|
|
print("MACRO INDICATORS (FRED - requires free API key)")
|
|
print("=" * 60)
|
|
|
|
return run_download_script("macro.py", ["--data-path", str(data_path)])
|
|
|
|
|
|
def download_fx(data_path: Path):
|
|
"""Download FX data from OANDA."""
|
|
print("\n" + "=" * 60)
|
|
print("FX PAIRS (OANDA - requires free API key)")
|
|
print("=" * 60)
|
|
|
|
return run_download_script("fx_pairs.py", ["--data-path", str(data_path)])
|
|
|
|
|
|
def download_ff_factors(data_path: Path):
|
|
"""Download Fama-French factors (free, no API key)."""
|
|
print("\n" + "=" * 60)
|
|
print("FAMA-FRENCH FACTORS (Free - Ken French Library)")
|
|
print("=" * 60)
|
|
|
|
return run_download_script("ff_factors.py", ["--data-path", str(data_path)])
|
|
|
|
|
|
def download_aqr_factors(data_path: Path):
|
|
"""Download AQR factors (free, no API key)."""
|
|
print("\n" + "=" * 60)
|
|
print("AQR FACTORS (Free - AQR Research)")
|
|
print("=" * 60)
|
|
|
|
return run_download_script("aqr_factors.py", ["--data-path", str(data_path)])
|
|
|
|
|
|
def download_firm_characteristics(data_path: Path):
|
|
"""Download Chen-Pelger-Zhu firm characteristics (free academic dataset).
|
|
|
|
The download script fetches the ~1.5 GB Google Drive folder and converts
|
|
RetChar.csv to parquet in one step (no separate --convert pass needed).
|
|
"""
|
|
print("\n" + "=" * 60)
|
|
print("FIRM CHARACTERISTICS (Free - Chen-Pelger-Zhu academic dataset)")
|
|
print("=" * 60)
|
|
print(" Largest free dataset: ~1.5 GB download (RetChar.csv is ~1.1 GB)")
|
|
print(" followed by a ~1.1 GB CSV -> parquet conversion.")
|
|
print(" Duration depends on your bandwidth (typically a few minutes).")
|
|
print(" Per-file download progress is shown below.")
|
|
print(" Skip with: python data/download_all.py --skip-firm-characteristics")
|
|
print("=" * 60)
|
|
|
|
return run_download_script("firm_characteristics.py", ["--data-path", str(data_path)])
|
|
|
|
|
|
def download_us_equities(data_path: Path):
|
|
"""Download US Equities from NASDAQ Data Link (free API key required)."""
|
|
print("\n" + "=" * 60)
|
|
print("US EQUITIES (NASDAQ Data Link - requires free API key)")
|
|
print("=" * 60)
|
|
|
|
return run_download_script("us_equities.py", ["--output", str(data_path / "equities")])
|
|
|
|
|
|
def download_yahoo_sp500(data_path: Path):
|
|
"""Download Yahoo S&P 500 for survivorship bias demo."""
|
|
print("\n" + "=" * 60)
|
|
print("YAHOO S&P 500 (For survivorship bias demonstration)")
|
|
print("=" * 60)
|
|
|
|
return run_download_script("etfs.py", ["--sp500-only", "--data-path", str(data_path)])
|
|
|
|
|
|
def download_futures(data_path: Path, estimate_only: bool = False):
|
|
"""Download futures data from Databento (paid) and consolidate."""
|
|
print("\n" + "=" * 60)
|
|
print("CME FUTURES (Databento - requires API key, $125 free credit)")
|
|
print("=" * 60)
|
|
|
|
previous_data_path = os.environ.get("ML4T_DATA_PATH")
|
|
os.environ["ML4T_DATA_PATH"] = str(data_path)
|
|
try:
|
|
extra_args = ["--max-cost", "125"]
|
|
if estimate_only:
|
|
extra_args.append("--estimate-only")
|
|
return run_download_script("cme_futures.py", extra_args)
|
|
finally:
|
|
if previous_data_path is None:
|
|
os.environ.pop("ML4T_DATA_PATH", None)
|
|
else:
|
|
os.environ["ML4T_DATA_PATH"] = previous_data_path
|
|
|
|
|
|
def download_prediction_markets(data_path: Path):
|
|
"""Download prediction market data from Kalshi + Polymarket (free)."""
|
|
print("\n" + "=" * 60)
|
|
print("PREDICTION MARKETS (Free - Kalshi + Polymarket)")
|
|
print("=" * 60)
|
|
|
|
return run_download_script("prediction_markets.py", ["--data-path", str(data_path)])
|
|
|
|
|
|
def download_cot(data_path: Path):
|
|
"""Download CFTC Commitment of Traders (free, public, no API key)."""
|
|
print("\n" + "=" * 60)
|
|
print("CFTC COT (Free - Commitment of Traders)")
|
|
print("=" * 60)
|
|
|
|
return run_download_script("cot.py", ["--data-path", str(data_path)])
|
|
|
|
|
|
def download_itch(data_path: Path):
|
|
"""Download NASDAQ ITCH sample data (5-6 GB)."""
|
|
print("\n" + "=" * 60)
|
|
print("NASDAQ ITCH SAMPLE (5-6 GB - for Chapter 4 microstructure)")
|
|
print("=" * 60)
|
|
|
|
return run_download_script("nasdaq_itch.py", ["--data-path", str(data_path)])
|
|
|
|
|
|
def update_datasets(data_path: Path) -> dict:
|
|
"""
|
|
Update all updateable datasets to the present date.
|
|
|
|
Extends datasets beyond their configured end date (e.g., 2025-12-31).
|
|
Skips frozen datasets (US Equities ends 2018, AlgoSeek is licensed snapshots).
|
|
|
|
Returns:
|
|
Dictionary of dataset names to success status
|
|
"""
|
|
from datetime import date
|
|
|
|
today = date.today().isoformat()
|
|
print("\n" + "=" * 60)
|
|
print(f"UPDATE MODE - Extending datasets to {today}")
|
|
print("=" * 60)
|
|
print("\nUpdateable datasets:")
|
|
print(" - ETF Universe (Yahoo Finance)")
|
|
print(" - Crypto Premium (Binance Public)")
|
|
print(" - Macro/Treasury (FRED)")
|
|
print(" - FX Pairs (OANDA/Yahoo)")
|
|
print(" - Fama-French Factors")
|
|
print(" - AQR Factors")
|
|
print(" - CFTC Commitment of Traders")
|
|
print("\nFrozen datasets (skipped):")
|
|
print(" - US Equities (ends 2018)")
|
|
print(" - AlgoSeek data (licensed snapshots)")
|
|
print()
|
|
|
|
results = {}
|
|
|
|
# Update ETFs
|
|
print("\n[1/7] Updating ETF Universe...")
|
|
args_list = ["--data-path", str(data_path), "--update"]
|
|
results["ETFs"] = run_download_script("etfs.py", args_list)
|
|
|
|
# Update Crypto
|
|
print("\n[2/7] Updating Crypto Premium...")
|
|
args_list = ["--data-path", str(data_path), "--update"]
|
|
results["Crypto"] = run_download_script("crypto.py", args_list)
|
|
|
|
# Update Macro (FRED)
|
|
print("\n[3/7] Updating Macro (FRED)...")
|
|
args_list = ["--data-path", str(data_path), "--update"]
|
|
results["Macro"] = run_download_script("macro.py", args_list)
|
|
|
|
# Update FX
|
|
print("\n[4/7] Updating FX Pairs...")
|
|
args_list = ["--data-path", str(data_path), "--update"]
|
|
results["FX"] = run_download_script("fx_pairs.py", args_list)
|
|
|
|
# Update Fama-French
|
|
print("\n[5/7] Updating Fama-French Factors...")
|
|
args_list = ["--data-path", str(data_path)]
|
|
results["Fama-French"] = run_download_script("ff_factors.py", args_list)
|
|
|
|
# Update AQR
|
|
print("\n[6/7] Updating AQR Factors...")
|
|
args_list = ["--data-path", str(data_path)]
|
|
results["AQR"] = run_download_script("aqr_factors.py", args_list)
|
|
|
|
# Update CoT (re-fetches through current year)
|
|
print("\n[7/7] Updating CFTC Commitment of Traders...")
|
|
args_list = ["--data-path", str(data_path)]
|
|
results["CFTC COT"] = run_download_script("cot.py", args_list)
|
|
|
|
return results
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Download all ML4T datasets")
|
|
parser.add_argument(
|
|
"--all", action="store_true", help="Download all datasets including paid/large ones"
|
|
)
|
|
parser.add_argument(
|
|
"--free-only", action="store_true", help="Only download free datasets (no API keys needed)"
|
|
)
|
|
parser.add_argument(
|
|
"--update",
|
|
action="store_true",
|
|
help="Update all datasets to present (extend beyond configured end date)",
|
|
)
|
|
parser.add_argument(
|
|
"--estimate-only", action="store_true", help="Show cost estimates for paid datasets"
|
|
)
|
|
parser.add_argument(
|
|
"--force", action="store_true", help="Force re-download even if data exists"
|
|
)
|
|
parser.add_argument(
|
|
"--skip-firm-characteristics",
|
|
action="store_true",
|
|
help="Skip the large (~1.5 GB) firm-characteristics academic dataset",
|
|
)
|
|
# Default to None so resolve_data_dir() applies the documented precedence:
|
|
# explicit --data-path > ML4T_DATA_PATH env var > <repo>/data. A non-None
|
|
# default here would be treated as an explicit CLI arg and override the env var.
|
|
parser.add_argument(
|
|
"--data-path",
|
|
type=Path,
|
|
default=None,
|
|
help="Data storage location (default: ML4T_DATA_PATH, else <repo>/data/)",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
# Load environment variables
|
|
load_dotenv()
|
|
|
|
# Paths - ML4T_DATA_PATH takes precedence (canonical env var)
|
|
data_path = resolve_data_dir(args.data_path)
|
|
|
|
print("=" * 60)
|
|
print("ML4T DATA DOWNLOAD")
|
|
print("=" * 60)
|
|
print(f"Data path: {data_path}")
|
|
|
|
# Determine mode
|
|
if args.update:
|
|
mode = "update"
|
|
elif args.all:
|
|
mode = "all"
|
|
elif args.free_only:
|
|
mode = "free-only"
|
|
else:
|
|
mode = "core"
|
|
print(f"Mode: {mode}")
|
|
|
|
# Heads-up on the one large free dataset so readers know what's coming.
|
|
if mode in ("core", "free-only", "all") and not args.skip_firm_characteristics:
|
|
print(
|
|
"\nHeads-up: this run includes the firm-characteristics academic dataset\n"
|
|
" (~1.5 GB download + a ~1.1 GB CSV -> parquet conversion).\n"
|
|
" Skip it with: --skip-firm-characteristics"
|
|
)
|
|
|
|
# Create data directory
|
|
data_path.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Handle update mode separately
|
|
if args.update:
|
|
results = update_datasets(data_path)
|
|
|
|
# Summary
|
|
print("\n" + "=" * 60)
|
|
print("UPDATE SUMMARY")
|
|
print("=" * 60)
|
|
for name, success in results.items():
|
|
status = "[OK]" if success else "[FAIL]"
|
|
print(f" {status} {name}")
|
|
|
|
total_success = sum(results.values())
|
|
total = len(results)
|
|
print(f"\nUpdated: {total_success}/{total} datasets")
|
|
|
|
if total_success < total:
|
|
print("\nNote: Some datasets may require API keys:")
|
|
print(" FRED_API_KEY - for Macro data")
|
|
print(" OANDA_API_KEY - for FX data (optional, uses Yahoo fallback)")
|
|
return
|
|
|
|
results = {}
|
|
|
|
# === CORE DATASETS (always download) ===
|
|
print("\n" + "=" * 60)
|
|
print("CORE DATASETS (Case Studies)")
|
|
print("=" * 60)
|
|
results["ETFs"] = download_etfs(data_path, args.force)
|
|
results["Crypto"] = download_crypto(data_path, args.force)
|
|
results["Prediction Markets"] = download_prediction_markets(data_path)
|
|
results["CFTC COT"] = download_cot(data_path)
|
|
|
|
# === FREE DATASETS (no API key) ===
|
|
print("\n" + "=" * 60)
|
|
print("FACTOR DATA (Free, no API key)")
|
|
print("=" * 60)
|
|
results["Fama-French"] = download_ff_factors(data_path)
|
|
results["AQR"] = download_aqr_factors(data_path)
|
|
if args.skip_firm_characteristics:
|
|
print("\n" + "=" * 60)
|
|
print("FIRM CHARACTERISTICS - skipped (--skip-firm-characteristics)")
|
|
print("=" * 60)
|
|
print(" ~1.5 GB academic dataset (Chen-Pelger-Zhu). Fetch it later with:")
|
|
print(" python data/equities/firm_characteristics/download.py")
|
|
print("=" * 60)
|
|
else:
|
|
results["Firm Characteristics"] = download_firm_characteristics(data_path)
|
|
|
|
# === FREE WITH API KEY ===
|
|
if not args.free_only:
|
|
print("\n" + "=" * 60)
|
|
print("DATASETS REQUIRING FREE API KEY")
|
|
print("=" * 60)
|
|
results["Macro (FRED)"] = download_macro(data_path, args.force)
|
|
results["FX"] = download_fx(data_path)
|
|
|
|
if args.all:
|
|
results["US Equities"] = download_us_equities(data_path)
|
|
results["Yahoo S&P500"] = download_yahoo_sp500(data_path)
|
|
|
|
# === PAID / LARGE DATASETS ===
|
|
if args.all:
|
|
print("\n" + "=" * 60)
|
|
print("PAID / LARGE DATASETS")
|
|
print("=" * 60)
|
|
results["Futures"] = download_futures(data_path, args.estimate_only)
|
|
|
|
# ITCH is large (5-6GB) so only download if explicitly requested
|
|
print("\nNote: ITCH sample data (5-6 GB) not included in --all")
|
|
print(" Run separately: python equities/nasdaq_itch_download.py")
|
|
|
|
# Summary
|
|
print("\n" + "=" * 60)
|
|
print("DOWNLOAD SUMMARY")
|
|
print("=" * 60)
|
|
for name, success in results.items():
|
|
status = "[OK]" if success else "[FAIL]"
|
|
print(f" {status} {name}")
|
|
|
|
total_success = sum(results.values())
|
|
total = len(results)
|
|
print(f"\nCompleted: {total_success}/{total} datasets")
|
|
|
|
if total_success < total:
|
|
print("\nTo fix failures:")
|
|
print(" 1. Ensure ml4t-data is installed: pip install ml4t-data")
|
|
print(" 2. Set required API keys in .env file (see .env.example)")
|
|
print(" 3. Re-run this script")
|
|
|
|
print("\nAdditional datasets available:")
|
|
print(
|
|
" python data/equities/market/us_equities/download.py # Historical equities (1962-2018)"
|
|
)
|
|
print(
|
|
" python data/equities/market/microstructure/nasdaq_itch_download.py # Tick data (5-6 GB)"
|
|
)
|
|
print(
|
|
" data/equities/market/microstructure/MBO_DOWNLOAD.md # MBO tick data (Databento, manual)"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|