Mercurial
view schwab_trader/schwab_cli.py @ 266:efaf4c63cc94
fix clean production bundle staging
Recreate deployment staging before copying the Bazel bundle and verify required inference runtime paths before promoting it.
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Fri, 07 Aug 2026 12:52:30 -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())