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

23 min read

Get 10-day Free Algo Trading Course

Loading

Last Updated on February 28, 2021

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. A fully functioning trading script – without WebSocket data
  15. 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 learn more about backtesting with Backtrader here: Backtrader for Backtesting (Python) – A Complete Guide

» Before you run your strategies, you need data to design and backtest them. Here are some (mostly) free data sources and guides:


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. UPDATE – Alpaca announced on July 30, 2020 that they are now accepting Non-US residents as part of a beta program. The announcement on their website contains full details.
  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.
  • Optional – 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 dictionary 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 offers both an in-house source for data as well as a third-party solution via Polygon. Currently, the Polygon service is only offered to users that have a live funded account.

UPDATE: As of Feb 26, 2021, Alpaca has discontinued their Polygon data offering. There are still two tiers of data, the difference is they both come from Alpaca (in-house).

The free version offers data from the IEX exchange while the “pro” offering has a broader scope of data as it comes from the NYSE and Nasdaq exchanges. Also, there are no API or Websocket limits for the pro version. The cost for pro data is $49 per month.

We will use the free source to get historic data for Apple (AAPL).

aapl = api.get_barset('AAPL', 'day')

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

The data that is returned is within a custom class created by the library. There a few things we can do at this point.

If you like working with Pandas DataFrame’s, the library will automatically create a dataframe of the returned data. We can access it by appending .df at the end of the variable we’ve created, like so:

print(aapl.df)

Alternatively, if you’re not a fan of Pandas, the raw data can be accessed in a dictionary format as follows –

print(aapl._raw)

The example above retrieved daily candles. We can easily change this for other time frames. The valid intervals are: 1Min, 5Min, 15Min and 1D

Here is a code example for obtaining 15-minute closing price data for Tesla: 

tsla = api.get_barset('TSLA', '15Min')

By default, the data is limited to the last 100 bars. We can increase this to as high as 1000 bars by passing through a limit into the function.

aapl = api.get_barset('AAPL', 'day', limit=1000)

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.

There are two main functions the Alpaca WebSocket provides – market price data updates and account updates. We will go through an example of setting up a WebSocket to listen for account updates first.

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

Getting price data from the Alpaca WebSocket is a beneficial functionality that saves you from constantly having to poll the API for the latest prices.

There are three main types of data that you can obtain from the WebSocket stream:

  1. The last price a stock traded at
  2. The current quote (also known as the bid and ask prices)
  3. The latest one-minute bar data

If you plan to use the WebSocket for price data, the StreamConn class will need to be instantiated slightly differently.

ws_url = 'wss://data.alpaca.markets'

conn = tradeapi.stream2.StreamConn(
    api_key, api_secret, base_url=base_url, data_url=ws_url, data_stream='alpacadatav1'
)

We’ve passed through two additional parameters compared to the earlier example where we instantiated StreamConn for just trade updates. First is the WebSocket URL and second is a data_stream parameter to let the library know we want to use Alpaca’s in-house data.

Now we are able to receive updates for both our account and for prices. But we’ll need to write some functions to let the library know how we wish to consume our data.

Recall that there are three types of data that we can request via the WebSocket. Let’s take a look at an example of all three.

@conn.on(r'^T.AAPL$')
async def trade_info(conn, channel, bar):
    print('bars', bar)
    print(bar._raw)

The first example is for trade data. In other words, the WebSocket will update us for every trade that gets conducted and let us know at which price.

The important part of the code is r'^T.AAPL$'.

The r’^$’ part of the code might be confusing if its the first time you’re seeing it. This is part of the RegEx library. You don’t necessarily have to know what it does but if you’re interested in learning more about it, you can do so at this link.

The more important part is T.AAPL. The T let’s the library know that we are looking for trade data. In this case we are looking for AAPL data but we can just as easily change it to T.TSLA if we want to stream TSLA data.

We’ve named this function trade_info, but you can name it anything you want.

The price data we are after will be in the bar variable. This is a custom object from the library. With any custom object in this library, you can always view its contents by printing out its contents using the ._raw attribute.

The trade stream will return the following variables: conditions, exchange, price, size, symbol, timestamp.

If you want to access only the price, you can do so like this:

print(bar.price)

The same convention applies for any other data returned by the WebSocket.

@conn.on(r'^Q.AAPL$')
async def quote_info(conn, channel, bar):
    print('bars', bar)

The next example is for quote data, or otherwise known as bid and ask prices. You’ll notice that the format is almost identical. The only change here is that we are using Q for quote instead of T for trade.

@conn.on(r'^AM.AAPL$')
async def on_minute_bars(conn, channel, bar):
    print('bars', bar)

The last example is for one-minute bar data. Once again, the only real change here is that we are using AM instead of T or Q. Also keep in mind that each of these functions require a unique name. In this case, we’ve called it on_minute_bars.

We’ve now defined a few functions which let the WebSocket know what we want to do with incoming data.

We still need to ‘request’ these data streams. We can do that as follows.

conn.run(['AM.AAPL'])

The above code will request one-minute bars for AAPL. In our function, we’ve instructed the library to print this data to screen once it arrives.

We can request multiple streams at the same time.

conn.run(['account_updates', 'trade_updates', 'AM.AAPL'])

The above code subscribes to account updates, trade updates, and the AAPL one-minute bar stream all from the same WebSocket connection. This is the best way to go about retrieving multiple streams as Alpaca will only allow one WebSocket connection at a time.

How can I use indicators with the Alpaca API?

In the past, Alpaca offered data via the Alpha Vantage API which had built-in support for indicators. They have since transitioned to an in-house solution and do not offer indicator data through the API.

There are several options to get indicator values. You could continue to use the Alpha Vantage API by signing up directly for a key on their website. Or, you can use a third-party library to calculate indicators.

Since the data returned by the Alpaca library comes in Pandas format, Pandas can be used to calculate various things like a moving average or a standard deviation.

Here is an example of calculating the 20 moving average for the AAPL daily bars we retrieved earlier:

aapl.df.AAPL.close.rolling(20).mean()

The above code uses the mean() function to calculate an average. The rolling function is where we can specify the size of the moving average, in this case it is 20.

If we wanted to calculate the standard deviation, all we need to do is substitute out the mean() function with the std() function, like this:

aapl.df.AAPL.close.rolling(20).std()

There are also several third-party libraries that simplify creating technical indicators. Here are a few popular ones:

  • Pandas TA – The Pandas Technical Analysis libraries offers over 120 indicators and works well in this scenario since the returned data is already in a Pandas Dataframe
  • TA-LIB – This is one of the most popular libraries out there. Several brokers use it in fact. It isn’t natively supported in Python but a wrapper is available.

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. To do that, we calculate the distance between the range high and range low of the grey box $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 logging
from time import sleep

import alpaca_trade_api as tradeapi
import pandas as pd

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

We start with our imports. The only third-party library we are using here is pandas which will be used to make calculations for our trade entries. Aside from that, a basic logger has been created to log any errors that may come up.

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

Next we declare a few constant variables. Again, if you’re trading in a live account, it is better to store your API keys as environment variables rather than hard coding them. The base URL will be used to instantiate the API and the data URL is needed to initialize the WebSocket as shown in the code below.

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

# init WebSocket
conn = tradeapi.stream2.StreamConn(
	api_key,
	api_secret,
	base_url=base_url,
	data_url=data_url,
	data_stream='alpacadatav1',
)

There will be a few instances where we will need to check if the market is open or not. The following two methods will make it easier to get this information later on.

def time_to_market_close():
	clock = api.get_clock()
	return (clock.next_close - clock.timestamp).total_seconds()


def wait_for_market_open():
	clock = api.get_clock()
	if not clock.is_open:
		time_to_open = (clock.next_open - clock.timestamp).total_seconds()
		sleep(round(time_to_open))

Both use the get_clock() function of the API. The first method in the code snippet above will let us know how many seconds are left until the market closes. We need this information as we don’t want to trigger any new trades 120 seconds, or 2 minutes, before the market closes for the day.

The second function will simply put the script to sleep until the market reopens again.

def set_trade_params(df):
	return {
		'high': df.high.tail(10).max(),
		'low': df.low.tail(10).min(),
		'trade_taken': False,
	}

The set trade params function takes in a pandas DataFrame and then returns the high and low over the last ten bars, in a dictionary format. This will be used to determine our trade entries.

The next function is a long one. This one will be used to send an order once we’ve established an entry.

def send_order(direction, bar):
	if time_to_market_close() > 120:
		print(f'sent {direction} trade')
		range_size = trade_params['high'] - trade_params['low']

		if direction == 'buy':
			sl = bar.high - range_size
			tp = bar.high + range_size
		elif direction == 'sell':
			sl = bar.low + range_size
			tp = bar.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

	wait_for_market_open()
	return False

The first thing this function does is check to see if there is at least 120 seconds left until the market closes. If this condition is not met, it skips sending the trade and puts the script to sleep until the market reopens.

It will also return False if this were to happen, just to let the script know that an order was not sent, even though this function was called.

If there is more than 120 seconds until the market close, the script will calculate the range size and determine the stop loss and take profit values. After that, a market order is submitted. Finally, True is returned to confirm that a trade went through.

That’s it for our functions. We can now move on to creating callback functions for the WebSocket.

There are two callbacks we will want to create. One is for the one-minute bar data that we will be requesting. The second is for the trade updates stream we discussed and showed an example of earlier on in this article.

One thing that is unique about this trading strategy is that we will be writing most of it within the WebSocket callback itself.

There are advantages and disadvantages of taking this approach.

A big advantage is that we don’t need to constantly put our script to sleep and keep checking to see if new data has arrived. As soon as the WebSocket receives new data, our necessary code will run.

The disadvantage of this is that running code in a WebSocket will block it from receiving further messages.

In this case, we are only expecting updates once a minute. And our code shouldn’t take more than a second to run anyway.

But if you were working with tick data, a better approach would be to write your code outside the callback function and then use either the threading module or an asynchronous framework to ensure the WebSocket is not being blocked.

@conn.on(r'^AM.AAPL$')
async def on_minute_bars(conn, channel, bar):
	if isinstance(candlesticks.df, pd.DataFrame):
		ts = pd.to_datetime(bar.timestamp, unit='ms')
		candlesticks.df.loc[ts] = [bar.open, bar.high, bar.low, bar.close, bar.volume]

	if not trade_params['trade_taken']:
		if bar.high > trade_params['high']:
			trade_params['trade_taken'] = send_order('buy', bar)

		elif bar.low < trade_params['low']:
			trade_params['trade_taken'] = send_order('sell', bar)

	if time_to_market_close() > 120:
		wait_for_market_open()

The above code will execute everytime the Alpaca WebSocket pushes a new one-minute bar to us.

In the first block of code, the latest bar gets appended to candlesticks.df. We haven’t created candlesticks.df but will get to it shortly.

The idea here is that he WebSocket only pushes out one bar at a time. But we need ten bars to calculate our range high and low.

What we can do is call the API once to get the initial 10 bars needed for our calculation. Then, this part of the code will keep our data up to date so that we don’t have to query the API for historical data next time we want to calculate our entry parameters.

After that, we check to see if the current high took out the 10-bar high calculated in the trade parameters dictionary. If so, a trade order is sent using the function we created earlier. The same applies if the 10-bar low gets taken out.

We only want to be in one trade at a time, therefore we use ‘trade_taken’ to inform the script if an open trade exits or not.

Lastly, we do a check here to make sure there is at least 120 seconds left until the market closes. If not, the script goes to sleep until the market next reopens again.

The second function will handle any trade updates from the WebSocket

@conn.on(r'^trade_updates$')
async def on_trade_updates(conn, channel, trade):
	if trade.order['order_type'] != 'market' and trade.order['filled_qty'] == '100':
		# trade closed - look for new trade
		trade_params = set_trade_params(candlesticks.df.AAPL)

All we are doing here is checking to see if a message has come in that indicates our trade has closed. If so, we can check for new trade parameters for the next trade.

We do this by checking the order type. Any entry orders we sent are market orders. If a trade is closed, it will either be by a limit order (take profit) or a stop order (stop loss).

So if we have any order type that is not a market order with a filled quantity of 100, we know that means our last trade was closed.

We are done with our methods and callbacks. Normally, this is where the main script starts. But in this case, we are nearly finished.

The three lines of code below is all that’s needed for the main part of our script.

candlesticks = api.get_barset('AAPL', 'minute', limit=10)
trade_params = set_trade_params(candlesticks.df.AAPL)
conn.run(['AM.AAPL', 'trade_updates'])

The first line makes the call for the initial 10 bars of historical data. This is the same object that the WebSocket will append new bars to.

We’ve also set the trade parameters for our first trade, and started the WebSocket.

Note that we are not running the WebSocket in a thread. Since all the code takes place in the callback functions, there is no need to run two separate threads, or even any loops for that matter.

A fully functioning trading script – without WebSocket data

When this article was first written, Alpaca didn’t offer a data WebSocket. So we had take an entirely different approach compared to the example above.

This trading example achieves the exact same thing as the last example but relies on making API calls for new one-minute bars.

The following script uses nested loops. The advantage of nested loops, in this case, is that it is readable code that offers clear logic as we progress through the various parts of a trade cycle.

The disadvantage is that nested loops can be slow. This is not an issue in this case because we are dealing with one-minute data but if you’re strategy involves tick data, it might be something to consider.

Loops are also a bit dangerous in programming in general but more so in trading. If something goes wrong, the script could accidently send multiple unintended orders. Extra care should always be taken when working with loops.

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) 
		print(past_trades[-1])

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.total_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
range_size = high - low
if range_size / candlesticks['AAPL'][0].c < 0.003:
	range_size = candlesticks['AAPL'][0].c * 0.003
for candle in candlesticks['AAPL']:
	if candle.h > high:
		high = candle.h
	elif candle.l < low:
		low = candle.l
range_size = high - low

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.

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, response times took longer than average when making calls for historical data in our testing. For trading systems where speed is of the essence, running further tests on latency is recommended.

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. 

There are a number of other functions in the library that we haven’t covered, 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.

Jignesh Davda