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:

alpaca-new-account.png

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.

alpaca-paper-account.png

The first time you open your paper account, you’ll see the dashboard with your equity and buying power.

alpaca-paper-account-overview.png

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.

api-keys-sn.png-mh.png

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.

code-runs-meme.png
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.

alpaca_dashboard_trade.png

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!