Snapshot before the DuckDB->PostgreSQL migration. data/, archive/, the 232MB rebuild zip, the Windows .venv, and .env are excluded via .gitignore. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
2.3 KiB
Python
68 lines
2.3 KiB
Python
"""Read Schwab orders (read-only, with unauthenticated fallback)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
import urllib.error
|
|
import urllib.parse
|
|
import urllib.request
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
from schwab.auth.lifecycle import TokenLifecycle
|
|
from schwab.settings import SchwabSettings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class OrdersReader:
|
|
def __init__(
|
|
self,
|
|
settings: SchwabSettings | None = None,
|
|
lifecycle: TokenLifecycle | None = None,
|
|
timeout: int = 15,
|
|
):
|
|
self.settings = settings or SchwabSettings.from_env()
|
|
self.lifecycle = lifecycle or TokenLifecycle(self.settings)
|
|
self.timeout = timeout
|
|
|
|
def read_orders(self, days_back: int = 2) -> list[dict]:
|
|
token = self.lifecycle.get_access_token()
|
|
if not token:
|
|
return []
|
|
now = datetime.now(timezone.utc)
|
|
params = urllib.parse.urlencode(
|
|
{
|
|
"fromEnteredTime": (now - timedelta(days=days_back)).strftime(
|
|
"%Y-%m-%dT%H:%M:%S.000Z"
|
|
),
|
|
"toEnteredTime": now.strftime("%Y-%m-%dT%H:%M:%S.000Z"),
|
|
}
|
|
)
|
|
url = f"{self.settings.api_base_url}/trader/v1/orders?{params}"
|
|
req = urllib.request.Request(
|
|
url, headers={"Authorization": f"Bearer {token}"}
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
|
|
raw = json.loads(resp.read().decode() or "[]")
|
|
except (urllib.error.URLError, OSError, json.JSONDecodeError):
|
|
logger.exception("schwab orders read failed")
|
|
return []
|
|
return [
|
|
{
|
|
"broker_order_id": str(o.get("orderId") or ""),
|
|
"symbol": (o.get("orderLegCollection") or [{}])[0]
|
|
.get("instrument", {})
|
|
.get("symbol"),
|
|
"side": (o.get("orderLegCollection") or [{}])[0].get("instruction"),
|
|
"quantity": o.get("quantity"),
|
|
"filled_quantity": o.get("filledQuantity"),
|
|
"status": o.get("status"),
|
|
"type": o.get("orderType"),
|
|
"price": o.get("price"),
|
|
"submitted_at": o.get("enteredTime"),
|
|
}
|
|
for o in raw
|
|
]
|