Alpaca Trading API Guide – A Step-by-step Guide

15 min read

Alpaca Trading Homepage Image
Alpaca’s Homepage

Table of Contents

  1. What is the Alpaca Trading API?
  2. Why should I use the Alpaca Trading API?
  3. Does the Alpaca API allow backtesting?
  4. How do I get started with the Alpaca API?
  5. Testing for connectivity
  6. How do I get historical data from the Alpaca API?
  7. How do I use Websockets to stream data with the Alpaca API?
  8. How can I use indicators with the Alpaca API?
  9. How can I fire order in the Alpaca API?
  10. How do I set a stop loss or take profit?
  11. Which stocks can you trade with Alpaca?
  12. How do I find out what time the market closes?
  13. Putting it all together – a fully functioning trading script
  14. Final thoughts on the Alpaca API

What is the Alpaca Trading API?

It is an interface that allows you to trade automatically with the stock broker Alpaca.

More specifically, the trading API allows you to send orders directly to Alpaca’s servers to automate the process of trading, bypassing a traditional client.

Why should I use the Alpaca Trading API?

There are several reasons you might want to use Alpaca’s trading API. A common use is to build an automated trading system. But many people have used it for other purposes as well such as creating a trading dashboard or a custom client app.

The main draw to Alpaca is that it doesn’t charge commissions on trades. Have a look at our Alpaca Stock Brokerage Review where we discuss the pros and cons of the broker and outline how they actually make money.

Does the Alpaca API allow backtesting?

Alpaca’s trading API does not come with backtesting functionalities. However, they have created an integration with a backtesting library called Backtrader.

You can more about backtesting with Backtrader here: Backtrader for Backtesting (Python) – A Complete Guide

» If you are looking for trading ideas to backtest, check out this guide: 4 Quantitative Trading Strategies that (might) Work Today

How do I get started with the Alpaca API?

  1. Create an account with Alpaca – You can either sign up for a live account or a paper trading account to get started. Navigate to the Alpaca home page – https://alpaca.markets/ and click on Sign up. At this time, Alpaca only accepts US clients for live accounts although you should still be able to open a paper trading account if you are not in the US.
  2. Get an API Key – After creating an account, log in to view your API key and secret key. The endpoint used to make calls to the REST API should also be displayed. Take note of all three of these values.
  3. Install the Alpaca Python Library – Alpaca has a library, otherwise known as the client SDK, which simplifies connecting to the API. To install it, type in pip3 install alpaca-trade-api from your command prompt. 

Notes about the Alpaca library – 

  • The Alpaca API library only works with Python 3.6 and above as it uses asynchronous functions that are not supported in older versions of Python.
  • Rather than installing via pip, you can download the zip file from GitHub – https://github.com/alpacahq/alpaca-trade-api-python/ and place the alpaca_trade_api folder in your projects folder after extracting. The library has several dependencies, a full list can be found in the setup.py script.
  • Alpaca has launched V2 of their trading API and will discontinue the earlier version soon. For this reason, it is recommended to stick with V2 functions only.
  • Alpaca currently imposes a rate limit of 200 requests per minute

Testing for connectivity

import alpaca_trade_api as tradeapi

#authentication and connection details
api_key = 'Insert_your_api_key_here'
api_secret = 'Insert_your_api_secret_here'
base_url = 'https://paper-api.alpaca.markets'

#instantiate REST API
api = tradeapi.REST(api_key, api_secret, base_url, api_version='v2')

#obtain account information
account = api.get_account()
print(account)

The above code instantiates the REST class which will be used for all of the calls to the REST API. We then use the get_account() function to get details about our account.

Your output should contain your account details in a JSON format. If you received a 401 authentication error, your API key or secret might have been typed incorrectly. 

Make sure the base URL matches the endpoint you noted down when obtaining your API keys.

We’ve hardcoded our API key and secret since we are testing Alpaca out with a paper trading account. In a live environment, however, it is a good idea to take the extra security precaution of storing your authentication details in environment variables

A cool feature of the Alpaca library is that it can automatically retrieve values you’ve stored as environment variables. Just make sure to use the following naming convention:

APCA_API_KEY_ID=<key_id>
APCA_API_SECRET_KEY=<secret_key>
APCA_API_BASE_URL=url

If you decide to store your API info as environment variables, you only need to pass through the API version when instantiating the REST class, like this: 

api = tradeapi.REST(api_version='v2')

How do I get historical data from the Alpaca API?

Alpaca uses third-party providers for data. If you have a live account, you’ll have access to Polygon data for free. Otherwise, you can still obtain free data via Alpha Vantage.

It’s worth mentioning that you don’t need to be signed up with Alpaca to access Alpha Vantage data. They offer direct access and even have a library to simplify calling the API. 

In fact, the Alpha Vantage library is a dependency of the Alpaca library and is already on your system if you used the pip install option in the previous step.

For more details on the difference between the two data providers, check out our Alpaca Stock Brokerage Review.

aapl = api.alpha_vantage.historic_quotes('AAPL', adjusted=True, output_format='pandas')

In the above example, we are making a call for historical prices for Apple stock which has the ticker AAPL. 

You’ll notice we set adjusted=True. With this parameter, the returned data will have a column for an adjusted closing price. This is simply the price adjusted for splits which is what all the common charting platform use.

The example returns the requested data in a pandas dataframe. We can just as easily retrieve data in CSV or JSON format by using output_format='csv' or output_format='json'.

Daily prices are returned by default. There are only three options available for the historical quote function: daily, weekly, and monthly. To change the time frame, add cadence='weekly' or cadence='monthly' as one of your parameters.

Here is a code example for obtaining weekly closing price data for Tesla chart returned in JSON format: 

tsla = api.alpha_vantage.historic_quotes('TSLA', adjusted=True, output_format='json', cadence='weekly')

If you’re looking for historical data for smaller time frames, you can use the intraday_quotes() function. Here is an example:

tsla = api.alpha_vantage.intraday_quotes ('TSLA', interval='5min', output_format='csv')

How do I use WebSockets to stream data with the Alpaca API?

Alpaca offers WebSockets which will push information to you without having to constantly make request calls to the API.

Unfortunately, live streaming of price data is only available for live accounts. If you are using a paper account, you can still use WebSockets to get automatic notification of account updates and trade updates.

This is a very useful function. Once you’ve sent your order, it can confirm that the order has been submitted to the exchange. It can also let you know when the orders fills or if you only got a partial fill. 

Here is the code: 

conn = tradeapi.stream2.StreamConn(api_key, api_secret, base_url)

@conn.on(r'^account_updates$')
async def on_account_updates(conn, channel, account):
    print('account', account)

@conn.on(r'^trade_updates$')
async def on_trade_updates(conn, channel, trade):
    print('trade', trade)

def ws_start():
	conn.run(['account_updates', 'trade_updates'])

#start WebSocket in a thread
ws_thread = threading.Thread(target=ws_start, daemon=True)
ws_thread.start()

The last half of the code snippet above serves to run the WebSocket in a thread. Otherwise, it would block your main script. Make sure to include import threading at the top of your script.

There are several other useful pieces of information that can be had from implementing WebSockets, here is a full list: – https://alpaca.markets/docs/api-documentation/api-v2/streaming/#common-events

How can I use indicators with the Alpaca API?

The Alpha Vantage API has several commonly used indicators that can be easily accessed via the Alpaca Python library.

The function used is techindicators() which is essentially a generic function that can be used to access any type of indicator available in the Alpha Vantage API.

Let’s first look at the Alpaca source code to examine the techindicators() function: 

def techindicators(
    self, techindicator='SMA', output_format='json', **kwargs):
    ''' Returns one of the technical indicator endpoints of the
    Alpha Vantage API.

    Params:
        techindicator: The technical indicator of choice
        params: Each technical indicator has additional optional parameters

    Returns:
        pandas, csv, or json
    '''
    if output_format:
        self._techindicators.output_format = output_format
    params = {'function': techindicator}
    for key, value in kwargs.items():
        params[key] = value
    data = self.get(params)
    return data

Alpaca has set this up so that it will grab the moving average by default, and output it in JSON format.

The important part of the source code is **kwargs which is where our parameters go.

The best way to go about getting indicators via the Alpaca API is to navigate straight to the Alpha Vantage documentation -https://www.alphavantage.co/documentation/#technical-indicators

From there, you will be able to see which additional parameters are required for the indicator you are looking for.

Here is an example of a 10-period simple moving average for Apple. It is based on a weekly interval.

sma = api.alpha_vantage.techindicators(symbol='AAPL', interval='weekly', time_period='10', series_type='close')

Here is an example of RSI or relative strength: 

rsi = api.alpha_vantage.techindicators(techindicator='RSI', symbol='AAPL', interval='weekly', time_period='14', series_type='close')

The main difference is that we had to use techindicator='RSI' to specify that we do not want the default, which is the simple moving average.

If you’re looking at the Alpha Vantage documentation, techindicator is the equivalent of function.

Alpaca could have made this easier by creating functions for some of the most popular indicators, it would have saved having to refer to the Alpha Vantage documentation to figure out the required parameters for each indicator. Nevertheless, this method worked without an issue for us.

How can I fire order in the Alpaca API?

api.submit_order(symbol='TSLA', 
		qty=1, 
		side='buy', 
		time_in_force='gtc', 
		type='limit', 
		limit_price=400.00, 
		client_order_id='001')

The above code snippets sends an order to buy one share of TSLA at a limit price of $400.

If you’d like to submit a market order, use type='market' and remove the limit_price parameter.

If you’d like to short sell, use side='sell'. It’s a good idea to check first to make sure you’re able to sell that particular security.

We’ve assigned a string value of 001 as an order id to this order so that we can reference it with ease later on. This is optional. If you do decide to include a client_order_id, make sure the value is unique otherwise the library will raise an exception.

If you’re using the built-in WebSockets feature, you will automatically be notified that the order has been submitted. Otherwise, you can make a request to check on your position as follows.

position = api.get_position('TSLA')

Alternatively, you can check on positions based on the order id you’ve assigned as follows:

position = api.get_order_by_client_order_id('001')

How do I set a stop loss or take profit?

When submitting an order, you can attach either a stop loss, take profit, or both. Here is an example: 

api.submit_order(symbol='TSLA', 
		qty=1, 
		side='buy', 
		time_in_force='gtc', 
		type='limit', 
		limit_price=400.00, 
		client_order_id=001, 
		order_class='bracket', 
		stop_loss=dict(stop_price='360.00'), 
		take_profit=dict(limit_price='440.00'))

We’ve added a parameter to indicate this is a bracket order which is required when setting a stop loss and take profit. Further, the stop loss and take profit need to be nested, so we use dict() with the required parameters and the price point as a string.

Which stocks can you trade with Alpaca?

active_assets = api.list_assets(status='active')

The above code snippet will return all the active stocks available for trading with Alpaca. It will include information such as the exchange the stock trades on and if you can short it.

You can reverse search as well. Say if you already have a stock that you want to trade, you can check to make sure Alpaca offers it with the following code:

aapl_asset = api.get_asset('AAPL')

If an asset is not found on Alpaca’s system, it will raise an error. For this reason, its a good idea to use a try/except block when running the above code.

How do I find out what time the market closes?

print(api.get_clock())

This is a useful function as it returns when the market closes, when it will open next, as well as the server time.

This way you can make sure you are using the same time format the API is using and you can pause your algo when the market is not open.

Putting it all together – a fully functioning trading script

We will go through a fully automated trading system that utilizes the Alpaca API. The objective is to show a practical use case for the functionality described in the guide thus far. Therefore, there is not much emphasis on the actual trading strategy, and we don’t expect it to be a profitable one.

The strategy we will use is a simple breakout system. 

Breakout Example

The image above provides a visual of the strategy, the grey box contains ten candles and we will filter out the absolute high and low. If the price rallies above the high, we will buy. If it falls below the low, we will submit a sell order.

The chart above eventually provided a sell signal. The next step is to determine our stop loss and take profit. First, we calculate the distance between the range high and range low $251.74 – $250.67 = $1.07.

We will set our stop loss $1.07 higher than our entry point and our take profit $1.07 lower than our entry point.

Let’s get started.

import alpaca_trade_api as tradeapi
import threading
from time import sleep 
import json
import logging

These are all our imports. We have gone over the first two already. The time module will be used to put the script at sleep when needed. The JSON module is needed so that we can export a list of our trades to a CSV. Lastly, we will use logging to keep a log file of any errors that may pop up.

#init
logging.basicConfig(filename='errlog.log',level=logging.WARNING,  
                format='%(asctime)s:%(levelname)s:%(message)s')

api_key = 'insert_api_key'
api_secret = 'insert_api_secret'
base_url = 'https://paper-api.alpaca.markets'

api = tradeapi.REST(api_key, api_secret, base_url, api_version='v2')

trade_msg = []
order_msg = []
past_trades = []

searching_for_trade = False
order_sent = False
order_submitted = False
active_trade = False
done_for_the_day = False

Here we initialize the API and the logging function. We are also defining several variables. Their use will become clear as we go through the script.

api.cancel_all_orders()

In this next part, we have our first experience with the API. We are calling API to cancel any open orders. It’s there in case our script got interrupted for whatever reason and we have to restart in the middle of a market day.

#check if market is open
clock = api.get_clock()

if clock.is_open:
	pass
else:
	time_to_open = clock.next_open - clock.timestamp
	sleep(time_to_open.total_seconds())

Next, we use the clock function to see if the markets are open. Ideally, we would start the script before the markets open. The code above checks what time the markets open and sleeps until then.

if len(api.list_positions()) == 0:
	searching_for_trade = True
else:
	active_trade = True

The following if statement checks to see if we have any open positions. Again, this is just some extra error checking in case our script was interrupted in the middle of a market day. In most cases, we won’t have a trade open when starting the script.

#init WebSocket
conn = tradeapi.stream2.StreamConn(api_key, api_secret, base_url)

@conn.on(r'^account_updates$')
async def on_account_updates(conn, channel, account):
	order_msg.append(account)

@conn.on(r'^trade_updates$')
async def on_trade_updates(conn, channel, trade):
	trade_msg.append(trade)
	if 'fill' in trade.event:
		past_trades.append([trade.order['updated_at'], trade.order['symbol'], trade.order['side'], 
					trade.order['filled_qty'], trade.order['filled_avg_price']])
		with open('past_trades.csv', 'w') as f:
			json.dump(past_trades, f, indent=4) 

def ws_start():
	conn.run(['account_updates', 'trade_updates'])

#start WebSocket in a thread
ws_thread = threading.Thread(target=ws_start, daemon=True)
ws_thread.start()
sleep(10)

We’ve already discussed the WebSocket. A small addition here is that some of the parameters from filled orders are appended to a list and then saved to a file. This way, we will have a clean list of any orders we’ve executed for our records.

The script sleeps for 10 seconds after the WebSocket is called just to give it enough time to start and authenticate.

#functions
def time_to_market_close():
	clock = api.get_clock()
	closing = clock.next_close - clock.timestamp
	return round(closing.seconds/60)

def send_order(direction):
	if time_to_market_close() > 20:
		if direction == 'buy':
			sl = high-range_size
			tp = high+range_size
		elif direction == 'sell':
			sl = low+range_size
			tp = low-range_size

		api.submit_order(symbol='AAPL', qty=100, side=direction, type='market', time_in_force='day', order_class='bracket', stop_loss=dict(stop_price=str(sl)), take_profit=dict(limit_price=str(tp)))
 		return True, False
	
	else:
		return False, True

Our functions come next. The first one simply calculates how many minutes are left until the market close. We will need to check this as we don’t want to execute any new orders just before the market closes. The intention is not to carry any trades overnight.

The second function is what we will use to submit orders. We first check to see if there is at least 20 minutes left until the market close. If so, we do a quick calculation of our take profit and stop loss and then submit a market order, similar to our prior example.

#main loop
while True:

	try:

We will now start the main loop. We’ve enclosed the entire loop in a try/except block as the Alpaca API raises an exception if something goes wrong.

candlesticks = api.get_barset('AAPL', 'minute', limit=10)
high = candlesticks['AAPL'][0].h
low = candlesticks['AAPL'][0].l
for candle in candlesticks['AAPL']:
	if candle.h > high:
		high = candle.h
	elif candle.l < low:
		low = candle.l
range_size = high - low
if range_size / candlesticks['AAPL'][0].c < 0.003:
	range_size = candlesticks['AAPL'][0].c * 0.003

The first part of our main loop is the code used to determine our range. It utilizes the barset function of the Alpaca API to get the last 10 bars. At that point, it iterates through the bars to pick out the highest and lowest values.

The API won’t allow us to send orders if the stop loss or take profit is less than 0.1% away from that price. For this reason, we’ve added an extra check to ensure our range is bigger than 0.3% of the current price. Although it only needs to be 0.1%, we added the extra buffer as there could be a price difference between the time we send the order and when it executes.

Our main script will have five main loops. We will go through them now.

while searching_for_trade:
	clock = api.get_clock()
	sleep(60-clock.timestamp.second)
	candlesticks = api.get_barset('AAPL', 'minute', limit=1)
	if candlesticks['AAPL'][0].c > high:
		searching_for_trade = False
		order_sent, done_for_the_day = send_order('buy')

	elif candlesticks['AAPL'][0].c < low:
		searching_for_trade = False
		order_sent, done_for_the_day = send_order('sell')

At this point, as the variable suggests, we are searching for a trade. The first step is to check the time and put the script to sleep until a fresh one-minute bar is available.

This is an inefficient way to go about things. If you use a real account, you will have access to Polygon data which is available through a WebSocket. In that scenario, new candles will be automatically pushed to you as they become available. Since we are using a paper trading account, we will manually query for new data using this method.

Once we have a new bar, we check to see if the price has crossed a new low or high. If so, we will send the appropriate buy or sell order.

When we send an order, we set the searching_for_trade variable to False, which signals to our script that its time to move on to the next loop.

The exception to this is if there is less than 20 minutes left to the market close as we don’t want to initiate new positions at that time. In that case, we set done_for_the_day to True so that the script can skip to the last loop which we will cover shortly.

while order_sent:
	sleep(1)
	for item in trade_msg:
		if item.event == 'new':
			order_submitted = True
			order_sent = False

The order sent loop is straight forward. Recall that we are storing our WebSocket data as a list item into a variable. We are simply checking this list to see if a contains a new event, or in other words, a new trade order.

This is an oversimplified error check. It works, but in your script, you might be interested in a more comprehensive check.

We’ve added a one second sleep here in order to avoid overusing the CPU as an infinite loop is being used.

while order_submitted:
	sleep(1)
	for item in trade_msg:
		if item.order['filled_qty'] == '100':
			order_submitted = False
			active_trade = True
			trade_msg = []

Next is the order_submitted loop which is very similar to the last loop. The difference here is that we are checking to make sure the order is filled before moving to the next step. 

Once the order is filled, we are also clearing out our trade message list. We do this to prepare it for the next trade. We didn’t see any use to keeping this data as you can always access old orders through the API.

while active_trade:
	for i in range(time_to_market_close()-5):
		sleep(60)
		if len(api.list_positions()) == 0:
			active_trade = False
			searching_for_trade = True
			break
	if active_trade:
		done_for_the_day = True
		active_trade = False

We now move on to the active_trade loop. This is an important one as we need to keep an eye on when the market closes. If we are still in a trade about five minutes to market close, we will just close it at market to avoid holding a position overnight.

This loop will query the API for open positions every minute. If no open positions have been found, it tells us that our stop loss or take profit has been hit. In this scenario, we can break out of the loop and start the process of searching for a new trade signal over again.

If the for loop runs out and we are still in an open position, the active_trade variable will still be True. This means there is five minutes left until the market close so we will signal that we are done for the day.

while done_for_the_day:
     api.close_all_positions()
     clock = api.get_clock()
     next_market_open = clock.next_open - clock.timestamp
     sleep(next_market_open.total_seconds())
     searching_for_trade = True

The done for the day loop simply closes all open positions. We can call this command as our strategy only has one open position at a time. 

Next, we call the clock function to found out when the market opens next and put the script back to sleep until then. Finally, we set searching_for_trade = True so that the script can start the process all over again once it wakes up at the next market open.

except Exception as e:
	logging.exception(e)

Lastly, we will log any exceptions so that we can keep an eye on any errors that might come up with the API.

And there you have it, a full trading script using the Alpaca API.

You are welcome to use this code and build on it. We would recommend doing some further error checking and also looking into handling specific exceptions that are commonly raised by the API.

Final thoughts on the Alpaca API

Overall, the Alpaca API does a good job at what it is supposed to do. 

We did run into a few quirks in our testing. For example, when making calls for historical data it took on average 15 seconds to get a response. This might not be practical for trading systems where speed is of the essence.

There were also a few discrepancies in the Alpaca documentation. Although to be fair, they appear to be in the middle of a migration to a new version. 

If you’re happy with the API, and plan to continue using it, we recommend taking a look at the rest.py source file included in the library to get a better understanding of all of the functions available.

help(tradeapi.REST)

You can also run the above line of code to get all of the functions available within the REST class.

The code used in the examples is available on GitHub. From the GitHub page, click the green button on the right “Clone or download” to download or clone the code.

1,892 Views
Jignesh Davda