Implementing a Trading Strategy with Alpaca Brokerage

Construct a global minimum variance (GMV) portffolio on Alpaca Brokerage.

Table of contents

In [1]:
import alpaca_trade_api as tradeapi
import time
import datetime
# config.py contains my keys
from config import *
import numpy as np
import pandas as pd
import edhec_risk_kit as erk
import yfinance as yf
from scipy.optimize import minimize
APCA_API_BASE_URL = "https://paper-api.alpaca.markets"
%load_ext autoreload
%autoreload 2

Prepare dataset of daily returns

Retrieve the S&P 500 tickers

These are the assets I will consider for my portfolio

In [2]:
sp500_data = pd.read_html('https://en.wikipedia.org/wiki/List_of_S%26P_500_companies')
tickers = sp500_data[0]['Ticker Symbol'].tolist()
# drop BKR, BRK.B and BF.B
# Alpaca doesn't like BKR
# Yahoo finance doesn't like BRK.B, BF.B, or BKR
tickers = [x for x in tickers if x != 'BKR']
tickers = [x for x in tickers if x != 'BRK.B']
tickers = [x for x in tickers if x != 'BF.B']
tickers = ' '.join(tickers)

Download stock price data for 2019

In [3]:
data = yf.download(tickers, start='2019-01-01', end='2019-12-31')
[*********************100%***********************]  502 of 502 completed

Calculate daily returns

In [4]:
rets = data['Adj Close'].pct_change()
# first drop columns where all of the values are NA
rets.dropna(axis=1, inplace=True, how='all')
# then drop the first row (where all the values are NA)
rets.dropna(axis=0, inplace=True, how='all')

Calculate weights of the global minimum variance (GMV) portfolio

Define a function to return the weights of the GMV portfolio

In [ ]:
def gmv(cov):
    """
    Returns the weights of the Global Minimum Volatility portfolio
    given a covariance matrix
    """
    n = cov.shape[0]
    return msr(0, np.repeat(1, n), cov)

def msr(riskfree_rate, er, cov):
    """
    Returns the weights of the portfolio that gives you the maximum sharpe ratio
    given the riskfree rate and expected returns and a covariance matrix
    """
    n = er.shape[0]
    init_guess = np.repeat(1/n, n)
    bounds = ((0.0, 1.0),) * n # an N-tuple of 2-tuples!
    # construct the constraints
    weights_sum_to_1 = {'type': 'eq',
                        'fun': lambda weights: np.sum(weights) - 1
    }
    def neg_sharpe(weights, riskfree_rate, er, cov):
        """
        Returns the negative of the sharpe ratio
        of the given portfolio
        """
        r = portfolio_return(weights, er)
        vol = portfolio_vol(weights, cov)
        return -(r - riskfree_rate)/vol
    
    weights = minimize(neg_sharpe, init_guess,
                       args=(riskfree_rate, er, cov), method='SLSQP',
                       options={'disp': False},
                       constraints=(weights_sum_to_1,),
                       bounds=bounds)
    return weights.x
In [5]:
weights = np.round(gmv(rets.cov()), 5)

Implement strategy on Alpaca

Connect to the API & retrieve the amount of cash I have in my paper account

In [3]:
alpaca = tradeapi.REST(API_KEY, API_SECRET, APCA_API_BASE_URL, 'v2')
cash = int(float(alpaca.get_account().cash))

Cancel any existing open orders

In [4]:
orders = alpaca.list_orders(status='open')
for order in orders:
    alpaca.cancel_order(order.id)

Calculate the dollar amount I want to buy of each stock

In [8]:
dollar_amounts = weights*cash

Define function to submit orders

In [9]:
# function from https://github.com/alpacahq/alpaca-trade-api-python/blob/master/examples/long-short.py
# slightly altered
def submitOrder(qty, stock, side):
    if qty > 0:
        try: 
            alpaca.submit_order(stock, qty, side, 'market', 'day')
            print('Submitted order to ' + side + ' ' + str(qty) + ' shares(s) of ' + stock)
        except:
            print('Order failed to submit: ' + side + ' of ' + str(qty) + ' share(s) of ' + stock)
    else:
        print('Quantity for ' + stock + ' is zero b/c dollar_amount < share_price')

Calculate quantity of shares of each stock that I'm buying & submit orders

In [10]:
stocks = list(data['Adj Close'].columns)
assets = alpaca.list_assets()
tradable_symbols = [a.symbol for a in assets if a.tradable and a.status == 'active']
for stock, amount in zip(stocks, dollar_amounts):
    try:
        # If the dollar amount that I want to buy of a stock is greater than $1,
        # attempt to retrieve the share price & submit an order
        # Also make sure the stock is active & tradable
        if (amount > 1) & (stock in tradable_symbols):
            qty = int(amount//alpaca.get_barset(stock, 'minute', 1)[stock][0].c)
            submitOrder(qty, stock, 'buy')
        if (stock not in tradable_symbols):
            print(stock + ' is not tradable')
    except:
        print("Couldn't pull stock data for " + stock + " from Alpaca")
Submitted order to buy 4 shares(s) of ABMD
Submitted order to buy 4 shares(s) of AGN
Submitted order to buy 492 shares(s) of AMCR
Quantity for AZO is zero b/c dollar_amount < share_price
Submitted order to buy 2 shares(s) of BIIB
Submitted order to buy 52 shares(s) of CBOE
Submitted order to buy 18 shares(s) of CHD
Submitted order to buy 1 shares(s) of CHTR
Submitted order to buy 2 shares(s) of CMA
Submitted order to buy 11 shares(s) of CTSH
Submitted order to buy 40 shares(s) of CTXS
Submitted order to buy 1 shares(s) of DG
Submitted order to buy 9 shares(s) of DISCK
Submitted order to buy 31 shares(s) of DLTR
Submitted order to buy 6 shares(s) of ES
Submitted order to buy 115 shares(s) of EVRG
Submitted order to buy 44 shares(s) of EXR
Submitted order to buy 6 shares(s) of FITB
Submitted order to buy 84 shares(s) of FOXA
Submitted order to buy 28 shares(s) of HP
Submitted order to buy 108 shares(s) of HRB
Submitted order to buy 26 shares(s) of HSY
Submitted order to buy 2 shares(s) of IFF
Submitted order to buy 11 shares(s) of JNJ
Submitted order to buy 12 shares(s) of KHC
Submitted order to buy 76 shares(s) of KR
Submitted order to buy 27 shares(s) of LKQ
Submitted order to buy 6 shares(s) of LW
Submitted order to buy 13 shares(s) of M
Submitted order to buy 14 shares(s) of MCD
Submitted order to buy 3 shares(s) of MKC
Submitted order to buy 9 shares(s) of MLM
Submitted order to buy 184 shares(s) of NEM
Submitted order to buy 2 shares(s) of NOV
Quantity for NVR is zero b/c dollar_amount < share_price
Submitted order to buy 60 shares(s) of OXY
Submitted order to buy 30 shares(s) of PSA
Submitted order to buy 8 shares(s) of QCOM
Submitted order to buy 2 shares(s) of RTN
Quantity for SCHW is zero b/c dollar_amount < share_price
Submitted order to buy 36 shares(s) of SO
Submitted order to buy 15 shares(s) of SPG
Quantity for STT is zero b/c dollar_amount < share_price
Submitted order to buy 38 shares(s) of SYY
Submitted order to buy 1 shares(s) of TGT
Submitted order to buy 2 shares(s) of TSN
Submitted order to buy 5 shares(s) of UNH
Submitted order to buy 29 shares(s) of VZ
Submitted order to buy 21 shares(s) of WEC
Submitted order to buy 45 shares(s) of WFC