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
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()
def __init__(self, qm: QueueMapper = None): self.market_open: Optional[datetime] self.market_close: Optional[datetime] self.alpaca_rest_client = REST( base_url=URL(config.alpaca_base_url), key_id=config.alpaca_api_key, secret_key=config.alpaca_api_secret, ) if not self.alpaca_rest_client: raise AssertionError( "Failed to authenticate Alpaca RESTful client" ) if qm: self.alpaca_ws_client = Stream( base_url=URL(config.alpaca_base_url), 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( AlpacaTrader.trade_update_handler ) self.running_task: Optional[asyncio.Task] = None now = datetime.now(nyc) calendar = self.alpaca_rest_client.get_calendar( start=now.strftime("%Y-%m-%d"), end=now.strftime("%Y-%m-%d") )[0] if now.date() >= calendar.date.date(): self.market_open = now.replace( hour=calendar.open.hour, minute=calendar.open.minute, second=0, microsecond=0, ) self.market_close = now.replace( hour=calendar.close.hour, minute=calendar.close.minute, second=0, microsecond=0, ) else: self.market_open = self.market_close = None super().__init__(qm)
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)
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)
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)
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)
def consumer_thread(): global conn conn = Stream(ALPACA_API_KEY, ALPACA_SECRET_KEY, base_url=URL('https://paper-api.alpaca.markets'), data_feed='iex') conn.subscribe_quotes(print_quote, 'AAPL') conn.run()
def consumer_thread(): try: # make sure we have an event loop, if not create a new one loop = asyncio.get_event_loop() loop.set_debug(True) except RuntimeError: asyncio.set_event_loop(asyncio.new_event_loop()) global conn conn = Stream(ALPACA_API_KEY, ALPACA_SECRET_KEY, base_url=URL('https://paper-api.alpaca.markets'), data_feed='iex') conn.subscribe_quotes(print_quote, 'AAPL') conn.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 AlpacaTrader(Trader): def __init__(self, qm: QueueMapper = None): self.market_open: Optional[datetime] self.market_close: Optional[datetime] self.alpaca_rest_client = REST( base_url=URL(config.alpaca_base_url), key_id=config.alpaca_api_key, secret_key=config.alpaca_api_secret, ) if not self.alpaca_rest_client: raise AssertionError( "Failed to authenticate Alpaca RESTful client" ) if qm: self.alpaca_ws_client = Stream( base_url=URL(config.alpaca_base_url), 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( AlpacaTrader.trade_update_handler ) self.running_task: Optional[asyncio.Task] = None now = datetime.now(nyc) calendar = self.alpaca_rest_client.get_calendar( start=now.strftime("%Y-%m-%d"), end=now.strftime("%Y-%m-%d") )[0] if now.date() >= calendar.date.date(): self.market_open = now.replace( hour=calendar.open.hour, minute=calendar.open.minute, second=0, microsecond=0, ) self.market_close = now.replace( hour=calendar.close.hour, minute=calendar.close.minute, second=0, microsecond=0, ) else: self.market_open = self.market_close = None super().__init__(qm) async def is_order_completed(self, order) -> Tuple[bool, float]: if not self.alpaca_rest_client: raise AssertionError("Must call w/ authenticated Alpaca client") status = self.alpaca_rest_client.get_order(order_id=order.id) if status.filled_qty == order.qty: return True, float(status.filled_avg_price) return False, 0.0 def get_market_schedule( self, ) -> Tuple[Optional[datetime], Optional[datetime]]: if not self.alpaca_rest_client: raise AssertionError("Must call w/ authenticated Alpaca client") return self.market_open, self.market_close def get_trading_days( self, start_date: date, end_date: date = date.today() ) -> pd.DataFrame: if not self.alpaca_rest_client: raise AssertionError("Must call w/ authenticated Alpaca client") calendars = self.alpaca_rest_client.get_calendar( start=str(start_date), end=str(end_date) ) _df = pd.DataFrame.from_dict([calendar._raw for calendar in calendars]) _df["date"] = pd.to_datetime(_df.date) return _df.set_index("date") def get_position(self, symbol: str) -> float: if not self.alpaca_rest_client: raise AssertionError("Must call w/ authenticated Alpaca client") return self.alpaca_rest_client.get_position(symbol) async def get_order(self, order_id: str): if not self.alpaca_rest_client: raise AssertionError("Must call w/ authenticated Alpaca client") return self.alpaca_rest_client.get_order(order_id) def is_market_open_today(self) -> bool: return self.market_open is not None def get_time_market_close(self) -> Optional[timedelta]: if not self.is_market_open_today(): raise AssertionError("Market closed today") return ( self.market_close - datetime.now(nyc) if self.market_close else None ) async def reconnect(self): self.alpaca_rest_client = REST( key_id=config.alpaca_api_key, secret_key=config.alpaca_api_secret ) if not self.alpaca_rest_client: raise AssertionError( "Failed to authenticate Alpaca RESTful client" ) async def run(self) -> Optional[asyncio.Task]: if not self.running_task: tlog("starting Alpaca listener") self.running_task = asyncio.create_task( self.alpaca_ws_client._trading_ws._run_forever() ) return self.running_task async def close(self): if not self.alpaca_ws_client: raise AssertionError("Must call w/ authenticated Alpaca client") if self.running_task: await self.alpaca_ws_client.stop_ws() async def get_tradeable_symbols(self) -> List[str]: if not self.alpaca_rest_client: raise AssertionError("Must call w/ authenticated Alpaca client") data = self.alpaca_rest_client.list_assets() return [asset.symbol for asset in data if asset.tradable] async def get_shortable_symbols(self) -> List[str]: if not self.alpaca_rest_client: raise AssertionError("Must call w/ authenticated Alpaca client") data = self.alpaca_rest_client.list_assets() return [ asset.symbol for asset in data if asset.tradable and asset.easy_to_borrow and asset.shortable ] async def is_shortable(self, symbol) -> bool: if not self.alpaca_rest_client: raise AssertionError("Must call w/ authenticated Alpaca client") asset = self.alpaca_rest_client.get_asset(symbol) return ( asset.tradable is not False and asset.shortable is not False and asset.status != "inactive" and asset.easy_to_borrow is not False ) async def cancel_order(self, order_id: str): if not self.alpaca_rest_client: raise AssertionError("Must call w/ authenticated Alpaca client") self.alpaca_rest_client.cancel_order(order_id) async def submit_order( self, symbol: str, qty: float, side: str, order_type: str, time_in_force: str, limit_price: str = None, stop_price: str = None, client_order_id: str = None, extended_hours: bool = None, order_class: str = None, take_profit: dict = None, stop_loss: dict = None, trail_price: str = None, trail_percent: str = None, ): if not self.alpaca_rest_client: raise AssertionError("Must call w/ authenticated Alpaca client") return self.alpaca_rest_client.submit_order( symbol, str(qty), side, order_type, time_in_force, limit_price, stop_price, client_order_id, extended_hours, order_class, take_profit, stop_loss, trail_price, trail_percent, ) @classmethod async def trade_update_handler(cls, data): symbol = data.__dict__["_raw"]["order"]["symbol"] data.__dict__["_raw"]["EV"] = "trade_update" data.__dict__["_raw"]["symbol"] = symbol try: cls.get_instance().queues[symbol].put( data.__dict__["_raw"], timeout=1 ) except queue.Full as f: tlog( f"[EXCEPTION] process_message(): queue for {symbol} is FULL:{f}, sleeping for 2 seconds and re-trying." ) raise except AssertionError: for q in cls.get_instance().queues.get_allqueues(): q.put(data.__dict__["_raw"], timeout=1) except Exception as e: tlog(f"[EXCEPTION] process_message(): exception {e}") traceback.print_exc()
import traceback import Util import alpaca_trade_api as tradeapi from alpaca_trade_api.stream import Stream from alpaca_trade_api.common import URL ACCOUNT_STATUS_ACTIVE = "ACTIVE" NANOSEC = 10**9 #core_domain = "https://api.alpaca.markets" core_domain = "https://paper-api.alpaca.markets" data_domain = "https://data.alpaca.markets" conn = Stream(base_url=URL(core_domain), data_feed='iex') async def on_minute_bars(bar): symbol = bar.symbol mins = 1 # BIG TODO - TIME (mins) SHOULD NOT BE HARDCODED epsilon = 30 bar_time = bar.timestamp / NANOSEC time_range_floor = (60 * mins - epsilon) + Core.prev_time[symbol] time_range_ceil = (60 * mins + epsilon) + Core.prev_time[symbol] if time_range_floor < bar_time and bar_time < time_range_ceil: curr = bar.close change = curr - Core.prev_close[symbol] if change > 0:
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( 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()