#!/usr/bin/env python3 """ market_series.py Fetch historical series and print CSV to stdout. Stocks/ETFs/indices: yfinance history (daily) FX: derived from ExchangeRate-API open endpoint (daily snapshots not provided here), so we return a simple "latest only" line unless you add a historical FX provider. Usage: python scripts/market_series.py AAPL --days 30 python scripts/market_series.py ^GSPC --days 120 python scripts/market_series.py USD/ZAR --days 30 """ from __future__ import annotations import argparse import sys import re from datetime import datetime, timedelta from typing import Optional, Tuple import pandas as pd def _parse_fx_pair(s: str) -> Optional[Tuple[str, str]]: s = s.strip().upper() s = s.replace(" ", "") s = s.replace("-", "/") if "/" in s: parts = s.split("/") if len(parts) == 2 and len(parts[0]) == 3 and len(parts[1]) == 3: return parts[0], parts[1] return None if len(s) == 6 and s.isalpha(): return s[:3], s[3:] return None def _stock_series(symbol: str, days: int) -> pd.DataFrame: import yfinance as yf end = datetime.utcnow() start = end - timedelta(days=days) df = yf.download(symbol, start=start.date().isoformat(), end=end.date().isoformat(), progress=False) if df is None or len(df) == 0: raise RuntimeError(f"No data returned for symbol: {symbol}") df = df.reset_index() # Normalize column names (handle MultiIndex tuples from yfinance) cols = {} for c in df.columns: if isinstance(c, tuple): # Flatten tuple to string (e.g., ('Close', 'AAPL') -> 'close') name = "_".join(str(x) for x in c if x).lower().replace(" ", "_") else: name = str(c).lower().replace(" ", "_") cols[c] = name df = df.rename(columns=cols) return df def main() -> None: ap = argparse.ArgumentParser() ap.add_argument("symbol", help="Ticker or FX pair") ap.add_argument("--days", type=int, default=30) args = ap.parse_args() fx = _parse_fx_pair(args.symbol) if fx: # Open access endpoint does not provide true historical in this mode. # We intentionally fail loudly so the agent explains the limitation. raise SystemExit( "FX historical series is not supported with the no-key open endpoint. " "Use latest quotes, or add a historical FX provider (paid or other source)." ) df = _stock_series(args.symbol, args.days) df.to_csv(sys.stdout, index=False) if __name__ == "__main__": main()