#!/usr/bin/env python3 """ market_watchlist.py Maintain a local watchlist and summarize current quotes. Usage: python scripts/market_watchlist.py add AAPL MSFT USD/ZAR python scripts/market_watchlist.py remove MSFT python scripts/market_watchlist.py list python scripts/market_watchlist.py summary """ from __future__ import annotations import argparse import json import os import subprocess import sys from typing import List WATCHLIST_PATH = os.path.join(".cache", "market-tracker", "watchlist.json") os.makedirs(os.path.dirname(WATCHLIST_PATH), exist_ok=True) def load_watchlist() -> List[str]: if not os.path.exists(WATCHLIST_PATH): return [] with open(WATCHLIST_PATH, "r", encoding="utf-8") as f: data = json.load(f) items = data.get("items") or [] # de-dupe, preserve order seen = set() out = [] for x in items: x = str(x).strip() if x and x not in seen: out.append(x) seen.add(x) return out def save_watchlist(items: List[str]) -> None: with open(WATCHLIST_PATH, "w", encoding="utf-8") as f: json.dump({"items": items}, f, ensure_ascii=False, indent=2) def cmd_add(symbols: List[str]) -> None: items = load_watchlist() for s in symbols: s = s.strip() if s and s not in items: items.append(s) save_watchlist(items) print(json.dumps({"ok": True, "items": items}, indent=2)) def cmd_remove(symbols: List[str]) -> None: items = load_watchlist() rm = set(s.strip() for s in symbols if s.strip()) items = [x for x in items if x not in rm] save_watchlist(items) print(json.dumps({"ok": True, "items": items}, indent=2)) def cmd_list() -> None: print(json.dumps({"items": load_watchlist()}, indent=2)) def cmd_summary() -> None: items = load_watchlist() if not items: print(json.dumps({"items": [], "summary": []}, indent=2)) return results = [] for sym in items: # Call market_quote.py to keep logic centralized p = subprocess.run( [sys.executable, os.path.join(os.path.dirname(__file__), "market_quote.py"), sym], capture_output=True, text=True, ) if p.returncode != 0: results.append({"symbol": sym, "error": p.stderr.strip() or p.stdout.strip()}) continue try: results.append(json.loads(p.stdout)) except Exception: results.append({"symbol": sym, "error": "Invalid JSON from market_quote.py", "raw": p.stdout[:500]}) print(json.dumps({"items": items, "summary": results}, ensure_ascii=False, indent=2)) def main() -> None: ap = argparse.ArgumentParser() sub = ap.add_subparsers(dest="cmd", required=True) sp_add = sub.add_parser("add") sp_add.add_argument("symbols", nargs="+") sp_rm = sub.add_parser("remove") sp_rm.add_argument("symbols", nargs="+") sub.add_parser("list") sub.add_parser("summary") args = ap.parse_args() if args.cmd == "add": cmd_add(args.symbols) elif args.cmd == "remove": cmd_remove(args.symbols) elif args.cmd == "list": cmd_list() elif args.cmd == "summary": cmd_summary() else: raise SystemExit("Unknown command") if __name__ == "__main__": main()