Streaming Client
A wrapper around the TD Ameritrade Streaming API. This API is a websockets-based streaming API that provides to up-to-the-second data on market activity. Most impressively, it provides realtime data, including Level Two and time of sale data for major equities, options, and futures exchanges.
Here’s an example of how you can receive book snapshots of GOOG
(note if you
run this outside regular trading hours you may not see anything):
from tda.auth import easy_client
from tda.client import Client
from tda.streaming import StreamClient
import asyncio
import json
client = easy_client(
api_key='APIKEY',
redirect_uri='https://localhost',
token_path='/tmp/token.json')
stream_client = StreamClient(client, account_id=1234567890)
async def read_stream():
await stream_client.login()
await stream_client.quality_of_service(StreamClient.QOSLevel.EXPRESS)
def print_message(message):
print(json.dumps(message, indent=4))
# Always add handlers before subscribing because many streams start sending
# data immediately after success, and messages with no handlers are dropped.
stream_client.add_nasdaq_book_handler(print_message)
await stream_client.nasdaq_book_subs(['GOOG'])
while True:
await stream_client.handle_message()
asyncio.run(read_stream())
This API uses Python
coroutines to simplify
implementation and preserve performance. As a result, it requires Python 3.8 or
higher to use. tda.stream
will not be available on older versions of Python.
Use Overview
The example above demonstrates the end-to-end workflow for using tda.stream
.
There’s more in there than meets the eye, so let’s dive into the details.
Logging In
Before we can perform any stream operations, the client must be logged in to the stream. Unlike the HTTP client, in which every request is authenticated using a token, this client sends unauthenticated requests and instead authenticates the entire stream. As a result, this login process is distinct from the token generation step that’s used in the HTTP client.
Stream login is accomplished simply by calling StreamClient.login()
. Once
this happens successfully, all stream operations can be performed. Attemping to
perform operations that require login before this function is called raises an
exception.
- async StreamClient.login(websocket_connect_args=None)
-
- Performs initial stream setup:
Fetches streaming information from the HTTP client’s
get_user_principals()
methodInitializes the socket
Builds and sends and authentication request
Waits for response indicating login success
All stream operations are available after this method completes.
- Parameters:
websocket_connect_args –
dict
of additional arguments to pass to the websocketconnect
call. Useful for setting timeouts and other connection parameters. See the official documentation for details.
Setting Quality of Service
By default, the stream’s update frequency is set to 1000ms. The frequency can be
increased by calling the quality_of_service
function and passing an
appropriate QOSLevel
value.
- async StreamClient.quality_of_service(qos_level)
-
Specifies the frequency with which updated data should be sent to the client. If not called, the frequency will default to every second.
- Parameters:
qos_level – Quality of service level to request. See
QOSLevel
for options.
- class StreamClient.QOSLevel(value)
Quality of service levels
- EXPRESS = '0'
500ms between updates. Fastest available
- REAL_TIME = '1'
750ms between updates
- FAST = '2'
1000ms between updates. Default value.
- MODERATE = '3'
1500ms between updates
- SLOW = '4'
3000ms between updates
- DELAYED = '5'
5000ms between updates
Subscribing to Streams
These functions have names that follow the pattern SERVICE_NAME_subs
. These
functions send a request to enable streaming data for a particular data stream.
They are not thread safe, so they should only be called in series.
When subscriptions are called multiple times on the same stream, the results vary. What’s more, these results aren’t documented in the official documentation. As a result, it’s recommended not to call a subscription function more than once for any given stream.
Some services, notably Equity Charts and Futures Charts,
offer SERVICE_NAME_add
functions which can be used to add symbols to the
stream after the subscription has been created. For others, calling the
subscription methods again seems to clear the old subscription and create a new
one. Note this behavior is not officially documented, so this interpretation may
be incorrect.
Un-Subscribing to Streams
These functions have names that follow the pattern SERVICE_NAME_unsubs
. These
functions send a request to disable the symbols of a streaming data for a particular data stream.
They are not thread safe, so they should only be called in series.
When unsubscribing to services with symbols, the service behaves such that the initial set of subscribe symbols minus the set of unsubscribe symbols – as a result you get set difference and the set of symbols that were subscribe but is not in unsubscribe will continue to publish to your handler.
It’s known but not confirmed that if you unsubscribe from all streams, TD will close your stream if there are no other
activities in 20 seconds. Please refer to this comment : https://github.com/alexgolec/tda-api/pull/256#issuecomment-939194648
for Case: When no services are subscribed for 20 seconds - verify the last 2 messages from td
Registering Handlers
By themselves, the subscription functions outlined above do nothing except cause
messages to be sent to the client. The add_SERVICE_NAME_handler
functions
register functions that will receive these messages when they arrive. When
messages arrive, these handlers will be called serially. There is no limit to
the number of handlers that can be registered to a service.
Handling Messages
Once the stream client is properly logged in, subscribed to streams, and has
handlers registered, we can start handling messages. This is done simply by
awaiting on the handle_message()
function. This function reads a single
message and dispatches it to the appropriate handler or handlers.
If a message is received for which no handler is registered, that message is ignored.
Handlers should take a single argument representing the stream message received:
import json
def sample_handler(msg):
print(json.dumps(msg, indent=4))
Data Field Relabeling
Under the hood, this API returns JSON objects with numerical key representing labels:
{
"service": "CHART_EQUITY",
"timestamp": 1590597641293,
"command": "SUBS",
"content": [
{
"seq": 985,
"key": "MSFT",
"1": 179.445,
"2": 179.57,
"3": 179.4299,
"4": 179.52,
"5": 53742.0,
"6": 339,
"7": 1590597540000,
"8": 18409
},
]
}
These labels are tricky to decode, and require a knowledge of the documentation
to decode properly. tda-api
makes your life easier by doing this decoding
for you, replacing numerical labels with strings pulled from the documentation.
For instance, the message above would be relabeled as:
{
"service": "CHART_EQUITY",
"timestamp": 1590597641293,
"command": "SUBS",
"content": [
{
"seq": 985,
"key": "MSFT",
"OPEN_PRICE": 179.445,
"HIGH_PRICE": 179.57,
"LOW_PRICE": 179.4299,
"CLOSE_PRICE": 179.52,
"VOLUME": 53742.0,
"SEQUENCE": 339,
"CHART_TIME": 1590597540000,
"CHART_DAY": 18409
},
]
}
This documentation describes the various fields and their numerical values. You
can find them by investigating the various enum classes ending in ***Fields
.
Some streams, such as the ones described in Level One Quotes, allow you to
specify a subset of fields to be returned. Subscription handlers for these
services take a list of the appropriate field enums the extra fields
parameter. If nothing is passed to this parameter, all supported fields are
requested.
Interpreting Sequence Numbers
Many endpoints include a seq
parameter in their data contents. The official
documentation is unclear on the interpretation of this value: the time of sale
documentation states that messages containing already-observed values of seq
can be ignored, but other streams contain this field both in their metadata and
in their content, and yet their documentation doesn’t mention ignoring any
seq
values.
This presents a design choice: should tda-api
ignore duplicate seq
values on users’ behalf? Given the ambiguity of the documentation, it was
decided to not ignore them and instead pass them to all handlers. Clients
are encouraged to use their judgment in handling these values.
Unimplemented Streams
This document lists the streams supported by tda-api
. Eagle-eyed readers may
notice that some streams are described in the documentation but were not
implemented. This is due to complexity or anticipated lack of interest. If you
feel you’d like a stream added, please file an issue
here or see the
contributing guidelines to learn how to add the functionality yourself.
Enabling Real-Time Data Access
By default, TD Ameritrade delivers delayed quotes. However, as of this writing, real time streaming is available for all streams, including quotes and level two depth of book data. It is also available for free, which in the author’s opinion is an impressive feature for a retail brokerage. For most users it’s enough to sign the relevant exchange agreements and then subscribe to the relevant streams, although your mileage may vary.
Please remember that your use of this API is subject to agreeing to TDAmeritrade’s terms of service. Please don’t reach out to us asking for help enabling real-time data. Answers to most questions are a Google search away.
OHLCV Charts
These streams summarize trading activity on a minute-by-minute basis for equities and futures, providing OHLCV (Open/High/Low/Close/Volume) data.
Equity Charts
Minute-by-minute OHLCV data for equities.
- async StreamClient.chart_equity_subs(symbols)
-
Subscribe to equity charts. Behavior is undefined if called multiple times.
- Parameters:
symbols – Equity symbols to subscribe to.
- async StreamClient.chart_equity_unsubs(symbols)
-
Un-Subscribe to equity charts. Behavior is undefined if called multiple times.
- Parameters:
symbols – Equity symbols to subscribe to.
- async StreamClient.chart_equity_add(symbols)
-
Add a symbol to the equity charts subscription. Behavior is undefined if called before
chart_equity_subs()
.- Parameters:
symbols – Equity symbols to add to the subscription.
- StreamClient.add_chart_equity_handler(handler)
Adds a handler to the equity chart subscription. See Handling Messages for details.
- class StreamClient.ChartEquityFields(value)
-
Data fields for equity OHLCV data. Primarily an implementation detail and not used in client code. Provided here as documentation for key values stored returned in the stream messages.
- SYMBOL = 0
Ticker symbol in upper case. Represented in the stream as the
key
field.
- OPEN_PRICE = 1
Opening price for the minute
- HIGH_PRICE = 2
Highest price for the minute
- LOW_PRICE = 3
Chart’s lowest price for the minute
- CLOSE_PRICE = 4
Closing price for the minute
- VOLUME = 5
Total volume for the minute
- SEQUENCE = 6
Identifies the candle minute. Explicitly labeled “not useful” in the official documentation.
- CHART_TIME = 7
Milliseconds since Epoch
- CHART_DAY = 8
Documented as not useful, included for completeness
Futures Charts
Minute-by-minute OHLCV data for futures.
- async StreamClient.chart_futures_subs(symbols)
-
Subscribe to futures charts. Behavior is undefined if called multiple times.
- Parameters:
symbols – Futures symbols to subscribe to.
- async StreamClient.chart_futures_unsubs(symbols)
-
Un-Subscribe to futures charts. Behavior is undefined if called multiple times.
- Parameters:
symbols – Futures symbols to subscribe to.
- async StreamClient.chart_futures_add(symbols)
-
Add a symbol to the futures chart subscription. Behavior is undefined if called before
chart_futures_subs()
.- Parameters:
symbols – Futures symbols to add to the subscription.
- StreamClient.add_chart_futures_handler(handler)
Adds a handler to the futures chart subscription. See Handling Messages for details.
- class StreamClient.ChartFuturesFields(value)
-
Data fields for equity OHLCV data. Primarily an implementation detail and not used in client code. Provided here as documentation for key values stored returned in the stream messages.
- SYMBOL = 0
Ticker symbol in upper case. Represented in the stream as the
key
field.
- CHART_TIME = 1
Milliseconds since Epoch
- OPEN_PRICE = 2
Opening price for the minute
- HIGH_PRICE = 3
Highest price for the minute
- LOW_PRICE = 4
Chart’s lowest price for the minute
- CLOSE_PRICE = 5
Closing price for the minute
- VOLUME = 6
Total volume for the minute
Level One Quotes
Level one quotes provide an up-to-date view of bid/ask/volume data. In particular they list the best available bid and ask prices, together with the requested volume of each. They are updated live as market conditions change.
Equities Quotes
Level one quotes for equities traded on NYSE, AMEX, and PACIFIC.
- async StreamClient.level_one_equity_subs(symbols, *, fields=None)
-
Subscribe to level one equity quote data.
- Parameters:
symbols – Equity symbols to receive quotes for
fields – Iterable of
LevelOneEquityFields
representing the fields to return in streaming entries. If unset, all fields will be requested.
- async StreamClient.level_one_equity_unsubs(symbols)
-
Un-Subscribe to level one equity quote data.
- Parameters:
symbols – Equity symbols to receive quotes for
- StreamClient.add_level_one_equity_handler(handler)
Register a function to handle level one equity quotes as they are sent. See Handling Messages for details.
- class StreamClient.LevelOneEquityFields(value)
-
Fields for equity quotes.
- SYMBOL = 0
Ticker symbol in upper case. Represented in the stream as the
key
field.
- BID_PRICE = 1
Current Best Bid Price
- ASK_PRICE = 2
Current Best Ask Price
- LAST_PRICE = 3
Price at which the last trade was matched
- BID_SIZE = 4
Number of shares for bid
- ASK_SIZE = 5
Number of shares for ask
- ASK_ID = 6
Exchange with the best ask
- BID_ID = 7
Exchange with the best bid
- TOTAL_VOLUME = 8
Aggregated shares traded throughout the day, including pre/post market hours. Note volume is set to zero at 7:28am ET.
- LAST_SIZE = 9
Number of shares traded with last trade, in 100’s
- TRADE_TIME = 10
Trade time of the last trade, in seconds since midnight EST
- QUOTE_TIME = 11
Trade time of the last quote, in seconds since midnight EST
- HIGH_PRICE = 12
Day’s high trade price. Notes:
According to industry standard, only regular session trades set the High and Low.
If a stock does not trade in the AM session, high and low will be zero.
High/low reset to 0 at 7:28am ET
- LOW_PRICE = 13
Day’s low trade price. Same notes as
HIGH_PRICE
.
- BID_TICK = 14
Indicates Up or Downtick (NASDAQ NMS & Small Cap). Updates whenever bid updates.
- CLOSE_PRICE = 15
Previous day’s closing price. Notes:
Closing prices are updated from the DB when Pre-Market tasks are run by TD Ameritrade at 7:29AM ET.
As long as the symbol is valid, this data is always present.
This field is updated every time the closing prices are loaded from DB
- EXCHANGE_ID = 16
Primary “listing” Exchange.
- MARGINABLE = 17
Stock approved by the Federal Reserve and an investor’s broker as being suitable for providing collateral for margin debt?
- SHORTABLE = 18
Stock can be sold short?
- ISLAND_BID_DEPRECATED = 19
Deprecated, documented for completeness.
- ISLAND_ASK_DEPRECATED = 20
Deprecated, documented for completeness.
- ISLAND_VOLUME_DEPRECATED = 21
Deprecated, documented for completeness.
- QUOTE_DAY = 22
Day of the quote
- TRADE_DAY = 23
Day of the trade
- VOLATILITY = 24
Option Risk/Volatility Measurement. Notes:
Volatility is reset to 0 when Pre-Market tasks are run at 7:28 AM ET
Once per day descriptions are loaded from the database when Pre-Market tasks are run at 7:29:50 AM ET.
- DESCRIPTION = 25
A company, index or fund name
- LAST_ID = 26
Exchange where last trade was executed
- DIGITS = 27
Valid decimal points. 4 digits for AMEX, NASDAQ, OTCBB, and PINKS, 2 for others.
- OPEN_PRICE = 28
Day’s Open Price. Notes:
Open is set to ZERO when Pre-Market tasks are run at 7:28.
If a stock doesn’t trade the whole day, then the open price is 0.
In the AM session, Open is blank because the AM session trades do not set the open.
- NET_CHANGE = 29
Current Last-Prev Close
- HIGH_52_WEEK = 30
Highest price traded in the past 12 months, or 52 weeks
- LOW_52_WEEK = 31
Lowest price traded in the past 12 months, or 52 weeks
- PE_RATIO = 32
Price to earnings ratio
- DIVIDEND_AMOUNT = 33
Dividen earnings Per Share
- DIVIDEND_YIELD = 34
Dividend Yield
- ISLAND_BID_SIZE_DEPRECATED = 35
Deprecated, documented for completeness.
- ISLAND_ASK_SIZE_DEPRECATED = 36
Deprecated, documented for completeness.
- NAV = 37
Mutual Fund Net Asset Value
- FUND_PRICE = 38
Mutual fund price
- EXCHANGE_NAME = 39
Display name of exchange
- DIVIDEND_DATE = 40
Dividend date
- IS_REGULAR_MARKET_QUOTE = 41
Is last quote a regular quote
- IS_REGULAR_MARKET_TRADE = 42
Is last trade a regular trade
- REGULAR_MARKET_LAST_PRICE = 43
Last price, only used when
IS_REGULAR_MARKET_TRADE
isTrue
- REGULAR_MARKET_LAST_SIZE = 44
Last trade size, only used when
IS_REGULAR_MARKET_TRADE
isTrue
- REGULAR_MARKET_TRADE_TIME = 45
Last trade time, only used when
IS_REGULAR_MARKET_TRADE
isTrue
- REGULAR_MARKET_TRADE_DAY = 46
Last trade date, only used when
IS_REGULAR_MARKET_TRADE
isTrue
- REGULAR_MARKET_NET_CHANGE = 47
REGULAR_MARKET_LAST_PRICE
minusCLOSE_PRICE
- SECURITY_STATUS = 48
Indicates a symbols current trading status, Normal, Halted, Closed
- MARK = 49
Mark Price
- QUOTE_TIME_IN_LONG = 50
Last quote time in milliseconds since Epoch
- TRADE_TIME_IN_LONG = 51
Last trade time in milliseconds since Epoch
- REGULAR_MARKET_TRADE_TIME_IN_LONG = 52
Regular market trade time in milliseconds since Epoch
Options Quotes
Level one quotes for options. Note you can use
Client.get_option_chain()
to fetch
available option symbols.
- async StreamClient.level_one_option_subs(symbols, *, fields=None)
-
Subscribe to level one option quote data.
- Parameters:
symbols – Option symbols to receive quotes for
fields – Iterable of
LevelOneOptionFields
representing the fields to return in streaming entries. If unset, all fields will be requested.
- async StreamClient.level_one_option_unsubs(symbols)
-
Un-Subscribe to level one option quote data.
- Parameters:
symbols – Option symbols to receive quotes for
- StreamClient.add_level_one_option_handler(handler)
Register a function to handle level one options quotes as they are sent. See Handling Messages for details.
- class StreamClient.LevelOneOptionFields(value)
-
- SYMBOL = 0
Ticker symbol in upper case. Represented in the stream as the
key
field.
- DESCRIPTION = 1
A company, index or fund name
- BID_PRICE = 2
Current Best Bid Price
- ASK_PRICE = 3
Current Best Ask Price
- LAST_PRICE = 4
Price at which the last trade was matched
- HIGH_PRICE = 5
Day’s high trade price. Notes:
According to industry standard, only regular session trades set the High and Low.
If an option does not trade in the AM session, high and low will be zero.
High/low reset to 0 at 7:28am ET.
- LOW_PRICE = 6
Day’s low trade price. Same notes as
HIGH_PRICE
.
- CLOSE_PRICE = 7
Previous day’s closing price. Closing prices are updated from the DB when Pre-Market tasks are run at 7:29AM ET.
- TOTAL_VOLUME = 8
Aggregated shares traded throughout the day, including pre/post market hours. Reset to zero at 7:28am ET.
- OPEN_INTEREST = 9
Open interest
- VOLATILITY = 10
Option Risk/Volatility Measurement. Volatility is reset to 0 when Pre-Market tasks are run at 7:28 AM ET.
- QUOTE_TIME = 11
Trade time of the last quote in seconds since midnight EST
- TRADE_TIME = 12
Trade time of the last quote in seconds since midnight EST
- MONEY_INTRINSIC_VALUE = 13
Money intrinsic value
- QUOTE_DAY = 14
Day of the quote
- TRADE_DAY = 15
Day of the trade
- EXPIRATION_YEAR = 16
Option expiration year
- MULTIPLIER = 17
Option multiplier
- DIGITS = 18
Valid decimal points. 4 digits for AMEX, NASDAQ, OTCBB, and PINKS, 2 for others.
- OPEN_PRICE = 19
Day’s Open Price. Notes:
Open is set to ZERO when Pre-Market tasks are run at 7:28.
If a stock doesn’t trade the whole day, then the open price is 0.
In the AM session, Open is blank because the AM session trades do not set the open.
- BID_SIZE = 20
Number of shares for bid
- ASK_SIZE = 21
Number of shares for ask
- LAST_SIZE = 22
Number of shares traded with last trade, in 100’s
- NET_CHANGE = 23
Current Last-Prev Close
- STRIKE_PRICE = 24
- CONTRACT_TYPE = 25
- UNDERLYING = 26
- EXPIRATION_MONTH = 27
- DELIVERABLES = 28
- TIME_VALUE = 29
- EXPIRATION_DAY = 30
- DAYS_TO_EXPIRATION = 31
- DELTA = 32
- GAMMA = 33
- THETA = 34
- VEGA = 35
- RHO = 36
- SECURITY_STATUS = 37
Indicates a symbols current trading status, Normal, Halted, Closed
- THEORETICAL_OPTION_VALUE = 38
- UNDERLYING_PRICE = 39
- UV_EXPIRATION_TYPE = 40
- MARK = 41
Mark Price
Futures Quotes
Level one quotes for futures.
- async StreamClient.level_one_futures_subs(symbols, *, fields=None)
-
Subscribe to level one futures quote data.
- Parameters:
symbols – Futures symbols to receive quotes for
fields – Iterable of
LevelOneFuturesFields
representing the fields to return in streaming entries. If unset, all fields will be requested.
- async StreamClient.level_one_futures_unsubs(symbols)
-
Un-Subscribe to level one futures quote data.
- Parameters:
symbols – Futures symbols to receive quotes for
- StreamClient.add_level_one_futures_handler(handler)
Register a function to handle level one futures quotes as they are sent. See Handling Messages for details.
- class StreamClient.LevelOneFuturesFields(value)
-
- SYMBOL = 0
Ticker symbol in upper case. Represented in the stream as the
key
field.
- BID_PRICE = 1
Current Best Bid Price
- ASK_PRICE = 2
Current Best Ask Price
- LAST_PRICE = 3
Price at which the last trade was matched
- BID_SIZE = 4
Number of shares for bid
- ASK_SIZE = 5
Number of shares for ask
- ASK_ID = 6
Exchange with the best ask
- BID_ID = 7
Exchange with the best bid
- TOTAL_VOLUME = 8
Aggregated shares traded throughout the day, including pre/post market hours
- LAST_SIZE = 9
Number of shares traded with last trade
- QUOTE_TIME = 10
Trade time of the last quote in milliseconds since epoch
- TRADE_TIME = 11
Trade time of the last trade in milliseconds since epoch
- HIGH_PRICE = 12
Day’s high trade price
- LOW_PRICE = 13
Day’s low trade price
- CLOSE_PRICE = 14
Previous day’s closing price
- EXCHANGE_ID = 15
Primary “listing” Exchange. Notes: * I → ICE * E → CME * L → LIFFEUS
- DESCRIPTION = 16
Description of the product
- LAST_ID = 17
Exchange where last trade was executed
- OPEN_PRICE = 18
Day’s Open Price
- NET_CHANGE = 19
Current Last-Prev Close
- FUTURE_PERCENT_CHANGE = 20
Current percent change
- EXCHANGE_NAME = 21
Name of exchange
- SECURITY_STATUS = 22
Trading status of the symbol. Indicates a symbol’s current trading status, Normal, Halted, Closed.
- OPEN_INTEREST = 23
The total number of futures ontracts that are not closed or delivered on a particular day
- MARK = 24
Mark-to-Market value is calculated daily using current prices to determine profit/loss
- TICK = 25
Minimum price movement
- TICK_AMOUNT = 26
Minimum amount that the price of the market can change
- PRODUCT = 27
Futures product
- FUTURE_PRICE_FORMAT = 28
Display in fraction or decimal format.
- FUTURE_TRADING_HOURS = 29
Trading hours. Notes:
days: 0 = monday-friday, 1 = sunday.
7 = Saturday
0 = [-2000,1700] ==> open, close
1 = [-1530,-1630,-1700,1515] ==> open, close, open, close
0 = [-1800,1700,d,-1700,1900] ==> open, close, DST-flag, open, close
If the DST-flag is present, the following hours are for DST days: http://www.cmegroup.com/trading_hours/
- FUTURE_IS_TRADEABLE = 30
Flag to indicate if this future contract is tradable
- FUTURE_MULTIPLIER = 31
Point value
- FUTURE_IS_ACTIVE = 32
Indicates if this contract is active
- FUTURE_SETTLEMENT_PRICE = 33
Closing price
- FUTURE_ACTIVE_SYMBOL = 34
Symbol of the active contract
- FUTURE_EXPIRATION_DATE = 35
Expiration date of this contract in milliseconds since epoch
Forex Quotes
Level one quotes for foreign exchange pairs.
- async StreamClient.level_one_forex_subs(symbols, *, fields=None)
-
Subscribe to level one forex quote data.
- Parameters:
symbols – Forex symbols to receive quotes for
fields – Iterable of
LevelOneForexFields
representing the fields to return in streaming entries. If unset, all fields will be requested.
- async StreamClient.level_one_forex_unsubs(symbols)
-
Un-Subscribe to level one forex quote data.
- Parameters:
symbols – Forex symbols to receive quotes for
- StreamClient.add_level_one_forex_handler(handler)
Register a function to handle level one forex quotes as they are sent. See Handling Messages for details.
- class StreamClient.LevelOneForexFields(value)
-
- SYMBOL = 0
Ticker symbol in upper case. Represented in the stream as the
key
field.
- BID_PRICE = 1
Current Best Bid Price
- ASK_PRICE = 2
Current Best Ask Price
- LAST_PRICE = 3
Price at which the last trade was matched
- BID_SIZE = 4
Number of shares for bid
- ASK_SIZE = 5
Number of shares for ask
- TOTAL_VOLUME = 6
Aggregated shares traded throughout the day, including pre/post market hours
- LAST_SIZE = 7
Number of shares traded with last trade
- QUOTE_TIME = 8
Trade time of the last quote in milliseconds since epoch
- TRADE_TIME = 9
Trade time of the last trade in milliseconds since epoch
- HIGH_PRICE = 10
Day’s high trade price
- LOW_PRICE = 11
Day’s low trade price
- CLOSE_PRICE = 12
Previous day’s closing price
- EXCHANGE_ID = 13
Primary “listing” Exchange
- DESCRIPTION = 14
Description of the product
- OPEN_PRICE = 15
Day’s Open Price
- NET_CHANGE = 16
Current Last-Prev Close
- EXCHANGE_NAME = 18
Name of exchange
- DIGITS = 19
Valid decimal points
- SECURITY_STATUS = 20
Trading status of the symbol. Indicates a symbols current trading status, Normal, Halted, Closed.
- TICK = 21
Minimum price movement
- TICK_AMOUNT = 22
Minimum amount that the price of the market can change
- PRODUCT = 23
Product name
- TRADING_HOURS = 24
Trading hours
- IS_TRADABLE = 25
Flag to indicate if this forex is tradable
- MARKET_MAKER = 26
- HIGH_52_WEEK = 27
Higest price traded in the past 12 months, or 52 weeks
- LOW_52_WEEK = 28
Lowest price traded in the past 12 months, or 52 weeks
- MARK = 29
Mark-to-Market value is calculated daily using current prices to determine profit/loss
Futures Options Quotes
Level one quotes for futures options.
- async StreamClient.level_one_futures_options_subs(symbols, *, fields=None)
-
Subscribe to level one futures options quote data.
- Parameters:
symbols – Futures options symbols to receive quotes for
fields – Iterable of
LevelOneFuturesOptionsFields
representing the fields to return in streaming entries. If unset, all fields will be requested.
- async StreamClient.level_one_futures_options_unsubs(symbols)
-
Un-Subscribe to level one futures options quote data.
- Parameters:
symbols – Futures options symbols to receive quotes for
- StreamClient.add_level_one_futures_options_handler(handler)
Register a function to handle level one futures options quotes as they are sent. See Handling Messages for details.
- class StreamClient.LevelOneFuturesOptionsFields(value)
-
- SYMBOL = 0
Ticker symbol in upper case. Represented in the stream as the
key
field.
- BID_PRICE = 1
Current Best Bid Price
- ASK_PRICE = 2
Current Best Ask Price
- LAST_PRICE = 3
Price at which the last trade was matched
- BID_SIZE = 4
Number of shares for bid
- ASK_SIZE = 5
Number of shares for ask
- ASK_ID = 6
Exchange with the best ask
- BID_ID = 7
Exchange with the best bid
- TOTAL_VOLUME = 8
Aggregated shares traded throughout the day, including pre/post market hours
- LAST_SIZE = 9
Number of shares traded with last trade
- QUOTE_TIME = 10
Trade time of the last quote in milliseconds since epoch
- TRADE_TIME = 11
Trade time of the last trade in milliseconds since epoch
- HIGH_PRICE = 12
Day’s high trade price
- LOW_PRICE = 13
Day’s low trade price
- CLOSE_PRICE = 14
Previous day’s closing price
- EXCHANGE_ID = 15
Primary “listing” Exchange. Notes: * I → ICE * E → CME * L → LIFFEUS
- DESCRIPTION = 16
Description of the product
- LAST_ID = 17
Exchange where last trade was executed
- OPEN_PRICE = 18
Day’s Open Price
- NET_CHANGE = 19
Current Last-Prev Close
- FUTURE_PERCENT_CHANGE = 20
Current percent change
- EXCHANGE_NAME = 21
Name of exchange
- SECURITY_STATUS = 22
Trading status of the symbol. Indicates a symbols current trading status, Normal, Halted, Closed.
- OPEN_INTEREST = 23
The total number of futures ontracts that are not closed or delivered on a particular day
- MARK = 24
Mark-to-Market value is calculated daily using current prices to determine profit/loss
- TICK = 25
Minimum price movement
- TICK_AMOUNT = 26
Minimum amount that the price of the market can change
- PRODUCT = 27
Futures product
- FUTURE_PRICE_FORMAT = 28
Display in fraction or decimal format
- FUTURE_TRADING_HOURS = 29
Trading hours
- FUTURE_IS_TRADEABLE = 30
Flag to indicate if this future contract is tradable
- FUTURE_MULTIPLIER = 31
Point value
- FUTURE_IS_ACTIVE = 32
Indicates if this contract is active
- FUTURE_SETTLEMENT_PRICE = 33
Closing price
- FUTURE_ACTIVE_SYMBOL = 34
Symbol of the active contract
- FUTURE_EXPIRATION_DATE = 35
Expiration date of this contract, in milliseconds since epoch
Level Two Order Book
Level two streams provide a view on continuous order books of various securities. The level two order book describes the current bids and asks on the market, and these streams provide snapshots of that state.
Due to the lack of official documentation, these streams are largely reverse engineered. While the labeled data represents a best effort attempt to interpret stream fields, it’s possible that something is wrong or incorrectly labeled.
The documentation lists more book types than are implemented here. In
particular, it also lists FOREX_BOOK
, FUTURES_BOOK
, and
FUTURES_OPTIONS_BOOK
as accessible streams. All experimentation has resulted
in these streams refusing to connect, typically returning errors about
unavailable services. Due to this behavior and the lack of official
documentation for book streams generally, tda-api
assumes these streams are not
actually implemented, and so excludes them. If you have any insight into using
them, please
let us know.
Equities Order Books: NYSE and NASDAQ
tda-api
supports level two data for NYSE and NASDAQ, which are the two major
exchanges dealing in equities, ETFs, etc. Stocks are typically listed on one or
the other, and it is useful to learn about the differences between them:
You can identify on which exchange a symbol is listed by using
Client.search_instruments()
:
r = c.search_instruments(['GOOG'], projection=c.Instrument.Projection.FUNDAMENTAL)
assert r.status_code == httpx.codes.OK, r.raise_for_status()
print(r.json()['GOOG']['exchange']) # Outputs NASDAQ
However, many symbols have order books available on these streams even though this API call returns neither NYSE nor NASDAQ. The only sure-fire way to find out whether the order book is available is to attempt to subscribe and see what happens.
Note to preserve equivalence with what little documentation there is, the NYSE book is called “listed.” Testing indicates this stream corresponds to the NYSE book, but if you find any behavior that suggests otherwise please let us know.
- async StreamClient.listed_book_subs(symbols)
Subscribe to the NYSE level two order book. Note this stream has no official documentation.
- async StreamClient.listed_book_unsubs(symbols)
Un-Subscribe to the NYSE level two order book. Note this stream has no official documentation.
- StreamClient.add_listed_book_handler(handler)
Register a function to handle level two NYSE book data as it is updated See Handling Messages for details.
- async StreamClient.nasdaq_book_subs(symbols)
Subscribe to the NASDAQ level two order book. Note this stream has no official documentation.
- async StreamClient.nasdaq_book_unsubs(symbols)
Un-Subscribe to the NASDAQ level two order book. Note this stream has no official documentation.
- StreamClient.add_nasdaq_book_handler(handler)
Register a function to handle level two NASDAQ book data as it is updated See Handling Messages for details.
Options Order Book
This stream provides the order book for options. It’s not entirely clear what exchange it aggregates from, but it’s been tested to work and deliver data. The leading hypothesis is that it is the order book for the Chicago Board of Exchange options exchanges, although this is an admittedly an uneducated guess.
- async StreamClient.options_book_subs(symbols)
Subscribe to the level two order book for options. Note this stream has no official documentation, and it’s not entirely clear what exchange it corresponds to. Use at your own risk.
- async StreamClient.options_book_unsubs(symbols)
Un-Subscribe to the level two order book for options. Note this stream has no official documentation, and it’s not entirely clear what exchange it corresponds to. Use at your own risk.
- StreamClient.add_options_book_handler(handler)
Register a function to handle level two options book data as it is updated See Handling Messages for details.
Time of Sale
The data in Level Two Order Book describes the bids and asks for various instruments, but by itself is insufficient to determine when trades actually take place. The time of sale streams notify on trades as they happen. Together with the level two data, they provide a fairly complete picture of what is happening on an exchange.
All time of sale streams uss a common set of fields:
- class StreamClient.TimesaleFields(value)
-
- SYMBOL = 0
Ticker symbol in upper case. Represented in the stream as the
key
field.
- TRADE_TIME = 1
Trade time of the last trade in milliseconds since epoch
- LAST_PRICE = 2
Price at which the last trade was matched
- LAST_SIZE = 3
Number of shares traded with last trade
- LAST_SEQUENCE = 4
Number of shares for bid
Equity Trades
- async StreamClient.timesale_equity_subs(symbols, *, fields=None)
-
Subscribe to time of sale notifications for equities.
- Parameters:
symbols – Equity symbols to subscribe to
- async StreamClient.timesale_equity_unsubs(symbols)
-
Un-Subscribe to time of sale notifications for equities.
- Parameters:
symbols – Equity symbols to subscribe to
- StreamClient.add_timesale_equity_handler(handler)
Register a function to handle equity trade notifications as they happen See Handling Messages for details.
Futures Trades
- async StreamClient.timesale_futures_subs(symbols, *, fields=None)
-
Subscribe to time of sale notifications for futures.
- Parameters:
symbols – Futures symbols to subscribe to
- async StreamClient.timesale_futures_unsubs(symbols)
-
Un-Subscribe to time of sale notifications for futures.
- Parameters:
symbols – Futures symbols to subscribe to
- StreamClient.add_timesale_futures_handler(handler)
Register a function to handle futures trade notifications as they happen See Handling Messages for details.
Options Trades
This stream is defined and will connect, however it appears that it does not provide data. Connecting to it results in heartbeat messages that indicate that the stream is open, but to date we haven’t seen any data be passed through. We currently believe this is an issue on TDA’s side.
- async StreamClient.timesale_options_subs(symbols, *, fields=None)
-
Subscribe to time of sale notifications for options.
- Parameters:
symbols – Options symbols to subscribe to
- async StreamClient.timesale_options_unsubs(symbols)
-
Un-Subscribe to time of sale notifications for options.
- Parameters:
symbols – Options symbols to subscribe to
- StreamClient.add_timesale_options_handler(handler)
Register a function to handle options trade notifications as they happen See Handling Messages for details.
News Headlines
This stream returns news headlines for a set of symbols. If you encounter issues activating this stream or receive an error response from TDA, please reach out to our Discord server for help.
- async StreamClient.news_headline_subs(symbols)
-
Subscribe to news headlines related to the given symbols.
- async StreamClient.news_headline_unsubs(symbols)
-
Un-Subscribe to news headlines related to the given symbols.
- StreamClient.add_news_headline_handler(handler)
Register a function to handle news headlines as they are provided. See Handling Messages for details.
- class StreamClient.NewsHeadlineFields(value)
-
- SYMBOL = 0
Ticker symbol in upper case. Represented in the stream as the
key
field.
- ERROR_CODE = 1
Specifies if there is any error
- STORY_DATETIME = 2
Headline’s datetime in milliseconds since epoch
- HEADLINE_ID = 3
Unique ID for the headline
- STATUS = 4
- HEADLINE = 5
News headline
- STORY_ID = 6
- COUNT_FOR_KEYWORD = 7
- KEYWORD_ARRAY = 8
- IS_HOT = 9
- STORY_SOURCE = 10
Account Activity
This stream allows you to monitor your account activity, including order
execution/cancellation/expiration/etc. tda-api
provide utilities for setting
up and reading the stream, but leaves the task of parsing the response XML
object
to the user.
- async StreamClient.account_activity_sub()
-
Subscribe to account activity for the account id associated with this streaming client. See
AccountActivityFields
for more info.
- async StreamClient.account_activity_unsubs()
-
Un-Subscribe to account activity for the account id associated with this streaming client. See
AccountActivityFields
for more info.
- StreamClient.add_account_activity_handler(handler)
Adds a handler to the account activity subscription. See Handling Messages for details.
- class StreamClient.AccountActivityFields(value)
-
Data fields for equity account activity. Primarily an implementation detail and not used in client code. Provided here as documentation for key values stored returned in the stream messages.
- SUBSCRIPTION_KEY = 0
Subscription key. Represented in the stream as the
key
field.
- ACCOUNT = 1
Account # subscribed
- MESSAGE_TYPE = 2
Refer to the message type table in the official documentation
- MESSAGE_DATA = 3
The core data for the message. Either XML Message data describing the update,
NULL
in some cases, or plain text in case ofERROR
.
Troubleshooting
There are a number of issues you might encounter when using the streaming client. This section attempts to provide a non-authoritative listing of the issues you may encounter when using this client.
Unfortunately, use of the streaming client by non-TDAmeritrade apps is poorly documented and apparently completely unsupported. This section attempts to provide a non-authoritative listing of the issues you may encounter, but please note that these are best effort explanations resulting from reverse engineering and crowdsourced experience. Take them with a grain of salt.
If you have specific questions, please join our Discord server to discuss with the community.
ConnectionClosedOK: code = 1000 (OK), no reason
Immediately on Stream Start
There are a few known causes for this issue:
Streaming Account ID Doesn’t Match Token Account
TDA allows you to link multiple accounts together, so that logging in to one
main account allows you to have access to data from all other linked accounts.
This is not a problem for the HTTP client, but the streaming client is a little
more restrictive. In particular, it appears that opening a StreamClient
with
an account ID that is different from the account ID corresponding to the username
that was used to create the token is disallowed.
If you’re encountering this issue, make sure you are using the account ID of the account which was used during token login. If you’re unsure which account was used to create the token, delete your token and create a new one, taking note of the account ID.
Multiple Concurrent Streams
TDA allows only one open stream per account ID. If you open a second one, it
will immediately close itself with this error. This is not a limitation of
tda-api
, this is a TDAmeritrade limitation. If you want to use multiple
streams, you need to have multiple accounts, create a separate token under each,
and pass each one’s account ID into its own client.
ConnectionClosedError: code = 1006 (connection closed abnormally [internal])
TDA has the right to kill the connection at any time for any reason, and this
error appears to be a catchall for these sorts of failures. If you are
encountering this error, it is almost certainly not the fault of the
tda-api
library, but rather either an internal failure on TDA’s side or a
failure in the logic of your own code.
That being said, there have been a number of situations where this error was encountered, and this section attempts to record the resolution of these failures.
Your Handler is Too Slow
tda-api
cannot perform websocket message acknowledgement when your handler
code is running. As a result, if your handler code takes longer than the stream
update frequency, a backlog of unacknowledged messages will develop. TDA has
been observed to terminate connections when many messages are unacknowledged.
Fixing this is a task for the application developer: if you are writing to a database or filesystem as part of your handler, consider profiling it to make the write faster. You may also consider deferring your writes so that slow operations don’t happen in the hotpath of the message handler.
JSONDecodeError
This is an error that is most often raised when TDA sends an invalid JSON string. See Custom JSON Decoding for details.
For reasons known only to TDAmeritrade’s development team, the API occasionally
emits invalid stream messages for some endpoints. Because this issue does not
affect all endpoints, and because tda-api
’s authors are not in the business
of handling quirks of an API they don’t control, the library simply passes these
errors up to the user.
However, some applications cannot handle complete failure. What’s more, some users have insight into how to work around these decoder errors. The streaming client supports setting a custom JSON decoder to help with this:
- StreamClient.set_json_decoder(json_decoder)
Sets a custom JSON decoder.
- Parameters:
json_decoder – Custom JSON decoder to use for to decode all incoming JSON strings. See
StreamJsonDecoder
for details.
Users are free to implement their own JSON decoders by subclassing the following abstract base class:
- class tda.streaming.StreamJsonDecoder
- abstract decode_json_string(raw)
Parse a JSON-formatted string into a proper object. Raises
JSONDecodeError
on parse failure.
Users looking for an out-of-the-box solution can consider using the community-maintained decoder described in Custom JSON Decoding. Note that while this decoder is constantly improving, it is not guaranteed to solve whatever JSON decoding errors your may be encountering.