示例#1
0
def maybe_invest(api: tradeapi.REST) -> None:
    target_symbols = ['AAPL', 'VOO']
    user_context = UserContext()
    user_context.set_account(api.get_account())
    stock_context_list = [
        StockContext(target_symbol) for target_symbol in target_symbols
    ]
    # Populate stock context list for potential investment.
    for stock_context in stock_context_list:
        last_trade = api.get_last_trade(stock_context.symbol)
        stock_context.set_last_trade(last_trade)
    # Filter out a list of stocks that should be considered
    # for investment at the moment.
    stock_feasibility_checker = StockFeasibilityChecker(user_context)
    filtered_stock_context_list = filter(stock_feasibility_checker,
                                         stock_context_list)
    # Rank the potential list of stocks to invest based on
    # the potential growth.
    stock_comparator = StockComparator(user_context)
    ranked_stock_context_list = sorted(filtered_stock_context_list,
                                       key=stock_comparator)
    # Send order to invest if fund is sufficient
    stock_action_executor = StockActionExecutor(user_context)
    for ranked_stock_context in ranked_stock_context_list:
        stock_action_executor(ranked_stock_context)
示例#2
0
def select_swing_stocks():
    # Get Alpaca API key and secret
    storage_client = storage.Client()
    bucket = storage_client.get_bucket('derek-algo-trading-bucket')
    blob = bucket.blob('alpaca-api-key.txt')
    api_key = blob.download_as_text()
    blob = bucket.blob('alpaca-secret-key.txt')
    secret_key = blob.download_as_text()
    base_url = 'https://paper-api.alpaca.markets'
    api = REST(api_key, secret_key, base_url, 'v2')

    # Get all stocks
    assets = api.list_assets('active')
    symbols = [asset.symbol for asset in assets if asset.tradable]

    # Get all currently held positions
    positions = api.list_positions()
    if positions:
        print('Current positions:')
    position_symbols = [position.symbol for position in positions]
    position_data = {}
    for position in positions:
        current_percent = (float(position.current_price) - float(position.avg_entry_price)) \
            / float(position.avg_entry_price) * 100
        print('{}: {}%'.format(position.symbol, '%.2f' % current_percent))
        position_data[position.symbol] = position
    print()

    # Get past 50 days data for all stocks
    data = {}
    symbols_chunked = list(chunks(list(set(symbols)), 200))
    for symbol_group in symbols_chunked:
        print(f'Retrieving {len(symbol_group)} symbol data')
        data_group = api.get_barset(','.join(symbol_group), '1D',
                                    limit=1000).df
        for symbol in symbol_group:
            data[symbol] = data_group[symbol]

    buy_df = pd.DataFrame()
    sell_df = pd.DataFrame()
    hold_df = pd.DataFrame()

    c = 0
    for symbol in data.keys():
        df = pd.DataFrame(data[symbol])
        df = df.loc[df['close'] > 0]
        if symbol not in position_symbols and len(df) == 1000:
            df['symbol'] = symbol

            # bullish pattern detection
            # bullish = [''] * len(df)
            # bullish[len(bullish) - 1] = pattern.detect_bullish_patterns(df)
            df['bullish'] = pattern.detect_bullish_patterns(df)
            buy_df = buy_df.append(df.loc[df.index == df.index.max()])

        elif symbol in position_symbols:
            print(f'\nCurrently holding {symbol}:')
            df['symbol'] = symbol
            df['qty'] = int(position_data[symbol].qty)
            df['market_value'] = float(position_data[symbol].market_value)

            latest_rsi = rsi(df).df.tail(1)['rsi'][0]
            latest_cci = cci(df).df.tail(1)['cci'][0]
            purchase_price = float(position_data[symbol].avg_entry_price)
            latest_price = float(position_data[symbol].current_price)
            df['purchase_price'] = purchase_price
            df['latest_price'] = latest_price

            # If it has dropped below the entry price, GET IT OUT
            if latest_price < purchase_price:
                print(
                    f'Price ${latest_price} is below entry ${purchase_price}: SELL'
                )
                sell_df = sell_df.append(df.loc[df.index == df.index.max()])
            # Or if the RSI or CCI are too high, GET IT OUT
            elif latest_rsi >= 70 or latest_cci >= 100:
                print(f'Overbought, RSI={latest_rsi}, CCI={latest_cci}: SELL')
                sell_df = sell_df.append(df.loc[df.index == df.index.max()])

            # If price is at/above entry
            # Check if the swing is still swinging
            elif latest_price >= purchase_price:
                print(
                    f'Price ${latest_price} is at/above entry ${purchase_price}'
                )
                obv_df = obv(df).df
                obv_df['obv_ema'] = obv_df['obv'].ewm(span=20).mean()

                # Is the OBV still above it's EMA
                if obv_df.tail(1)['obv'][0] > obv_df.tail(1)['obv_ema'][0]:
                    print('OBV is still above OBV_EMA')
                    slope = linregress([0, 1, 2],
                                       obv_df.tail(3)['obv'].tolist()).slope
                    if slope > 0:
                        print(
                            f'OBV is increasing with a slope of {slope}: HOLD')
                        hold_df = hold_df.append(
                            df.loc[df.index == df.index.max()])

            print()

        c += 1
        if c % 100 == 0:
            print(f'{c}/{len(data.keys())}')
    print(f'{c}/{len(data.keys())}\n')

    # END SCREENING SECTION

    # Consolidate results
    print('DECISION:\n')
    hold_stocks = hold_df.loc[hold_df.index == hold_df.index.max()]
    if not hold_stocks.empty:
        print(f'Hold {len(hold_stocks)}:\n' +
              '\n'.join(hold_stocks['symbol'].tolist()) + '\n')

    sell_stocks = sell_df.loc[sell_df.index == sell_df.index.max()]
    if not sell_stocks.empty:
        print(f'Sell {len(sell_stocks)}:\n' +
              '\n'.join(sell_stocks['symbol'].tolist()) + '\n')

    portfolio_size = 20
    purchase_size = portfolio_size - len(hold_stocks)

    # If there's room in the portfolio to buy more
    if purchase_size > 0:
        print(f'We can purchase {purchase_size} new stocks today.')
        buy_stocks = buy_df.loc[buy_df.index == buy_df.index.max()]
        buy_stocks = buy_stocks[buy_stocks['bullish'] != '']
        buy_stocks = buy_stocks.sort_values(
            by='volume', ascending=False).head(purchase_size)

        account = api.get_account()
        holding_value = hold_stocks['market_value'].tolist().sum(
        ) if not hold_stocks.empty else 0.0
        available_funds = (float(account.portfolio_value) +
                           float(account.cash) - holding_value) * 0.95
        print(f'Available funds to buy: ${available_funds}')

        # For now this will create equal-weight positions
        funds_per_stock = available_funds / len(buy_stocks)

        # Calculate quantity to buy for each stock
        buy_qty = []
        symbols = buy_stocks['symbol'].tolist()
        prices = buy_stocks['close'].tolist()
        for i in range(0, len(symbols)):
            price = prices[i]
            qty = math.floor(funds_per_stock / price)
            buy_qty.append(qty)

        buy_stocks['qty'] = buy_qty
        buy_stocks['market_value'] = buy_stocks['close'] * buy_stocks['qty']
        print(f'Buy {len(buy_stocks)}:')
        print(buy_stocks)

    else:
        print('There is no room to buy.')
        buy_stocks = pd.DataFrame()

    return (buy_stocks, sell_stocks, hold_stocks)
示例#3
0
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
示例#4
0
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