from __future__ import annotations from brokers.base_broker import BaseBroker, Position, AccountBalance, Order class SchwabBroker(BaseBroker): name = "schwab" def __init__(self, app_key: str, app_secret: str): self._app_key = app_key self._app_secret = app_secret self._client = None self._connected = False def connect(self) -> bool: if not self._app_key or not self._app_secret: return False try: import schwabdev self._client = schwabdev.Client(self._app_key, self._app_secret) self._connected = True return True except Exception as e: self._connected = False raise RuntimeError(f"Schwab connection failed: {e}") def is_connected(self) -> bool: return self._connected and self._client is not None def get_positions(self) -> list[Position]: if not self.is_connected(): return [] try: response = self._client.account_linked().json() account_hash = response[0].get("hashValue", "") positions_resp = self._client.account(account_hash, fields="positions").json() raw_positions = positions_resp.get("securitiesAccount", {}).get("positions", []) result = [] for p in raw_positions: instr = p.get("instrument", {}) symbol = instr.get("symbol", "") shares = float(p.get("longQuantity", 0)) avg_cost = float(p.get("averagePrice", 0)) market_value = float(p.get("marketValue", 0)) current_price = market_value / shares if shares else 0 cost_basis = avg_cost * shares pnl = market_value - cost_basis pnl_pct = (pnl / cost_basis * 100) if cost_basis else 0 result.append(Position( symbol=symbol, shares=shares, avg_cost=avg_cost, market_value=market_value, current_price=current_price, pnl=pnl, pnl_pct=pnl_pct, description=instr.get("description", ""), )) return result except Exception: return [] def get_balance(self) -> AccountBalance: if not self.is_connected(): return AccountBalance() try: response = self._client.account_linked().json() account_hash = response[0].get("hashValue", "") acct = self._client.account(account_hash).json() balances = acct.get("securitiesAccount", {}).get("currentBalances", {}) return AccountBalance( total_equity=float(balances.get("equity", 0)), cash=float(balances.get("cashBalance", 0)), buying_power=float(balances.get("buyingPower", 0)), day_pnl=float(acct.get("securitiesAccount", {}).get("currentBalances", {}).get("dayProfitLoss", 0)), ) except Exception: return AccountBalance() def get_orders(self, limit: int = 25) -> list[Order]: if not self.is_connected(): return [] try: response = self._client.account_linked().json() account_hash = response[0].get("hashValue", "") from datetime import datetime, timedelta from_time = (datetime.now() - timedelta(days=30)).strftime("%Y-%m-%dT%H:%M:%S+0000") to_time = datetime.now().strftime("%Y-%m-%dT%H:%M:%S+0000") orders_resp = self._client.account_orders(account_hash, from_time, to_time, max_results=limit).json() result = [] for o in orders_resp: legs = o.get("orderLegCollection", [{}]) instr = legs[0].get("instrument", {}) if legs else {} result.append(Order( order_id=str(o.get("orderId", "")), symbol=instr.get("symbol", ""), side=legs[0].get("instruction", "").lower() if legs else "", quantity=float(o.get("quantity", 0)), price=float(o.get("price", 0)), status=o.get("status", ""), order_type=o.get("orderType", ""), placed_at=o.get("enteredTime", ""), )) return result except Exception: return []