This is a curated post, and the original post is here: Brokerages Suck: Navigating the Challenges of Live Algo Trading
Over the last 6 years my ambitions in algo trading have shifted a little. As I already summarized in one of my earlier pieces, I originally set out to create a trade journal, and scope-creeped my way into a profitable all-weather strategy.
A secret ambition, however, is to try and create an “Immortal System”. If my bot were able to pay for its own hosting directly by way of ACH from my brokerage account, it might be close to possible. However, being a full time “Bot Shepherd” requires fairly regular inspection and adjustment at the brokerage level. This is not because of my software, but because all the brokerage APIs that exist are crap.
In this article I’m going to cover some of the frustrations I’ve encountered as I built and continue to manage my trading system “Impresario”. For anyone trying to transition from backtesting and data science into live trading, the following should be very informative.
Ancient Authentication Practices
Only a few brokerages out there really have sensible API authentication: Oanda, Alpaca, and Tradier, to be precise. These take a simple pair of API key and secret, manageable from their web interfaces, and the APIs have effectively zero configuration to get started. Collective2 is not a brokerage API, but it too uses simple key+secret authentication, which we love to see. IEX likewise provides a simple auth strategy, but that is just a data platform not a brokerage.
In general, any brokerage API that is accessible as part of the TradingView platform probably also uses simple key+secret API auth like this.
TD Ameritrade and Kraken require more complex authentication strategies. TDA requires an Oauth handshake process, which therefore requires a redirect-capable web server as part of your trading system to authenticate. Kraken requires an HMAC-SHA512 encoding handshake, but most (not ALL!) of the libraries out there have this sorted out for you. TDA also requires a Developer account (with separate credentials and a separate URL from your main TDA account) to authenticate, and once authenticated you 1) cannot ever change your TDA password or the grant token expires, and 2) need to have your trader identify by numeric account ID which account you’re trying to trade, because the API returns every account you have access to. This account ID is actually the bank account ID you would use for ACH transfers so this means your trading system has to store half the sensitive bank information for your account in its config or database, while the other part (the routing ID) is public knowledge. This is hardly ideal.
Interactive Brokers (IBKR) has the worst authentication strategy imaginable. Depending on which API you use, it either requires an Oauth handshake or pubkey encryption. Additionally, authentication requires use of a web interface to enter your username+password, and authentication must be performed every 24 hours. As they say on their own documentation,
“To ensure client account security, Interactive Brokers’ trading solutions were designed with the need for the user to manually re-authenticate with the backend on a daily basis. As such, the maximum period of time that a Client Portal API session can remain authenticated is 24 hours.”
It’s quite unfortunate that IBKR has such a strong foothold in the algo trading community considering how bad this is. Authentication should be a set-it-and-forget-it step in preparing for live trading, because it only gets worse from there.
Acquisitions, Delistings, Mergers, Splits
Trading strategies rely on identifying and capturing some market inefficiency in price variation. But sometimes more challenging market events take place. Historical data often provides “adjusted price” which incorporates basic splits and dividend information into the pricing, which helps with backtesting. But in live trading, it gets much trickier.
To date I haven’t found a good source for same-day recognition of acquisitions, delistings, and mergers. I have been the beneficiary of large market swings when a stock is announced for a merger or acquisition, but I have also lost in such situations.
One thing that is particularly interesting is what happens in the options markets when acquisition news has stopped making waves in the spot price. Often, you will see OTM options bid/asks all over the place while the spot doesn’t budge (as the spot price will naturally coalesce toward the announced price-per-share of the company). These wild variations in the options order book are spoofed orders by HFTs out there who similarly struggle with detecting these market events, and have not yet deactivated their algorithms.
Upgrades, Outages, and Hours of Operation
In early 2021, Alpaca went through a very rough migration from API v1 to v2. At the time I was using Alpaca for live trading with about $40k in the account, and overnight the market historical data and live websockets stopped sending price updates. This was one of my main motivations in migrating off of Alpaca and onto TD Ameritrade.
IBKR does a nightly reset of their entire system and boots every client off the system. This is unacceptable.
TD Ameritrade has had incredible uptime over the last 2 years, with only the occasional service timeout. The websockets do frequently disconnect overnight, but some keep-alive logic is all that was needed to contend with this. The one thing TDA does do that is mildly annoying, is sometimes their API will reflect that the market is open on bank holidays, when it actually is not.
Rate Limiting Requests
Every brokerage API will rate limit you. Alpaca limits you to 200 requests per minute. Tradier and TD Ameritrade limit you to 120 per minute. This appears to be on a per-account basis rather than on a per-API-key basis, at least in TDA’s case.
This works fine for my overall strategy, because I hand-pick my stocks to trade, and only trade about 90 of them. Where this could be problematic is for traders who have the ambition to try and trade the whole market, or want to do historical filtration of every stock for ideal fits for their model, and then trade the top-K tickers that work well for their model. A strategy like that would require many developer accounts to work.
This is one of the few places IBKR shines. The rate limit is up to 50 orders per second.
Real-Time WebSocket Data Isn’t Dependable
With rate limiting in mind, you might think “okay, well I’ll just use websockets for everything instead,” and you would be wrong. For every brokerage I have interacted with, long-term historical data is not available over websockets. Additionally, account details and ordering are not usually available over websockets. IBKR is the only brokerage I know of that supports these things over a websocket.
Real-time pricing is another matter. Every brokerage seems to support bid/ask spread information and last trade price over websockets in some fashion. Oanda doesn’t actually implement a websocket, but instead a “streaming HTTP” interface which was a little tricky to implement. One issue I’ve seen with TDA’s websockets is that sometimes the pricing gets weird, which I think is due to them interfacing with many exchanges behind-the-scenes. Often I will see a 1-second batch of price data come across the websocket like:
There seems to be no rhyme or reason why sometimes the price data is off by such a large percentage. And, there does not appear to be any way to isolate that data from the rest of the “good” pricing coming through the socket. This can and has led to issues where I’ve tried to take an entry or profit target for a price change that has not really yet occurred.
Slippage, Spreads, and Unexpected Costs
Speaking of which, let’s talk about slippage, because Mr. Slippage is very real and he will hurt you. I thought crypto markets and Alpaca had bad slippage until I tried working with Oanda. Oanda slippage is truly awful. The above screenshot is not the first half-percent slip I have seen, and won’t be the last. Thankfully, this was paper trading at 1.0x leverage. Were I live-trading this with up to 50x leverage, this one slip could have bankrupted me under the right conditions.
The regrettable state of Forex is that slippage is allegedly best for non-US and unregulated brokers. Forex also usually doesn’t offer “stop-limit” orders that would allow for the situation in the above screenshot to have a reasonable exit price. Instead, in the world of Forex there are “Guaranteed Stop Loss Orders” where you pay a premium (instead of the slippage) for exiting at the desired price. This is effectively a Stop Limit order, they just charge you more commission for it. Unfortunately for me, Oanda’s paper-trade/demo accounts do not support GSLO, and I have not been able to verify if US accounts support the feature at all.
Unless you’re trading penny stocks, slippage on TDA is really only an issue with Options trading. In the web interfaces you cannot toggle the price trigger for an order between the Bid/Mark/Ask/Last Trade prices, but in the API you can. I strongly recommend all trades are placed via API and tracking the Mark price, especially with Options, because often times during the first and last 15–60 minutes of the trading day, the value of a contract can slip a lot due to movements in the underlying spot price, without trades occurring. Since the default price trigger for options is the Last Trade price, this means you will not exit a position if the underlying price starts to move quickly against you, ruining your risk management and potentially resulting in a 100% loss on the position.
TDA has another couple of quirks that are not related to slippage or bid/ask spread data. My strategy involves a lot of programmatic cancellation and replacement of stop-loss orders instead of using on-the-broker trailing stops. This is for a variety of reasons, but the main one is because unlike Oanda, TDA will not let you sell-through a stop-loss. If, for example, I have a position and stop-loss of 10 shares and want to take profit on 2 of them, I must first cancel/replace the stop-loss order to 8 shares, and then I can take my 2 shares of profit.
Because of this, 2 things can occur:
- sometimes TDA just doesn’t cancel the order, but reflects as if it is. When a position fully exits, this orphaned order gets left as a stop, but as a buy-to-open or sell-to-open order instead of trying to close a position. The highlighted orders in the screenshot above were never placed by my system, and so occasionally I have to go in and clear these out.
- TDA appears to only return the last 100 orders in the system, period. This means if you have open stop-loss orders, and then 100 new orders come and go, you stop seeing some of the older, still open and in-effect orders, when you query the account details for open orders. To handle this I had to build a whole caching layer in Redis to track every single order I ever open, which is how I know I never opened the orders in the bullet #1, and also how I keep track of what stops I actually have in effect. This caching practice works nicely, but it’s ridiculous I ever had to do this.
I hope some of you find this information useful. It would be lovely to have a brokerage API with reasonable rate limits, simple key+secret authentication, straightforward order persistence, sensible sell-through profit takes, minimal slippage, and amazing uptime. Unfortunately, this is the real world.
All things considered, TD Ameritrade is my preferred brokerage API for stocks and options. It helps that TDA has the best pricing and execution of API-capable brokerages. Unfortunately, they do not support trading commodities, futures, or forex on their API.
It is worth noting that since the Schwab acquisition, they are eventually migrating the whole thing, so I have some concerns about long-term support. If TDA gets worse with the migration to Schwab, I would recommend looking at Tradier or TradeStation as the next best thing.