Mercurial
annotate schwab_trader/schwab_client.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 |
| rev | line source |
|---|---|
|
220
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
1 from __future__ import annotations |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
2 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
3 import base64 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
4 import json |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
5 import os |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
6 import stat |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
7 import time |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
8 import urllib.error |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
9 import urllib.parse |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
10 import urllib.request |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
11 from dataclasses import dataclass |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
12 from pathlib import Path |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
13 from typing import Any |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
14 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
15 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
16 AUTH_URL = "https://api.schwabapi.com/v1/oauth/authorize" |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
17 TOKEN_URL = "https://api.schwabapi.com/v1/oauth/token" |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
18 TRADER_BASE_URL = "https://api.schwabapi.com/trader/v1" |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
19 DEFAULT_TOKEN_FILE = "~/.config/zenbu/schwab_tokens.json" |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
20 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
21 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
22 class SchwabError(RuntimeError): |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
23 pass |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
24 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
25 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
26 @dataclass(frozen=True) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
27 class SchwabConfig: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
28 app_key: str |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
29 app_secret: str |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
30 redirect_uri: str |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
31 token_file: Path |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
32 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
33 @classmethod |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
34 def from_env(cls) -> "SchwabConfig": |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
35 app_key = os.environ.get("SCHWAB_APP_KEY", "").strip() |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
36 app_secret = os.environ.get("SCHWAB_APP_SECRET", "").strip() |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
37 redirect_uri = os.environ.get("SCHWAB_REDIRECT_URI", "").strip() |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
38 token_file = Path(os.environ.get("SCHWAB_TOKEN_FILE", DEFAULT_TOKEN_FILE)).expanduser() |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
39 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
40 missing = [ |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
41 name |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
42 for name, value in ( |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
43 ("SCHWAB_APP_KEY", app_key), |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
44 ("SCHWAB_APP_SECRET", app_secret), |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
45 ("SCHWAB_REDIRECT_URI", redirect_uri), |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
46 ) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
47 if not value |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
48 ] |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
49 if missing: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
50 raise SchwabError("Missing required environment variables: " + ", ".join(missing)) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
51 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
52 return cls( |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
53 app_key=app_key, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
54 app_secret=app_secret, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
55 redirect_uri=redirect_uri, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
56 token_file=token_file, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
57 ) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
58 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
59 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
60 @dataclass(frozen=True) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
61 class ApiResponse: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
62 status: int |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
63 headers: dict[str, str] |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
64 body: Any |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
65 raw_body: str |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
66 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
67 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
68 def build_authorization_url(app_key: str, redirect_uri: str, state: str | None = None) -> str: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
69 params = { |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
70 "response_type": "code", |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
71 "client_id": app_key, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
72 "redirect_uri": redirect_uri, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
73 } |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
74 if state: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
75 params["state"] = state |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
76 return AUTH_URL + "?" + urllib.parse.urlencode(params) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
77 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
78 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
79 def extract_authorization_code(code_or_url: str) -> str: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
80 value = code_or_url.strip() |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
81 if not value: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
82 raise SchwabError("Authorization code is empty") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
83 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
84 parsed = urllib.parse.urlparse(value) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
85 if parsed.scheme and parsed.netloc: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
86 query = urllib.parse.parse_qs(parsed.query) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
87 codes = query.get("code") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
88 if not codes or not codes[0]: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
89 raise SchwabError("No code= parameter found in callback URL") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
90 return codes[0] |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
91 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
92 return urllib.parse.unquote(value) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
93 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
94 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
95 def exchange_code_for_tokens(config: SchwabConfig, code_or_url: str) -> dict[str, Any]: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
96 code = extract_authorization_code(code_or_url) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
97 return _token_request( |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
98 config, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
99 { |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
100 "grant_type": "authorization_code", |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
101 "code": code, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
102 "redirect_uri": config.redirect_uri, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
103 }, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
104 ) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
105 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
106 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
107 def refresh_tokens(config: SchwabConfig, refresh_token: str | None = None) -> dict[str, Any]: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
108 token_value = refresh_token |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
109 if token_value is None: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
110 existing = load_tokens(config.token_file) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
111 token_value = existing.get("refresh_token") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
112 if not token_value: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
113 raise SchwabError("No refresh token available") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
114 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
115 return _token_request( |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
116 config, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
117 { |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
118 "grant_type": "refresh_token", |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
119 "refresh_token": token_value, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
120 }, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
121 ) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
122 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
123 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
124 def save_tokens(path: Path, tokens: dict[str, Any]) -> None: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
125 payload = dict(tokens) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
126 payload["saved_at"] = int(time.time()) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
127 path.parent.mkdir(parents=True, exist_ok=True) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
128 path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
129 path.chmod(stat.S_IRUSR | stat.S_IWUSR) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
130 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
131 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
132 def load_tokens(path: Path) -> dict[str, Any]: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
133 if not path.exists(): |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
134 raise SchwabError(f"Token file does not exist: {path}") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
135 return json.loads(path.read_text(encoding="utf-8")) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
136 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
137 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
138 def get_account_numbers(access_token: str) -> ApiResponse: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
139 return _api_request("GET", "/accounts/accountNumbers", access_token) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
140 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
141 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
142 def get_accounts(access_token: str, fields: str | None = None) -> ApiResponse: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
143 query = "" |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
144 if fields: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
145 query = "?" + urllib.parse.urlencode({"fields": fields}) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
146 return _api_request("GET", "/accounts" + query, access_token) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
147 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
148 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
149 def get_account(access_token: str, account_hash: str, fields: str | None = None) -> ApiResponse: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
150 query = "" |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
151 if fields: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
152 query = "?" + urllib.parse.urlencode({"fields": fields}) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
153 return _api_request("GET", f"/accounts/{urllib.parse.quote(account_hash)}{query}", access_token) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
154 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
155 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
156 def build_equity_order( |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
157 action: str, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
158 symbol: str, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
159 quantity: float, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
160 order_type: str = "MARKET", |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
161 price: float | None = None, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
162 duration: str = "DAY", |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
163 session: str = "NORMAL", |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
164 ) -> dict[str, Any]: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
165 normalized_action = action.upper() |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
166 normalized_symbol = symbol.upper() |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
167 normalized_order_type = order_type.upper() |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
168 normalized_duration = duration.upper() |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
169 normalized_session = session.upper() |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
170 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
171 if normalized_action not in {"BUY", "SELL"}: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
172 raise SchwabError("action must be BUY or SELL") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
173 if not normalized_symbol: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
174 raise SchwabError("symbol is required") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
175 if quantity <= 0: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
176 raise SchwabError("quantity must be greater than zero") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
177 if normalized_order_type not in {"MARKET", "LIMIT"}: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
178 raise SchwabError("order_type must be MARKET or LIMIT") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
179 if normalized_order_type == "LIMIT" and price is None: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
180 raise SchwabError("LIMIT orders require --price") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
181 if normalized_order_type == "MARKET" and price is not None: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
182 raise SchwabError("MARKET orders cannot include --price") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
183 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
184 order: dict[str, Any] = { |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
185 "orderType": normalized_order_type, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
186 "session": normalized_session, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
187 "duration": normalized_duration, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
188 "orderStrategyType": "SINGLE", |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
189 "orderLegCollection": [ |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
190 { |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
191 "instruction": normalized_action, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
192 "quantity": quantity, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
193 "instrument": { |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
194 "symbol": normalized_symbol, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
195 "assetType": "EQUITY", |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
196 }, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
197 } |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
198 ], |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
199 } |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
200 if price is not None: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
201 order["price"] = f"{price:.2f}" |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
202 return order |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
203 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
204 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
205 def place_order(access_token: str, account_hash: str, order: dict[str, Any]) -> ApiResponse: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
206 path = f"/accounts/{urllib.parse.quote(account_hash)}/orders" |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
207 return _api_request("POST", path, access_token, order) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
208 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
209 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
210 def _token_request(config: SchwabConfig, form: dict[str, str]) -> dict[str, Any]: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
211 credentials = f"{config.app_key}:{config.app_secret}".encode("utf-8") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
212 headers = { |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
213 "Authorization": "Basic " + base64.b64encode(credentials).decode("ascii"), |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
214 "Content-Type": "application/x-www-form-urlencoded", |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
215 "Accept": "application/json", |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
216 } |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
217 data = urllib.parse.urlencode(form).encode("utf-8") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
218 request = urllib.request.Request(TOKEN_URL, data=data, headers=headers, method="POST") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
219 response = _open_request(request) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
220 if not isinstance(response.body, dict): |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
221 raise SchwabError("Token endpoint did not return a JSON object") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
222 return response.body |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
223 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
224 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
225 def _api_request( |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
226 method: str, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
227 path: str, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
228 access_token: str, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
229 body: dict[str, Any] | None = None, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
230 ) -> ApiResponse: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
231 headers = { |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
232 "Authorization": f"Bearer {access_token}", |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
233 "Accept": "application/json", |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
234 } |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
235 data = None |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
236 if body is not None: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
237 data = json.dumps(body).encode("utf-8") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
238 headers["Content-Type"] = "application/json" |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
239 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
240 request = urllib.request.Request( |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
241 TRADER_BASE_URL + path, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
242 data=data, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
243 headers=headers, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
244 method=method, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
245 ) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
246 return _open_request(request) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
247 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
248 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
249 def _open_request(request: urllib.request.Request) -> ApiResponse: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
250 try: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
251 with urllib.request.urlopen(request, timeout=30) as response: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
252 raw = response.read().decode("utf-8") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
253 return ApiResponse( |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
254 status=response.status, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
255 headers=dict(response.headers.items()), |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
256 body=_parse_json_or_text(raw), |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
257 raw_body=raw, |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
258 ) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
259 except urllib.error.HTTPError as error: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
260 raw = error.read().decode("utf-8", errors="replace") |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
261 raise SchwabError( |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
262 f"Schwab API request failed with HTTP {error.code}: {raw or error.reason}" |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
263 ) from error |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
264 except urllib.error.URLError as error: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
265 raise SchwabError(f"Schwab API request failed: {error.reason}") from error |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
266 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
267 |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
268 def _parse_json_or_text(raw: str) -> Any: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
269 if not raw: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
270 return None |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
271 try: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
272 return json.loads(raw) |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
273 except json.JSONDecodeError: |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
274 return raw |
|
eb8b4230fdb9
[schwab-trader] Add guarded trading experiment
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
275 |