from __future__ import annotations from brokers.base_broker import BaseBroker, Position, AccountBalance, Order class RobinhoodBroker(BaseBroker): name = "robinhood" def __init__(self, username: str, password: str): self._username = username self._password = password self._connected = False def connect(self) -> bool: if not self._username or not self._password: return False try: import robin_stocks.robinhood as rh rh.login(self._username, self._password) self._connected = True return True except Exception as e: self._connected = False raise RuntimeError(f"Robinhood login failed: {e}") def is_connected(self) -> bool: return self._connected def get_positions(self) -> list[Position]: if not self.is_connected(): return [] try: import robin_stocks.robinhood as rh holdings = rh.account.build_holdings() result = [] for symbol, data in holdings.items(): shares = float(data.get("quantity", 0)) avg_cost = float(data.get("average_buy_price", 0)) current_price = float(data.get("price", 0)) market_value = shares * current_price cost_basis = shares * avg_cost 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=data.get("name", ""), )) return result except Exception: return [] def get_balance(self) -> AccountBalance: if not self.is_connected(): return AccountBalance() try: import robin_stocks.robinhood as rh profile = rh.profiles.load_portfolio_profile() return AccountBalance( total_equity=float(profile.get("equity", 0)), cash=float(profile.get("withdrawable_amount", 0)), buying_power=float(profile.get("buying_power", profile.get("withdrawable_amount", 0))), day_pnl=float(profile.get("equity_previous_close", 0)) - float(profile.get("equity", 0)), ) except Exception: return AccountBalance() def get_orders(self, limit: int = 25) -> list[Order]: if not self.is_connected(): return [] try: import robin_stocks.robinhood as rh orders = rh.orders.get_all_stock_orders()[:limit] result = [] for o in orders: symbol = "" try: instr = rh.stocks.get_instrument_by_url(o.get("instrument", "")) symbol = instr.get("symbol", "") if instr else "" except Exception: pass result.append(Order( order_id=o.get("id", ""), symbol=symbol, side=o.get("side", ""), quantity=float(o.get("quantity", 0)), price=float(o.get("price") or o.get("average_price") or 0), status=o.get("state", ""), order_type=o.get("type", ""), placed_at=o.get("created_at", ""), )) return result except Exception: return []