We’ve spent the past few posts building up the Starter System laid out in Rob Carver’s book, Leveraged Trading.
We’ve gone from a simple moving average cross-over model, to a volatility targeting system with multiple instruments and time frames that dynamically sizes and re-positions your portfolio as market conditions change.
You’ve done all this work, now it’s time to make it pay off by setting it up to trade live in your brokerage account!
To do this, we’re going to trade with a platform called Alpaca.
Why are we Trading with Llamas?
As my mother would be quick to tell me, alpacas aren’t llamas (and she should know given that she owned a pair until they had a brutal llama fight).
What are they then? I don’t know.
However, I do know that you can trade for free with the Alpaca API.
So that’s something.
Setting Up Your Account
Signing up is simple. Just go to the Alpaca sign-up page and create an account. It’s free and all you need is an email address to get started.
You’ll see your account dashboard the first time you log in:
You can sign up for an individual account or business account if you like — it’s just like opening any other brokerage. Or, just continue on because those won’t be needed for the rest of this article.
Do you have Paper Hands?
You’ll never learn to trade until you get your hands dirty by taking your system live. Does this mean you should just throw some cash in and go?
Not necessarily — and especially not when you’re trying to learn a new API!
Alpaca has a paper trading account that lets you simulate your trading system without putting money on the table. This is a great feature because you can test your system to make sure it works as expected without worrying about losing money.
To access the paper account, you’ll click the “Live Trading” menu in the top left and click on your paper account. You don’t need to provide any additional info to use this feature.
The first time you open your paper account, you’ll see the dashboard with your equity and buying power.
By default, our account assumes we have $100k to trade with, but we could buy up to $200k of assets thanks to our buying power.
If you scroll down you can see where you can manually enter trades, view your portfolio (empty right now), order history, watch list, and so forth. That’s fine, but we’re interested in the API because we’d rather have our computer do all of this for us.
Getting your Keys
To connect your trading bot to your account, you need to get your keys. This is as easy as clicking “View” on the right hand side of the page to view your keys.
Here you’ll see the API endpoint for the paper account and your API keys.
Protect these!
If anyone gets your keys, they’ll be able to access your account and I doubt they’d be working hard to generate you some alpha.
Make sure you save those somewhere safe before moving on.
Important Endpoints and How to Use Them
Ok, now it’s time to submit some orders and actually use this new account.
Alpaca makes it easy to do so with a Python package called alpaca-trade-api.
It’s easily installed with:
pip install alpaca-trade-api
To connect to your account, run the following:
from alpaca_trade_aip import tradeapi
api = tradeapi.REST(API_KEY, SECRET_KEY, ENDPOINT_URL)
The API_KEY, SECRET_KEY, and ENDPOINT_URL are the values you got in the section above.
You can get your account data with api.get_account(). This will return an Account object that contains a bunch of information ranging from your equity, when the account was created, your buying power, margin, portfolio value, value of short positions, and info on transfers and account status.
For our system, we’re primarily going to be concerned with the portfolio value and the cash, values we use to size our positions.
Submitting orders is done using the submit_order() method.
Let’s say you want to buy 100 shares of Apple. Once you have connected to the API via the commands above, you simply write:
api.submit_order(symbol='AAPL', qty=100, side='buy', type='market', time_in_force='gtc')
This instructs Alpaca to buy 100 shares using a market order and keep that order open until it’s closed (GTC = good till closed).
Market orders are typically executed instantly, so you should get a response back giving the level that the order is filled at and all the other relevant info for you to examine.
To sell, you can do the exact same thing, just change side to ‘sell’ and you’ll liquidate your position.
If you sell more shares than you have in your account, then you’re going to go short. So no need to do anything extra in your code to add shorts.
You can get a list of all of your positions with the list_positions() method. We'll be using this in our trading system as we make adjustments to our portfolio.
Getting Data from Alpaca
The API also gives you access to free data. This is done with the get_barset() method.
Going back to our Apple trading example, if we’re looking for data there, we can run:
bars = api.get_barset('AAPL', 'day', limit=320)
This gives us a dictionary containing a list Bar objects:
{'AAPL': [Bar({ 'c': 146.1427,
'h': 147.0997,
'l': 142.96,
'o': 143.47,
't': 1626753600,
'v': 80491263}),
Bar({ 'c': 145.39,
'h': 146.13,
'l': 144.63,
'o': 145.53,
't': 1626840000,
'v': 62647175})]
}
Unfortunately, Alpaca will limit you to the most recent 1,000 bars, so it isn’t suitable for long-term backtests. Additionally, Alpaca doesn’t provide dividend data (as of this writing) anywhere in its API, so our carry signal isn’t going to be possible if we just rely on their data feed. We need to augment it with some other data to generate that signal.
We’ll walk through all of this in the Python code below.
Running Your Strategy
Now it’s finally time to hook up Carver’s complete Starter System to Alpaca so we can use this algorithm to trade automatically.
One disclaimer on the code, it’s not as clean and elegant as my overly picky self would like. This is because this series began with the goal of building out Carver’s system and backtesting it, but thanks to some reader feedback, I decided we’ll take this all the way to implementation with Alpaca. Rather than re-writing all of the code, we’re going to bolt on live trading support to our existing system in the least obtrusive way possible. So backtesting will still be supported as will all of the other features we built up, but so will live trading, which requires a slightly different set up. Ideally, it would be built to handle all of this up-front.
I’m probably being to persnickety and should just shut up already and get to the code.
class AlpacaStarterSystem(DiversifiedStarterSystem):
'''
Carver's Starter System without stop losses, multiple entry rules,
a forecast for position sizing and rebalancing, and multiple instruments.
Adapted from Rob Carver's Leveraged Trading: https://amzn.to/3C1owYn
Allows live trading via the Alpaca API.
DiversifiedStarterSystem found here:
https://gist.github.com/raposatech/d3f10df41c8745b00cb608bd590a986d
'''
def __init__(self, tickers: list, signals: dict,
base_url: str,
target_risk: float = 0.12, margin_cost: float = 0.04,
short_cost: float = 0.001, interest_on_balance: float = 0.0,
start: str = '2000-01-01', end: str = '2020-12-31',
shorts: bool = True, weights: list = [],
max_forecast: float = 2, min_forecast: float = -2,
exposure_drift: float = 0.1, max_leverage: float = 3,
*args, **kwargs):
self.tickers = tickers
self.n_instruments = len(tickers)
self.signals = signals
self.target_risk = target_risk
self.shorts = shorts
self.start = start
self.end = end
self.margin_cost = margin_cost
self.short_cost = short_cost
self.interest_on_balance = interest_on_balance
self.daily_iob = (1 + self.interest_on_balance) ** (1 / 252)
self.daily_margin_cost = (1 + self.margin_cost) ** (1 / 252)
self.daily_short_cost = self.short_cost / 360
self.max_forecast = max_forecast
self.min_forecast = min_forecast
self.max_leverage = max_leverage
self.exposure_drift = exposure_drift
self.signal_names = []
self.weights = weights
self.idm_dict = {
1: 1,
2: 1.15,
3: 1.22,
4: 1.27,
5: 1.29,
6: 1.31,
7: 1.32,
8: 1.34,
15: 1.36,
25: 1.38,
30: 1.4
}
self.base_url = base_url
self._parseBars = np.vectorize(self.__parseBars)
self.max_time = self._getMaxTime()
self.instrument_index = {t: i
for i, t in enumerate(self.tickers)}
def alpacaInit(self, api_key, api_secret, backtest=False):
self.api = tradeapi.REST(api_key, api_secret, self.base_url)
self._setWeights()
self._setIDM()
self._getAlpacaData(backtest)
if 'CAR' in self.signals.keys():
# Add dividends from YFinance
self._getYFData(True)
self._getPortfolio()
self._calcSignals()
self._calcTotalSignal()
def _getMaxTime(self):
max_time = 0
for v in self.signals.values():
for v1 in v.values():
for k, v2 in v1.items():
if k != 'scale' and v2 > max_time:
max_time = v2
return max_time
def __parseBars(self, bar):
return bar.t.date(), bar.c
def _getYFData(self, dividends_only=False):
yfObj = yf.Tickers(self.tickers)
df = yfObj.history(start=self.start, end=self.end)
df.drop(['High', 'Open', 'Stock Splits', 'Volume', 'Low'],
axis=1, inplace=True)
df.columns = df.columns.swaplevel()
if dividends_only:
divs = df.loc[:, (slice(None), 'Dividends')]
divs.index.rename(self.data.index.name, inplace=True)
df = self.data.merge(divs, how='outer',
left_index=True, right_index=True)
data = df.fillna(0)
self.data = data.copy()
def _getPortfolio(self):
account = self.api.get_account()
self.cash = float(account.cash)
# Get current positions
self.current_positions = self.api.list_positions()
self.portfolio_value = np.dot(
self.data.loc[:, (slice(None), 'Close')].iloc[-1].values,
self._getPositions()) + self.cash
self.current_portfolio_value = sum([float(i.market_value)
for i in self.current_positions]) + self.cash
def _getAlpacaData(self, backtest):
'''
Backtests with Alpaca data are not recommended because data pulls
are limited to 1000 bars.
'''
if backtest:
limit = (datetime.today() - pd.to_datetime(self.start)).days
else:
limit = self._getMaxTime()
if limit > 1000:
warn(f'Alpaca data is limited to 1,000 bars. {limit} bars requested.')
limit = 1000
bars = self.api.get_barset(
self.tickers, 'day', limit=limit)
data = pd.DataFrame()
for t in self.tickers:
time, close = self._parseBars(bars[t])
df = pd.DataFrame(close, index=time)
data = pd.concat([data, df], axis=1)
midx = pd.MultiIndex.from_arrays(
[self.tickers, self.n_instruments*['Close']])
data.columns = midx
self.data = data
self.start = data.index[0]
self.end = data.index[-1]
def trade(self):
row = self.data.iloc[-1]
prices = row.loc[(slice(None), 'Close')].values
sigs = row.loc[(slice(None), 'signal')].values
stds = row.loc[(slice(None), 'STD')].values
positions = self._getPositions()
new_pos, cash, shares, delta_exp = self._processBar(
prices, sigs, stds, positions.copy(), self.cash)
orders = self._executeMarketOrder(new_pos - positions)
def _getPositions(self):
positions = np.zeros(self.n_instruments)
for p in self.current_positions:
try:
idx = self.instrument_index[p.symbol]
except KeyError:
# Occurs if there are non-system instruments in the account
continue
positions[idx] += int(p.qty)
return positions
def _executeMarketOrder(self, shares):
print(f"\nPurchase Shares: {shares}")
orders = []
for s, t in zip(shares, self.tickers):
if s > 0:
side = 'buy'
elif s < 0:
side = 'sell'
else:
continue
order = self.api.submit_order(
symbol=t, qty=np.abs(s), side=side,
type='market', time_in_force='gtc')
print(f"{order.side} {order.qty} shares of {order.symbol}")
orders.append(order)
return orders
def backtestInit(self, source='yahoo', starting_capital=10000):
'''Ability to run backtest with this class as well.'''
self.starting_capital = starting_capital
self._getData(source, backtest=True)
self._calcSignals()
self._setWeights()
self._calcTotalSignal()
self._setIDM()
The majority of updates are around getting data from Alpaca and ensuring we don’t break any existing functionality.
To run this, we’d define a dictionary of signals and scales, get a list of tickers, and set any parameters as we see fit. From there, we’d run alpacaInit(KEY, SECRET) with our credentials, then call the trade() method to execute our strategy.
sys = AlpacaStarterSystem(tickers, sig_dict, base_url=URL)
sys.alpacaInit(KEY, SECRET)
sys.trade()
That’s it.
Running it, I get the following output:
Purchase Shares: [ 18. 0. 2. 5. 413.]
buy 18 shares of FB
buy 2 shares of GM
buy 5 shares of GS
buy 413 shares of F
If you check back with your Alpaca dashboard, you can see the updates to your portfolio.
Code to run this can be found here.
Running an Algorithmic Trading System
Now that you have a complete trading system on hand, you can run this every day to do your trading. To fully automate it, have it run as a cron job on a daily basis and you’ll be able to sit back knowing that your bot is working to execute your strategy on your behalf.
Now you can do your day job (or get a day job) without worrying about the market.
Of course, there are a lot of ways to improve this system. It is a “starter” system after all!
Rob Carver, the developer and author we’ve been following in this series, recently released a full breakdown of his actual system. If you have been following along in this series, or read Leveraged Trading, you should be in a good position to understand what he’s doing and how his system works.
Try taking some of those ideas and applying it to yours to suit your style and preference of returns.
Or, build something from scratch — whatever you want.
Of course, this all takes time to do (your day job is suffering again) so we decided to make it easy and develop an online platform to handle all of this for you.
No coding, no worrying about data subscriptions, no messing with APIs and the like.
Just design your system with a few clicks, test it, and let it run for you.
It’s that easy.
If that sounds appealing, then check out our free demo here and let us know what you think!