tda-api
: An Unofficial TD Ameritrade Client¶
Getting Started¶
Welcome to tda-api
! Read this page to learn how to install and configure
your first TD Ameritrade Python application.
TD Ameritrade API Access¶
All API calls to the TD Ameritrade API require an API key. Before we do
anything with tda-api
, you’ll need to create a developer account with TD
Ameritrade and register an application. By the end of this section, you’ll have
accomplished the three prerequisites for using tda-api
:
- Create an application.
- Choose and save the callback URL (important for authenticating).
- Receive an API key.
You can create a developer account here. The instructions from here on out assume you’re logged in, so make sure you log into the developer site after you’ve created your account.
Next, you’ll want to create an application. The app name and purpose aren’t particularly important right now, but the callback URL is. In a nutshell, the OAuth login flow that TD Ameritrade uses works by opening a TD Ameritrade login page, securely collecting credentials on their domain, and then sending an HTTP request to the callback URL with the token in the URL query.
How you use to choose your callback URL depends on whether and how you
plan on distributing your app. If you’re writing an app for your own personal
use, and plan to run entirely on your own machine, use https://localhost
. If
you plan on running on a server and having users send requests to you, use a URL
you own, such as a dedicated endpoint on your domain.
Once your app is created and approved, you will receive your API key, also known as the Client ID. This will be visible in TDA’s app listing page. Record this key, since it is necessary to access most API endpoints.
Installing tda-api
¶
This section outlines the installation process for client users. For developers, check out Contributing to tda-api.
The recommended method of installing tda-api
is using pip
from
PyPi in a virtualenv. First create a virtualenv in your project
diretory. Here we assume your virtualenv is called my-venv
:
pip install virtualenv
virtualenv -v my-venv
source my-venv/bin/activate
You are now ready to install tda-api
:
pip install tda-api
That’s it! You’re done! You can verify the install succeeded by importing the package:
import tda
If this succeeded, you’re ready to move on to Authentication and Client Creation.
Authentication and Client Creation¶
By now, you should have followed the instructions in Getting Started and are ready to start making API calls. Read this page to learn how to get over the last remaining hurdle: OAuth authentication.
Before we begin, however, note that this guide is meant to users who want to run applications on their own machines, without distributing them to others. If you plan on distributing your app, or if you plan on running it on a server and allowing access to other users, this login flow is not for you.
OAuth Refresher¶
This section is purely for the curious. If you already understand OAuth (wow, congrats) or if you don’t care and just want to use this package as fast as possible, feel free to skip this section. If you encounter any weird behavior, this section may help you understand that’s going on.
Webapp authentication is a complex beast. The OAuth protocol was created to allow applications to access one anothers’ APIs securely and with the minimum level of trust possible. A full treatise on this topic is well beyond the scope of this guide, but in order to alleviate some of the confusion and complexity that seems to surround this part of the API, let’s give a quick explanation of how OAuth works in the context of TD Ameritrade’s API.
The first thing to understand is that the OAuth webapp flow was created to allow client-side applications consisting of a webapp frontend and a remotely hosted backend to interact with a third party API. Unlike the backend application flow, in which the remotely hosted backend has a secret which allows it to access the API on its own behalf, the webapp flow allows either the webapp frontend or the remotely host backend to access the API on behalf of its users.
If you’ve ever installed a GitHub, Facebook, Twitter, GMail, etc. app, you’ve seen this flow. You click on the “install” link, a login window pops up, you enter your password, and you’re presented with a page that asks whether you want to grant the app access to your account.
Here’s what’s happening under the hood. The window that pops up is the authentication URL, which opens a login page for the target API. The aim is to allow the user to input their username and password without the webapp frontend or the remotely hosted backend seeing it. On web browsers, this is accomplished using the browser’s refusal to send credentials from one domain to another.
Once login here is successful, the API replies with a redirect to a URL that the remotely hosted backend controls. This is the callback URL. This redirect will contain a code which securely identifies the user to the API, embedded in the query of the request.
You might think that code is enough to access the API, and it would be if the API author were willing to sacrifice long-term security. The exact reasons why it doesn’t work involve some deep security topics like robustness against replay attacks and session duration limitation, but we’ll skip them here.
This code is useful only for fetching a token from the authentication endpoint. This token is what we want: a secure secret which the client can use to access API endpoints, and can be refreshed over time.
If you’ve gotten this far and your head isn’t spinning, you haven’t been paying
attention. Security-sensitive protocols can be very complicated, and you should
never build your own implementation. Fortunately there exist very robust
implementations of this flow, and tda-api
’s authentication module makes
using them easy.
Fetching a Token and Creating a Client¶
tda-api
provides an easy implementation of the client-side login flow in the
auth
package. It uses a selenium webdriver to open the TD Ameritrade
authentication URL, take your login credentials, catch the post-login redirect,
and fetch a reusable token. It returns a fully-configured HTTP Client, ready
to send API calls. It also handles token refreshing, and writes updated tokens
to the token file.
These functions are webdriver-agnostic, meaning you can use whatever webdriver-supported browser you can available on your system. You can find information about available webdriver on the Selenium documentation.
-
tda.auth.
client_from_login_flow
(webdriver, api_key, redirect_url, token_path, redirect_wait_time_seconds=0.1)¶ Uses the webdriver to perform an OAuth webapp login flow and creates a client wrapped around the resulting token. The client will be configured to refresh the token as necessary, writing each updated version to
token_path
.Parameters: - webdriver – selenium webdriver which will be used to perform the login flow.
- api_key – Your TD Ameritrade application’s API key, also known as the client ID.
- redirect_url – Your TD Ameritrade application’s redirect URL. Note this must exactly match the value you’ve entered in your application configuration, otherwise login will fail with a security error.
- token_path – Path to which the new token will be written. Updated tokens will be written to this path as well.
Once you have a token written on disk, you can reuse it without going through the login flow again.
-
tda.auth.
client_from_token_file
(token_path, api_key)¶ Returns a session from the specified token path. The session will perform an auth refresh as needed. It will also update the token on disk whenever appropriate.
Parameters: - token_path – Path to the token. Updated tokens will be written to this path.
- api_key – Your TD Ameritrade application’s API key, also known as the client ID.
The following is a convenient wrapper around these two methods, calling each when appropriate:
-
tda.auth.
easy_client
(api_key, redirect_uri, token_path, webdriver_func=None)¶ Convenient wrapper around
client_from_login_flow()
andclient_from_token_file()
. Iftoken_path
exists, loads the token from it. Otherwise open a login flow to fetch a new token. Returns a client configured to refresh the token totoken_path
.Parameters: - api_key – Your TD Ameritrade application’s API key, also known as the client ID.
- redirect_url – Your TD Ameritrade application’s redirect URL. Note this must exactly match the value you’ve entered in your application configuration, otherwise login will fail with a security error.
- token_path – Path that new token will be read from and written to. Updated tokens will be written to this path as well.
- webdriver_func – Function that returns a webdriver for use in fetching a new token. Will only be called if the token file cannot be found.
Troubleshooting¶
As simple as it seems, this process is complex and mistakes are easy to make. This section outlines some of the more common issues you might encounter. If you find yourself dealing with something that isn’t listed here, or if you try the suggested remedies and are still seeing issues, please file a ticket on our issues page.
“A third-party application may be attempting to make unauthorized access to your account”¶
One attack on improperly implemented OAuth login flows involves tricking a user
into submitting their credentials for a real app and then redirecting to a
malicious web server (remember the GET
request to the redirect URI contains
all credentials required to access the user’s account). This is especially
pernicious because from the user’s perspective, they see a real login window and
probably never realize they’ve been sent to a malicious server, especially if
the landing page is designed to resemble the target API’s landing page.
TD Ameritrade correctly prevents this attack by refusing to allow a login if the redirect URI does not exactly match the redirect URI with which the app is configured. If you make any mistake in setting your API key or redirect URI, you’ll see this instead of a login page:

If this happens, you almost certainly copied your API key or redirect URI incorrectly. Go back to your application list and copy-paste it again.
tda-api
Hangs After Successful Login¶
After opening the login window, tda-api
loops and waits until the
webdriver’s current URL starts with the given redirect URI:
callback_url = ''
while not callback_url.startswith(redirect_url):
callback_url = webdriver.current_url
time.sleep(redirect_wait_time_seconds)
Usually, it would be impossible for a successful post-login callback to not
start with the callback URI, but there’s one major exception: when the callback
URI starts with http
. Behavior varies by browser and app configuration, but
a callback URI starting with http
can sometimes be redirected to one
starting with https
, in which case tda-api
will never notice the
redirect.
If this is happening to you, consider changing your callback URI to use
https
instead of http
. Not only will it make your life easier here, but
it is extremely bad practice to send credentials like this over an unencrypted
channel like that provided by http
.
HTTP Client¶
A naive, unopinionated wrapper around the TD Ameritrade HTTP API. This client provides access to all endpoints of the API in as easy and direct a way as possible. For example, here is how you can fetch the past 20 years of data for Apple stock:
from tda.auth import easy_client
from tda.client import Client
c = easy_client(
api_key='APIKEY',
redirect_uri='https://localhost',
token_path='/tmp/token.pickle')
resp = c.get_price_history('AAPL',
period_type=Client.PriceHistory.PeriodType.YEAR,
period=Client.PriceHistory.Period.TWENTY_YEARS,
frequency_type=Client.PriceHistory.FrequencyType.DAILY,
frequency=Client.PriceHistory.Frequency.DAILY)
assert resp.ok
history = resp.json()
Note we we create a new client using the auth
package as described in
Authentication and Client Creation. Creating a client directly is possible, but not recommended.
Calling Conventions¶
Function parameters are categorized as either required or optional.
Required parameters, such as 'AAPL'
in the example above, are passed as
positional arguments. Optional parameters, like period_type
and the rest,
are passed as keyword arguments.
Parameters which have special values recognized by the API are represented by Python enums. This is because the API rejects requests which pass unrecognized values, and this enum wrapping is provided as a convenient mechanism to avoid consternation caused by accidentally passing an unrecognized value.
By default, passing values other than the required enums will raise a
ValueError
. If you believe the API accepts a value that isn’t supported
here, you can use set_enforce_enums
to disable this behavior at your own
risk. If you do find a supported value that isn’t listed here, please open an
issue describing it or submit a PR adding the new functionality.
Return Values¶
All methods return a response object generated under the hood by the requests module. For a full listing of what’s possible, read that module’s documentation. Most if not all users can simply use the following pattern:
r = client.some_endpoint()
assert r.ok, r.raise_for_status()
data = r.json()
The API indicates errors using the response status code, and this pattern will
raise the appropriate exception if the response is not a success. The data can
be fetched by calling the .json()
method.
Note: Because the author has no relationship whatsoever with TD Ameritrade, this document makes no effort to describe the structure of the returned JSON objects. TDA might change them at any time, at which point this document will become silently out of date. Instead, each of the methods described below contains a link to the official documentation. For endpoints that return meaningful JSON objects, it includes a JSON schema which describes the return value. Please use that documentation or your own experimentation when figuring out how to use the data returned by this API.
Creating a New Client¶
99.9% of users should not create their own clients, and should instead follow the instructions outlined in Authentication and Client Creation. For those brave enough to build their own, the constructor looks like this:
-
Client.
__init__
(api_key, session, *, enforce_enums=True)¶ Create a new client with the given API key and session. Set enforce_enums=False to disable strict input type checking.
Orders¶
Placing New Orders¶
Placing new orders can be a complicated task. The Client.place_order()
method is
used to create all orders, from equities to options. The precise order type is
defined by a complex order spec. TDA provides some example order specs to
illustrate the process and provides a schema in the place order documentation, but beyond that we’re on our own.
tda-api
includes some helpers, described in Creating Order Specifications, which provide an
incomplete utility for creating various order types. While it only scratches the
surface of what’s possible, we encourage you to use that module instead of
creating your own order specs.
-
Client.
place_order
(account_id, order_spec)¶ Place an order for a specific account. If order creation was successful, the response will contain the ID of the generated order. See
tda.utils.Utils.extract_order_id()
for more details. Official documentation.
Accessing Existing Orders¶
-
Client.
get_orders_by_path
(account_id, *, max_results=None, from_entered_datetime=None, to_entered_datetime=None, status=None, statuses=None)¶ Orders for a specific account. At most one of
status
andstatuses
may be set. Official documentation.Parameters: - max_results – The maximum number of orders to retrieve.
- from_entered_datetime – Specifies that no orders entered before
this time should be returned. Date must
be within 60 days from today’s date.
toEnteredTime
must also be set. - to_entered_datetime – Specifies that no orders entered after this
time should be returned.
fromEnteredTime
must also be set. - status – Restrict query to orders with this status. See
Order.Status
for options. - statuses – Restrict query to orders with any of these statuses.
See
Order.Status
for options.
-
Client.
get_orders_by_query
(*, max_results=None, from_entered_datetime=None, to_entered_datetime=None, status=None, statuses=None)¶ Orders for all linked accounts. At most one of
status
andstatuses
may be set. Official documentation.Parameters: - max_results – The maximum number of orders to retrieve.
- from_entered_datetime – Specifies that no orders entered before
this time should be returned. Date must
be within 60 days from today’s date.
toEnteredTime
must also be set. - to_entered_datetime – Specifies that no orders entered after this
time should be returned.
fromEnteredTime
must also be set. - status – Restrict query to orders with this status. See
Order.Status
for options. - statuses – Restrict query to orders with any of these statuses.
See
Order.Status
for options.
-
Client.
get_order
(order_id, account_id)¶ Get a specific order for a specific account by its order ID. Official documentation.
-
class
tda.client.Client.
Order
¶ -
class
Status
¶ Order statuses passed to
get_orders_by_path()
andget_orders_by_query()
-
ACCEPTED
= 'ACCEPTED'¶
-
AWAITING_CONDITION
= 'AWAITING_CONDITION'¶
-
AWAITING_MANUAL_REVIEW
= 'AWAITING_MANUAL_REVIEW'¶
-
AWAITING_PARENT_ORDER
= 'AWAITING_PARENT_ORDER'¶
-
AWAITING_UR_OUR
= 'AWAITING_UR_OUR'¶
-
CANCELLED
= 'CANCELLED'¶
-
EXPIRED
= 'EXPIRED'¶
-
FILLED
= 'FILLED'¶
-
PENDING_ACTIVATION
= 'PENDING_ACTIVATION'¶
-
PENDING_CANCEL
= 'PENDING_CANCEL'¶
-
PENDING_REPLACE
= 'PENDING_REPLACE'¶
-
QUEUED
= 'QUEUED'¶
-
REJECTED
= 'REJECTED'¶
-
REPLACED
= 'REPLACED'¶
-
WORKING
= 'WORKING'¶
-
-
class
Editing Existing Orders¶
Endpoints for canceling and replacing existing orders. Annoyingly, while these endpoints require an order ID, it seems that when placing new orders the API does not return any metadata about the new order. As a result, if you want to cancel or replace an order after you’ve created it, you must search for it using the methods described in Accessing Existing Orders.
-
Client.
cancel_order
(order_id, account_id)¶ Cancel a specific order for a specific account. Official documentation.
-
Client.
replace_order
(account_id, order_id, order_spec)¶ Replace an existing order for an account. The existing order will be replaced by the new order. Once replaced, the old order will be canceled and a new order will be created. Official documentation.
Account Info¶
These methods provide access to useful information about accounts. An incomplete list of the most interesting bits:
- Account balances, including available trading balance
- Positions
- Order history
See the official documentation for each method for a complete response schema.
-
Client.
get_account
(account_id, *, fields=None)¶ Account balances, positions, and orders for a specific account. Official documentation.
Parameters: fields – Balances displayed by default, additional fields can be added here by adding values from Account.Fields
.
-
Client.
get_accounts
(*, fields=None)¶ Account balances, positions, and orders for all linked accounts. Official documentation.
Parameters: fields – Balances displayed by default, additional fields can be added here by adding values from Account.Fields
.
-
class
tda.client.Client.
Account
¶ -
class
Fields
¶ Account fields passed to
get_account()
andget_accounts()
-
ORDERS
= 'orders'¶
-
POSITIONS
= 'positions'¶
-
-
class
Instrument Info¶
Note: symbol fundamentals (P/E ratios, number of shares outstanding, dividend
yield, etc.) is available using the Instrument.Projection.FUNDAMENTAL
projection.
-
Client.
search_instruments
(symbols, projection)¶ Search or retrieve instrument data, including fundamental data. Official documentation.
Parameters: projection – Query type. See Instrument.Projection
for options.
-
Client.
get_instrument
(cusip)¶ Get an instrument by CUSIP. Official documentation.
-
class
tda.client.Client.
Instrument
¶ -
class
Projection
¶ Search query type for
search_instruments()
. See the official documentation for details on the semantics of each.-
DESC_REGEX
= 'desc-regex'¶
-
DESC_SEARCH
= 'desc-search'¶
-
FUNDAMENTAL
= 'fundamental'¶
-
SYMBOL_REGEX
= 'symbol-regex'¶
-
SYMBOL_SEARCH
= 'symbol-search'¶
-
-
class
Option Chains¶
Unfortunately, option chains are well beyond the ability of your humble author. You are encouraged to read the official API documentation to learn more.
If you are knowledgeable enough to write something more substantive here, please follow the instructions in Contributing to tda-api to send in a patch.
-
Client.
get_option_chain
(symbol, *, contract_type=None, strike_count=None, include_quotes=None, strategy=None, interval=None, strike=None, strike_range=None, strike_from_date=None, strike_to_date=None, volatility=None, underlying_price=None, interest_rate=None, days_to_expiration=None, exp_month=None, option_type=None)¶ Get option chain for an optionable Symbol. Official documentation.
Parameters: - contract_type – Type of contracts to return in the chain. See
Options.ContractType
for choices. - strike_count – The number of strikes to return above and below the at-the-money price.
- include_quotes – Include quotes for options in the option chain?
- strategy – If passed, returns a Strategy Chain. See
Options.Strategy
for choices. - interval – Strike interval for spread strategy chains (see
strategy
param). - strike – Return options only at this strike price.
- strike_range – Return options for the given range. See
Options.StrikeRange
for choices. - strike_from_date – Only return expirations after this date. For
strategies, expiration refers to the nearest
term expiration in the strategy. Accepts
datetime.date
anddatetime.datetime
. - strike_to_date – Only return expirations before this date. For
strategies, expiration refers to the nearest
term expiration in the strategy. Accepts
datetime.date
anddatetime.datetime
. - volatility – Volatility to use in calculations. Applies only to
ANALYTICAL
strategy chains. - underlying_price – Underlying price to use in calculations.
Applies only to
ANALYTICAL
strategy chains. - interest_rate – Interest rate to use in calculations. Applies only
to
ANALYTICAL
strategy chains. - days_to_expiration – Days to expiration to use in calculations.
Applies only to
ANALYTICAL
strategy chains - exp_month – Return only options expiring in the specified month. See
Options.ExpirationMonth
for choices. - option_type – Types of options to return. See
Options.Type
for choices.
- contract_type – Type of contracts to return in the chain. See
-
class
tda.client.Client.
Options
¶ -
-
class
ExpirationMonth
¶ An enumeration.
-
APRIL
= 'APR'¶
-
AUGUST
= 'AUG'¶
-
DECEMBER
= 'DEC'¶
-
FEBRUARY
= 'FEB'¶
-
JANUARY
= 'JAN'¶
-
JULY
= 'JUL'¶
-
JUN
= 'JUN'¶
-
MARCH
= 'MAR'¶
-
MAY
= 'MAY'¶
-
NOVEMBER
= 'NOV'¶
-
OCTOBER
= 'OCT'¶
-
SEPTEMBER
= 'SEP'¶
-
-
class
Strategy
¶ An enumeration.
-
ANALYTICAL
= 'ANALYTICAL'¶
-
BUTTERFLY
= 'BUTTERFLY'¶
-
CALENDAR
= 'CALENDAR'¶
-
COLLAR
= 'COLLAR'¶
-
CONDOR
= 'CONDOR'¶
-
COVERED
= 'COVERED'¶
-
DIAGONAL
= 'DIAGONAL'¶
-
ROLL
= 'ROLL'¶
-
SINGLE
= 'SINGLE'¶
-
STRADDLE
= 'STRADDLE'¶
-
STRANGLE
= 'STRANGLE'¶
-
VERTICAL
= 'VERTICAL'¶
-
-
class
Price History¶
Fetching price history is somewhat complicated due to the fact that only certain combinations of parameters are valid. To avoid accidentally making it impossible to send valid requests, this method performs no validation on its parameters. If you are receiving empty requests or other weird return values, see the official documentation for more details.
-
Client.
get_price_history
(symbol, *, period_type=None, period=None, frequency_type=None, frequency=None, start_datetime=None, end_datetime=None, need_extended_hours_data=None)¶ Get price history for a symbol. Official documentation.
Parameters: - period_type – The type of period to show.
- period – The number of periods to show. Should not be provided if
start_datetime
andend_datetime
. - frequency_type – The type of frequency with which a new candle is formed.
- frequency – The number of the frequencyType to be included in each candle.
- start_datetime – End date. Default is previous trading day.
- end_datetime – Start date.
- need_extended_hours_data – If true, return extended hours data. Otherwise return regular market hours only.
-
class
tda.client.Client.
PriceHistory
¶ -
class
Frequency
¶ An enumeration.
-
DAILY
= 1¶
-
EVERY_FIFTEEN_MINUTES
= 15¶
-
EVERY_FIVE_MINUTES
= 5¶
-
EVERY_MINUTE
= 1¶
-
EVERY_TEN_MINUTES
= 10¶
-
EVERY_THIRTY_MINUTES
= 30¶
-
MONTHLY
= 1¶
-
WEEKLY
= 1¶
-
-
class
FrequencyType
¶ An enumeration.
-
DAILY
= 'daily'¶
-
MINUTE
= 'minute'¶
-
MONTHLY
= 'monthly'¶
-
WEEKLY
= 'weekly'¶
-
-
class
Period
¶ An enumeration.
-
FIFTEEN_YEARS
= 15¶
-
FIVE_DAYS
= 5¶
-
FIVE_YEARS
= 5¶
-
FOUR_DAYS
= 4¶
-
ONE_DAY
= 1¶
-
ONE_MONTH
= 1¶
-
ONE_YEAR
= 1¶
-
SIX_MONTHS
= 6¶
-
TEN_DAYS
= 10¶
-
TEN_YEARS
= 10¶
-
THREE_DAYS
= 3¶
-
THREE_MONTHS
= 3¶
-
THREE_YEARS
= 3¶
-
TWENTY_YEARS
= 20¶
-
TWO_DAYS
= 2¶
-
TWO_MONTHS
= 2¶
-
TWO_YEARS
= 2¶
-
YEAR_TO_DATE
= 1¶
-
-
class
Current Quotes¶
-
Client.
get_quote
(symbol)¶ Get quote for a symbol. Note due to limitations in URL encoding, this method is not recommended for instruments with symbols symbols containing non-alphanumeric characters, for example as futures like
/ES
. To get quotes for those symbols, useClient.get_quotes()
.
-
Client.
get_quotes
(symbols)¶ Get quote for a symbol. This method supports all symbols, including those containing non-alphanumeric characters like
/ES
. Official documentation.
Other Endpoints¶
Note If your account limited to delayed quotes, these quotes will also be delayed.
Transaction History¶
-
Client.
get_transaction
(account_id, transaction_id)¶ Transaction for a specific account. Official documentation.
-
Client.
get_transactions
(account_id, *, transaction_type=None, symbol=None, start_date=None, end_date=None)¶ Transaction for a specific account. Official documentation.
Parameters: - transaction_type – Only transactions with the specified type will be returned.
- symbol – Only transactions with the specified symbol will be returned.
- start_date – Only transactions after this date will be returned.
Note the maximum date range is one year.
Accepts
datetime.date
anddatetime.datetime
. - end_date – Only transactions before this date will be returned
Note the maximum date range is one year.
Accepts
datetime.date
anddatetime.datetime
.
-
class
tda.client.Client.
Transactions
¶
Saved Orders¶
-
Client.
create_saved_order
(account_id, order_spec)¶ Save an order for a specific account. Official documentation.
-
Client.
delete_saved_order
(account_id, order_id)¶ Delete a specific saved order for a specific account. Official documentation.
-
Client.
get_saved_order
(account_id, order_id)¶ Specific saved order by its ID, for a specific account. Official documentation.
-
Client.
get_saved_orders_by_path
(account_id)¶ Saved orders for a specific account. Official documentation.
-
Client.
replace_saved_order
(account_id, order_id, order_spec)¶ Replace an existing saved order for an account. The existing saved order will be replaced by the new order. Official documentation.
Market Hours¶
-
Client.
get_hours_for_multiple_markets
(markets, date)¶ Retrieve market hours for specified markets. Official documentation.
Parameters: - markets – Market to return hours for. Iterable of
Markets
. - date – The date for which market hours information is requested.
Accepts
datetime.date
anddatetime.datetime
.
- markets – Market to return hours for. Iterable of
-
Client.
get_hours_for_single_market
(market, date)¶ Retrieve market hours for specified single market. Official documentation.
Parameters: - markets – Market to return hours for. Instance of
Markets
. - date – The date for which market hours information is requested.
Accepts
datetime.date
anddatetime.datetime
.
- markets – Market to return hours for. Instance of
-
class
tda.client.Client.
Markets
¶ Values for
get_hours_for_multiple_markets()
andget_hours_for_single_market()
.-
BOND
= 'BOND'¶
-
EQUITY
= 'EQUITY'¶
-
FOREX
= 'FOREX'¶
-
FUTURE
= 'FUTURE'¶
-
OPTION
= 'OPTION'¶
-
Movers¶
-
Client.
get_movers
(index, direction, change)¶ Top 10 (up or down) movers by value or percent for a particular market. Official documentation.
Parameters: - direction – See
Movers.Direction
- change – See
Movers.Change
- direction – See
-
class
tda.client.Client.
Movers
¶ -
class
Change
¶ Values for
get_movers()
-
PERCENT
= 'percent'¶
-
VALUE
= 'value'¶
-
-
class
Direction
¶ Values for
get_movers()
-
DOWN
= 'down'¶
-
UP
= 'up'¶
-
-
class
User Info and Preferences¶
-
Client.
get_preferences
(account_id)¶ Preferences for a specific account. Official documentation.
-
Client.
get_user_principals
(fields=None)¶ User Principal details. Official documentation.
-
Client.
update_preferences
(account_id, preferences)¶ Update preferences for a specific account.
Please note that the directOptionsRouting and directEquityRouting values cannot be modified via this operation. Official documentation.
Watchlists¶
-
Client.
create_watchlist
(account_id, watchlist_spec)¶ ‘Create watchlist for specific account.This method does not verify that the symbol or asset type are valid. Official documentation.
-
Client.
delete_watchlist
(account_id, watchlist_id)¶ Delete watchlist for a specific account. Official documentation.
-
Client.
get_watchlist
(account_id, watchlist_id)¶ Specific watchlist for a specific account. Official documentation.
-
Client.
get_watchlists_for_multiple_accounts
()¶ All watchlists for all of the user’s linked accounts. Official documentation.
-
Client.
get_watchlists_for_single_account
(account_id)¶ All watchlists of an account. Official documentation.
-
Client.
replace_watchlist
(account_id, watchlist_id, watchlist_spec)¶ Replace watchlist for a specific account. This method does not verify that the symbol or asset type are valid. Official documentation.
-
Client.
update_watchlist
(account_id, watchlist_id, watchlist_spec)¶ Partially update watchlist for a specific account: change watchlist name, add to the beginning/end of a watchlist, update or delete items in a watchlist. This method does not verify that the symbol or asset type are valid. Official documentation.
Streaming Client¶
A wapper 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.pickle')
stream_client = StreamClient(client, account_id=1234567890)
async def read_stream():
await stream_client.login()
await stream_client.quality_of_service(StreamClient.QOSLevel.EXPRESS)
await stream_client.nasdaq_book_subs(['GOOG'])
stream_client.add_timesale_options_handler(
lambda msg: print(json.dumps(msg, indent=4)))
while True:
await stream_client.handle_message()
asyncio.get_event_loop().run_until_complete(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.
-
StreamClient.
login
()¶ -
- Performs initial stream setup:
- Fetches streaming information from the HTTP client’s
get_user_principals()
method - Initializes the socket
- Builds and sends and authentication request
- Waits for response indicating login success
- Fetches streaming information from the HTTP client’s
All stream operations are available after this method completes.
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.
-
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
¶ 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.
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, 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.
-
StreamClient.
chart_equity_subs
(symbols)¶ -
Subscribe to equity charts. Behavior is undefined if called multiple times.
Parameters: symbols – Equity symbols to subscribe to.
-
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
¶ -
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.
-
StreamClient.
chart_futures_subs
(symbols)¶ -
Subscribe to futures charts. Behavior is undefined if called multiple times.
Parameters: symbols – Futures symbols to subscribe to.
-
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
¶ -
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.
-
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.
-
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
¶ -
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.
-
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.
-
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
¶ -
-
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.
-
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.
-
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
¶ -
-
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.
-
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.
-
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
¶ -
-
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.
-
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.
-
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
¶ -
-
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 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.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.
-
StreamClient.
listed_book_subs
(symbols)¶ 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.
-
StreamClient.
nasdaq_book_subs
(symbols)¶ 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 bethe order book for the Chicago Board of Exchange options exchanges, although this is an admittedly an uneducated guess.
-
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.
-
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
¶ -
-
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¶
-
StreamClient.
timesale_equity_subs
(symbols, *, fields=None)¶ -
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¶
-
StreamClient.
timesale_futures_subs
(symbols, *, fields=None)¶ -
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¶
-
StreamClient.
timesale_options_subs
(symbols, *, fields=None)¶ -
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.
Creating Order Specifications¶
The Client.place_order()
method expects
a rather complex JSON object that describes the desired order. TDA provides some
example order specs to
illustrate the process and provides a schema in the place order documentation, but beyond that we’re on our own.
The tda.orders
module provides an incomplete set of helpers for building
these order specs. The aim is to make it impossible to build an invalid JSON
object. For example, here is how you might use this module to place a market
order for ten shares of Apple common stock:
from tda.orders import EquityOrderBuilder, Duration, Session
builder = EquityOrderBuilder('AAPL', 10)
builder.set_instruction(EquityOrderBuilder.Instruction.SELL)
builder.set_order_type(EquityOrderBuilder.OrderType.MARKET)
builder.set_duration(Duration.DAY)
builder.set_session(Session.NORMAL)
client = ... # Get a client however you see fit
account_id = 12345678
resp = client.place_order(account_id, builder.build())
assert resp.ok
Common Values¶
-
class
tda.orders.
Duration
¶ An enumeration.
-
DAY
= 'DAY'¶
-
GOOD_TILL_CANCEL
= 'GOOD_TILL_CANCEL'¶
-
FILL_OR_KILL
= 'FILL_OR_KILL'¶
-
-
class
tda.orders.
Session
¶ An enumeration.
-
NORMAL
= 'NORMAL'¶
-
AM
= 'AM'¶
-
PM
= 'PM'¶
-
SEAMESS
= 'SEAMLESS'¶
-
-
exception
tda.orders.
InvalidOrderException
¶ Raised when attempting to build an incomplete order
Equity Orders¶
-
class
tda.orders.
EquityOrderBuilder
(symbol, quantity)¶ Helper class to construct equity orders.
-
__init__
(symbol, quantity)¶ Create an order for the given symbol and quantity. Note all unspecified parameters must be set prior to building the order spec.
Parameters: - symbol – Symbol for the order
- quantity – Quantity of the order
-
set_instruction
(instruction)¶ Set the order instruction
-
set_order_type
(order_type)¶ Set the order type
-
set_price
(price)¶ Set the order price. Must be set for
LIMIT
orders.
-
set_duration
(duration)¶ Set the order duration
-
set_session
(session)¶ Set the order’s session
-
build
()¶ Build the order spec.
Raises: InvalidOrderException – if the order is not fully specified
-
matches
(order)¶ Takes a real object, as might be returned from the TD Ameritrade API, and indicates whether this order object matches it. Returns true if the given order if the given order could have been placed by calling
Client.place_order()
with this order.This method may be called on incomplete orders builders (builders whose
build()
method would fail if called. In such a case, unset values are ignored and have no effect on filtering.
-
Utilities¶
This section describes miscellaneous utility methods provided by tda-api
.
All utilities are presented under the Utils
class:
-
class
tda.utils.
Utils
(client, account_id)¶ Helper for placing orders on equities. Provides easy-to-use implementations for common tasks such as market and limit orders.
-
__init__
(client, account_id)¶ Creates a new
Utils
instance. For convenience, this object assumes the user wants to work with a single account ID at a time.
-
set_account_id
(account_id)¶ Set the account ID used by this
Utils
instance.
-
Get the Most Recent Order¶
For successfully placed orders, tda.client.Client.place_order()
returns
the ID of the newly created order, encoded in the headers. This method inspects
the response and extracts the order ID from the contents, if it’s there. This
order ID can then be used to monitor or modify the order as described in the
Client documentation. Example usage:
# Assume client and order already exist and are valid
account_id = 123456
r = client.place_order(account_id, order)
assert r.ok, raise_for_status()
order_id = Utils(account_id, client).extract_order_id(r)
assert order_id is not None
-
Utils.
extract_order_id
(place_order_response)¶ Attempts to extract the order ID from a response object returned by
Client.place_order()
. ReturnNone
if the order location is not contained in the response.Parameters: place_order_response – Order response as returned by Client.place_order()
. Note this method requires that the order was successful.Raises: ValueError – if the order was not succesful or if the order’s account ID is not equal to the account ID set in this Utils
object.
For orders that were rejected or whose order responses for whatever other reason might not contain the order ID, we can do a best-effort lookup using this method:
-
Utils.
find_most_recent_order
(*, symbol=None, quantity=None, instruction=None, order_type=None, lookback_window=datetime.timedelta(days=1))¶ When placing orders, the TDA API does not always return the order ID of the newly placed order, especially when the order was rejected. This means if we want to make extra sure of its status, we have to take a guess as to which order we just placed. This method simplifies things by returning the most recently-placed order with the given order signature.
Note: This method cannot guarantee that the calling process was the one which placed an order. This means that if there are multiple sources of orders, this method may return an order which was placed by another process.
Parameters: - symbol – Limit search to orders for this symbol.
- quantity – Limit search to orders of this quantity.
- instruction – Limit search to orders with this instruction. See
tda.orders.EquityOrderBuilder.Instruction
- order_type – Limit search to orders with this order type. See
tda.orders.EquityOrderBuilder.OrderType
- lookback_window – Limit search to orders entered less than this long ago. Note the TDA API does not provide orders older than 60 days.
Example Application¶
To illustrate some of the functionality of tda-api
, here is an example
application that finds stocks that pay a dividend during the month of your
birthday and purchases one of each.
from urllib.request import urlopen
import atexit
import datetime
import dateutil
import sys
import tda
API_KEY = 'YOUR_API_KEY@AMER.OAUTHAP'
REDIRECT_URI = 'YOUR_REDIRECT_URI'
TOKEN_PATH = '/YOUR/TOKEN/PATH'
YOUR_BIRTHDAY = datetime.datetime(year=1969, month=4, day=20)
def make_webdriver():
# Import selenium here because it's slow to import
from selenium import webdriver
driver = webdriver.Chrome()
atexit.register(lambda: driver.quit())
return driver
# Create a new client
client = tda.auth.easy_client(
API_KEY,
REDIRECT_URI,
TOKEN_PATH,
make_webdriver)
# Load S&P 500 composition from documentation
sp500 = urlopen(
'https://tda-api.readthedocs.io/en/latest/_static/sp500.txt').read().decode().split()
# Fetch fundamentals for all symbols and filter out the ones with ex-dividend
# dates in the future and dividend payment dates on your birth month. Note we
# perform the fetch in two calls because the API places an upper limit on the
# number of symbols you can fetch at once.
today = datetime.datetime.today()
birth_month_dividends = []
for s in (sp500[:250], sp500[250:]):
r = client.search_instruments(
s, tda.client.Client.Instrument.Projection.FUNDAMENTAL)
assert r.ok, r.raise_for_status()
for symbol, f in r.json().items():
# Parse ex-dividend date
ex_div_string = f['fundamental']['dividendDate']
if not ex_div_string.strip():
continue
ex_dividend_date = dateutil.parser.parse(ex_div_string)
# Parse payment date
pay_date_string = f['fundamental']['dividendPayDate']
if not pay_date_string.strip():
continue
pay_date = dateutil.parser.parse(pay_date_string)
# Check dates
if (ex_dividend_date > today
and pay_date.month == YOUR_BIRTHDAY.month):
birth_month_dividends.append(symbol)
if not birth_month_dividends:
print('Sorry, no stocks are paying out in your birth month yet. This is ',
'most likely because the dividends haven\'t been announced yet. ',
'Try again closer to your birthday.')
sys.exit(1)
# Purchase one share of each the stocks that pay in your birthday month.
account_id = int(input(
'Input your TDA account number to place orders (<Ctrl-C> to quit): '))
for symbol in birth_month_dividends:
print('Buying one share of', symbol)
# Build the order spec and place the order
builder = tda.orders.EquityOrderBuilder(symbol, 1)
builder.set_instruction(builder.Instruction.BUY)
builder.set_order_type(builder.OrderType.MARKET)
builder.set_duration(tda.orders.Duration.DAY)
builder.set_session(tda.orders.Session.NORMAL)
order = builder.build()
r = client.place_order(account_id, order)
assert r.ok, r.raise_for_status()
Contributing to tda-api
¶
Fixing a bug? Adding a feature? Just cleaning up for the sake of cleaning up? Great! No improvement is too small for me, and I’m always happy to take pull requests. Read this guide to learn how to set up your environment so you can contribute.
Setting up the Dev Environment¶
Dependencies are listed in the requirements.txt file. These development requirements are distinct from the requirements listed in setup.py and include some additional packages around testing, documentation generation, etc.
Before you install anything, I highly recommend setting up a virtualenv so you don’t pollute your system installation directories:
pip install virtualenv
virtualenv -v virtualenv
source virtualenv/bin/activate
Next, install project requirements:
pip install -r requirements.txt
Finally, verify everything works by running tests:
make test
At this point you can make your changes.
Development Guidelines¶
Test your changes¶
This project aims for high test coverage. All changes must be properly tested, and we will accept no PRs that lack appropriate unit testing. We also expect existing tests to pass. You can run your tests using:
make test
Document your code¶
Documentation is how users learn to use your code, and no feature is complete without a full description of how to use it. If your PR changes external-facing interfaces, or if it alters semantics, the changes must be thoroughly described in the docstrings of the affected components. If your change adds a substantial new module, a new section in the documentation may be justified.
Documentation is built using Sphinx. You can build the documentation using the Makefile.sphinx makefile. For example you can build the HTML documentation like so:
make -f Makefile.sphinx
Indices and tables¶
Disclaimer: tda-api is an unofficial API wrapper. It is in no way endorsed by or affiliated with TD Ameritrade or any associated organization. Make sure to read and understand the terms of service of the underlying API before using this package. This authors accept no responsibility for any damage that might stem from use of this package. See the LICENSE file for more details.