class Streamer: conn = None def __init__(self, q, api_key='', api_secret='', instrument='', method: StreamingMethod = StreamingMethod.AccountUpdate, base_url='', data_url='', data_feed='iex', *args, **kwargs): try: # make sure we have an event loop, if not create a new one asyncio.get_event_loop() except RuntimeError: asyncio.set_event_loop(asyncio.new_event_loop()) self.conn = Stream(api_key, api_secret, base_url, data_stream_url=data_url, data_feed=data_feed) self.instrument = instrument self.method = method self.q = q def run(self): if self.method == StreamingMethod.AccountUpdate: self.conn.subscribe_trade_updates(self.on_trade) elif self.method == StreamingMethod.MinuteAgg: self.conn.subscribe_bars(self.on_agg_min, self.instrument) elif self.method == StreamingMethod.Quote: self.conn.subscribe_quotes(self.on_quotes, self.instrument) # this code runs in a new thread. we need to set the loop for it loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) self.conn.run() async def on_listen(self, conn, stream, msg): pass async def on_quotes(self, msg): msg._raw['time'] = msg.timestamp self.q.put(msg._raw) async def on_agg_min(self, msg): msg._raw['time'] = msg.timestamp self.q.put(msg._raw) async def on_account(self, msg): self.q.put(msg) async def on_trade(self, msg): self.q.put(msg)
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
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()