def __init__( self, q, api_key="", api_secret="", instrument="", method="", base_url="", data_url="", data_stream="", *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.data_stream = data_stream self.conn = tradeapi.StreamConn( api_key, api_secret, base_url, data_url=data_url, data_stream=self.data_stream, ) self.instrument = instrument self.method = method self.q = q self.conn.on("authenticated")(self.on_auth) self.conn.on(r"Q.*")(self.on_quotes) self.conn.on(r"account_updates")(self.on_account) self.conn.on(r"trade_updates")(self.on_trade)
def _get_stream(self, context): set_context(context) asyncio.set_event_loop(asyncio.new_event_loop()) conn = tradeapi.StreamConn(self._key_id, self._secret, self._base_url) channels = ['trade_updates'] @conn.on(r'trade_updates') async def handle_trade_update(conn, channel, data): # Check for any pending orders waiting_order = self._orders_pending_submission.get( data.order['client_order_id'] ) if waiting_order is not None: if data.event == 'fill': # Submit the waiting order self.order(*waiting_order) self._orders_pending_submission.pop( data.order['client_order_id'], None ) elif data.event in ['canceled', 'rejected']: # Remove the waiting order self._orders_pending_submission.pop( data.order['client_order_id'], None ) if data.event in ['canceled', 'rejected', 'fill']: self._open_orders.pop(data.order['client_order_id'], None) else: self._open_orders[data.order['client_order_id']] = ( self._order2zp(Order(data.order)) ) conn.run(channels)
def __init__(self, q, api_key='', api_secret='', instrument='', method='', base_url='', data_stream='', *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.data_stream = data_stream self.conn = tradeapi.StreamConn(api_key, api_secret, base_url, data_stream=self.data_stream) self.instrument = instrument self.method = method self.q = q self.conn.on('authenticated')(self.on_auth) self.conn.on(r'Q.*')(self.on_quotes) self.conn.on(r'account_updates')(self.on_account) self.conn.on(r'trade_updates')(self.on_trade)
def run(args): symbols = [s.upper() for s in args.symbols.split(',')] max_shares = args.quantity opts = {} if args.key_id: opts['key_id'] = args.key_id if args.secret_key: opts['secret_key'] = args.secret_key if args.base_url: opts['base_url'] = args.base_url elif 'key_id' in opts and opts['key_id'].startswith('PK'): opts['base_url'] = 'https://paper-api.alpaca.markets' # Create an API object which can be used to submit orders, etc. api = tradeapi.REST(**opts) qc = ['Q.' + symbol for symbol in symbols] tc = ['T.' + symbol for symbol in symbols] unit = args.unit # Establish streaming connection conn = tradeapi.StreamConn(**opts) setup(api, conn, symbols, unit, max_shares) conn.run(['trade_updates'] + tc + qc)
def consumer_thread(channels): 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()) global conn if not conn: conn = tradeapi.StreamConn( key_id=_key_id, secret_key=_secret_key if not USE_POLYGON else 'DUMMY', base_url=URL(_base_url), data_url=URL(_data_url), data_stream='polygon' if USE_POLYGON else 'alpacadatav1', raw_data=True, ) conn.on('authenticated')(on_auth) conn.on(r'Q.*')(on_message) conn.on(r'T.*')(on_message) conn.on(r'listening')(listen) if USE_POLYGON: conn.on(r'A.*')(on_message) conn.on(r'AM.*')(on_message) conn.on(r'account_updates')(on_account) conn.on(r'trade_updates')(on_trade) conn.run(channels)
def main(args): api = alpaca.REST( base_url=base_url, key_id=api_key_id, secret_key=api_secret) stream = alpaca.StreamConn(base_url=base_url, key_id=api_key_id, secret_key=api_secret, data_stream='polygon') tickers = api.polygon.all_tickers()
def __init__(self, q, api_key='', api_secret='', instrument='', method='', base_url='', *args, **kwargs): self.conn = tradeapi.StreamConn(api_key, api_secret, base_url) self.instrument = instrument self.method = method self.q = q self.conn.on('authenticated')(self.on_auth) self.conn.on(r'Q.*')(self.on_quotes) self.conn.on(r'account_updates')(self.on_account) self.conn.on(r'trade_updates')(self.on_trade)
def main(): api = alpaca.REST() stream = alpaca.StreamConn() symbol = args.symbol scalpalgo = Algo(api, symbol, lot=args.lot) @stream.on(r'^AM') async def on_bars(conn, channel, data): if data.symbol in fleet: fleet[data.symbol].on_bar(data)
def main(args): print(base_url) api = alpaca.REST( base_url=base_url, key_id=api_key_id, secret_key=api_secret ) stream = alpaca.StreamConn( base_url=base_url, key_id=api_key_id, secret_key=api_secret ) fleet = {} symbols = args.symbols for symbol in symbols: algo = ScalpAlgo(api, symbol, lot=args.lot) fleet[symbol] = algo @stream.on(r'^AM') async def on_bars(conn, channel, data): if data.symbol in fleet: fleet[data.symbol].on_bar(data) @stream.on(r'trade_updates') async def on_trade_updates(conn, channel, data): logger.info(f'trade_updates {data}') symbol = data.order['symbol'] if symbol in fleet: fleet[symbol].on_order_update(data.event, data.order) async def periodic(): while True: if not api.get_clock().is_open: logger.info('exit as market is not open') sys.exit(0) await asyncio.sleep(30) positions = api.list_positions() for symbol, algo in fleet.items(): pos = [p for p in positions if p.symbol == symbol] algo.checkup(pos[0] if len(pos) > 0 else None) channels = ['trade_updates'] + [ 'AM.' + symbol for symbol in symbols ] loop = stream.loop loop.run_until_complete(asyncio.gather( stream.subscribe(channels), periodic(), )) loop.close()
def _get_stream(self, context): set_context(context) asyncio.set_event_loop(asyncio.new_event_loop()) conn = tradeapi.StreamConn(self._key_id, self._secret, self._base_url) channels = ['trade_updates'] @conn.on(r'trade_updates') async def handle_trade_update(conn, channel, data): if data.event in ['canceled', 'rejected', 'fill']: del self._open_orders[data.order['client_order_id']] else: self._open_orders[data.order['client_order_id']] = ( self._order2zp(Order(data.order))) conn.run(channels)
def __init__(self, key_id: str, secret_key: str, base_url: str, data_url: str, db: str, table: str, lock: Lock, submitted: Dict[str, str], closing: Set[str]): self.conn = alpaca.StreamConn(key_id=key_id, secret_key=secret_key, base_url=base_url, data_url=data_url, data_stream='alpacadatav1') self.db = db self.table = table self.lock = lock self._submitted = submitted self._closing = closing self.thread = None logger.info('WSHandler initialized')
def main(args): base_url = 'https://paper-api.alpaca.markets' # 'https://paper-api.alpaca.markets' - used for paper account api_key_id = 'PKDCQCXDXMFDHBARUXDE' #paper trading(PKK2HPWQFY9I25KIDVP9) api_secret = 'HhXt0uYUqW3KqEv2G2w8dTdPSeMECU7rDFcxppDC' #paper trading(IKSKvUlQp5iv9fGMkVlJ27pFiKqE0symg2KseZpQ) api = alpaca.REST(base_url=base_url, key_id=api_key_id, secret_key=api_secret, api_version='v2') stream = alpaca.StreamConn(base_url=base_url, key_id=api_key_id, secret_key=api_secret) fleet = {} symbols = args.symbols for symbol in symbols: algo = ScalpAlgo(api, symbol, lot=args.lot) fleet[symbol] = algo @stream.on(r'^AM') async def on_bars(conn, channel, data): if data.symbol in fleet: fleet[data.symbol].on_bar(data) @stream.on(r'trade_updates') async def on_trade_updates(conn, channel, data): logger.info(f'trade_updates {data}') symbol = data.order['symbol'] if symbol in fleet: fleet[symbol].on_order_update(data.event, data.order) async def periodic(): while True: if not api.get_clock().is_open: logger.info('exit as market is not open') sys.exit(0) await asyncio.sleep(30) positions = api.list_positions() for symbol, algo in fleet.items(): pos = [p for p in positions if p.symbol == symbol] algo.checkup(pos[0] if len(pos) > 0 else None) channels = ['trade_updates'] + ['AM.' + symbol for symbol in symbols] loop = stream.loop loop.run_until_complete( asyncio.gather(stream.subscribe(channels), periodic())) loop.close()
def __init__(self, q, api_key='ayQ7JwRPeUF2k_2UyLkM_6PDe_VdM8yJdagJm7', api_secret='eu6M9B3dKL3MdGrc8o6CivArUtvsYNKhDQC4kXj/', instrument='', method='', base_url='', *args, **kwargs): self.conn = tradeapi.StreamConn(api_key, api_secret, base_url) self.instrument = instrument self.method = method self.q = q self.conn.on('authenticated')(self.on_auth) self.conn.on(r'Q.*')(self.on_quotes) self.conn.on(r'account_updates')(self.on_account) self.conn.on(r'trade_updates')(self.on_trade)
def main(args): api = alpaca.REST(API_KEY_ID, API_SECRET_KEY, api_version='v2') stream = alpaca.StreamConn( LIVE_API_KEY_ID, LIVE_API_SECRET_KEY) # need live account to access streaming fleet = {} symbols = [symbol.strip() for symbol in TRADE_SYMBOLS.split(',')] for symbol in symbols: algo = RSIstrategy(symbol, lot=args.lot) fleet[symbol] = algo # Event handlers: @stream.on(r'^AM') async def on_bars(conn, channel, data): if data.symbol in fleet: fleet[data.symbol].on_bar(data) @stream.on(r'trade_updates') async def on_trade_updates(conn, channel, data): logger.info(f'trade_updates {data}') symbol = data.order['symbol'] if symbol in fleet: fleet[symbol].on_order_update(data.event, data.order) async def periodic(): while True: if not api.get_clock().is_open: logger.info('exit as market is not open') sys.exit(0) await asyncio.sleep(30) positions = api.list_positions() for symbol, algo in fleet.items(): pos = [p for p in positions if p.symbol == symbol] algo.checkup(pos[0] if len(pos) > 0 else None) channels = ['trade_updates'] + ['AM.' + symbol for symbol in symbols] loop = stream.loop loop.run_until_complete( asyncio.gather( stream.subscribe(channels), periodic(), )) loop.close()
def main(args): api = alpaca.REST() stream = alpaca.StreamConn() fleet = {} symbols = args.symbols for symbol in symbols: algo = ScalpAlgo(api, symbol, lot=args.lot) fleet[symbol] = algo @stream.on(r"^AM") async def on_bars(conn, channel, data): if data.symbol in fleet: fleet[data.symbol].on_bar(data) @stream.on(r"trade_updates") async def on_trade_updates(conn, channel, data): logger.info(f"trade_updates {data}") symbol = data.order["symbol"] if symbol in fleet: fleet[symbol].on_order_update(data.event, data.order) async def periodic(): while True: if not api.get_clock().is_open: logger.info("exit as market is not open") sys.exit(0) await asyncio.sleep(30) positions = api.list_positions() for symbol, algo in fleet.items(): pos = [p for p in positions if p.symbol == symbol] algo.checkup(pos[0] if len(pos) > 0 else None) channels = ["trade_updates"] + ["AM." + symbol for symbol in symbols] loop = stream.loop loop.run_until_complete( asyncio.gather( stream.subscribe(channels), periodic(), )) loop.close()
def _get_stream(self, context): set_context(context) asyncio.set_event_loop(asyncio.new_event_loop()) conn = tradeapi.StreamConn( self._key_id, self._secret, self._base_url, data_url=os.environ.get("DATA_PROXY_WS", ''), data_stream='polygon' if self._use_polygon else 'alpacadatav1') channels = ['trade_updates'] @conn.on(r'trade_updates') async def handle_trade_update(conn, channel, data): # Check for any pending orders waiting_order = self._orders_pending_submission.get( data.order['client_order_id']) if waiting_order is not None: if data.event == 'fill': # Submit the waiting order self.order(*waiting_order) self._orders_pending_submission.pop( data.order['client_order_id'], None) elif data.event in ['canceled', 'rejected']: # Remove the waiting order self._orders_pending_submission.pop( data.order['client_order_id'], None) if data.event in ['canceled', 'rejected', 'fill']: self._open_orders.pop(data.order['client_order_id'], None) else: self._open_orders[data.order['client_order_id']] = ( self._order2zp(Order(data.order))) while 1: try: conn.run(channels) log.info("Connection reestablished") except Exception: from time import sleep sleep(5) asyncio.set_event_loop(asyncio.new_event_loop())
def run(): global quote global position # init global variables quote = Quote() position = Position() # inputs symbol = 'SNAP' max_shares = 500 opts = dict(base_url="https://paper-api.alpaca.markets", **cfg.keys) # Create API objects which can be used to submit orders, stream data etc. api = tradeapi.REST(**opts) conn = tradeapi.StreamConn(**opts) # Initiliaze our message handling @conn.on(r"Q.*$") async def on_quote(conn, channel, data): """ Quote update """ streams.process_quote(channel, data, old_quote=quote) @conn.on(r"T.*$") async def on_trade(conn, channel, data): """ Trade update """ streams.process_trade(channel, data) @conn.on(r"trade_updates") async def on_trade_updates(conn, channel, data, position=position): """ Order update """ streams.process_order(channel, data) # start listening - blocks forever conn.run(["trade_updates", f"T.{symbol}", f"Q.{symbol}"])
async def producer_async_main( queues: List[Queue], scanner_queue: Queue, num_consumer_processes: int, ): await create_db_connection(str(config.dsn)) await run(queues=queues) trade_ws = tradeapi.StreamConn( base_url=config.alpaca_base_url, key_id=config.alpaca_api_key, secret_key=config.alpaca_api_secret, ) trade_updates_task = asyncio.create_task( trade_run(ws=trade_ws, queues=queues), name="trade_updates_task", ) scanner_input_task = asyncio.create_task( scanner_input(scanner_queue, queues, num_consumer_processes), name="scanner_input", ) tear_down = asyncio.create_task( teardown_task( timezone("America/New_York"), [trade_ws], [scanner_input_task], ) ) await asyncio.gather( trade_updates_task, scanner_input_task, tear_down, return_exceptions=True, ) tlog("producer_async_main() completed")
def run(): # conn = tradeapi.stream2.StreamConn(base_url=base_url, key_id=api_key, secret_key=secret_key) conn = tradeapi.StreamConn(base_url=base_url, key_id=api_key, secret_key=secret_key) symbols = [ 'AAPL', 'MSFT', 'TSLA' ] channels = ['trade_updates'] for symbol in symbols: symbol_channels = ['AM.{}'.format(symbol)] channels += symbol_channels print('Watching {} symbols.'.format(len(symbols))) count = { 'AAPL': -1, 'MSFT': 0, 'TSLA': 1 } @conn.on(r'^AM\..+$') async def on_bar(connection, channel, data): symbol = data.symbol if symbol in symbols: count[symbol] += 1 print(f'{symbol}: {count[symbol]}') if count[symbol] > 3: symbols.remove(symbol) if len(symbols) <= 0: print(conn) conn.close(renew=False) print('Stream connection closed.') conn.deregister(['AM.{}'.format(symbol)]) print(f'Stopped watching {symbol}.') @conn.on(r'close') async def on_close(): print('On close') run_ws(conn, channels) print('Trading completed!')
def main(args): logger.info("Inside main function") api = alpaca.REST() stream = alpaca.StreamConn() logger.info("Instantiating scalp object with arguments ...") scalp = algo.Algo(api=api, symbol=args.symbol, lot=args.lot) @stream.on(r'^AM') async def on_bars(conn, channel, data): scalp.on_bar(data) @stream.on(r'trade_updates') async def on_trade_updates(conn, channel, data): logger.info(f'trade_updates {data}') symbol = data.order['symbol'] if symbol == args.symbol: scalp.on_order_update(data.event, data.order) async def periodic(): while True: if not api.get_clock().is_open: logger.info('exit as market is not open') sys.exit(0) await asyncio.sleep(30) positions = api.list_positions() pos = [p for p in positions if p.symbol == args.symbol] scalp.checkup(pos[0] if len(pos) > 0 else None) channels = ['trade_updates', args.symbol] loop = stream.loop loop.run_until_complete( asyncio.gather( stream.subscribe(channels), periodic(), ))
def start_trading(self): conn = tradeapi.StreamConn(self.key_id, self.secret_key, self.base_url) # Listen for second aggregates and perform trading logic @conn.on(r'A$', [self.symbol]) async def handle_agg(conn, channel, data): self.tick_index = (self.tick_index + 1) % (self.tick_size) print(self.tick_index) if self.tick_index == 0: # It's time to update # Update price info tick_open = self.last_price tick_close = data.close self.last_price = tick_close # Update streak info diff = truncate(tick_close, 2) - truncate(tick_open, 2) if diff != 0: # There was a meaningful change in the price self.streak_count += 1 increasing = tick_open > tick_close if self.streak_increasing != increasing: # It moved in the opposite direction of the streak. # Therefore, the streak is over, and we should reset. # Empty out the position self.send_order(0) # Reset variables self.streak_increasing = increasing self.streak_start = tick_open self.streak_count = 0 else: # Calculate the number of shares we want to be holding total_buying_power = self.equity * \ self.margin_multiplier target_value = (2**self.streak_count) * \ (self.base_bet / 100) * total_buying_power if target_value > total_buying_power: # Limit the amount we can buy to a bit (1 share) # less than our total buying power target_value = total_buying_power - self.last_price target_qty = int(target_value / self.last_price) if self.streak_increasing: target_qty = target_qty * -1 self.send_order(target_qty) # Update our account balance self.equity = float(self.api.get_account().equity) # Listen for updates to our orders @conn.on(r'trade_updates') async def handle_trade(conn, channel, 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.run([f'A.{self.symbol}', 'trade_updates'])
def run(args): symbol = args.symbol max_shares = args.quantity opts = {} if args.key_id: opts['PKWN2459FP6IFHSCDMO9'] = args.key_id if args.secret_key: opts['ibvqEPLK7BQvb/7nCfqIsW6Jsi8kMf4xE/QOYz4u'] = args.secret_key if args.base_url: opts['base_url'] = args.base_url elif 'PKWN2459FP6IFHSCDMO9' in opts and opts['PKWN2459FP6IFHSCDMO9'].startswith('PK'): opts['base_url'] = 'https://paper-api.alpaca.markets' # Create an API object which can be used to submit orders, etc. api = tradeapi.REST(**opts) symbol = symbol.upper() quote = Quote() qc = 'Q.%s' % symbol tc = 'T.%s' % symbol position = Position() # Establish streaming connection conn = tradeapi.StreamConn(**opts) # Define our message handling @conn.on(r'Q$') async def on_quote(conn, channel, data): # Quote update received quote.update(data) @conn.on(r'T$') async def on_trade(conn, channel, data): if quote.traded: return # We've received a trade and might be ready to follow it if ( data.timestamp <= ( quote.time + pd.Timedelta(np.timedelta64(50, 'ms')) ) ): # The trade came too close to the quote update # and may have been for the previous level return if data.size >= 100: # The trade was large enough to follow, so we check to see if # we're ready to trade. We also check to see that the # bid vs ask quantities (order book imbalance) indicate # a movement in that direction. We also want to be sure that # we're not buying or selling more than we should. if ( data.price == quote.ask and quote.bid_size > (quote.ask_size * 1.8) and ( position.total_shares + position.pending_buy_shares ) < max_shares - 100 ): # Everything looks right, so we submit our buy at the ask try: o = api.submit_order( symbol=symbol, qty='100', side='buy', type='limit', time_in_force='day', limit_price=str(quote.ask) ) # Approximate an IOC order by immediately cancelling api.cancel_order(o.id) position.update_pending_buy_shares(100) position.orders_filled_amount[o.id] = 0 print('Buy at', quote.ask, flush=True) quote.traded = True except Exception as e: print(e) elif ( data.price == quote.bid and quote.ask_size > (quote.bid_size * 1.8) and ( position.total_shares - position.pending_sell_shares ) >= 100 ): # Everything looks right, so we submit our sell at the bid try: o = api.submit_order( symbol=symbol, qty='100', side='sell', type='limit', time_in_force='day', limit_price=str(quote.bid) ) # Approximate an IOC order by immediately cancelling api.cancel_order(o.id) position.update_pending_sell_shares(100) position.orders_filled_amount[o.id] = 0 print('Sell at', quote.bid, flush=True) quote.traded = True except Exception as e: print(e) @conn.on(r'trade_updates') async def on_trade_updates(conn, channel, data): # We got an update on one of the orders we submitted. We need to # update our position with the new information. event = data.event if event == 'fill': if data.order['side'] == 'buy': position.update_total_shares( int(data.order['filled_qty']) ) else: position.update_total_shares( -1 * int(data.order['filled_qty']) ) position.remove_pending_order( data.order['id'], data.order['side'] ) elif event == 'partial_fill': position.update_filled_amount( data.order['id'], int(data.order['filled_qty']), data.order['side'] ) elif event == 'canceled' or event == 'rejected': position.remove_pending_order( data.order['id'], data.order['side'] ) conn.run( ['trade_updates', tc, qc] )
# Asset used as volatility indicator (VXX by default) volatility = 'VXX' # ETF for up (leverage should ideally match spy_down) spy_up = 'SPXL' # ETF for down (leverage should ideally match spy_up) spy_down = 'SPXS' # REST API Instance for placing orders order_api = api.REST(base_url=os.getenv("ORDERS_BASE_URL"), key_id=os.getenv("ORDERS_KEY"), secret_key=os.getenv("ORDERS_SECRET")) # Streaming API instance for real-time quotes quote_api = api.StreamConn(base_url=os.getenv("QUOTES_BASE_URL"), key_id=os.getenv("QUOTES_KEY"), secret_key=os.getenv("QUOTES_SECRET")) def run(tickers, order_api, quote_api): # Use trade updates to keep track of our portfolio @quote_api.on(r'trade_update') async def handle_trade_update(conn, channel, data): print('Trade update') @quote_api.on(r'A$') async def handle_second_bar(conn, channel, data): print('1s bar update') # Enter main loop
def connect(self): return tradeapi.StreamConn(str(self.api_key), str(self.api_secret), str(self.base_url))
key_id = "PKW2JHY75SLSKA1VUX74" secret_key = "mS59yaHEGv5CyBFmaGqrmziRRkF14ioEWS8ENmGG" base_url = 'https://paper-api.alpaca.markets' data_url = 'https://data.alpaca.markets' # The connection to the Alpaca API api = tradeapi.REST( key_id, secret_key, base_url ) conn = tradeapi.StreamConn( key_id, secret_key, base_url=base_url, data_url=data_url, data_stream='polygon' ) # This is another way to setup wrappers for websocket callbacks, handy if conn is not global. on_minute = conn.on(r'AM$')(on_minute) on_tick = conn.on(r'A$')(on_tick) on_data = conn.on(r'.*')(on_data) # This is an example of how you can add your own async functions into the loop # This one just watches this program for edits and tries to restart it asyncio.ensure_future(reloadWatch(__file__, sys.argv)()) try: if opt.all: # Note to see all these channels, you'd need to add a handler
class AlpacaStockTradingEnv(gym.Env): """ Description: A live stock trading environment utilizing Alpaca Trade API. The environment utilizes websockets for trade updates and market data. Alpaca API keys are required for this environment to operate. The data is the normalized OHLC and volume values for 1 minute candlesticks. Observation: Type: Box(low=0, high=1, shape=(5, observation_size), dtype=np.float16) Description: The observation_size is set during environment creation and represents the number of 1min candlesticks in the observation. Num Observation Min Max 0 Open 0 1 1 High 0 1 2 Low 0 1 3 Close 0 1 4 Volume 0 1 Actions: Type: spaces.Box( low=np.array([-1]), high=np.array([1]), dtype=np.float16) Description: The action space represents the percentage of the account to invest. Negative is short, Positive is long. Num Action Min Max 0 Percentage of account to invest -1 1 Reward: Reward is the unrealized profit/loss for the next observation. Realized Profit/Loss is also tracked in the info dict. Starting State: First candlestick observation once the market opens Episode Termination: Agent loses more than 5% or 11 minutes before market close. """ metadata = {'render.modes': ['human']} eastern = timezone('US/Eastern') channels = ['AM.*', 'trade_updates'] live_conn = tradeapi.StreamConn(LIVE_APCA_API_KEY_ID, LIVE_APCA_API_SECRET_KEY) paper_conn = tradeapi.StreamConn(PAPER_APCA_API_KEY_ID, PAPER_APCA_API_SECRET_KEY, PAPER_APCA_API_BASE_URL) try: # tLWS = threading.Thread( # target=live_conn.run, args=[channels]) # tLWS.start() logger.info('Connecting to channels: %s', channels) tPWS = threading.Thread(target=paper_conn.run, args=[channels]) tPWS.start() except RuntimeError as e: # Already running logger.error(e) def __init__(self, symbol, previous_close, daily_avg_volume=None, live=False, observation_size=1, volume_enabled=True, allotted_amount=10000.0): super(AlpacaStockTradingEnv, self).__init__() self.current_step = 0 self.current_episode = 0 self.live = live self.symbol = symbol self.market = None self.paper_api = tradeapi.REST(PAPER_APCA_API_KEY_ID, PAPER_APCA_API_SECRET_KEY, PAPER_APCA_API_BASE_URL, api_version='v2') self.live_api = tradeapi.REST(LIVE_APCA_API_KEY_ID, LIVE_APCA_API_SECRET_KEY, api_version='v2') if self.live: self._on_minute_bars =\ self.live_conn.on(r'^AM$')(self._on_minute_bars) self._on_trade_updates =\ self.live_conn.on(r'trade_updates$')(self._on_trade_updates) else: self._on_minute_bars =\ self.paper_conn.on(r'^AM$')(self._on_minute_bars) self._on_trade_updates =\ self.paper_conn.on(r'trade_updates$')(self._on_trade_updates) self.volume_enabled = volume_enabled self.asset_data = None self.normalized_asset_data = None self.previous_close = previous_close if self.volume_enabled: self.daily_avg_volume = daily_avg_volume self.observation_size = observation_size self.base_value = allotted_amount self.equity = [allotted_amount] self.cash = [allotted_amount] self.profit_loss = [0.0] self.positions = [(0, 0.0)] # (qty, price) self.alpaca_positions = [(0, 0.0)] # actual traded positions self.current_alpaca_position = (0, 0.0) self.trades = [] self.rewards = [0.0] self.max_qty = None # Each action represents the amount of the portfolio that should be # invested ranging from -1 to 1. Negative is short, positive is long. self.action_space = spaces.Box(low=np.array([-1]), high=np.array([1]), dtype=np.float16) # Normalized values for: Open, High, Low, Close, Volume self.observation_space = spaces.Box(low=0, high=1, shape=(5, observation_size), dtype=np.float16) logger.info('Initialized AlpacaStockTradingEnv: ') logger.info('Live: %s', self.live) logger.info('Symbol: %s', self.symbol) logger.info('volume_enabled: %s', self.volume_enabled) logger.info('previous_close: %s', self.previous_close) if self.volume_enabled: logger.info('daily_avg_volume: %s', self.daily_avg_volume) logger.info('observation_size: %s', self.observation_size) logger.info('base_value: %s', self.base_value) logger.info('equity: %s', self.equity) logger.info('cash: %s', self.cash) logger.info('profit_loss: %s', self.profit_loss) def _normalize_data(self): normalized_dataframe = self.asset_data.copy() normalized_dataframe['open'] =\ normalized_dataframe['open'] / (2 * self.previous_close) normalized_dataframe['high'] =\ normalized_dataframe['high'] / (2 * self.previous_close) normalized_dataframe['low'] =\ normalized_dataframe['low'] / (2 * self.previous_close) normalized_dataframe['close'] =\ normalized_dataframe['close'] / (2 * self.previous_close) normalized_dataframe['volume'] =\ normalized_dataframe['volume'] / self.daily_avg_volume return normalized_dataframe def _initialize_data(self): self.market = self.live_api.get_clock() today = datetime.datetime.now(self.eastern) if self.market.is_open: _open = int( self.eastern.localize( datetime.datetime.combine(today, datetime.time( 9, 30))).timestamp() * 1000) else: _open = int(self.market.next_open.timestamp() * 1000) close = int(self.market.next_close.timestamp() * 1000) self.asset_data = self.live_api.polygon.historic_agg_v2( self.symbol, 1, 'minute', _open, close).df self.current_step = len(self.asset_data) self.normalized_asset_data = self._normalize_data() def _await_market_open(self): while not self.market.is_open: curr_time = datetime.datetime.now(self.eastern) next_open = self.market.next_open.astimezone(self.eastern) wait_time = (next_open - curr_time).seconds + 10 print('Waiting ' + str(wait_time) + ' seconds for market to open.') time.sleep(wait_time) self.market = self.live_api.get_clock() async def _on_minute_bars(self, conn, channel, bar): if self.normalized_asset_data is not None: if self.market.is_open: if bar.symbol == self.symbol: # if len(self.asset_data) != 0: # # TODO test # # Missing a bar or market is frozen # if bar.start >\ # self.asset_data.index[-1]\ # + timedelta(seconds=90): # self._initialize_data() new_row = { 'open': bar.open, 'high': bar.high, 'low': bar.low, 'close': bar.close, 'volume': bar.volume } self.asset_data.loc[bar.start] = new_row self.normalized_asset_data = self._normalize_data() async def _on_trade_updates(self, conn, channel, account): event = account.event order = account.order # logger.info('async _on_trade_updates called for %s', order) if order['symbol'] == self.symbol: if event == 'fill' or event == 'partial_fill': close_price = self.asset_data.iloc[-1].close fill_price = float(account.price) position_qty = int(account.position_qty) curr_qty = self.current_alpaca_position[0] curr_avg_price = self.current_alpaca_position[1] if order['side'] == 'buy': fill_qty = int(account.qty) slippage_per_share = close_price - fill_price else: fill_qty = -int(account.qty) slippage_per_share = fill_price - close_price total_slippage = slippage_per_share * abs(fill_qty) if position_qty != 0: if curr_qty >= 0 and fill_qty > 0\ or curr_qty <= 0 and fill_qty < 0: avg_price = ((abs(fill_qty) * fill_price) + (abs(curr_qty) * curr_avg_price)) / abs(position_qty) else: avg_price = curr_avg_price else: avg_price = 0.0 self.current_alpaca_position = (position_qty, avg_price) trade = { 'symbol': self.symbol, 'order_type': order['order_type'], 'filled_at': order['filled_at'], 'fill_price': fill_price, 'fill_qty': int(account.qty), 'observation_time': self.asset_data.index[-1], 'observation_price': close_price, 'side': order['side'], 'slippage_per_share': slippage_per_share, 'total_slippage': total_slippage, } self.trades.append(trade) def _next_observation(self): """Get the stock data for the current observation size.""" if not self.market.is_open: tAMO = threading.Thread(target=self._await_market_open) tAMO.start() tAMO.join() # TODO clean up with wait() or threading while len(self.normalized_asset_data) <= self.current_step: # Wait for new data to be appended continue offset = self.current_step + 1 - self.observation_size if offset < 0: # Less data than observation_size if self.volume_enabled: observation_zeros = np.zeros([5, abs(offset)]) else: observation_zeros = np.zeros([4, abs(offset)]) offset = 0 observation = np.array([ self.normalized_asset_data.iloc[offset:self.current_step + 1]['open'].values, self.normalized_asset_data.iloc[offset:self.current_step + 1]['high'].values, self.normalized_asset_data.iloc[offset:self.current_step + 1]['low'].values, self.normalized_asset_data.iloc[offset:self.current_step + 1]['close'].values ]) if self.volume_enabled: observation = np.vstack( (observation, self.normalized_asset_data.iloc[offset:self.current_step + 1]['volume'].values)) if observation.shape[1] < self.observation_size: observation = np.concatenate((observation, observation_zeros), axis=1) return observation def _submit_order(self, qty, order_type='market'): if qty == 0: # no trade needed return side = 'buy' if qty > 0 else 'sell' try: if self.live: self.live_api.submit_order(symbol=self.symbol, qty=abs(qty), side=side, type=order_type, time_in_force='day') else: self.paper_api.submit_order(symbol=self.symbol, qty=abs(qty), side=side, type=order_type, time_in_force='day') except Exception as e: print(e) # TODO return error # check for: # 403 Forbidden: Buying power or shares is not sufficient. # 422 Unprocessable: Input parameters are not recognized. return e def _close_position(self): if self.current_alpaca_position[0] != 0: if self.live: self.live_api.close_position(self.symbol) else: self.paper_api.close_position(self.symbol) # TODO clean up with wait() or threading while self.current_alpaca_position[0] != 0: # wait for position to close continue def _take_action(self, action): curr_price = self.asset_data.iloc[self.current_step]['close'] if self.positions[-1][0] != self.current_alpaca_position[0]: print('Order did not complete..') # TODO determine how to cancel incomplete orders pass # Current position curr_qty, avg_price = self.current_alpaca_position curr_invested = curr_qty / self.max_qty if action == 0: # Close position trade_qty = -curr_qty else: target_change = action - curr_invested trade_qty = int(target_change * self.equity[-1] / curr_price) if curr_qty == 0: # Simple short or long trade purchase_amount = abs(trade_qty * curr_price) new_position = (trade_qty, curr_price) self.positions.append(new_position) self.cash.append(self.cash[-1] - purchase_amount) self.equity.append(self.cash[-1] + purchase_amount) self.profit_loss.append(0.0) # Submit order tOrder = threading.Thread(target=self._submit_order, args=[trade_qty]) tOrder.start() elif curr_qty > 0 and curr_qty + trade_qty < 0 or\ curr_qty < 0 and curr_qty + trade_qty > 0: # Trade crosses from short to long or long to short # Close current position, update P/L and cash if curr_qty > 0: # Closing long position self.cash.append(self.cash[-1] + curr_qty * curr_price) self.profit_loss.append((curr_price - avg_price) * curr_qty) tOrder = threading.Thread(target=self._close_position) tOrder.start() tOrder.join() else: # Closing short position self.cash.append(self.cash[-1] + abs(curr_qty) * (avg_price - (curr_price - avg_price))) self.profit_loss.append( (avg_price - curr_price) * abs(curr_qty)) tOrder = threading.Thread(target=self._close_position) tOrder.start() tOrder.join() # Simple short or long trade trade_qty += curr_qty purchase_amount = abs(trade_qty * curr_price) new_position = (trade_qty, curr_price) self.positions.append(new_position) self.cash.append(self.cash[-1] - purchase_amount) self.equity.append(self.cash[-1] + purchase_amount) # Submit order tOrder = threading.Thread(target=self._submit_order, args=[trade_qty]) tOrder.start() else: # Trade increases or reduces position (including closing out) if curr_qty > 0 and trade_qty > 0 or\ curr_qty < 0 and trade_qty < 0: # Adding to position purchase_amount = abs(trade_qty * curr_price) while self.cash[-1] < purchase_amount: # Descrease trade_qty if not enough cash trade_qty = trade_qty - 1 if trade_qty > 0\ else trade_qty + 1 purchase_amount = abs(trade_qty * curr_price) total_qty = trade_qty + curr_qty avg_price = (((trade_qty * curr_price) + (curr_qty * avg_price)) / total_qty) new_position = (total_qty, avg_price) self.positions.append(new_position) self.cash.append(self.cash[-1] - purchase_amount) self.profit_loss.append(0.0) if total_qty > 0: # Long position self.equity.append(self.cash[-1] + (total_qty * curr_price)) else: # Short position self.equity.append(self.cash[-1] + abs(total_qty) * (avg_price - (curr_price - avg_price))) # Submit order tOrder = threading.Thread(target=self._submit_order, args=[trade_qty]) tOrder.start() # Reducing position or not changing else: if trade_qty > 0: # Reducing short position self.cash.append(self.cash[-1] + abs(trade_qty) * (avg_price - (curr_price - avg_price))) self.profit_loss.append( (avg_price - curr_price) * trade_qty) else: # Reducing long position self.cash.append(self.cash[-1] + abs(trade_qty * curr_price)) self.profit_loss.append( (curr_price - avg_price) * abs(trade_qty)) net_qty = curr_qty + trade_qty if net_qty == 0: new_position = (net_qty, 0.0) else: new_position = (net_qty, avg_price) self.positions.append(new_position) if net_qty > 0: # Long position self.equity.append(self.cash[-1] + abs(net_qty * curr_price)) else: # Short position self.equity.append(self.cash[-1] + abs(net_qty) * (avg_price - (curr_price - avg_price))) # Submit order tOrder = threading.Thread(target=self._submit_order, args=[trade_qty]) tOrder.start() def step(self, action): # Execute one time step within the environment self._take_action(action) curr_price = self.asset_data.iloc[self.current_step]['close'] self.current_step += 1 obs = self._next_observation() next_price = self.asset_data.iloc[self.current_step]['close'] reward = (next_price - curr_price) * self.positions[-1][0] self.equity[-1] += reward self.rewards.append(reward) self.alpaca_positions.append(self.current_alpaca_position) info = { "profit_loss": { "date_time": self.asset_data.index[-2], "profit_loss": self.profit_loss[-1], "reward": reward, "mode": 'live' if self.live else 'paper' }, "trades": self.trades, "positions": { "env_position": self.positions[-1], "actual_position": self.current_alpaca_position } } # Clear trades for future trades self.trades = [] # Close 11 minutes before end of day now = datetime.datetime.now(self.eastern) stop_time = self.eastern.localize( datetime.datetime.combine(now, datetime.time(15, 49))) if now > stop_time: done = True # TODO this needs to be more reflective of real data in future elif (self.equity[-1] - self.base_value) / self.base_value <= -0.02: done = True else: done = False if done: self.close() reward = 0.0 self.rewards.append(reward) self.alpaca_positions.append(self.current_alpaca_position) info["closing_trades"] = { "profit_loss": { "date_time": self.asset_data.index[-1], "profit_loss": self.profit_loss[-1], "reward": reward, "mode": 'live' if self.live else 'paper' }, "trades": self.trades, "positions": { "env_position": self.positions[-1], "actual_position": self.current_alpaca_position } } return obs, reward, done, info def reset(self): """Reset the state of the environment to an initial state""" self.current_step = 0 self._initialize_data() self.equity = [self.base_value] self.profit_loss = [0.0] self.cash = [self.base_value] self.positions = [(0, 0.0)] self.rewards = [0.0] self.trades = [] observation = self._next_observation() self.max_qty = int((self.base_value / self.asset_data.iloc[self.current_step]['open'])) return observation def render(self, mode='human', close=False): """Render the environment to the screen""" pass def close(self): # Ensure no positions are held over night # tOrder = threading.Thread( # target=self._close_position) # tOrder.start() # tOrder.join() # Close positions action = np.array([0.0]) self._take_action(action)
def start_trading(self): conn = tradeapi.StreamConn( self.key_id, self.secret_key, base_url=self.base_url, data_url=self.data_url, data_stream='polygon' if USE_POLYGON else 'alpacadatav1' ) # Listen for second aggregates and perform trading logic @conn.on(r'A$', [self.symbol]) async def handle_agg(conn, channel, data): 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 = data.close self.last_price = tick_close self.process_current_tick(tick_open, tick_close) # Listen for quote data and perform trading logic @conn.on(r'T\..+', [self.symbol]) async def handle_alpaca_aggs(conn, channel, data): 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 = data.price self.last_price = tick_close self.process_current_tick(tick_open, tick_close) # Listen for updates to our orders @conn.on(r'trade_updates') async def handle_trade(conn, channel, 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') if USE_POLYGON: conn.run([f'A.{self.symbol}', 'trade_updates']) else: conn.run([f'alpacadatav1/T.{self.symbol}', 'trade_updates'])
SECRET_KEY = "" # Your Secret Key SLACK_TOKEN = "" # Slack OAuth Access Token CHANNEL = "" config = { "key_id": os.environ.get("KEY_ID", KEY_ID), "secret_key": os.environ.get("SECRET_KEY", SECRET_KEY), "base_url": os.environ.get("BASE_URL", "https://paper-api.alpaca.markets"), "slack_token": os.environ.get("SLACK_TOKEN", SLACK_TOKEN), "channel": os.environ.get("CHANNEL", CHANNEL) } # Set up environment conn = tradeapi.StreamConn( key_id=config.get('key_id'), secret_key=config.get('secret_key'), base_url=config.get('base_url'), ) api = tradeapi.REST( key_id=config.get('key_id'), secret_key=config.get('secret_key'), base_url=config.get('base_url'), ) # Initialize the Flask object which will be used to handle HTTP requests # from Slack app = Flask(__name__) # Initialize the dictionary of streams that we are listening to; None # denotes not listening streams = {
def run(tickers, market_open_dt, market_close_dt): # Establish streaming connection conn = tradeapi.StreamConn(base_url=base_url, key_id=api_key_id, secret_key=api_secret) # Update initial state with information from tickers volume_today = {} prev_closes = {} for ticker in tickers: symbol = ticker.ticker prev_closes[symbol] = ticker.prevDay['c'] volume_today[symbol] = ticker.day['v'] symbols = [ticker.ticker for ticker in tickers] print('Tracking {} symbols.'.format(len(symbols))) minute_history = get_1000m_history_data(symbols) portfolio_value = float(api.get_account().portfolio_value) open_orders = {} positions = {} # Cancel any existing open orders on watched symbols existing_orders = api.list_orders(limit=500) for order in existing_orders: if order.symbol in symbols: api.cancel_order(order.id) stop_prices = {} latest_cost_basis = {} # Track any positions bought during previous executions existing_positions = api.list_positions() for position in existing_positions: if position.symbol in symbols: positions[position.symbol] = float(position.qty) # Recalculate cost basis and stop price latest_cost_basis[position.symbol] = float(position.cost_basis) stop_prices[position.symbol] = (float(position.cost_basis) * default_stop) # Keep track of what we're buying/selling target_prices = {} partial_fills = {} # Use trade updates to keep track of our portfolio @conn.on(r'trade_update') async def handle_trade_update(conn, channel, data): symbol = data.order['symbol'] last_order = open_orders.get(symbol) if last_order is not None: event = data.event if event == 'partial_fill': qty = int(data.order['filled_qty']) if data.order['side'] == 'sell': qty = qty * -1 positions[symbol] = (positions.get(symbol, 0) - partial_fills.get(symbol, 0)) partial_fills[symbol] = qty positions[symbol] += qty open_orders[symbol] = data.order elif event == 'fill': qty = int(data.order['filled_qty']) if data.order['side'] == 'sell': qty = qty * -1 positions[symbol] = (positions.get(symbol, 0) - partial_fills.get(symbol, 0)) partial_fills[symbol] = 0 positions[symbol] += qty open_orders[symbol] = None elif event == 'canceled' or event == 'rejected': partial_fills[symbol] = 0 open_orders[symbol] = None @conn.on(r'A$') async def handle_second_bar(conn, channel, data): symbol = data.symbol # First, aggregate 1s bars for up-to-date MACD calculations ts = data.start ts -= timedelta(seconds=ts.second, microseconds=ts.microsecond) try: current = minute_history[data.symbol].loc[ts] except KeyError: current = None new_data = [] if current is None: new_data = [ data.open, data.high, data.low, data.close, data.volume ] else: new_data = [ current.open, data.high if data.high > current.high else current.high, data.low if data.low < current.low else current.low, data.close, current.volume + data.volume ] minute_history[symbol].loc[ts] = new_data # Next, check for existing orders for the stock existing_order = open_orders.get(symbol) if existing_order is not None: # Make sure the order's not too old submission_ts = existing_order.submitted_at.astimezone( timezone('America/New_York')) order_lifetime = ts - submission_ts if order_lifetime.seconds // 60 > 1: # Cancel it so we can try again for a fill api.cancel_order(existing_order.id) return # Now we check to see if it might be time to buy or sell since_market_open = ts - market_open_dt until_market_close = market_close_dt - ts if (since_market_open.seconds // 60 > 15 and since_market_open.seconds // 60 < 60): # Check for buy signals # See if we've already bought in first position = positions.get(symbol, 0) if position > 0: return # See how high the price went during the first 15 minutes lbound = market_open_dt ubound = lbound + timedelta(minutes=15) high_15m = 0 try: high_15m = minute_history[symbol][lbound:ubound]['high'].max() except Exception as e: # Because we're aggregating on the fly, sometimes the datetime # index can get messy until it's healed by the minute bars return # Get the change since yesterday's market close daily_pct_change = ((data.close - prev_closes[symbol]) / prev_closes[symbol]) if (daily_pct_change > .04 and data.close > high_15m and volume_today[symbol] > 30000): # check for a positive, increasing MACD hist = macd(minute_history[symbol]['close'].dropna(), n_fast=12, n_slow=26) if (hist[-1] < 0 or not (hist[-3] < hist[-2] < hist[-1])): return hist = macd(minute_history[symbol]['close'].dropna(), n_fast=40, n_slow=60) if hist[-1] < 0 or np.diff(hist)[-1] < 0: return # Stock has passed all checks; figure out how much to buy stop_price = find_stop(data.close, minute_history[symbol], ts) stop_prices[symbol] = stop_price target_prices[symbol] = data.close + ( (data.close - stop_price) * 3) shares_to_buy = portfolio_value * risk // (data.close - stop_price) if shares_to_buy == 0: shares_to_buy = 1 shares_to_buy -= positions.get(symbol, 0) if shares_to_buy <= 0: return print('Submitting buy for {} shares of {} at {}'.format( shares_to_buy, symbol, data.close)) try: o = api.submit_order(symbol=symbol, qty=str(shares_to_buy), side='buy', type='limit', time_in_force='day', limit_price=str(data.close)) open_orders[symbol] = o latest_cost_basis[symbol] = data.close except Exception as e: print(e) return if (since_market_open.seconds // 60 >= 24 and until_market_close.seconds // 60 > 15): # Check for liquidation signals # We can't liquidate if there's no position position = positions.get(symbol, 0) if position == 0: return # Sell for a loss if it's fallen below our stop price # Sell for a loss if it's below our cost basis and MACD < 0 # Sell for a profit if it's above our target price hist = macd(minute_history[symbol]['close'].dropna(), n_fast=13, n_slow=21) if (data.close <= stop_prices[symbol] or (data.close >= target_prices[symbol] and hist[-1] <= 0) or (data.close <= latest_cost_basis[symbol] and hist[-1] <= 0)): print('Submitting sell for {} shares of {} at {}'.format( position, symbol, data.close)) try: o = api.submit_order(symbol=symbol, qty=str(position), side='sell', type='limit', time_in_force='day', limit_price=str(data.close)) open_orders[symbol] = o latest_cost_basis[symbol] = data.close except Exception as e: print(e) return elif (until_market_close.seconds // 60 <= 15): # Liquidate remaining positions on watched symbols at market try: position = api.get_position(symbol) except Exception as e: # Exception here indicates that we have no position return print('Trading over, liquidating remaining position in {}'.format( symbol)) api.submit_order(symbol=symbol, qty=position.qty, side='sell', type='market', time_in_force='day') symbols.remove(symbol) if len(symbols) <= 0: conn.close() conn.deregister(['A.{}'.format(symbol), 'AM.{}'.format(symbol)]) # Replace aggregated 1s bars with incoming 1m bars @conn.on(r'AM$') async def handle_minute_bar(conn, channel, data): ts = data.start ts -= timedelta(microseconds=ts.microsecond) minute_history[data.symbol].loc[ts] = [ data.open, data.high, data.low, data.close, data.volume ] volume_today[data.symbol] += data.volume channels = ['trade_updates'] for symbol in symbols: symbol_channels = ['A.{}'.format(symbol), 'AM.{}'.format(symbol)] channels += symbol_channels print('Watching {} symbols.'.format(len(symbols))) run_ws(conn, channels)
def run(tickers): # Establish streaming connection conn = tradeapi.StreamConn() symbols = [ticker.ticker for ticker in tickers] # symbols = ["SNOA"] print('Tracking {} symbols.'.format(len(symbols))) minute_history = get_min_history_data(symbols) # Connect to Minute Bars Data via Polygon @conn.on(r'AM$') async def handle_minute_bar(conn, channel, data): # add the new bar data to the minute history dataframe ts = data.start ts -= timedelta(microseconds=ts.microsecond) minute_history[data.symbol].loc[ts] = [ data.open, data.high, data.low, data.close, data.volume ] alert = False # strip out only the bars we need totalBarsToEval = trend_bar_count + eval_bar_count history_in_scope = minute_history[data.symbol].tail(totalBarsToEval) df = history_in_scope.copy() # add the Heiken Ashi bar data: df = addHeikenAshi(df) # print('symbol = ' , data.symbol) # print('df =', df) # add some extra data to the frame df['symbol'] = data.symbol # price change df['prev_close'] = df['close'].shift() df['price_change'] = df['close'] - df['prev_close'] df['%_price_change'] = ( (df['close'] - df['prev_close']) / df['prev_close']) * 100 # volume changes df['prev_volume'] = df['volume'].shift() df['volume_change'] = df['volume'] - df['prev_volume'] df['%_volume_change'] = ( (df['volume'] - df['prev_volume']) / df['prev_volume']) * 100 # bar sizes absolute df['bar_size_abs'] = (df['close'] - df['open']).abs() # df.index = df.index.strftime("%x %I %p") # df = df.tail(1) # print(df) # trend values: trend_bars = df.head(trend_bar_count) trend_volume = trend_bars["volume"].mean() trend_price_avg = trend_bars["close"].mean() trend_price_max = trend_bars["close"].max() trend_price_min = trend_bars["close"].min() trend_bar_size_avg = trend_bars["bar_size_abs"].mean() # evaluation values: eval_bars = df.tail(eval_bar_count) eval_volume = eval_bars["volume"].mean() eval_price_avg = eval_bars["close"].mean() eval_ % _price_change_avg = eval_bars["%_price_change"].mean() eval_bar_size_avg = eval_bars["bar_size_abs"].mean() # calculated variables # FIXME: invalid value encountered in double_scalars # bar_size_factor = eval_bar_size_avg / trend_bar_size_avg # determine if it should be alerted: # if( eval_price_avg > trend_price_max and eval_volume > minimum_bar_volume): # if(eval_%_price_change_avg > price_percentage_threshold or # (bar_size_factor > bar_size_factor_threshold and eval_bar_size_avg > bar_size_threshold)): # alert = True if (eval_price_avg > trend_price_max and eval_volume > minimum_bar_volume): if (eval_ % _price_change_avg > price_percentage_threshold): alert = True # return if the alert signal is flase if (alert == False): return # drop some unecesarry columns df = df.drop(columns=[ 'open', 'high', 'low', 'prev_close', 'prev_volume', 'volume_change', '%_volume_change' ]) # trim to the last 5 items in the frame alert_df = df.tail(5) # message = 'Price Momentum Alert:\n' + tabulate(alert_df, headers='keys', tablefmt='github', showindex=False, floatfmt=(",.2f",",.2f",",.2f",",.2f",",.2f",",.2f",",.0f")) message = 'Price Momentum Alert:\n' + tabulate( alert_df, headers='keys', tablefmt='github', showindex=True) print(message) if (postToDiscord): # retrieve the channel channel = client.get_channel(721931969138786364) print('Sending Results to Discord Channel - ', channel) # format the message as a block for discord message = '```' + message + '```' await channel.send(message) # define channels and run the scanner for each channels = [] for symbol in symbols: symbol_channels = ['A.{}'.format(symbol), 'AM.{}'.format(symbol)] channels += symbol_channels print('Watching {} symbols.'.format(len(symbols))) run_ws(conn, channels)