Java rest client for OAuth2 TD Ameritrade Api. Uses OKHttp 3 under the hood.
- Javadoc API
- How-To on the Wiki shows how to use this Java API.
- TDA-Client-Example - Simple Java and Maven example project.
The client only requires a TDA client ID (consumer key) and current OAuth refresh token. The refresh token expires every 90 days. Note that the client id should not have appended the @AMER.OAUTHAP which is used only when refreshing your OAuth token.
See the Getting Started to set up the developer account itself.
and the wiki has directions on how to generate a refresh token.
Add the following to your Maven build file:
<dependency>
<groupId>com.studerw.tda</groupId>
<artifactId>td-ameritrade-client</artifactId>
<version>2.4.1</version>
</dependency>
Or for Gradle:
dependencies {
compile "com.studerw.tda:td-ameritrade-client:2.4.1"
}
You need to obtain a valid TDA Developer refresh token every 90 days. See TDA's Simple Auth for Local Apps.
Properties props = new Properties();
props.setProperty("tda.client_id", "...");
props.setProperty("tda.token.refresh", "...")
TdaClient tdaClient = new HttpTdaClient(props);
final Quote quote = tdaClient.fetchQuote("msft");
EquityQuote equityQuote = (EquityQuote) quote;
System.out.println("Current price of MSFT: " + equityQuote.getAskPrice());
To build the jar, check out the source and run:
git clone https://github.com/studerw/td-ameritrade-client.git
cd td-ameritrade-client
mvn clean install -Dgpg.skip
You do not need to build the project to use it. The latest release is available on Maven Central, so just include the dependency in your Maven pom or Gradle build file.
Integration tests do require a client app ID and refresh token to run.
To run integration tests, you will need create the file src/test/resources/com/studerw/tda/my-test.properties and fill in the necessary TDA properties:
tda.token.refresh=<VALID_REFRESH_TOKEN>
tda.client_id=<CLIENT_OR_CUSTOMER_ID>
tda.account.id=<ACCOUNT_ID>
Then run the following command.
mvn failsafe:integration-test
The TDA API seems to be in a constant state of change, and some documentation is out of sync with the actual responses.
Thus, in order to ensure that all properties are deserialized from the returned JSON into our Java pojos,
an otherfields
Map is contained in most types. You can get any new or undocumented fields using code similar
to the following:
Quote quote = tdaClient.fetchQuote("msft");
String someField = (String)quote.getOtherFields().get("someField"))
Most TDA dates and times are returned as longs (i.e. milliseconds since the epoch UTC). An easy way to convert them to Java's new DateTime is via the following:
long someMilliseconds= ...
ZonedDateTime dateTime = Instant.ofEpochMilli(someDateTime)
.atZone(ZoneId.systemDefault());
Or you could use the deprecated java.util.Date.
long someDateTime = ...
Date date = new Date(someDateTime);
To convert a long to human readable ISO 8601 String, use the following:
long currentTime = System.currentTimeMillis();
String formattedDate = Utils.epochToStr(currentTime);
System.out.println(formattedDate) // 2019-09-13T19:59-04:00[America/New_York]
Only unchecked exceptions are thrown to avoid littering your code with try / catch
blocks.
Before the call is made, request parameter validation exceptions can be thrown. Usually you won't have to catch these in your program, though they'll be helpful when testing.
Once the call is made, the TDA server can return 200 success responses even if the call was not successful, for example, you've sent an invalid request type or set of parameters. Often this means the body is an empty JSON string.
The rules are this:
-
OAuth authentication problems, explicitly signalled by a 401 response codes, usually mean an invalid TDA client id (consumer key) or an expired refresh token, and this will throw an
IllegalStateException
. -
Validation issues that are known before the call is made, e.g. null or empty required parameters, will throw unchecked
IllegalArgumentException
. -
All non 200 HTTP responses throw unchecked
RuntimeExceptions
since there is no way for the API to recover. -
Responses that are completely empty but should have returned a full json body throw a
RunTimeException
as well. -
If there is an error parsing the JSON into a Java pojo, the
RuntimeException
wrapping theIOException
from Jackson will be thrown.
The API uses SLF4J as does OKHttp 3. You can use any implementation like Logback or Log4j2.
Specific Loggers that you can tune:
TDA_HTTP
- set this to INFO for basic request / response info, or DEBUG to see full headers and body.com.studerw.tda.client.OauthInterceptor
- detailed info on OAUTH can be seen using either INFO or DEBUGcom.studerw.tda
- basic API logging with either INFO or DEBUGcom.squareup.okhttp3
- lower level OKHTTP library.
Add Logback
to your pom:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
Add a logback.xml
file to you classpath (e.g. src/main/resources/)
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="false" scanPeriod="3 seconds" debug="false">
<contextName>main</contextName>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
</pattern>
</encoder>
</appender>
<logger name="TDA_HTTP" level="INFO"/>
<logger name="com.studerw.tda" level="INFO"/>
<logger name="com.studerw.tda.client.OauthInterceptor" level="INFO"/>
<logger name="com.squareup.okhttp3" level="INFO"/>
<root level="WARN">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
See the old-xml-api branch for the previous project based on the soon-to-be-deprecated TDA XML API.
Sometime in-between the beginning of this project (based on TDA's older XML API) and now, TDA released a restful API. Unfortunately the old API is being deprecated in 2020 and so the original source code for this project has been moved to the old-xml-api branch and is now known as version 1.0.0.
- Figure out a way to deal with the order ID upon submitting an order. Either show an example or delay
- didn't someone mention there is a URL returned in the header to follow up?
- Use these ameritrade-api-schemas to monitor changes to API -> models
- Junit 5
- OptionChain query parameters are commented out in HttpTdaClient
- Streaming API
- convert to jakarta packages for validation / javax (or maybe get rid of it completely)
- Maybe get rid of Commons IO and Commons Lang to pare down dependencies
- Add EZ order abstraction (e.g. simple buy and sell equity)
- JSON Client