diff --git a/src/main/java/com/sleet/api/Constants.kt b/src/main/java/com/sleet/api/Constants.kt index 31c698b..6166ed3 100644 --- a/src/main/java/com/sleet/api/Constants.kt +++ b/src/main/java/com/sleet/api/Constants.kt @@ -23,6 +23,7 @@ object Constants { const val EQUALS = '=' const val GRANT_TYPE = "grant_type" const val MARKETDATA = "marketdata" + const val PRICEHISTORY = "pricehistory" const val OFFLINE = "offline" const val ORDERS = "orders" const val REDIRECT_URI = "redirect_uri" @@ -31,6 +32,7 @@ object Constants { const val SLASH = "/" const val TOKEN_ENDPOINT = "oauth2/token" const val URL_ENCODED = "application/x-www-form-urlencoded" + const val QUESTION_MARK = "?" const val QUERY_PARAM_OTM = "&range=OTM" const val QUERY_PARAM_CONTRACT_TYPE = "&contractType=" @@ -44,4 +46,8 @@ object Constants { const val QUERY_PARAM_TO_ENTERED_TIME = "&toEnteredTime=" const val QUERY_PARAM_ORDERS_AND_POSITIONS = "?fields=positions%2Corders" const val QUERY_PARAM_USER_PRINCIPALS = "userprincipals?fields=streamerSubscriptionKeys%2CstreamerConnectionInfo" + const val QUERY_PARAM_PERIOD_TYPE = "&periodType=" + const val QUERY_PARAM_PERIOD = "&period=" + const val QUERY_PARAM_FREQUENCY_TYPE = "&frequencyType=" + const val QUERY_PARAM_FREQUENCY = "&frequency=" } \ No newline at end of file diff --git a/src/main/java/com/sleet/api/model/Candles.kt b/src/main/java/com/sleet/api/model/Candles.kt new file mode 100644 index 0000000..6913307 --- /dev/null +++ b/src/main/java/com/sleet/api/model/Candles.kt @@ -0,0 +1,8 @@ +package com.sleet.api.model + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties + +@JsonIgnoreProperties(ignoreUnknown = true) +class Candles { + val candles: Array = emptyArray() +} \ No newline at end of file diff --git a/src/main/java/com/sleet/api/model/PriceHistory.kt b/src/main/java/com/sleet/api/model/PriceHistory.kt new file mode 100644 index 0000000..ee55aa5 --- /dev/null +++ b/src/main/java/com/sleet/api/model/PriceHistory.kt @@ -0,0 +1,13 @@ +package com.sleet.api.model + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties + +@JsonIgnoreProperties(ignoreUnknown = true) +class PriceHistory { + val open: Double = 0.0 + val high: Double = 0.0 + val low: Double = 0.0 + val close: Double = 0.0 + val volume: Int = 0 + val datetime: String? = null +} \ No newline at end of file diff --git a/src/main/java/com/sleet/api/service/QuoteService.kt b/src/main/java/com/sleet/api/service/QuoteService.kt index 8f8b9ed..a3cde87 100644 --- a/src/main/java/com/sleet/api/service/QuoteService.kt +++ b/src/main/java/com/sleet/api/service/QuoteService.kt @@ -6,9 +6,7 @@ import com.fasterxml.jackson.databind.JsonNode import com.sleet.api.util.RequestUtil.Companion.createGetRequest import com.sleet.api.Constants import com.sleet.api.Constants.DEFAULT_TIMEOUT_MILLIS -import com.sleet.api.model.Asset -import com.sleet.api.model.Contract -import com.sleet.api.model.OptionChain +import com.sleet.api.model.* import org.asynchttpclient.AsyncHttpClient import org.asynchttpclient.Response import org.slf4j.LoggerFactory @@ -33,6 +31,8 @@ class QuoteService( ) { private var OPTION_CHAIN_URL: String = Constants.API_URL + Constants.MARKETDATA + "/chains?apikey=" + apiKey + private var HIST_PRICE_URL: String = Constants.API_URL + Constants.MARKETDATA + private var HIST_PRICE_API: String = "/pricehistory?apikey=$apiKey" private var QUOTE_URL: String = "/quotes?apikey=$apiKey" companion object { @@ -324,6 +324,43 @@ class QuoteService( return getOptionChainForStrikeAsync(ticker, strike)[DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS] } + /** + * Queries the TD API endpoint for historical prices for a specified period & frequency + * + * @param ticker of security to retrieve prices for + * @param periodType type of period to show, e.g. [day, month, year, ytd] + * @param period number of periods to show + * @param frequencyType type of frequency with which a new candle is formed, e.g. [minute, daily, weekly, monthly] + * @param frequency the number of frequencies to be included + * @param startDate start date as milliseconds since epoch + * @param endDate end date as milliseconds since epoch + * @return [Response] with all option data for the ticker + */ + @Throws(Exception::class) + fun getPriceHistory(ticker: String?, periodType: String?, period: String?, frequencyType: String?, frequency: String?, + startDate: String?, endDate: String?): CompletableFuture { + + val builder = StringBuilder() + .append(HIST_PRICE_URL) + .append(Constants.SLASH) + .append(ticker) + .append(HIST_PRICE_API) + .append(Constants.QUERY_PARAM_PERIOD_TYPE) + .append(periodType) + .append(Constants.QUERY_PARAM_PERIOD) + .append(period) + .append(Constants.QUERY_PARAM_FREQUENCY_TYPE) + .append(frequencyType) + .append(Constants.QUERY_PARAM_FREQUENCY) + .append(frequency) + + val future = CompletableFuture() + val request = createGetRequest(builder.toString(), null) + httpClient.executeRequest(request).toCompletableFuture() + .whenComplete { resp: Response, _: Throwable? -> future.complete(deserializeHistoryResponse(resp)) } + return future + } + /** * Queries the TD API endpoint asynchronously for all options for a ticker with a specific strike price * @@ -439,6 +476,23 @@ class QuoteService( return null } + /** + * Deserialize a JSON response string into an [PriceHistory] + * + * @param response to deserialize + * @return [PriceHistory] for the original request, or null if exception occurs + */ + private fun deserializeHistoryResponse(response: Response): Candles? { + if (response.statusCode == 200) { + try { + return mapper.readValue(response.responseBody, Candles::class.java) + } catch (e: Exception) { + logFailure(e) + } + } + return null + } + /** * Method for logging exceptions after failed HTTP requests. Exit program * if api key expires. diff --git a/src/test/java/com/sleet/api/service/QuoteServiceTest.kt b/src/test/java/com/sleet/api/service/QuoteServiceTest.kt index 64af1c5..d4e7475 100644 --- a/src/test/java/com/sleet/api/service/QuoteServiceTest.kt +++ b/src/test/java/com/sleet/api/service/QuoteServiceTest.kt @@ -130,4 +130,18 @@ class QuoteServiceTest { Thread.sleep(2000) } } + + + @Test + @Ignore + @Throws(Exception::class) + fun testHistoricalPrices() { + val quoteService = QuoteService(TestConstants.API_KEY, Dsl.asyncHttpClient(Dsl.config())) + val time = System.currentTimeMillis() + val priceHistory = quoteService.getPriceHistory("SPY", "month", "1", "daily", "1", "", "") + println("Retrieval for price history " + (System.currentTimeMillis() - time) + " ms") + + val history = priceHistory.get() + Assert.assertNotNull(history) + } } \ No newline at end of file