def main(): logging.basicConfig(level=logging.INFO) feed = 'iex' # <- replace to SIP if you have PRO subscription stream = Stream(data_feed=feed, raw_data=True) stream.subscribe_trade_updates(print_trade_update) stream.subscribe_trades(print_trade, 'AAPL') stream.subscribe_quotes(print_quote, 'IBM') stream.subscribe_crypto_trades(print_crypto_trade, 'BTCUSD') @stream.on_bar('MSFT') async def _(bar): print('bar', bar) @stream.on_updated_bar('MSFT') async def _(bar): print('updated bar', bar) @stream.on_status("*") async def _(status): print('status', status) @stream.on_luld('AAPL', 'MSFT') async def _(luld): print('LULD', luld) stream.run()
class AlpacaStream(StreamingAPI): def __init__(self, queues: QueueMapper): self.alpaca_ws_client = Stream( key_id=config.alpaca_api_key, secret_key=config.alpaca_api_secret ) if not self.alpaca_ws_client: raise AssertionError( "Failed to authenticate Alpaca web_socket client" ) self.alpaca_ws_client.subscribe_trade_updates( AlpacaStream.trade_update_handler ) self.alpaca_ws_client.run() super().__init__(queues) @classmethod async def trade_update_handler(cls, msg): print(f"trade_update_handler:{msg}") @classmethod async def bar_handler(cls, msg): print(f"bar_handler:{msg}") @classmethod async def trades_handler(cls, msg): print(f"trades_handler:{msg}") @classmethod async def quotes_handler(cls, msg): print(f"quotes_handler:{msg}") async def subscribe( self, symbols: List[str], events: List[WSEventType] ) -> bool: for event in events: if event == WSEventType.SEC_AGG: raise NotImplementedError( f"event {event} not implemente in Alpaca" ) elif event == WSEventType.MIN_AGG: self.alpaca_ws_client.subscribe_bars( AlpacaStream.bar_handler, symbols ) elif event == WSEventType.TRADE: self.alpaca_ws_client.subscribe_trades( AlpacaStream.trades_handler, symbols ) elif event == WSEventType.QUOTE: self.alpaca_ws_client.subscribe_quotes( AlpacaStream.quotes_handler, symbols ) return True
def main(): logging.basicConfig(level=logging.INFO) feed = 'iex' # <- replace to SIP if you have PRO subscription stream = Stream(data_feed=feed, raw_data=True) stream.subscribe_trade_updates(print_trade_update) stream.subscribe_trades(print_trade, 'AAPL') stream.subscribe_quotes(print_quote, 'IBM') @stream.on_bar('MSFT') async def _(bar): print('bar', bar) stream.run()
class MarketData: """Threadsafe class that handles the concurrent reading and writing of the market data for the relevant tickers. Should really be a singleton.""" def __init__(self, tickers, alpaca_key, alpaca_secret_key, history_len, trend_len): # all for hashless O(1) access of our sweet sweet data self.tickers = tickers self.tickers_to_indices = {} for i in range(len(tickers)): self.tickers_to_indices[tickers[i]] = i self.stream = Stream(alpaca_key, alpaca_secret_key, data_feed='iex') initial_data = r.stocks.get_latest_price(self.tickers, priceType=None, includeExtendedHours=True) self.data = [] for ticker in tickers: # for each ticker, we need: # - most recent price # - rwlock # - callback function for stream that updates most recent price ticker_data = TickerData(initial_data[self.tickers_to_indices[ticker]], history_len, trend_len) self.stream.subscribe_trades(ticker_data.trade_update_callback, ticker) self.data.append(ticker_data) # only start stream when the market is open def get_ticker_data_for_ticker(self, ticker): return self.data[self.tickers_to_indices[ticker]] def start_stream(self): """Call this function when the market is open. Starts a daemon thread which consumes new ticker updates from the Alpaca stream and updates the relevant data in this object.""" self.stream_thread = threading.Thread(target=self.stream.run, daemon=True) self.stream_thread.start() def get_data_for_ticker(self, ticker): # can be called by any thread return self.get_ticker_data_for_ticker(ticker).get_price() def get_next_data_for_ticker(self, ticker): return self.get_ticker_data_for_ticker(ticker).get_next_price() def get_trend_for_ticker(self, ticker): return self.get_ticker_data_for_ticker(ticker).get_trend() def get_first_price_of_day_for_ticker(self, ticker): return self.get_ticker_data_for_ticker(ticker).get_first_price_of_day() def print_data(self): """Pretty printing for the internal data of this object.""" print_with_lock("---- MARKET DATA ----") for ticker, index in self.tickers_to_indices.items(): ticker_data = self.data[index] with ticker_data.lock.gen_rlock(): if len(ticker_data.prices) < ticker_data.trend_len: # make a reversed copy of the list data_snippet = ticker_data.prices[::-1] else: data_snippet = ticker_data.get_last_k_prices_in_order() # data will always be nonempty for a ticker print_with_lock("{}: {} {}".format(ticker, data_snippet[0], data_snippet)) print_with_lock("---------------------")
class AlpacaStream(StreamingAPI): def __init__(self, queues: QueueMapper): self.alpaca_ws_client = Stream( base_url=URL(config.alpaca_base_url), key_id=config.alpaca_api_key, secret_key=config.alpaca_api_secret, data_feed="sip", # config.alpaca_data_feed, ) if not self.alpaca_ws_client: raise AssertionError( "Failed to authenticate Alpaca web_socket client") self.running = False super().__init__(queues) async def run(self): if not self.running: if not self.queues: raise AssertionError( "can't call `AlpacaStream.run()` without queues") asyncio.create_task(self.alpaca_ws_client._run_forever()) self.running = True @classmethod async def bar_handler(cls, msg): try: event = { "symbol": msg.symbol, "open": msg.open, "close": msg.close, "high": msg.high, "low": msg.low, "start": int(msg.timestamp // 1000000), "volume": msg.volume, "count": 0.0, "vwap": 0.0, "average": 0.0, "totalvolume": None, "EV": "AM", } cls.get_instance().queues[msg.symbol].put(event, timeout=1) except queue.Full as f: tlog( f"[EXCEPTION] process_message(): queue for {event['sym']} is FULL:{f}, sleeping for 2 seconds and re-trying." ) raise except Exception as e: tlog( f"[EXCEPTION] process_message(): exception of type {type(e).__name__} with args {e.args}" ) traceback.print_exc() @classmethod async def trades_handler(cls, msg): try: event = { "symbol": msg.symbol, "price": msg.price, "timestamp": pd.to_datetime(msg.timestamp), "volume": msg.size, "exchange": msg.exchange, "conditions": msg.conditions, "tape": msg.tape, "EV": "T", } cls.get_instance().queues[msg.symbol].put(event, timeout=1) except queue.Full as f: tlog( f"[EXCEPTION] process_message(): queue for {event['sym']} is FULL:{f}, sleeping for 2 seconds and re-trying." ) raise except Exception as e: tlog( f"[EXCEPTION] process_message(): exception of type {type(e).__name__} with args {e.args}" ) traceback.print_exc() @classmethod async def quotes_handler(cls, msg): print(f"quotes_handler:{msg}") async def subscribe(self, symbols: List[str], events: List[WSEventType]) -> bool: for event in events: if event == WSEventType.SEC_AGG: tlog(f"event {event} not implemented in Alpaca") elif event == WSEventType.MIN_AGG: self.alpaca_ws_client._data_ws._running = False self.alpaca_ws_client.subscribe_bars(AlpacaStream.bar_handler, *symbols) elif event == WSEventType.TRADE: self.alpaca_ws_client.subscribe_trades( AlpacaStream.trades_handler, *symbols) elif event == WSEventType.QUOTE: self.alpaca_ws_client.subscribe_quotes( AlpacaStream.quotes_handler, *symbols) return True
def start_trading(self): conn = Stream( self.key_id, self.secret_key, base_url=self.base_url, data_feed='iex') # <- replace to SIP if you have PRO subscription # Listen for second aggregates and perform trading logic async def handle_bar(bar): self.tick_index = (self.tick_index + 1) % self.tick_size if self.tick_index == 0: # It's time to update # Update price info tick_open = self.last_price tick_close = bar.close self.last_price = tick_close self.process_current_tick(tick_open, tick_close) conn.subscribe_bars(handle_bar, self.symbol) # Listen for quote data and perform trading logic async def handle_trades(trade): now = datetime.datetime.utcnow() if now - self.last_trade_time < datetime.timedelta(seconds=1): # don't react every tick unless at least 1 second past return self.last_trade_time = now self.tick_index = (self.tick_index + 1) % self.tick_size if self.tick_index == 0: # It's time to update # Update price info tick_open = self.last_price tick_close = trade.price self.last_price = tick_close self.process_current_tick(tick_open, tick_close) conn.subscribe_trades(handle_trades, self.symbol) # Listen for updates to our orders async def handle_trade_updates(data): symbol = data.order['symbol'] if symbol != self.symbol: # The order was for a position unrelated to this script return event_type = data.event qty = int(data.order['filled_qty']) side = data.order['side'] oid = data.order['id'] if event_type == 'fill' or event_type == 'partial_fill': # Our position size has changed self.position = int(data.position_qty) print(f'New position size due to order fill: {self.position}') if (event_type == 'fill' and self.current_order and self.current_order.id == oid): self.current_order = None elif event_type == 'rejected' or event_type == 'canceled': if self.current_order and self.current_order.id == oid: # Our last order should be removed self.current_order = None elif event_type != 'new': print(f'Unexpected order event type {event_type} received') conn.subscribe_trade_updates(handle_trade_updates) conn.run()