Java rest client for OAuth2 TD Ameritrade Api. Uses OKHttp 3 under the hood.
- Javadoc API
- How-To on the Wiki.
I'm happy to collaborate contractually or OSS with other devs.
The client only requires a TDA Client ID and current OAuth Refresh Token. The refresh token expires every 90 days. See the Getting Started and Simple Auth for Local Apps for help.
To build the jar, checkout the source and run:
mvn clean install
Until the project is finished, you will need to have built this locally in order to put the necessary jars in your local Maven repo.
Once we have a 1.0.0 2.0.0 version, it will be submitted to Maven Central.
Add the following to your Maven build file:
<dependency>
<groupId>com.studerw.tda</groupId>
<artifactId>td-ameritrade-client</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
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.set("tda.client_id", "...");
props.set("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());
Integration tests do require a Client App ID user and refresh token, though are not needed to build the jar.
To run integration tests, you will need to rename this file src/test/resources/my-test.properties.changeme to my-test.properties and fill in the necessary TDA properties.
Then run the following command.
mvn failsafe:integration-test
The TDA API seems to be in a constant state of change and some of the documentation is sometimes wrong.
Thus, in order to ensure that all properties are deserialized from the returned JSON into our Java objects,
an otherfields
Map is contained in most types. You can get any new or undocumented fields using the code similar
to the following:
Quote quote = httpTdaClient.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 someDateTime = ...
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 = FormatUtils.epochToStr(currentTime);
System.out.println(formattedDate) // 2019-09-13T19:59-04:00[America/New_York]
Before the call is even made, validator or other exceptions can be thrown. Usually you won't have to catch these in your program, they'll be helpful when testing, though.
Once the call is made, the TDA server returns 200 success responses even if the call was not successful, for example you've sent an invalid request type or some other issue. Often this means the body is an empty JSON string.
The rules are this within the Client:
-
Most validation exception before the call was made will throw unchecked
IllegalArgumentException
. -
All non 200 HTTP responses throw unchecked
RuntimeExceptions
since there is no way to recover, usually. -
Responses that are completely empty but should have returned a full json body throw a
RunTimeException
also. -
If there is an error parsing the JSON into a Java pojo, the
RuntimeException
wrapping theIOException
from Jackson will be thrown.
The only exception to this rule is if we cannot login - either due to bad credentials, locked account, or otherwise.
When this occurs, an IllegalStateException
is thrown. This is explicitly signalled by a 401 response code.
The API uses SLF4J as does OKHttp 3. You can use any implementation like Logback or Log4j.
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
- low-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-config.xml
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.