
If you’re thinking of algo trading for the first time but have no idea of where to start, you’re not alone. I was in the same place not too long ago. However, as I learned more about manual trading, the concept of algorithmic trading started to pique my interest more and more, despite having no coding experience at the time. Since then, I’ve made a few changes, in both my education and career, that have helped me gain the confidence and skills needed to start algo trading.
That’s why I’ve written this piece–to take you through my journey as an algorithmic trader. This includes a little bit about my background, how I got started, my algorithmic development process, how I built it using Alpaca’s Trading API and paper trading environment, and how I’ve learned to refine my strategies.
Here are some useful resources to help you follow along:
- Sign up for an Alpaca Trading API account
- How to Connect to Alpaca's Trading API
- How to Start Paper Trading with Alpaca's Trading API
- How to Trade Options with Alpaca’s Trading API
How I Started Algo Trading
Hi! I’m Stephen Coley, a Master's student currently studying Financial Mathematics at the University of Chicago. I became interested in trading around 2019, initially focusing on options spreads. As I learned more about trading, I realized algorithmic trading was something I wanted to try even though I had little to no programming skills. However, I knew that if I wanted to take it seriously, I was going to have to make some changes. So I switched my major from Theatre to Data Science to help me build those skills.
It was in my undergrad that I really started to understand all the components of algorithmic trading. I learned how to use Python for data wrangling, data analysis, and machine learning. With this as my focus, I completed several projects attempting to predict future returns both at intraday and daily intervals. I also did an internship as a Quantitative Analyst at Deep Q Digital, a high frequency cryptocurrency trading firm, during which I further developed my interest and understanding of machine learning for trading. While I was there, I completed a project using gradient boosted tree models in Python to predict crypto returns at high frequencies.
Looking for other opportunities to practice my skills, I also competed in one of IMC's algorithmic trading competitions. This experience helped me gain an understanding of translating trading signals into orders that can be sent to the exchange. These internships and external initiatives have been beneficial in refining my skills as a programmer and trader which helped me develop better algorithmic trading systems in recent projects.
Algorithm Development Process
One of my recent projects started as a class assignment for Computing for Finance in Python at the University of Chicago. Over the course of the semester I slowly built the different pieces of a trading system, going over everything including data analysis and visualization, backtesting, exchange simulation, data management, and machine learning, all from an object oriented paradigm. The project culminated in an algorithmic trading competition that involved using Alpaca’s Trading API. Note: The model and some of its code is included below in more detail.
I really found my groove during this project, putting many hours into it during the semester, and even continued working on it after the competition ended. My system pulls data from Alpaca every minute, adds features to the data, passes it to a machine learning model for a prediction, then based on that prediction, it sends orders to Alpaca. I built this model primarily using their Python package, alpaca-py, which serves as an easy to use, easy to read interface for interacting with Alpaca's Trading API.
The first thing I needed, before I could start training a model, was data. I started by pulling trade data from Alpaca using their StockTradesRequest function and aggregating it using polars. However, I realized this created unnecessary complications for the proof of concept algo that I was trying to build. As such, I switched the process and resolved to pull the data every minute. Alpaca facilitates pulling batch data very well by giving the option to return the pulled data as a pandas dataframe.
After I had the data I needed, I had to give it features. I started by adding technical indicators like Bollinger Bands, simple moving averages (SMA), and relative strength index (RSI). Most of these features needed to be normalized so the model wouldn't memorize prices. For many of them, I did this by subtracting the value of the indicator from the last closing price and dividing by the volatility of the stock estimated via exponentially weighted moving average (EWMA).
This gave the model a good idea of how far away the current price is from these moving averages without too much risk of memorization or overfitting. I did this part using built in functions in the pandas library as well as some custom functions, primarily building features off of price and volume.
The next step was to choose a target variable for my model to predict. I was using minute-increment bars, so my prediction needed to be relatively short term, but I didn’t want it to over trade, so arbitrarily chose a 15-minute holding horizon. In my previous internship experience at Deep Q Digital, I learned that sometimes it’s easier for tree-based models to predict a few categorical variables representing return amounts we care about than to predict the return itself. I chose to label the top 25% of returns as buy signals and the bottom 25% as sell/short signals.
To predict the return category, I chose to use the CatBoost classifier, which is an implementation of gradient boosted trees that can be GPU accelerated and has native support for categorical features. For example, if the model predicted 1, I would take that as a buy signal, if 0, a hold or flatten out signal, and if -1, a short signal. For my target variable I focused on predicting whether the return 15 minutes out would be greater than the third quartile of returns, below the first quartile, or somewhere in the middle. I chose this time horizon as an attempt to prevent overtrading, and to give trades time to make money before being closed out.
In evaluating model performance, I trained the model with a forward-chaining approach, using the first chunk of the data to train the model, and the next chunk for testing. On the out-of-sample data, I ran a backtest using the pretrained model parameters as well as ROC/AUC curves and confusion matrices. Once I was satisfied with model performance, I moved on to implementing the signals. Within my trading logic, I decided to stick to trading AAPL shares. This is what I had trained my model on so I was confident that the model would trade in a similar way to my backtests.
In the current version of the algorithm, I initialize my features by pulling the last couple of days of 1 minute bars from Alpaca using StockBarsRequest. I also initialize the Alpaca TradingClient and load my Machine Learning model from a joblib file. From there, my system enters an infinite loop that pulls data every minute, calculates the features, takes the last row of the featurized data, passes it to the model, then based on the prediction, sends a trade via Alpaca's TradingClient.
As for sizing, I focused on just trading one share, holding that share if the signal remained the same and either flattening out or switching the trade to the other direction depending on the new signal. In order to facilitate this logic, I check whether I have a position in the account using Alpaca's TradingClient object.
For the purposes of this project, I used market orders to place my trades, primarily to simplify the problem and ensure that trades would be placed. Alpaca's Python package was immensely helpful in building this strategy as it facilitates API calls, checking account positions, error handling, data pulls and trade management all from one easy to use, easy to read interface. Using Alpaca's package allowed me to focus more of my attention on trading logic and building a robust model rather than worrying about messing with a difficult API.
Code Example:
I have included a code snippet that contains the trading logic for my model in its currently functional version. Because the functions for pulling data and adding features I built are specifically for this project and integral to my model, I won't share them here. However, I hope this code helps demonstrate how I’m using Alpaca and other packages to build an algo trading model.
Please keep in mind:
- I am using AAPL as an example, and it should not be considered investment advice.
- This is not a finished algorithm. It is just a starting point for traders who are looking to understand and build a model.
- The indents are formatted below with intention. Do not adjust.
Imports
# Imports
USE_GPU = True # Set to True for 'GPU' and False for 'CPU'
import os
#### We use paper environment for this example ####
paper = True # Please do not modify this. This example is for paper trading only.
####
# Below are the variables for development this documents
# Please do not change these variables
trade_api_url = None
trade_api_wss = None
data_api_url = None
stream_data_wss = None
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
import alpaca
from alpaca.trading.client import TradingClient
from alpaca.data.timeframe import TimeFrame, TimeFrameUnit
from alpaca.data.historical.corporate_actions import CorporateActionsClient
from alpaca.data.historical.stock import StockHistoricalDataClient
from alpaca.trading.stream import TradingStream
from alpaca.data.live.stock import StockDataStream
from alpaca.data.requests import (
CorporateActionsRequest,
StockBarsRequest,
StockQuotesRequest,
StockTradesRequest,
)
from alpaca.trading.requests import (
ClosePositionRequest,
GetAssetsRequest,
GetOrdersRequest,
LimitOrderRequest,
MarketOrderRequest,
StopLimitOrderRequest,
StopLossRequest,
StopOrderRequest,
TakeProfitRequest,
TrailingStopOrderRequest,
)
from alpaca.trading.enums import (
AssetExchange,
AssetStatus,
OrderClass,
OrderSide,
OrderType,
QueryOrderStatus,
TimeInForce,
PositionSide,
)
from alpaca.common.exceptions import APIError
# to run async code in jupyter notebook
import nest_asyncio
nest_asyncio.apply()
import numpy as np
import time
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
import pandas as pd
import numpy as np
import os
import sys
from pathlib import Path
base_dir = Path.cwd()
sys.path.append(str(base_dir))
import config # Now you can import config.py
os.chdir('.')
api_key = config.API_KEY
secret_key = config.SECRET_KEY
BASE_URL = config.APCA_API_BASE_URL
endpoint = BASE_URL + '/v2'
import pandas as pd
import numpy as np
import sys
sys.path.append(str(base_dir) + "/utils")
os.chdir('.')
from datetime import datetime
from requests.exceptions import ConnectTimeout
sys.path.append(str(base_dir) + "/data_processing")
from ta_indicators import ta_df, filter_to_market_hours, calculate_positions, forward_chaining, initialize_ta
os.chdir('.')
sys.path.append(str(base_dir) + "/data_ingestion")
from pull_1m_bars import get_1mbars_for_symbol
os.chdir('.')
import joblib
from datetime import datetime
from urllib.error import HTTPError
In this part of the code, I’m just setting up my environment, loading in my personal libraries, pandas libraries, and alpaca libraries. These imports allow me to run many of the functions in my code.
Load Data and Add Features
# Record current time, and time 4 days prior to use as parameters in data pull
end_time = datetime.now(ZoneInfo("America/New_York"))
start_time = end_time - timedelta(days=4)
# Pull initial data
aapl_1m = get_1mbars_for_symbol('AAPL', start_time, end_time-timedelta(minutes=16), save_file=False, feed='sip')
# List to keep track of average time per run
time_per_run = []
# Infinite loop to execute trading logic
while True:
start = datetime.now()
# Get trade signal
symbol = 'AAPL'
nominal_val = 10
end_time = datetime.now(ZoneInfo("America/New_York"))
try:
# Start time is most recent timestamp in the data
start_time = aapl_1m['timestamp'].iloc[-1]
# Get most recent prices
aapl_1m_iex = get_1mbars_for_symbol('AAPL', start_time+timedelta(minutes=1), end_time, save_file=False, feed='iex')
aapl_1m = pd.concat([aapl_1m, aapl_1m_iex], ignore_index=True)
# Add features to the data
aapl_1m_ta = ta_df(aapl_1m['close'], aapl_1m['vwap'], aapl_1m['volume'], aapl_1m['timestamp'], 1, aapl_1m, initialize = True)
except Exception as e:
# If pulling data fails, wait one minute before rerunning the loop
print(f'an exception occured: {e}')
end = datetime.now()
elapsed_time = end-start
time_per_run.append(elapsed_time)
time.sleep(60-elapsed_time.total_seconds())
continue
I start by pulling the last few days of minute bar data before entering the trading logic loop. After entering the loop, I then pull the most recent minute bar from Alpaca, adding it into the data I’ve already pulled. I then add technical analysis features in one custom built function. I also use a try/except structure here in case I get an error when pulling data.
Predict 15-Minute Return
#Load in the model if not already loaded in
try:
model.classes_
except NameError as e:
print(os.getcwd())
model = joblib.load('models/aapl_corrected_model')
#Try predicting. If there is an error, we probably have an empty dataframe and need to wait for data
try:
trade = model.predict(aapl_1m_ta.iloc[-1])[0]
except IndexError as e:
print(f'an exception occured: {e}')
end = datetime.now()
elapsed_time = end-start
time_per_run.append(elapsed_time)
time.sleep(60-elapsed_time.total_seconds())
continue
# Load in the trade_client if not already loaded in
try:
print(trade_client)
except NameError:
trade_client = TradingClient(api_key=api_key, secret_key=secret_key, paper=paper, url_override=trade_api_url)
# Market is closed or data is stale, skip this run
if end_time-aapl_1m.iloc[-1]['timestamp'] > timedelta(minutes=2.5):
print('data is out of date, no trade yet')
end = datetime.now()
elapsed_time = end-start
time_per_run.append(elapsed_time)
time.sleep(60-elapsed_time.total_seconds())
continue
In this section, I load in my model if it isn’t already loaded, then I attempt to make a prediction. I have another try/except here to catch an error that may happen if the data is empty. I then make sure I have Alpaca’s trading client loaded in so I can use it to trade. If the data is more than a couple minutes old at this point, I skip the trading logic until a minute later.
Execute Trading Logic
# get position
# Use full account to trade
acct = trade_client.get_account()
portfolio_value = float(acct.portfolio_value)
multiplier = float(acct.multiplier)
last_price = aapl_1m.iloc[-1]['close']
# need to shrink size so that trades will go through, especially for shorting
tradeable_value = portfolio_value*2*.96
trade_size = np.floor((tradeable_value)/last_price)
print('Trade Signal:', trade)
print('Possible shares:', trade_size)
# check current position
try:
position = trade_client.get_open_position(symbol_or_asset_id=symbol)
qty = float(position.qty)
except APIError as e:
qty = 0.0
except HTTPError as e:
qty = 0.0
except ConnectTimeout as e:
print(f'Connection timed out: {e}')
end = datetime.now()
elapsed_time = end-start
time_per_run.append(elapsed_time)
print(elapsed_time)
time.sleep(60-elapsed_time.total_seconds())
continue
# execute the trade based on the signal
if trade_size !=0:
if trade == -1 and qty>=0.0:
print('Going short')
if qty != 0.0:
# close pos
trade_client.close_position(
symbol_or_asset_id = symbol,
close_options = ClosePositionRequest(
qty = str(qty),
))
time.sleep(0.5)
# go short
try:
req = MarketOrderRequest(
symbol = symbol,
qty = trade_size,
side = OrderSide.SELL,
type = OrderType.MARKET,
time_in_force = TimeInForce.DAY,)
res = trade_client.submit_order(req)
except APIError as e:
req = MarketOrderRequest(
symbol = symbol,
# qty = trade_size*0.99,
qty = trade_size,
side = OrderSide.SELL,
type = OrderType.MARKET,
time_in_force = TimeInForce.DAY,)
res = trade_client.submit_order(req)
elif trade == 1 and qty <=0:
print('Going long')
if qty != 0.0:
# close pos
trade_client.close_position(
symbol_or_asset_id = symbol,
close_options = ClosePositionRequest(
qty = str(np.abs(qty)),
))
time.sleep(0.5)
# go long
try:
req = MarketOrderRequest(
symbol = symbol,
qty = trade_size,
side = OrderSide.BUY,
type = OrderType.MARKET,
time_in_force = TimeInForce.DAY,)
res = trade_client.submit_order(req)
except APIError as e:
req = MarketOrderRequest(
symbol = symbol,
qty = trade_size*0.99,
side = OrderSide.BUY,
type = OrderType.MARKET,
time_in_force = TimeInForce.DAY,)
res = trade_client.submit_order(req)
elif trade ==0 and qty!= 0.0:
print('Flattening out')
# close pos
trade_client.close_position(
symbol_or_asset_id = symbol,
close_options = ClosePositionRequest(
qty = str(np.abs(qty)),
))
else:
print('No trade')
else:
print('Not enough buying power to trade')
old_position = qty
# get position
try:
position = trade_client.get_open_position(symbol_or_asset_id=symbol)
qty = float(position.qty)
except APIError as e:
qty = 0.0
except HTTPError as e:
qty = 0.0
new_position = qty
print('Old position:', old_position)
print('New position:', new_position)
end = datetime.now()
elapsed_time = end-start
time_per_run.append(elapsed_time)
print('Total Time to run:', elapsed_time)
print('Average Time to run:', np.mean(time_per_run))
print()
print()
time.sleep(60-elapsed_time.total_seconds())
Here I implement my trading logic. I first find out the possible size of the position, which in this case, I’m basing off of taking the whole account and multiplying it by two to account for margin and multiplying by 0.96 to prevent errors in case the price moves while I’m still sending the trade.
The trade client actually does give margin multiplier information, which would be more accurate than using a static 2x number. They also provide buying power information which would potentially also be more helpful than using portfolio value here. In the case of this strategy as a proof of concept, this structure works, but there are potential improvements.
I then check what my current position is, and then run through a series of if statements based on the quantity of stock I currently have and the trade signal. If I currently have a position in the opposite direction of the trade my model predicted, I first need to close the position before I send a new market order. Alpaca provides a really easy-to-use function for closing positions that I utilize here. Once the position is closed, or if there is no position, I route a market order in the direction predicted by my model.
After I’m done sending the trade, I use time.sleep()
to wait for the new minute before I run the model again. I track the elapsed time to get as close as possible to the start of the next minute. After the sleep, the algorithm goes back through the loop until the code is manually interrupted.
Possible Algorithmic Improvements
While I have been working hard at building my current algotrading strategies, this algorithm was very rudimentary and there are many things I could do to take it farther. Some of my ideas are:
- Try other symbols or asset classes: Alpaca offers crypto trading, which could be interesting to test.
- Use a longer history of data to train the model: As of now, it's trained on a year's worth of minute bars from AAPL. This could be increased to continue to advance the model.
- Build a streaming trades version of the algorithm, aggregating data into minute bars or something more granular. This would remove the need to sleep my model and speed up the amount of time from the new minute to sending a trade to the exchange.
- Incorporate alternative data into the model.
- Logging functionality for errors and trades.
Conclusion
Overall, building this project in class with Alpaca’s APIs has cemented my desire to pursue a career in algo trading. I learned a lot through the project including better ways to build features, implementing forward chaining and tracking performance metrics, and handling errors as the model runs. Though imperfect, Alpaca's Python package served me well during this project, and I look forward to leveraging it in future algo trading projects.
Interested in learning more about options trading with Alpaca’s Trading API? Here are some resources:
- Sign up for an Alpaca Trading API account
- How to Connect to Alpaca's Trading API
- How to Start Paper Trading with Alpaca's Trading API
- How to Trade Options with Alpaca’s Trading API
- Documentation: Options Trading
Trader to Trader is a new series where we ask Alpaca Trading API users to share how they build their algorithmic trading strategies. Whether you’re building for the first time or have years of experience, we want to encourage our community to share how they started algo trading and why they use Alpaca. If you’re interested in being featured in an article, please email us at marketing[at]alpaca.markets.
Options trading is not suitable for all investors due to its inherent high risk, which can potentially result in significant losses. Please read Characteristics and Risks of Standardized Options before investing in options.
Past hypothetical backtest results do not guarantee future returns, and actual results may vary from the analysis.
The Paper Trading API is offered by AlpacaDB, Inc. and does not require real money or permit a user to transact in real securities in the market. Providing use of the Paper Trading API is not an offer or solicitation to buy or sell securities, securities derivative or futures products of any kind, or any type of trading or investment advice, recommendation or strategy, given or in any manner endorsed by AlpacaDB, Inc. or any AlpacaDB, Inc. affiliate and the information made available through the Paper Trading API is not an offer or solicitation of any kind in any jurisdiction where AlpacaDB, Inc. or any AlpacaDB, Inc. affiliate (collectively, “Alpaca”) is not authorized to do business.
Alpaca Securities LLC and the entities referenced in this article are not affiliated and are not responsible for the liabilities of the others.
Please note that this article is for educational and general informational purposes only and is believed to be accurate as of the posting date but may be subject to change. The examples above are for illustrative purposes only. The views and opinions expressed are those of the author and do not reflect or represent the views and opinions of Alpaca. Alpaca does not recommend any specific securities or investment strategies.
Alpaca does not prepare, edit, or endorse Third Party Content. Alpaca does not guarantee the accuracy, timeliness, completeness or usefulness of Third Party Content, and is not responsible or liable for any content, advertising, products, or other materials on or available from third party sites.
Securities brokerage services are provided by Alpaca Securities LLC ("Alpaca Securities"), member FINRA/SIPC, a wholly-owned subsidiary of AlpacaDB, Inc. Technology and services are offered by AlpacaDB, Inc.
Cryptocurrency services are made available by Alpaca Crypto LLC ("Alpaca Crypto"), a FinCEN registered money services business (NMLS # 2160858), and a wholly-owned subsidiary of AlpacaDB, Inc. Alpaca Crypto is not a member of SIPC or FINRA. Cryptocurrencies are not stocks and your cryptocurrency investments are not protected by either FDIC or SIPC. Please see the Disclosure Library for more information.
This is not an offer, solicitation of an offer, or advice to buy or sell securities or cryptocurrencies or open a brokerage account or cryptocurrency account in any jurisdiction where Alpaca Securities or Alpaca Crypto, respectively, are not registered or licensed, as applicable.
All investments involve risk, and the past performance of a security, or financial product does not guarantee future results or returns. There is no guarantee that any investment strategy will achieve its objectives. Please note that diversification does not ensure a profit, or protect against loss. There is always the potential of losing money when you invest in securities, or other financial products. Investors should consider their investment objectives and risks carefully before investing.
Cryptocurrency is highly speculative in nature, involves a high degree of risks, such as volatile market price swings, market manipulation, flash crashes, and cybersecurity risks. Cryptocurrency is not regulated or is lightly regulated in most countries. Cryptocurrency trading can lead to large, immediate and permanent loss of financial value. You should have appropriate knowledge and experience before engaging in cryptocurrency trading. For additional information please click here.