Mercurial
view schwab_trader/schwab_cli.py @ 279:b3b547563ec7
Add Google connector service and agent wiki
Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code.
Co-authored-by: Copilot <[email protected]>
Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 17 Aug 2026 22:22:36 -0700 |
| parents | eb8b4230fdb9 |
| children |
line wrap: on
line source
from __future__ import annotations import argparse import json import sys from schwab_trader.schwab_client import ( SchwabConfig, SchwabError, build_authorization_url, build_equity_order, exchange_code_for_tokens, get_account, get_account_numbers, get_accounts, load_tokens, place_order, refresh_tokens, save_tokens, ) def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser( description="Safe Schwab Trader API helper. This tool never chooses trades for you.", ) subparsers = parser.add_subparsers(dest="command", required=True) auth_url = subparsers.add_parser("auth-url", help="Print the Schwab OAuth authorization URL") auth_url.add_argument("--state", help="Optional OAuth state value") token = subparsers.add_parser("token", help="Exchange an authorization code/callback URL for tokens") token.add_argument("--code", required=True, help="Authorization code or full callback URL") subparsers.add_parser("refresh", help="Refresh and save tokens") subparsers.add_parser("account-numbers", help="Print Schwab account-number/account-hash mapping") accounts = subparsers.add_parser("accounts", help="Print account data") accounts.add_argument("--positions", action="store_true", help="Include positions if API permissions allow it") account = subparsers.add_parser("account", help="Print one account by account hash") account.add_argument("--account-hash", required=True) account.add_argument("--positions", action="store_true", help="Include positions if API permissions allow it") build_order = subparsers.add_parser("build-equity-order", help="Build and print a stock order JSON payload") _add_order_args(build_order) place_equity = subparsers.add_parser( "place-equity-order", help="Place a user-specified stock order; defaults to dry-run output only", ) _add_order_args(place_equity) place_equity.add_argument("--account-hash", required=True) place_equity.add_argument("--live", action="store_true", help="Actually submit the order to Schwab") place_equity.add_argument( "--confirm-live-trade", action="store_true", help="Required with --live to reduce accidental orders", ) args = parser.parse_args(argv) try: if args.command == "auth-url": config = SchwabConfig.from_env() print(build_authorization_url(config.app_key, config.redirect_uri, args.state)) return 0 if args.command == "token": config = SchwabConfig.from_env() tokens = exchange_code_for_tokens(config, args.code) save_tokens(config.token_file, tokens) print(f"Saved tokens to {config.token_file}") return 0 if args.command == "refresh": config = SchwabConfig.from_env() tokens = refresh_tokens(config) save_tokens(config.token_file, tokens) print(f"Refreshed tokens in {config.token_file}") return 0 if args.command == "account-numbers": config = SchwabConfig.from_env() access_token = _load_access_token(config) _print_json(get_account_numbers(access_token).body) return 0 if args.command == "accounts": config = SchwabConfig.from_env() access_token = _load_access_token(config) fields = "positions" if args.positions else None _print_json(get_accounts(access_token, fields).body) return 0 if args.command == "account": config = SchwabConfig.from_env() access_token = _load_access_token(config) fields = "positions" if args.positions else None _print_json(get_account(access_token, args.account_hash, fields).body) return 0 if args.command == "build-equity-order": order = _build_order_from_args(args) _print_json(order) return 0 if args.command == "place-equity-order": order = _build_order_from_args(args) if not args.live: print("DRY RUN: order was not sent. Add --live --confirm-live-trade to submit.") _print_json(order) return 0 if not args.confirm_live_trade: raise SchwabError("--live requires --confirm-live-trade") config = SchwabConfig.from_env() access_token = _load_access_token(config) response = place_order(access_token, args.account_hash, order) print(f"Schwab order response status: {response.status}") location = response.headers.get("Location") if location: print(f"Order location: {location}") if response.body is not None: _print_json(response.body) return 0 raise SchwabError(f"Unknown command: {args.command}") except SchwabError as error: print(f"error: {error}", file=sys.stderr) return 1 def _add_order_args(parser: argparse.ArgumentParser) -> None: parser.add_argument("--action", required=True, choices=["BUY", "SELL", "buy", "sell"]) parser.add_argument("--symbol", required=True) parser.add_argument("--quantity", required=True, type=float) parser.add_argument("--order-type", default="MARKET", choices=["MARKET", "LIMIT", "market", "limit"]) parser.add_argument("--price", type=float, help="Required for LIMIT orders; invalid for MARKET orders") parser.add_argument("--duration", default="DAY") parser.add_argument("--session", default="NORMAL") def _build_order_from_args(args: argparse.Namespace) -> dict: return build_equity_order( action=args.action, symbol=args.symbol, quantity=args.quantity, order_type=args.order_type, price=args.price, duration=args.duration, session=args.session, ) def _load_access_token(config: SchwabConfig) -> str: tokens = load_tokens(config.token_file) access_token = tokens.get("access_token") if not access_token: raise SchwabError(f"No access_token in {config.token_file}") return access_token def _print_json(value: object) -> None: print(json.dumps(value, indent=2, sort_keys=True)) if __name__ == "__main__": raise SystemExit(main())