class AlpacaTradeInterface(TradeInterface): def __init__(self, key, secret, endpoint): self.api = Alpaca(key, secret, endpoint) self.pending_orders = {} self.cached_tickers = {} def initialize(self, env: Environment): try: # Load cash balance account = self.api.get_account() env.get_portfolio().cash = float( account.cash) # We have margin but shouldn't use it # Load open orders orders = self.api.list_orders(status='open', limit=500) self.pending_orders = { order.id: Order(order.symbol, OrderType(order.order_type), order.qty, order.side == 'buy', order_id=order.id, limit_price=order.limit_price, stop_price=order.stop_price) for order in orders } # Load open positions positions = self.api.list_positions() for pos in positions: env.get_portfolio()._add_position( Position(pos.symbol, pos.qty, pos.avg_entry_price, pos.current_price)) except Exception: logger.exception('Failed to pull current orders and cash balance') return False return True def update(self, env: Environment): orders = self.api.list_orders(status='closed', after=datetime.now() - timedelta(days=2), limit=500) for order in orders: if order.id in self.pending_orders: logger.info(f'Order {order.id} executed') env.notify_order_completed( ExecutedOrder(order.symbol, order.filled_qty, order.filled_avg_price, order.side == 'buy')) def place_order(self, order: Order): logger.info(f'Placing order for {order.quantity} {order.ticker}') try: result = self.api.submit_order(order.ticker, order.quantity, 'buy' if order.is_buy else 'sell', order.order_type.value, 'day', limit_price=order.limit_price, stop_price=order.stop_price) order.order_id = result.id self.pending_orders[result.id] = order return True except Exception: logger.exception( f'Failed to place order for {order.quantity} {order.ticker}') return False def cancel_order(self, order_id): logger.info(f'Canceling order {order_id}') try: self.api.cancel_order(order_id) self.pending_orders.pop(order_id, None) return True except Exception: logger.exception(f'Failed to cancel order {order_id}') return False def market_open(self): clock = self.api.get_clock() return clock.is_open # TODO - we also have open/close times. May want to avoid holding overnight def open_orders(self): return [order for order in self.pending_orders.values()] def ticker_exists(self, ticker): try: if ticker in self.cached_tickers: return self.cached_tickers[ticker] asset = self.api.get_asset(ticker) self.cached_tickers[ticker] = asset.tradable return asset.tradable except: self.cached_tickers[ticker] = False return False
df_sell = df_sell.set_index('symbol', drop=True) print('\nSELLING:') alert += '\n**SELL (Estimated gains)**' for symbol in df_sell.index: qty = df_sell['qty'][symbol] alert += f'\n{symbol}: {qty}' print(f'{symbol}: {qty}') latest_price = df_sell['latest_price'][symbol] purchase_price = df_sell['purchase_price'][symbol] gain = (latest_price - purchase_price) / purchase_price * 100 alert += ' ({}{}%)'.format('+' if gain > 0 else '', '%.1f' % gain) # Submit limit sell order api.submit_order(symbol=symbol, qty=str(qty), side='sell', type='limit', limit_price=str(latest_price), time_in_force='day') # Buy stocks in DataFrame if not df_buy.empty: df_buy = df_buy.set_index('symbol', drop=True) print('\nBUYING:') alert += '\n\n**BUY**' for symbol in df_buy.index: qty = df_buy['qty'][symbol] price = df_buy['close'][symbol] print(f'{symbol}: {qty}') alert += f'\n{symbol}: {qty}' # Submit limit sell order
class AlpacaExchange(Exchange): client: REST access_key: str secret_key: str @staticmethod def build(account: Account, is_paper=False): base_url_live = "https://api.alpaca.markets" base_url_paper = "https://paper-api.alpaca.markets" return AlpacaExchange( account=account, base_url=base_url_paper if is_paper else base_url_live) def __init__(self, account: Account, base_url: str): super().__init__() self.client = REST(key_id=account.get_access_key(), secret_key=account.get_secret_key(), base_url=URL(base_url)) def get_day_candles(self, ticker: str, start: str, end: str) -> List[Candle]: pass def get_last_candle(self, ticker: str) -> Candle: snapshot = self.client.get_snapshot(symbol=ticker) return BarAdapter(ticker=ticker, bar=snapshot.minute_bar) def get_all_assets(self) -> List[Asset]: account = self.client.get_account() positions = self.client.list_positions() cash = AccountAdapter(account.__dict__.get('_raw')) stocks = [ PositionAdapter(position.__dict__.get('_raw')) for position in positions ] return [cash, *stocks] def buy(self, ticker: str, amount: Decimal) -> Optional[Order]: return self.__order(side="buy", ticker=ticker, amount=amount) def sell(self, ticker: str, volume: Decimal) -> Optional[Order]: return self.__order(side="sell", ticker=ticker, volume=volume) def get_order(self, order_id: str) -> Optional[Order]: order = self.client.get_order_by_client_order_id( client_order_id=order_id) return OrderAdapter(AlpacaOrder(**order.__dict__.get("_raw"))) def __order(self, side: str, ticker: str, amount: Decimal = None, volume: Decimal = None) -> Optional[Order]: if amount is None and volume is None: raise ValueError("order amount or volume should not be None") if not self.__is_open_now(): return None custom_order_id = str(uuid.uuid4()) qty = float(volume) if volume is not None else None notional = float(amount) if amount is not None else None order = self.client.submit_order(symbol=ticker, qty=qty, notional=notional, side=side, type="market", client_order_id=custom_order_id) return OrderAdapter(AlpacaOrder(**order.__dict__.get("_raw"))) def __is_open_now(self): """check if US exchanges (NYSE, NASDAQ, etc) are open 09:30 (EST) ~ 16:00 (EST) """ clock = self.client.get_clock() return clock.is_open