Files

87 lines
2.5 KiB
Python
Raw Permalink Normal View History

#!/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()