From a500f98a1692973572b53a8d8253a5d013bf032d Mon Sep 17 00:00:00 2001 From: Dan Date: Sat, 29 May 2021 14:40:51 -0600 Subject: [PATCH 1/2] price history --- src/main/java/com/sleet/api/Constants.kt | 6 ++ src/main/java/com/sleet/api/model/Candles.kt | 9 +++ .../java/com/sleet/api/model/PriceHistory.kt | 14 +++++ .../com/sleet/api/service/QuoteService.kt | 63 ++++++++++++++++++- 4 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/sleet/api/model/Candles.kt create mode 100644 src/main/java/com/sleet/api/model/PriceHistory.kt diff --git a/src/main/java/com/sleet/api/Constants.kt b/src/main/java/com/sleet/api/Constants.kt index 31c698b..25b3afe 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..f64ec64 --- /dev/null +++ b/src/main/java/com/sleet/api/model/Candles.kt @@ -0,0 +1,9 @@ +package com.sleet.api.model + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty + +@JsonIgnoreProperties(ignoreUnknown = true) +class Candles { + @get:JsonProperty("candles") var 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..2bf508c --- /dev/null +++ b/src/main/java/com/sleet/api/model/PriceHistory.kt @@ -0,0 +1,14 @@ +package com.sleet.api.model + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty + +@JsonIgnoreProperties(ignoreUnknown = true) +class PriceHistory { + @get:JsonProperty("open") var open: Double = 0.0 + @get:JsonProperty("high") var high: Double? = 0.0 + @get:JsonProperty("low") var low: Double? = 0.0 + @get:JsonProperty("close") var close: Double? = 0.0 + @get:JsonProperty("volume") var volume: Int? = 0 + @get:JsonProperty("datetime") var 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..e614403 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,7 @@ 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 QUOTE_URL: String = "/quotes?apikey=$apiKey" companion object { @@ -324,6 +323,47 @@ 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(Constants.PRICEHISTORY) + .append(Constants.QUESTION_MARK) + .append(Constants.QUERY_PARAM_PERIOD_TYPE) + .append(periodType) + .append(Constants.AND) + .append(Constants.QUERY_PARAM_PERIOD) + .append(period) + .append(Constants.AND) + .append(Constants.QUERY_PARAM_FREQUENCY_TYPE) + .append(frequencyType) + .append(Constants.AND) + .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 +479,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. From 7bf2278b4ba9a8a1c9caf6edfe936d88eead180a Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 31 May 2021 17:05:12 -0600 Subject: [PATCH 2/2] new changes --- src/main/java/com/sleet/api/Constants.kt | 8 ++++---- src/main/java/com/sleet/api/model/Candles.kt | 3 +-- src/main/java/com/sleet/api/model/PriceHistory.kt | 13 ++++++------- .../java/com/sleet/api/service/QuoteService.kt | 7 ++----- .../java/com/sleet/api/service/QuoteServiceTest.kt | 14 ++++++++++++++ 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/sleet/api/Constants.kt b/src/main/java/com/sleet/api/Constants.kt index 25b3afe..6166ed3 100644 --- a/src/main/java/com/sleet/api/Constants.kt +++ b/src/main/java/com/sleet/api/Constants.kt @@ -46,8 +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=" + 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 index f64ec64..6913307 100644 --- a/src/main/java/com/sleet/api/model/Candles.kt +++ b/src/main/java/com/sleet/api/model/Candles.kt @@ -1,9 +1,8 @@ package com.sleet.api.model import com.fasterxml.jackson.annotation.JsonIgnoreProperties -import com.fasterxml.jackson.annotation.JsonProperty @JsonIgnoreProperties(ignoreUnknown = true) class Candles { - @get:JsonProperty("candles") var candles: Array = emptyArray() + 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 index 2bf508c..ee55aa5 100644 --- a/src/main/java/com/sleet/api/model/PriceHistory.kt +++ b/src/main/java/com/sleet/api/model/PriceHistory.kt @@ -1,14 +1,13 @@ package com.sleet.api.model import com.fasterxml.jackson.annotation.JsonIgnoreProperties -import com.fasterxml.jackson.annotation.JsonProperty @JsonIgnoreProperties(ignoreUnknown = true) class PriceHistory { - @get:JsonProperty("open") var open: Double = 0.0 - @get:JsonProperty("high") var high: Double? = 0.0 - @get:JsonProperty("low") var low: Double? = 0.0 - @get:JsonProperty("close") var close: Double? = 0.0 - @get:JsonProperty("volume") var volume: Int? = 0 - @get:JsonProperty("datetime") var datetime: String? = null + 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 e614403..a3cde87 100644 --- a/src/main/java/com/sleet/api/service/QuoteService.kt +++ b/src/main/java/com/sleet/api/service/QuoteService.kt @@ -32,6 +32,7 @@ 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 { @@ -343,17 +344,13 @@ class QuoteService( .append(HIST_PRICE_URL) .append(Constants.SLASH) .append(ticker) - .append(Constants.PRICEHISTORY) - .append(Constants.QUESTION_MARK) + .append(HIST_PRICE_API) .append(Constants.QUERY_PARAM_PERIOD_TYPE) .append(periodType) - .append(Constants.AND) .append(Constants.QUERY_PARAM_PERIOD) .append(period) - .append(Constants.AND) .append(Constants.QUERY_PARAM_FREQUENCY_TYPE) .append(frequencyType) - .append(Constants.AND) .append(Constants.QUERY_PARAM_FREQUENCY) .append(frequency) 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