Files
StockMind/stockmind/brokers/schwab.py
T
2026-05-26 11:04:27 -04:00

106 lines
4.4 KiB
Python

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 []